1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21/*
22 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 */
25
26#include <stdio.h>
27#include <stdlib.h>
28#include <libintl.h>
29#include <locale.h>
30#include <signal.h>
31#include <errno.h>
32#include <string.h>
33#include <unistd.h>
34#include <sys/mman.h>
35#include <sys/socket.h>
36#include <netinet/in.h>
37#include <arpa/inet.h>
38#include <netdb.h>
39#include <fcntl.h>
40#include <syslog.h>
41#include <sys/utsname.h>
42#include "netpr.h"
43
44
45static void usage_exit();
46
47static void pipehandler(int);
48char data_file_type = 0;
49
50/*
51 *  null() is to be used as a signal handler that does nothing.  It is used in
52 *      place of SIG_IGN, because we want the signal to be delivered and
53 *      interupt the current system call.
54 */
55static void
56null(int i)
57{
58	syslog(LOG_DEBUG, "null(%d)", i);
59}
60
61/*
62 *  net_open() opens a tcp connection to the printer port on the host specified
63 *      in the arguments passed in.  If the connection is not made in the
64 *      timeout (in seconds) passed in, an error it returned.  If the host is
65 *      unknown, an error is returned.  If all is well, a file descriptor is
66 *      returned to be used for future communications.
67 */
68int
69net_open(char *host, int timeout)
70{
71	struct hostent *hp;
72	struct servent *sp;
73	struct sockaddr_in6 sin;
74	void (*old_handler)();
75	static struct utsname uts;
76
77	int s;
78	int lport;
79	int err;
80	int error_num;
81	unsigned timo = 1;
82
83	syslog(LOG_DEBUG, "net_open(%s, %d)", (host != NULL ? host : "NULL"),
84	    timeout);
85	/*
86	 * Get the host address and port number to connect to.
87	 */
88	if (host == NULL) {
89		return (-1);
90	}
91
92	(void) memset((char *)&sin, 0, sizeof (sin));
93	if ((hp = getipnodebyname(host, AF_INET6, AI_DEFAULT,
94	    &error_num)) == NULL) {
95		syslog(LOG_DEBUG|LOG_ERR, "unknown host %s "
96		    "getipnodebyname() returned %d", host, error_num);
97		return (NETWORK_ERROR_HOST);
98	}
99	(void) memcpy((caddr_t)&sin.sin6_addr, hp->h_addr, hp->h_length);
100	sin.sin6_family = hp->h_addrtype;
101	freehostent(hp);
102
103	if ((sp = getservbyname("printer", "tcp")) == NULL) {
104		syslog(LOG_DEBUG|LOG_ERR, "printer/tcp: unknown service");
105		return (NETWORK_ERROR_SERVICE);
106	}
107	sin.sin6_port = sp->s_port;
108
109retry:
110	/*
111	 * Try connecting to the server.
112	 *
113	 * Use 0 as lport means that rresvport_af() will bind to a port in
114	 * the anonymous privileged port range.
115	 */
116	lport = 0;
117	s = rresvport_af(&lport, AF_INET6);
118	if (s < 0)
119		return (NETWORK_ERROR_PORT);
120
121	old_handler = signal(SIGALRM, null);
122	(void) alarm(timeout);
123	if (connect(s, (struct sockaddr *)&sin, sizeof (sin)) < 0) {
124		(void) alarm(0);
125		(void) signal(SIGALRM, old_handler);
126		err = errno;
127		(void) close(s);
128		errno = err;
129		if (errno == EADDRINUSE) {
130			goto retry;
131		}
132		/*
133		 * If connecting to the local system fails, try
134		 * again with "localhost" address instead.
135		 */
136		if (uts.nodename[0] == '\0')
137			(void) uname(&uts);
138		if (strcmp(host, uts.nodename) == 0) {
139			IN6_IPADDR_TO_V4MAPPED(htonl(INADDR_LOOPBACK),
140			    &sin.sin6_addr);
141			sin.sin6_family = AF_INET6;
142			goto retry;
143		}
144		if (errno == ECONNREFUSED && timo <= 16) {
145			(void) sleep(timo);
146			timo *= 2;
147			goto retry;
148		}
149		return (NETWORK_ERROR_UNKNOWN);
150	}
151	(void) alarm(0);
152	(void) signal(SIGALRM, old_handler);
153	return (s);
154}
155
156int
157main(int argc, char *argv[])
158{
159	extern char *optarg;
160	extern int optind;
161	int opt;
162	np_job_t *job_data;
163	char *destination = NULL;
164	np_bsdjob_t *bsdjob;
165	np_tcpjob_t *tcpjob;
166	int sockfd;
167	int pr_order = CONTROL_FIRST;
168	char *vendor_pr_name = NULL;
169	char *tcp_port = NULL;
170	size_t filesize;
171	int fd;
172	caddr_t pa;
173	int jobstatus;
174	int exit_status = 0;
175	int on = 1;
176
177
178	(void) setlocale(LC_ALL, "");
179#if	!defined(TEXT_DOMAIN)   /* Should be defined by cc -D */
180#define	TEXT_DOMAIN "SYS_TEST"  /* Use this only if it weren't */
181#endif
182	(void) textdomain(TEXT_DOMAIN);
183
184	openlog("netpr", LOG_PID, LOG_LPR);
185	(void) signal(SIGPIPE, pipehandler);
186
187	/* reduce privileges until needed to open reserved port */
188	if (seteuid(getuid())) {
189		syslog(LOG_DEBUG, "seteuid failed, exiting netpr");
190		exit(E_FAILURE);
191	}
192
193	if ((job_data = init_job()) == NULL) {
194		fprintf(stderr, gettext("init_job(): out of memory\n"));
195		exit(E_RETRY);
196	}
197
198	while ((opt = getopt(argc, argv, "f:I:p:d:T:P:t:U:c:b")) != EOF)
199		switch (opt) {
200		case 'f':
201			data_file_type = optarg[0];
202			break;
203		case 'I': /* foo-49 */
204			job_data->request_id = alloc_str((char *)optarg);
205			syslog(LOG_DEBUG, "request_id: %s",
206			    job_data->request_id);
207			break;
208		case 'U': /* awe172-126!wendyp */
209			job_data->username = alloc_str((char *)optarg);
210			syslog(LOG_DEBUG, "username: %s", job_data->username);
211			break;
212		case 'p': /* foo */
213			job_data->printer = alloc_str((char *)optarg);
214			syslog(LOG_DEBUG, "printer: %s", job_data->printer);
215			break;
216		case 'd': /* server for printer */
217			job_data->dest = alloc_str((char *)optarg);
218			syslog(LOG_DEBUG, "dest: %s", job_data->dest);
219			break;
220		case 'T': /* /tmp/file2 */
221			job_data->title = alloc_str((char *)optarg);
222			syslog(LOG_DEBUG, "title: %s", job_data->title);
223			break;
224		case 'P':
225			if ((strcmp(optarg, "bsd")) == 0)
226				job_data->protocol = BSD;
227			else if ((strcmp(optarg, "tcp")) == 0)
228				job_data->protocol = TCP;
229			else
230				usage_exit();
231
232			syslog(LOG_DEBUG, "protocol: %d", job_data->protocol);
233			break;
234		case 't':
235			job_data->timeout = atoi(optarg);
236			if (job_data->timeout < 0)
237				usage_exit();
238			break;
239		case 'c':
240			if ((strcmp(optarg, "first")) == 0)
241				pr_order = CONTROL_FIRST;
242			else if ((strcmp(optarg, "last")) == 0)
243				pr_order = DATA_FIRST;
244			else
245				usage_exit();
246
247			syslog(LOG_DEBUG, "bsd print order: %d", pr_order);
248			break;
249		case 'b':
250			job_data->banner = NOBANNER;
251			syslog(LOG_DEBUG, "banner : %d", job_data->banner);
252			break;
253		case '?':
254			usage_exit();
255		}
256
257
258	if ((job_data->dest == NULL) || (job_data->request_id == NULL) ||
259	    (job_data->printer == NULL) || (job_data->username == NULL))
260		usage_exit();
261
262	/*
263	 * Check that there is a file
264	 */
265	if (optind == argc) {
266		usage_exit();
267	}
268
269	job_data->filename = alloc_str(argv[optind]);
270	syslog(LOG_DEBUG, "filename : %s", job_data->filename);
271
272
273	/*
274	 * Sanity check the file
275	 * returns filesize
276	 */
277
278	if ((filesize = check_file(job_data->filename)) == -1) {
279		syslog(LOG_DEBUG, "Skipping file %s",
280		    job_data->filename ?
281		    job_data->filename : "Error NULL file");
282
283		switch (errno) {
284		case EISDIR:
285			(void) fprintf(stderr,
286			    gettext("Netpr: %s: Not a regular file\n"),
287			    job_data->filename ?
288			    job_data->filename : "Noname");
289			syslog(LOG_DEBUG, "Not a regular file");
290			break;
291		case ESRCH:
292			(void) fprintf(stderr,
293			    gettext("Netpr: %s: Empty file\n"),
294			    job_data->filename ?
295			    job_data->filename : "Noname");
296			syslog(LOG_DEBUG, "Empty file");
297			break;
298		default:
299			perror(job_data->filename);
300			(void) fprintf(stderr,
301			    gettext("Netpr: Cannot access file %s\n"),
302			    job_data->filename ?
303			    job_data->filename : "Noname");
304			syslog(LOG_DEBUG, "Cannot access file.");
305			break;
306
307		}
308
309		/*
310		 * This file not valid, so bail
311		 * Exit with zero so system will keep printing
312		 */
313		exit(0);
314	}
315
316	/*
317	 * file looks ok, open and mmap it
318	 */
319	if ((fd = open(job_data->filename, O_RDONLY)) < 0) {
320		(void) fprintf(stderr, gettext("Netpr: Cannot open file %s\n"),
321		    job_data->filename ?
322		    job_data->filename : "Error: NULL file");
323		syslog(LOG_DEBUG, "Cannot open file: %s",
324		    job_data->filename ?
325		    job_data->filename : "Error NULL file");
326		exit(E_BAD_FILE);
327	}
328
329	if ((pa = mmap((caddr_t)0, filesize, PROT_READ,
330	    (MAP_SHARED | MAP_NORESERVE), fd, (off_t)0)) == MAP_FAILED) {
331
332		(void) close(fd);
333		(void) fprintf(stderr, gettext("Netpr: Cannot mmap file %s"),
334		    job_data->filename ?
335		    job_data->filename : "Error: NULL file");
336
337		syslog(LOG_DEBUG, "Cannot mmap file: %s",
338		    job_data->filename ?
339		    job_data->filename : "Error NULL file");
340
341		exit(E_RETRY);
342	}
343
344
345	if (job_data->protocol == BSD) {
346		bsdjob = (np_bsdjob_t *)
347		    create_bsd_job(job_data, pr_order, filesize);
348		if (bsdjob == NULL)
349			exit(E_FAILURE);
350	} else {
351		tcpjob = (np_tcpjob_t *)create_tcp_job(job_data, filesize);
352		if (tcpjob == NULL)
353			exit(E_FAILURE);
354	}
355
356	/*
357	 * Parse destination
358	 */
359
360	if ((strpbrk(job_data->dest, DEST_SEP)) != NULL) {
361		if (job_data->protocol == BSD) {
362			parse_dest(job_data->dest, &destination,
363			    &vendor_pr_name, DEST_SEP);
364			if (vendor_pr_name != NULL) {
365				bsdjob->np_printer = vendor_pr_name;
366				syslog(LOG_DEBUG, "bsd vendor name: %s",
367				    bsdjob->np_printer);
368			}
369		} else {
370			parse_dest(job_data->dest, &destination, &tcp_port,
371			    DEST_SEP);
372			if (tcp_port != NULL)
373				tcpjob->np_port = tcp_port;
374			syslog(LOG_DEBUG, "tcp_port %s", tcpjob->np_port);
375		}
376		if (destination == NULL ||
377		    (job_data->protocol == TCP && tcp_port == NULL)) {
378			(void) fprintf(stderr, gettext("Netpr: system error "
379			    "parsing destination %s\n"), job_data->dest);
380			syslog(LOG_DEBUG, "system error parsing destination %s",
381			    job_data->dest);
382
383			exit(E_FAILURE);
384		}
385
386	} else {
387		destination = job_data->dest;
388	}
389	syslog(LOG_DEBUG, "destination : %s", destination);
390
391	/*
392	 * We are now ready to open a connection to the printer
393	 * and print each of the files
394	 */
395
396	if (job_data->protocol == BSD) {
397
398		/* set privileges to get reserved port */
399		if (seteuid(0)) {
400			syslog(LOG_DEBUG, "seteuid(0) failed, exiting netpr");
401			exit(E_FAILURE);
402		}
403		if ((sockfd =  net_open(destination, 20)) < 0) {
404			(void) fprintf(stderr,
405			    gettext("Netpr: Cannot open connection to <%s>\n"),
406			    destination);
407			syslog(LOG_DEBUG,
408			    "Cannot open connection to %s: retrying",
409			    destination);
410			exit(E_RETRY);
411		}
412	} else {
413		if ((sockfd = tcp_open(destination, tcpjob, 20)) == -1) {
414			exit(E_RETRY);
415		}
416	}
417
418	/* lower privileges as we now have the reserved port */
419	if (setuid(getuid())) {
420		syslog(LOG_DEBUG, "setuid() failed, exiting netpr");
421		exit(E_FAILURE);
422	}
423
424
425	/* Set SO_KEEPALIVE on socket to keep open */
426	if ((setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE,
427	    (char *)&on, sizeof (on))) < 0) {
428		syslog(LOG_DEBUG, "setsocket (SO_KEEPALIVE): %m");
429	}
430
431	if (job_data->protocol == BSD) {
432		if ((jobstatus = bsd_print(sockfd, pa,  bsdjob)) != 0) {
433			(void) fprintf(stderr, gettext("Netpr: Error return "
434			    "from bsd_print <%d>\n"), jobstatus);
435			syslog(LOG_DEBUG,
436			    "Error return from bsd_print <%d>", jobstatus);
437			exit_status = E_RETRY;
438		}
439	} else {
440		if ((jobstatus =
441		    tcp_print(sockfd, pa, tcpjob)) != 0) {
442			(void) fprintf(stderr, gettext("Netpr: Error return "
443			    "from tcp_print <%d>\n"), jobstatus);
444			syslog(LOG_DEBUG,
445			    "Error return from tcp_print <%d>", jobstatus);
446			exit_status = E_RETRY;
447		}
448	}
449
450	(void) close(fd);
451	(void) close(sockfd);
452	(void) munmap(pa, filesize);
453
454	syslog(LOG_DEBUG, "exit status: %d", exit_status);
455	return (exit_status);
456}
457
458static void
459usage_exit()
460{
461	(void) fprintf(stderr,
462	gettext("Usage: netpr -I request_id -p printer -d destination\n"));
463	(void) fprintf(stderr,
464	gettext("\t\t-U username [ -f type ] [ -T title ] [ -P protocol ]\n"));
465	(void) fprintf(stderr,
466	    gettext("\t\t[-t timeout] [ -c ] [ -b ]\n"));
467	(void) fprintf(stderr, gettext("\t\tfiles\n"));
468	exit(E_BAD_INPUT);
469}
470
471/*ARGSUSED*/
472void
473pipehandler(int i)
474{
475	(void) signal(SIGPIPE, pipehandler);
476	syslog(LOG_DEBUG, "Received SIGPIPE, connection to printer broken");
477	exit(E_SIGPIPE);
478}
479