/*
 *  pserver.c
 *
 *  Copyright (C) 1996 by Volker Lendecke
 *
 */


#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <syslog.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <signal.h>
#include "ncplib.h"

struct nw_queue {
	struct ncp_conn *conn;

	char queue_name[NCP_BINDERY_NAME_LEN];
	__u32 queue_id;
	__u16 job_type;

	char *command;
};

static struct nw_queue q;

static int term_request;
static char *progname;

static int
init_queue(struct ncp_conn *conn, char *queue_name,
	   char *command, struct nw_queue *q);

static int
poll_queue(struct nw_queue *q);

static void
usage(void)
{
	fprintf(stderr, "usage: %s [options] file\n", progname);
	exit(1);
}

static void
help(void)
{
        printf("\n");
        printf("usage: %s [options]\n", progname);
        printf("\n"
	       "-S server      Server name to be used\n"
               "-U username    Print Server name sent to server\n"
               "-P password    Use this password\n"
               "-n             Do not use any password\n"
               "-C             Don't convert password to uppercase\n"
               "-q queue name  Name of the printing queue to use\n"
	       "-c command     Name of print command, default: 'lpr'\n"
	       "-j job type    Type of job (Form number) to service\n"
	       "-t timeout     Polling interval, default: 30 sec\n"
	       "-d             Debug: don't daemonize\n"
               "-h             print this help text\n"
	       "\n");
}

#ifndef NCP_BINDERY_PSERVER
#define NCP_BINDERY_PSERVER (0x0007)
#endif

static void
terminate_handler()
{
	signal(SIGTERM,terminate_handler);
	signal(SIGINT, terminate_handler);
	term_request=1;
}

/* Daemon_init is taken from Stevens, Adv. Unix programming */
static int
daemon_init(void)
{
	pid_t pid;

	if ((pid = fork()) < 0)
	{
		return -1;
	}
	else if (pid != 0)
	{
		exit(0);	/* parent vanishes */
	}

	/* child process */
	setsid();
	chdir("/");
	umask(0);
	close(0);
	close(1);
	close(2);
	return 0;
}

int
main(int argc, char *argv[])
{
	struct ncp_conn *conn;
	int poll_timeout = 30;
	int opt;
	int job_type = 0xffff;
	int debug = 0;
	int i;
	long err;

	char *queue_name = NULL;

	char default_command[] = "lpr";
	char *command = default_command;

	progname = argv[0];

	for (i = 1; i < argc; i += 1)
	{
		if (   (strcmp(argv[i], "-h") == 0)
		    || (strcmp(argv[i], "-?") == 0))
		{
			help();
			exit(0);
		}
	}

	for (i = 1; i < argc; i += 1)
	{
		if (strcmp(argv[i], "-d") == 0)
		{
			debug = 1;
			break;
		}
	}

	if (debug == 0)
	{
		daemon_init();
		openlog("pserver", LOG_PID, LOG_LPR);
	}

	if ((conn = ncp_initialize_as(&argc, argv, 1,
				      NCP_BINDERY_PSERVER, &err)) == NULL)
	{
		com_err(argv[0], err, "when initializing");
		return 1;
	}

	while ((opt = getopt(argc, argv, "q:c:j:t:dh")) != EOF)
	{
		switch (opt)
		{
		case 'q':
			queue_name = optarg;
			break;
		case 'c':
			command = optarg;
			break;
		case 'j':
			job_type = atoi(optarg);
			break;
		case 't':
			poll_timeout = atoi(optarg);
			break;
		case 'd':
			debug = 1;
			break;
		case 'h':
			break;
		default:
			usage();
			return -1;
		}
	}

	if (argc != optind)
	{
		usage();
		return -1;
	}

	memzero(q);

	if (queue_name == NULL)
	{
		fprintf(stderr, "You must specify a queue\n");
		return 1;
	}

	if (init_queue(conn, queue_name, command, &q) != 0)
	{
		perror("Could not init queue");
		ncp_close(conn);
		return 1;
	}

	q.job_type = job_type;

	term_request = 0;
	signal(SIGTERM,terminate_handler);
	signal(SIGINT, terminate_handler);

	while (1)
	{
		if (   (poll_queue(&q) != 0)
		    && (term_request == 0))
		{
			continue;
		}

		if (term_request != 0)
		{
			break;
		}
		sleep(poll_timeout);
	}

	ncp_detach_from_queue(conn, q.queue_id);

	ncp_close(conn);
	return 0;
}

static int
init_queue(struct ncp_conn *conn, char *queue_name, char *command,
	   struct nw_queue *q)
{
	struct ncp_bindery_object obj;

	str_upper(queue_name);

	q->conn = conn;
	q->command = command;
	
	if (ncp_get_bindery_object_id(conn, NCP_BINDERY_PQUEUE,
				      queue_name, &obj) != 0)
	{
		fprintf(stderr, "Queue %s not found\n", queue_name);
		return -1;
	}

	q->queue_id = obj.object_id;
	memcpy(q->queue_name, obj.object_name, sizeof(q->queue_name));

	if (ncp_attach_to_queue(conn, q->queue_id) != 0)
	{
		fprintf(stderr, "Could not attach to queue %s\n",
			queue_name);
		return -1;
	}
	return 0;
}
	

void
build_command(struct nw_queue *q, struct queue_job *j,
	      char *target, int target_size)
{
	char *s = q->command;
	char *target_end = target+target_size;

	void add_string(char *s)
		{
			int len = strlen(s);
			if (target + len + 1 > target_end)
			{
				len = target_end - target - 1;
			}
			strncpy(target, s, len);
			target += len;
		}
			

	memset(target, 0, target_size);

	while ((*s != 0) && (target < target_end))
	{
		if (*s != '%')
		{
			*target = *s;
			target += 1;
			s += 1;
			continue;
		}
			
		switch (*(s+1))
		{
		case '%':
			*target = '%';
			target += 1;
		case 'u':
			{
				char *user;
				struct ncp_bindery_object u;
				if (ncp_get_bindery_object_name
				    (q->conn, j->j.ClientObjectID, &u)
				    == 0)
				{
					user = u.object_name;
				}
				else
				{
					user = "*UNKNOWN USER*";
				}
				add_string(user);
			}
		default:
			*target = '%';
			*(target+1) = *(s+1);
			target += 2;
		}
		s += 2;
	}
}


	

static int
poll_queue(struct nw_queue *q)
{
	struct queue_job job;
	int fd[2];
	int pid;

	if (ncp_service_queue_job(q->conn, q->queue_id, q->job_type,
				  &job) != 0)
	{
		/* No job for us */
		return 0;
	}

	if (pipe(fd) < 0)
	{
		syslog(LOG_ERR, "pipe error: %m");
		goto fail;
	}

	if ((pid = fork()) < 0)
	{
		syslog(LOG_ERR, "fork error: %m");
		goto fail;
	}

	if (pid > 0)
	{
		/* parent */
		char buf[1024];
		size_t result;
		off_t offset = 0;

		close(fd[0]);	/* close read end */

		while ((result = ncp_read(q->conn, job.file_handle, offset,
					  sizeof(buf), buf)) > 0)
		{
			offset += result;
			if (write(fd[1], buf, result) != result)
			{
				goto fail;
			}
		}

		close(fd[1]);	/* and close write end */

		if (waitpid(pid, NULL, 0) < 0)
		{
			syslog(LOG_ERR, "waitpid: %m\n");
		}
	}
	else
	{
		/* child */

		char command[2048];

		close(fd[1]);	/* close write end */

		if (fd[0] != STDIN_FILENO)
		{
			if (dup2(fd[0], STDIN_FILENO) != STDIN_FILENO)
			{
				syslog(LOG_ERR, "dup2 error: %m\n");
				close(fd[0]);
				exit(1);
			}
			close(fd[0]);
		}

		build_command(q, &job, command, sizeof(command));

		execl("/bin/sh", "sh", "-c", command, NULL);
		syslog(LOG_ERR, "exec error: %m\n");
		close(fd[0]);
		exit(1);
	}

	ncp_finish_servicing_job(q->conn, q->queue_id, job.j.JobNumber,0);
	return 1;		

 fail:
	ncp_abort_servicing_job(q->conn, q->queue_id, job.j.JobNumber);
	/* We tell that we did not have a job to avoid overloading
	   when something's wrong */
	return 0;
}
