/*
 * Copyright (c) 1994-1999 by Sun Microsystems, Inc.
 * All rights reserved.
 */

#pragma ident	"@(#)lp.c	1.45	99/10/07 SMI"

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <ctype.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/systeminfo.h>
#include <sys/param.h>
#include <stdarg.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <syslog.h>
#include <termios.h>
#include <libintl.h>
#include <locale.h>
#include <pwd.h>
#include <grp.h>

#include <print/ns.h>
#include <print/network.h>
#include <print/misc.h>
#include <print/list.h>
#include <print/job.h>

/*
 *	 lpr/lp
 *	This program will submit print jobs to a spooler using the BSD
 *	printing protcol as defined in RFC1179, plus some extension for
 *	support of additional lp functionality.
 */

extern char *optarg;
extern int optind, opterr, optopt;
extern char *getenv(const char *);

#define	SEND_RETRY	-1
#define	SEND_ABORT	-2


static int	priority = -1,
		copies = 1,
		width = -1,		/* pr width */
		indent = -1,		/* pr indent */
		linked = 0,
		mail = 0,
		delete = 0,
		suppress = 1,
		banner = 1,
		connection_failed = 0;
static char	*printer = NULL,
		*form = NULL,
		*charset = NULL,
		*title = NULL,		/* pr title */
		*class = NULL,
		*jobName = NULL,
		*notification = NULL,
		*handling = NULL,
		*pages = NULL,
		**mode = NULL,
		**s5options = NULL,
		*s5type = NULL,
		*internal_type = NULL,
		*fontR = NULL,
		*fontI = NULL,
		*fontB = NULL,
		*fontS = NULL,
		type = CF_PRINT_ASCII;

static struct s5_types {
	char *name;
	char type;
} output_types[] = {			/* known LP "-T" types */
/*
 *	Switched to ASCII, because some BSD systems don't like the 'o' file
 *	type.
 */
	{ "postscript", CF_PRINT_ASCII },
	{ "ps", CF_PRINT_ASCII },
	{ "simple", CF_PRINT_ASCII },
	{ "ascii", CF_PRINT_ASCII },
	{ "raw", CF_PRINT_RAW },
	{ "dvi", CF_PRINT_DVI },
	{ "tex", CF_PRINT_DVI },
	{ "raster", CF_PRINT_RAS },
	{ "ditroff", CF_PRINT_DROFF },
	{ "otroff", CF_PRINT_TROFF },
	{ "troff", CF_PRINT_DROFF },
	{ "cif", CF_PRINT_CIF },
	{ "plot", CF_PRINT_PLOT },
	{ "fortran", CF_PRINT_FORT },
	{ "pr", CF_PRINT_PR },
	NULL
};

/*ARGSUSED*/
static void sigbus_handler(int i)
{
	(void) fprintf(stderr,
		gettext("No space in /var/spool/print to store job"));
	exit(-1);
}

/*ARGSUSED*/
static void sigpipe_handler(int i)
{
	syslog(LOG_ERR, "Warning: Received SIGPIPE; continuing");
	(void) signal(SIGPIPE, sigpipe_handler);
}


#define	OLD_LP "/usr/lib/lp/local/lp"	/* for local lpsched printers */
#ifdef OLD_LP
/*
 * this will submit the job to a local lpsched using the old interface.
 * the argument vector is rebuilt with a new destination, because
 * the old name may have been an alias or because it was actually
 * lpr(1b) that was called.
 */
static void
submit_local_lp(char *program, int ac, char *av[])
{
	uid_t ruid = getuid();
	struct passwd *pw;
	int argc = 0;
	char *argv[64];


	argv[argc++] = OLD_LP;
	argv[argc++] = "-d";
	argv[argc++] = printer;

	if (strcmp(program, "lp") == 0) {
		int i = 0;

		while (++i < ac)
			if (strncmp(av[i], "-d", 2) != 0) {
				argv[argc++] = av[i];
			} else if (strlen(av[i]) == 2)
				i++;
	} else { /* convert lpr options */
		argv[argc++] = "-s";	/* supress id message */

		if (linked == 0)
			argv[argc++] = "-c";

		if (copies > 1) {
			char buf[12];
			(void) sprintf(buf, "%d", copies);
			argv[argc++] = "-n";
			argv[argc++] = strdup(buf);
		}
		if (banner == 0) {
			argv[argc++] = "-o";
			argv[argc++] = "nobanner";
		}
		if (title != NULL) {
			char buf[BUFSIZ];
			(void) sprintf(buf, "prtitle='%s'", title);
			argv[argc++] = "-y";
			argv[argc++] = strdup(buf);
		}
		if (width > 0) {
			char buf[16];
			(void) sprintf(buf, "prwidth=%d", width);
			argv[argc++] = "-y";
			argv[argc++] = strdup(buf);
		}
		if (indent > 0) {
			char buf[16];
			(void) sprintf(buf, "indent=%d", indent);
			argv[argc++] = "-y";
			argv[argc++] = strdup(buf);
		}
		if (mail != 0)
			argv[argc++] = "-m";
		if (jobName != NULL) {
			argv[argc++] = "-t";
			argv[argc++] = jobName;
		}

		if (type != CF_PRINT_ASCII) {
			struct s5_types *tmp;

			for (tmp = output_types; tmp->name != NULL; tmp++)
				if (tmp->type == type) {
					argv[argc++] = "-T";
					argv[argc++] = tmp->name;
					break;
				}
		}

		while (optind < ac)
			argv[argc++] = av[optind++];

	}

	ruid = getuid();
	if ((pw = getpwuid(ruid)) != NULL)
		(void) initgroups(pw->pw_name, pw->pw_gid);
	(void) setuid(ruid);

	argv[argc++] = NULL;
	(void) execv(OLD_LP, argv);
}
#endif


/*
 * cheat and look in the LP interface to determine if a local printer is
 * rejecting.  If so, don't queue the job.  If the printer is remote or
 * accepting, queue it.  This approximates behaviour of previous releases
 * The check is being done this way for performance.
 */
static int
rejecting(char *printer)
{
	int rc = 0, found = 0;
	FILE *fp;

	if ((fp = fopen("/usr/spool/lp/system/pstatus", "r+")) != NULL) {
		char buf[BUFSIZ];

		while (fgets(buf, sizeof (buf), fp) != NULL) {
			buf[strlen(buf)-1] = NULL;
			if (strcmp(buf, printer) == 0) {
				char *ptr;

				found = 1;
				(void) fgets(buf, sizeof (buf), fp);
				buf[strlen(buf)-1] = NULL;
				ptr = strrchr(buf, ' ');
				if (ptr && (strcmp(++ptr, "rejecting") == 0)) {
					rc = 1;
					break;
				}
			}
		}
	}
	(void) fclose(fp);

	/* if we have'nt found the name it could be a class */
	if (!found) {
		if ((fp = fopen("/usr/spool/lp/system/cstatus",
		    "r+")) != NULL) {

			char buf2[BUFSIZ];

			while (fgets(buf2, sizeof (buf2), fp) != NULL) {
				buf2[strlen(buf2)-1] = NULL;
				if (strcmp(buf2, printer) == 0) {
					fgets(buf2, sizeof (buf2), fp);
					buf2[strlen(buf2)-1] = NULL;
					if (strcmp(buf2, "rejecting") == 0) {
						rc = 1;
						break;
					}
				}
			}

		}
	}

	(void) fclose(fp);
	return (rc);
}


static int _notified = 0;
static void
error_notify(char *user, int id, char *msg, ...)
{
	if (_notified++ == 0) {
		char cmd[BUFSIZ];
		FILE *fp;
		va_list ap;

		va_start(ap, msg);
		(void) sprintf(cmd, "/bin/write %s >/dev/null 2>&1", user);
		fp = popen(cmd, "w+");
		(void) fprintf(fp,
			gettext("\n\tError transfering print job %d\n"), id);
		(void) vfprintf(fp, msg, ap);
		(void) pclose(fp);
		va_end(ap);
	}
}



/*
 *  bsd_options() parses the command line using the BSD lpr semantics and sets
 *	several global variables for use in building the print request.
 */
static void
bsd_options(int ac, char *av[])
{
	int c;

	while ((c = getopt(ac, av, "P:#:C:J:T:w:i:hplrstdgvcfmn1234")) != EOF)
		switch (c) {
		case 'P':
			printer = optarg;
			break;
		case '#':
			copies = atoi(optarg);
			break;
		case 'C':
			class = optarg;
			break;
		case 'J':
			jobName = optarg;
			break;
		case 'T':
			title = optarg;
			break;
		case 'w':
			width = atoi(optarg);
			break;
		case 'm':
			mail++;
			break;
		case 'i':	/* this may or may not have an arguement */
			if (isdigit(optarg[0]) == 0) {
				indent = 8;
				optind--;
			} else
				indent = atoi(optarg);
			break;
		case 'h':
			banner = 0;
			break;
		case 'r':
			delete = 1;
			break;
		case 's':
			linked = 1;
			break;
		case 'l' :
			type = CF_PRINT_RAW;
			break;
		case 'd' :
			type = CF_PRINT_DVI;
			break;
		case 't' :
			type = CF_PRINT_TROFF;
			break;
		case 'g' :
			type = CF_PRINT_PLOT;
			break;
		case 'v' :
			type = CF_PRINT_RAS;
			break;
		case 'c' :
			type = CF_PRINT_CIF;
			break;
		case 'f' :
			type = CF_PRINT_FORT;
			break;
		case 'n' :
			type = CF_PRINT_DROFF;
			break;
		case 'o' :
			type = CF_PRINT_PS;
			break;
		case 'p' :
			type = CF_PRINT_PR;
			break;
		case '1' :
			fontR = optarg;
			break;
		case '2' :
			fontI = optarg;
			break;
		case '3' :
			fontB = optarg;
			break;
		case '4' :
			fontB = optarg;
			break;
		default:
			(void) fprintf(stderr,
				gettext("Usage: %s [-Pprinter] [-#num] "
				"[-Cclass] [-Jjob] [-Ttitle] [-i [indent]] "
				"[-1234 font] [-wcols] [-m] [-h] [-s] "
				"[-pltndgvcf] files ...\n"),
				av[0]);
			exit(1);
		}

		/*
		 * The pr filter must be specified with the
		 * title, width, and indent options
		 */
		if ((title != NULL) && (type != CF_PRINT_PR))
			(void) fprintf(stderr, gettext(
				"Warning: title option ignored as the pr "
				"filter option was not specified\n"));
		if ((width > 0) && (type != CF_PRINT_PR))
			(void) fprintf(stderr, gettext(
				"Warning: width option ignored as the pr "
				"filter option was not specified\n"));
		if ((indent > 0) && (type != CF_PRINT_PR))
			(void) fprintf(stderr, gettext(
				"Warning: indent option ignored as the pr "
				"filter option was not specified\n"));
}

/*
 *  sysv_options() parses the command line using the BSD lpr semantics and sets
 *	several global variables for use in building the print request.
 */
static void
sysv_options(int ac, char *av[])
{
	int c;

#ifdef OLD_LP
	if ((ac > 2) && (strcmp(av[1], "-i") == 0)) {
		if (access(OLD_LP, F_OK) == 0) {
			/*
			 * limit ourselves to real user's perms before exec'ing
			 */
			(void) setuid(getuid());
			(void) execv(OLD_LP, av);
			perror("exec local modify");
		} else
			(void) printf(gettext(
				"job modification not supported on clients\n"));
		exit(-1);
	}
#endif

	linked = 1;
	suppress = 0;
	while ((c = getopt(ac, av, "H:P:S:T:d:f:i:o:q:t:y:cmwn:prs")) != EOF)
		switch (c) {
		case 'q':
			priority = atoi(optarg);
			break;
		case 'H':
			handling = optarg;
			break;
		case 'f':
			form = optarg;
			break;
		case 'd':
			printer = optarg;
			break;
		case 'T':
			{
			struct s5_types *tmp;
			int flag = 0;

			for (tmp = output_types;
			    ((flag == 0) && (tmp->name != NULL)); tmp++)
				if (strcasecmp(tmp->name, optarg) == 0) {
					type = tmp->type;
					flag++;
				}
			if (flag == 0)
				s5type = optarg;
			else
				internal_type = optarg;
			break;
			}
		case 'S':
			charset = optarg;
			break;
		case 'o':
			{
			char *p, *q = strdup(optarg);

			/*
			 * -o nobanner will no longer generate a warning or
			 * Onobanner in the control file.  If "nobanner" is
			 * embedded in an option list, the option list will
			 * still generate a warning or 'O' message in the
			 * control file.
			 */
			if (strcmp("nobanner", optarg) != 0)
				s5options = (char **)list_append(
						(void**)s5options,
						(void *)strdup(optarg));

			for (p = strtok(q, "\t ,"); p != NULL;
					p = strtok(NULL, "\t ,"))
				if (strcmp(p, "nobanner") == 0) {
					banner = 0;
					break;
				}
			}
			break;
		case 'y':
			{
			char *p, *q = strdup(optarg);

			for (p = strtok(q, "\t ,"); p != NULL;
					p = strtok(NULL, "\t ,"))
				if (strcmp(p, "catv_filter") == 0)
					type = CF_PRINT_RAW;
				else
					mode = (char **)list_append(
							    (void **)mode,
							    (void *)p);
			}
			break;
		case 'P':
			pages = optarg;
			break;
		case 'i':
			(void) printf(gettext(
			"job modification (-i) only supported on server\n"));
			break;
		case 'c':
			linked = 0;
			break;
		case 'm':
			mail++;
			break;
		case 'w':
			mail++;
			break;
		case 'p':
			notification = optarg;
			break;
		case 'n':
			if ((optarg == 0) || (*optarg == '-')) {
				(void) fprintf(stderr, gettext(
				"-n requires a positive integer argument\n"));
				exit(1);
			}
			copies = atoi(optarg);
			break;
		case 's':
			suppress = 1;
			break;
		case 't':
			jobName = optarg;
			break;
		case 'r':
			/* not supported - raw */
			break;
		default:
			(void) fprintf(stderr,
				gettext("Usage: %s [-d dest] [-cmwsr] [-n num] "
				"[-t title] [-p notification] [-P page-list] "
				"[-i job-id] [y modes] [-o options] "
				"[-S char-set] [-T input-type] [H handling] "
				"[-q priority] files ...\n"),
				av[0]);
			exit(1);
		}
}


/*
 *  stdin_to_file() reads standard input into a file and returns the file name
 *	to the caller
 */
static char *
stdin_to_file()
{
	int	fd,
		rc;
	char	*name,
		buf[BUFSIZ];

	(void) putenv("TMPDIR="); /* stop user moving the temp file */

	name = tempnam("/tmp", "stdin");
	if ((fd = open(name, O_CREAT|O_TRUNC|O_WRONLY|O_EXCL, 0640)) < 0)
		return (NULL);
	syslog(LOG_DEBUG, "stdin_to_file: %s", name);
	while ((rc = read(0, buf, sizeof (buf))) > 0)
		(void) write(fd, buf, rc);
	(void) close(fd);
	return (name);
}


static int
sendfile(jobfile_t *file, int nd, int type)
{
	int rc = -1;

	syslog(LOG_DEBUG, "sendfile(%s, %d, %d)",
		((file != NULL) ? file->jf_spl_path : "NULL"), nd, type);
	if (file && file->jf_spl_path) {
		rc = net_send_file(nd, file->jf_spl_path, file->jf_data,
				file->jf_size, type);
	}
	return (rc);
}


/*
 *  send_job() sends a job to a remote print server.
 */
static int
send_job(job_t *job)
{
	int	lockfd,
		lock_size,
		nd,
		tmp,
		rc = 0;
	struct passwd *p = NULL;
	char	buf[BUFSIZ];

	syslog(LOG_DEBUG, "send_job(%s, %s, %d): called", job->job_printer,
		job->job_server, job->job_id);
	if ((lockfd = get_lock(job->job_cf->jf_src_path, 0)) < 0) {
		(void) close(lockfd);
		return (SEND_RETRY);
	}

	/* is job complete ? */

	lock_size = file_size(job->job_cf->jf_src_path);
	(void) sprintf(buf, "%ld\n", getpid());	/* add pid to lock file */
	(void) lseek(lockfd, 0, SEEK_END);
	(void) write(lockfd, buf, strlen(buf));

	syslog(LOG_DEBUG, "send_job(%s, %s, %d): have lock", job->job_printer,
		job->job_server, job->job_id);
	connection_failed = 0;
	if ((nd = net_open(job->job_server, 5)) < 0) {
		connection_failed = 1;
		if (nd != NETWORK_ERROR_UNKNOWN)
			job_destroy(job);
		else
			(void) ftruncate(lockfd, lock_size);
		error_notify(job->job_user, job->job_id,
			gettext("\t\t check queue for (%s@%s)\n"),
			job->job_printer, job->job_server);
		(void) close(lockfd);
		return ((nd == NETWORK_ERROR_UNKNOWN) ? SEND_RETRY :
			SEND_ABORT);
	}

	if (net_send_message(nd, "%c%s\n", XFER_REQUEST, job->job_printer)
	    != 0) {
		(void) net_close(nd);
		syslog(LOG_WARNING,
			"send_job failed job %d (%s@%s) check status\n",
			job->job_id, job->job_printer, job->job_server);
		error_notify(job->job_user, job->job_id,
			gettext("\t\t check queue for (%s@%s)\n"),
			job->job_printer, job->job_server);
		(void) ftruncate(lockfd, lock_size);
		(void) close(lockfd);
		return (SEND_RETRY);
	}

	syslog(LOG_DEBUG, "send_job(%s, %s, %d): send data", job->job_printer,
		job->job_server, job->job_id);

	if ((p = getpwnam(job->job_user)) != NULL) {
		(void) setuid(0);
		/* lose our priviledge while we attempt to send the job data */
		(void) seteuid(p->pw_uid);
		(void) initgroups(job->job_user, p->pw_gid);
	}

	for (tmp = 0; job->job_df_list[tmp] != NULL; tmp++)
		if ((rc = sendfile(job->job_df_list[tmp], nd, XFER_DATA)) < 0)
			break; /* there was an error, quit now */
	tmp = errno;
	if (p != NULL) {
		/* get back the privilege we gave up earlier */
		(void) seteuid(0);
		(void) initgroups("root", 1);
	}
	errno = tmp;

	if (rc < 0) {
		if (errno == ENOENT) {
			(void) net_close(nd);
			error_notify(job->job_user, job->job_id, gettext(
				"\t\tdata removed before transfer, job "
				"canceled.\n\t\tTry \"lp -c\" or \"lpr\"\n"));
			job_destroy(job);
			(void) close(lockfd);
			return (SEND_ABORT);
		} else if (errno == EACCES) {
			/* probably trying to circumvent file security */
			(void) net_close(nd);
			error_notify(job->job_user, job->job_id, gettext(
				"\t\tunable to read job data.\n"));
			job_destroy(job);
			(void) close(lockfd);
			return (SEND_ABORT);
		} else {
			(void) net_close(nd);
			(void) ftruncate(lockfd, lock_size);
			error_notify(job->job_user, job->job_id,
				gettext("\t\t check queue for (%s@%s)\n"),
				job->job_printer, job->job_server);
			(void) close(lockfd);
			return (SEND_RETRY);
		}
	}

	if (sendfile(job->job_cf, nd, XFER_CONTROL) < 0) {
		(void) net_send_message(nd, "%c\n", XFER_CLEANUP);
		(void) net_close(nd);
		(void) ftruncate(lockfd, lock_size);
		error_notify(job->job_user, job->job_id,
			gettext("\t\t check queue for (%s@%s)\n"),
			job->job_printer, job->job_server);
		(void) close(lockfd);
		return (SEND_RETRY);
	}

	syslog(LOG_DEBUG, "send_job(%s, %s, %d): complete", job->job_printer,
		job->job_server, job->job_id);
	(void) net_close(nd);
	job_destroy(job);
	(void) close(lockfd);
	return (0);
}


/*
 *  xfer_daemon() attempts to start up a daemon for transfering jobs to a remote
 *	print server.  The daemon runs if it can get the master lock, and it
 *	runs until there are no jobs waiting for transfer.
 */
static void
xfer_daemon()
{
	job_t **list = NULL;
	int i,
	    rc;



	closelog();
	for (i = 0; i < 5; i++)
		(void) close(i);

	_notified = 1;
	(void) open("/dev/null", O_RDONLY);
	(void) open("/dev/null", O_WRONLY);
	(void) dup(1);
	if ((i = open("/dev/tty", O_RDWR)) > 0) {
		(void) ioctl(i, TIOCNOTTY, 0);
		(void) close(i);
	}

	(void) setuid(0);
	(void) setsid();
	openlog("printd", LOG_PID, LOG_LPR);
	if (fork() != 0)
		exit(0);

	if ((i = get_lock(MASTER_LOCK, 1)) < 0)
		exit(0);

	(void) chdir(SPOOL_DIR);
	while ((list = job_list_append(NULL, NULL, NULL, SPOOL_DIR)) != NULL) {
		job_t **tmp;

		syslog(LOG_DEBUG, "got the queue...");
		for (tmp = list; *tmp != NULL; tmp++) {
		/*
		 * Bugid: 4133175 printd dies when data is removed or
		 * permissions are changed.  Memory is freed twice.
		 * Fix: Do not process anything else in the list
		 * if the return code is SEND_ABORT as the memory
		 * has already been freed by job_destroy().
		 */
			rc = send_job(*tmp);
			if ((rc != 0) && (rc != SEND_ABORT)) {
				char *s = strdup((*tmp)->job_server);
				char *p = strdup((*tmp)->job_printer);

				job_free(*tmp);

				for (tmp++; ((*tmp != NULL) &&
					(strcmp(s, (*tmp)->job_server) == 0));
					tmp++)
					if ((connection_failed == 0) &&
					    (strcmp(p,
						    (*tmp)->job_printer) == 0))
						job_free(*tmp);
					else
						break;
				tmp--;
				free(s);
				free(p);
			}
		}
		free(list);

		/* look for more work to do before we sleep */
		if ((list = job_list_append(NULL, NULL, NULL,
				SPOOL_DIR)) != NULL) {
			(void) list_iterate((void **)list, (VFUNC_T)job_free);
			free(list);
			(void) sleep(60);
		}
	}
	syslog(LOG_DEBUG, "daemon exiting...");
}

static void
append_string(char *s, va_list ap)
{
	char *buf = va_arg(ap, char *);

	if (strlen(buf) != 0)
		(void) strcat(buf, " ");
	(void) strcat(buf, s);
}


static char *
build_string(char **list)
{
	int size = 0;
	char *buf = NULL;

	if (list != NULL) {
		size = list_iterate((void **)list, (VFUNC_T)strlen);
		size += 16;
		buf = malloc(size);
		(void) memset(buf, NULL, size);
		(void) list_iterate((void **)list, (VFUNC_T)append_string, buf);
	}
	return (buf);
}


#define	ADD_PRIMATIVE(job, primative, value) \
	if ((job != NULL) && (value != NULL)) \
		(void) job_primative(job, primative, value);
#define	ADD_SVR4_PRIMATIVE(job, primative, value) \
	if ((job != NULL) && (value != NULL)) (void) job_svr4_primative(job, \
							primative, value);

#define	ADD_INT_PRIMATIVE(job, primative, value, ok) \
	if ((job != NULL) && (value != ok)) { \
				(void) sprintf(buf, "%d", value); \
				(void) job_primative(job, primative, buf); \
				}
#define	ADD_SVR4_INT_PRIMATIVE(job, primative, value, ok) \
	if ((job != NULL) && (value != ok)) { \
				(void) sprintf(buf, "%d", value); \
				(void) job_svr4_primative(job, primative, \
								buf); \
				}

#define	OPTION_ERROR(option, value) \
	if (value != NULL) \
		(void) fprintf(stderr, gettext("\tignoring: %s %s\n"), \
				option, value);

#define	OPTION_ERROR_INT(option, value) \
	if (value != -1) \
		(void) fprintf(stderr, gettext("\tignoring: %s %d\n"), \
				option, value);



/*
 * Main program.  if called with "lpr" use the BSD syntax, if called
 * with "lp", use the SYSV syntax.  If called by any other name,
 * become a transfer daemon.  In the lpr/lp case, build a job and
 * attempt to send it to the print server.  If the server doesn't
 * respond, become a daemon if none is currently running and attempt
 * to xfer all waiting jobs.
 */
main(int ac, char *av[])
{
	ns_bsd_addr_t *binding = NULL;
	int	numFiles = 0,
		queueStdin = 0,
		exit_code = 0;
	char	*program,
		*user,
		hostname[128],
		buf[BUFSIZ];
	job_t *job;

	(void) setlocale(LC_ALL, "");

#if	!defined(TEXT_DOMAIN)
#define	TEXT_DOMAIN "SYS_TEST"
#endif
	(void) textdomain(TEXT_DOMAIN);

	if ((program = strrchr(av[0], '/')) == NULL)
		program = av[0];
	else
		program++;

	openlog(program, LOG_PID, LOG_LPR);

	/*
	 * Bugid: 4013980 Application changed fd 1 to a pipe that has
	 * no reader; we write to stdout and catch a sigpipe and exit.
	 * Fix: catch signal, complain to syslog, and continue.
	 */
	(void) signal(SIGPIPE, sigpipe_handler);

	if (check_client_spool(NULL) < 0) {
		(void) fprintf(stderr,
			gettext("couldn't validate local spool area (%s)\n"),
			SPOOL_DIR);
		return (-1);
	}
	if (strcmp(program, "lpr") == 0) {
		if ((printer = getenv((const char *)"PRINTER")) == NULL)
			printer = getenv((const char *)"LPDEST");
		bsd_options(ac, av);
	} else if (strcmp(program, "lp") == 0) {
		if ((printer = getenv((const char *)"LPDEST")) == NULL)
			printer = getenv((const char *)"PRINTER");
		sysv_options(ac, av);
	} else {
		xfer_daemon();
		return (0);
	}

	if (printer == NULL) {
		ns_printer_t *pobj = ns_printer_get_name(NS_NAME_DEFAULT, NULL);

		if (pobj != NULL) {
			printer = ns_get_value_string(NS_KEY_USE, pobj);
			ns_printer_destroy(pobj);
		}

		if (printer == NULL)
			printer = NS_NAME_DEFAULT;
	}

	if (printer == NULL) {
		(void) fprintf(stderr, gettext("No default destination\n"));
		return (1);
	}

	if ((binding = ns_bsd_addr_get_name(printer)) == NULL) {
		(void) fprintf(stderr, gettext("%s: unknown printer\n"),
				printer);
		return (1);
	}

	if (rejecting(binding->printer) != 0) {
		(void) fprintf(stderr, gettext(
			"%s: requests are not being accepted\n"),
			printer);
		return (1);
	}

	(void) sysinfo(SI_HOSTNAME, hostname, sizeof (hostname));
#ifdef OLD_LP
	/*
	 * If the server is local, there is lp server configuration, and
	 * the old lp is still hanging around, use it to submit the job.
	 */
	{
		char	cpath[MAXPATHLEN],
			ppath[MAXPATHLEN];

		(void) snprintf(ppath, sizeof (ppath),
			"/etc/lp/printers/%s/configuration", binding->printer);
		(void) snprintf(cpath, sizeof (cpath),
			"/etc/lp/classes/%s", binding->printer);
		if ((strcasecmp(binding->server, hostname) == 0) &&
			((access(ppath, F_OK) == 0) ||
			(access(cpath, F_OK) == 0)) &&
			(access(OLD_LP, F_OK) == 0)) {
				printer = binding->printer;
				submit_local_lp(program, ac, av);
		}
	}
#endif

	if ((job = job_create(strdup(binding->printer), strdup(binding->server),
			SPOOL_DIR)) == NULL) {
		syslog(LOG_ERR,
			"Error creating job: check spooling directory: %s",
			SPOOL_DIR);
		(void) fprintf(stderr, gettext(
			"Error creating job: check spooling directory: %s\n"),
			SPOOL_DIR);
		return (-1);
	}

	(void) umask(0);
	user = get_user_name();

	ADD_PRIMATIVE(job, CF_HOST, hostname);
	ADD_PRIMATIVE(job, CF_USER, user);
	ADD_PRIMATIVE(job, CF_TITLE, title);


	if (banner != 0) {
		if (jobName != NULL) {
			ADD_PRIMATIVE(job, CF_JOBNAME, jobName);
		} else if ((av[optind] == NULL) ||
				(strcmp(av[optind], "-") == 0)) {
			ADD_PRIMATIVE(job, CF_JOBNAME, "standard input");
		} else {
			ADD_PRIMATIVE(job, CF_JOBNAME, av[optind]);
		}
		ADD_PRIMATIVE(job, CF_CLASS, (class ? class : hostname));
		ADD_PRIMATIVE(job, CF_PRINT_BANNER, user);
	}

	if (mail != 0) {
		(void) sprintf(buf, "%s@%s", user, hostname);
		ADD_PRIMATIVE(job, CF_MAIL, buf);
	}

	ADD_INT_PRIMATIVE(job, CF_INDENT, indent, -1); /* ASCII */
	ADD_INT_PRIMATIVE(job, CF_WIDTH, width, -1);

	if ((type == CF_PRINT_DVI) || (type == CF_PRINT_DROFF) ||
	    (type == CF_PRINT_TROFF)) {
		ADD_PRIMATIVE(job, CF_FONT_TROFF_R, fontR);
		ADD_PRIMATIVE(job, CF_FONT_TROFF_I, fontI);
		ADD_PRIMATIVE(job, CF_FONT_TROFF_B, fontB);
		ADD_PRIMATIVE(job, CF_FONT_TROFF_S, fontS);
	}

	if (binding->extension == NULL)
		binding->extension = "";

	if ((strcasecmp(binding->extension, NS_EXT_SOLARIS) == 0) ||
	    (strcasecmp(binding->extension, NS_EXT_GENERIC) == 0)) {
		/* RFC1179 compliant don't get this */
		syslog(LOG_DEBUG, "main(): add Solaris extensions");
		ADD_PRIMATIVE(job, CF_SYSV_OPTION, build_string(s5options));
		ADD_SVR4_INT_PRIMATIVE(job, CF_SYSV_PRIORITY, priority, -1);
		ADD_SVR4_PRIMATIVE(job, CF_SYSV_FORM, form);
		ADD_SVR4_PRIMATIVE(job, CF_SYSV_CHARSET, charset);
		ADD_SVR4_PRIMATIVE(job, CF_SYSV_NOTIFICATION, notification);
		ADD_SVR4_PRIMATIVE(job, CF_SYSV_HANDLING, handling);
		ADD_SVR4_PRIMATIVE(job, CF_SYSV_PAGES, pages);
		if (s5type != NULL) {
			ADD_SVR4_PRIMATIVE(job, CF_SYSV_TYPE, s5type);
		} else if (internal_type != NULL)
			ADD_SVR4_PRIMATIVE(job, CF_SYSV_TYPE, internal_type);
		ADD_SVR4_PRIMATIVE(job, CF_SYSV_MODE, build_string(mode));
	} else if (strcasecmp(binding->extension, NS_EXT_HPUX) == 0) {
		syslog(LOG_DEBUG, "main(): add HP-UX extensions");
		if (s5options != NULL) {
			char buf[BUFSIZ];

			(void) sprintf(buf, " O%-.*s", sizeof (buf) - 3,
				build_string(s5options));
			ADD_PRIMATIVE(job, CF_SOURCE_NAME, buf);
		}
	} else {
		if ((s5options != NULL) || (form != NULL) || (pages != NULL) ||
		    (charset != NULL) || (notification != NULL) ||
		    (handling != NULL) || (s5type != NULL) || (mode != NULL) ||
		    (priority != -1))
			(void) fprintf(stderr, gettext(
		"Warning: %s not configured to handle all lp options:\n"),
			printer);
		OPTION_ERROR("-o", build_string(s5options));
		OPTION_ERROR("-f", form);
		OPTION_ERROR("-P", pages);
		OPTION_ERROR("-S", charset);
		OPTION_ERROR("-p", notification);
		OPTION_ERROR("-H", handling);
		OPTION_ERROR("-T", s5type);
		OPTION_ERROR("-y", build_string(mode));
		OPTION_ERROR_INT("-q", priority);
	}

	syslog(LOG_DEBUG, "main(): add files");
	if (ac-optind > 0) {
		while (optind < ac)
			if (strcmp(av[optind++], "-") == 0)
				queueStdin++;
			else if (job_add_data_file(job, av[optind-1], title,
					type, copies, linked, delete) < 0) {
				switch (errno) {
				case EISDIR:
					(void) fprintf(stderr, gettext(
						"%s: not a regular file\n"),
						av[optind-1]);
					break;
				case ESRCH:
					(void) fprintf(stderr, gettext(
						"%s: empty file\n"),
						av[optind-1]);
					break;
				case ENFILE:
					(void) fprintf(stderr, gettext(
					"too many files, ignoring %s\n"),
						av[optind-1]);
					break;
				case EOVERFLOW:
					(void) fprintf(stderr, gettext(
					"%s: largefile (>= 2GB), ignoring\n"),
						av[optind-1]);
					break;
				default:
					perror(av[optind-1]);
				}
				exit_code = -1;
			} else
				numFiles++;
	} else
		queueStdin++;

	if (queueStdin != 0) {
		char *name;

		/* standard input */
		if ((name = stdin_to_file()) != NULL) {
			if (job_add_data_file(job, name,
					gettext("standard input"),
					type, copies, 0, 0) < 0) {
				switch (errno) {
				case ESRCH:
					(void) fprintf(stderr, gettext(
						"standard input empty\n"));
					break;
				case ENFILE:
					(void) fprintf(stderr, gettext(
				"too many files, ignoring standard input\n"));
					break;
				default:
					perror(name);
				}
				exit_code = -1;
			} else
				numFiles++;
			(void) unlink(name);
			free(name);
		}
	}

	if (numFiles == 0)
		return (-1);

	if (seteuid(0) < 0)
		perror("seteuid(0)");

	(void) signal(SIGBUS, sigbus_handler);
	(void) chdir(SPOOL_DIR);
	(void) job_store(job);

	if (suppress == 0)
		if (numFiles == 1)
			(void) printf(
				gettext("request id is %s-%d (%d file)\n"),
				printer, job->job_id, numFiles);
		else
			(void) printf(
				gettext("request id is %s-%d (%d files)\n"),
				printer, job->job_id, numFiles);
	(void) fflush(stdout);

	/*
	 * bgolden 10/2/96
	 * BUG 1264627
	 * when executed from xemacs, a sighup will kill
	 * the child before the job is sent. ignore the signal
	 */
	(void) signal(SIGHUP, SIG_IGN);

	switch (fork()) {	/* for immediate response */
	case -1:
		syslog(LOG_ERR, "fork() failed: %m");
		break;
	case 0:
		break;
	default:
		return (exit_code);
	}

	if (send_job(job) == SEND_RETRY) {
		syslog(LOG_DEBUG, "main(): transfer failed");
		start_daemon(0);
	}
	else
		syslog(LOG_DEBUG, "main(): transfer succeeded");

	return (0);
}
