1/*
2   Unix SMB/CIFS implementation.
3   SMB backend for the Common UNIX Printing System ("CUPS")
4
5   Copyright (C) Michael R Sweet            1999
6   Copyright (C) Andrew Tridgell	    1994-1998
7   Copyright (C) Andrew Bartlett	    2002
8   Copyright (C) Rodrigo Fernandez-Vizarra  2005
9   Copyright (C) James Peach		    2008
10
11   This program is free software; you can redistribute it and/or modify
12   it under the terms of the GNU General Public License as published by
13   the Free Software Foundation; either version 3 of the License, or
14   (at your option) any later version.
15
16   This program is distributed in the hope that it will be useful,
17   but WITHOUT ANY WARRANTY; without even the implied warranty of
18   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19   GNU General Public License for more details.
20
21   You should have received a copy of the GNU General Public License
22   along with this program.  If not, see <http://www.gnu.org/licenses/>.
23*/
24
25#include "includes.h"
26
27/*
28 * Starting with CUPS 1.3, Kerberos support is provided by cupsd including
29 * the forwarding of user credentials via the authenticated session between
30 * user and server and the KRB5CCNAME environment variable which will point
31 * to a temporary file or an in-memory representation depending on the version
32 * of Kerberos you use.  As a result, all of the ticket code that used to
33 * live here has been removed, and we depend on the user session (if you
34 * run smbspool by hand) or cupsd to provide the necessary Kerberos info.
35 *
36 * Also, the AUTH_USERNAME and AUTH_PASSWORD environment variables provide
37 * for per-job authentication for non-Kerberized printing.  We use those
38 * if there is no username and password specified in the device URI.
39 *
40 * Finally, if we have an authentication failure we return exit code 2
41 * which tells CUPS to hold the job for authentication and bug the user
42 * to get the necessary credentials.
43 */
44
45#define MAX_RETRY_CONNECT        3
46
47
48/*
49 * Globals...
50 */
51
52
53
54/*
55 * Local functions...
56 */
57
58static int      get_exit_code(struct cli_state * cli, NTSTATUS nt_status);
59static void     list_devices(void);
60static struct cli_state *smb_complete_connection(const char *, const char *,
61	int, const char *, const char *, const char *, const char *, int, bool *need_auth);
62static struct cli_state *smb_connect(const char *, const char *, int, const
63	char *, const char *, const char *, const char *, bool *need_auth);
64static int      smb_print(struct cli_state *, char *, FILE *);
65static char    *uri_unescape_alloc(const char *);
66#if 0
67static bool     smb_encrypt;
68#endif
69
70/*
71 * 'main()' - Main entry for SMB backend.
72 */
73
74int				/* O - Exit status */
75main(int argc,			/* I - Number of command-line arguments */
76     char *argv[])
77{				/* I - Command-line arguments */
78	int             i;	/* Looping var */
79	int             copies;	/* Number of copies */
80	int             port;	/* Port number */
81	char            uri[1024],	/* URI */
82	               *sep,	/* Pointer to separator */
83	               *tmp, *tmp2,	/* Temp pointers to do escaping */
84	               *password;	/* Password */
85	char           *username,	/* Username */
86	               *server,	/* Server name */
87	               *printer;/* Printer name */
88	const char     *workgroup;	/* Workgroup */
89	FILE           *fp;	/* File to print */
90	int             status = 1;	/* Status of LPD job */
91	struct cli_state *cli;	/* SMB interface */
92	char            null_str[1];
93	int             tries = 0;
94	bool		need_auth = true;
95	const char     *dev_uri;
96	TALLOC_CTX     *frame = talloc_stackframe();
97
98	null_str[0] = '\0';
99
100	/*
101	 * we expect the URI in argv[0]. Detect the case where it is in
102	 * argv[1] and cope
103	 */
104	if (argc > 2 && strncmp(argv[0], "smb://", 6) &&
105	    strncmp(argv[1], "smb://", 6) == 0) {
106		argv++;
107		argc--;
108	}
109
110	if (argc == 1) {
111		/*
112	         * NEW!  In CUPS 1.1 the backends are run with no arguments
113		 * to list the available devices.  These can be devices
114		 * served by this backend or any other backends (i.e. you
115		 * can have an SNMP backend that is only used to enumerate
116		 * the available network printers... :)
117	         */
118
119		list_devices();
120		status = 0;
121		goto done;
122	}
123
124	if (argc < 6 || argc > 7) {
125		fprintf(stderr,
126"Usage: %s [DEVICE_URI] job-id user title copies options [file]\n"
127"       The DEVICE_URI environment variable can also contain the\n"
128"       destination printer:\n"
129"\n"
130"           smb://[username:password@][workgroup/]server[:port]/printer\n",
131			argv[0]);
132		goto done;
133	}
134
135	/*
136         * If we have 7 arguments, print the file named on the command-line.
137         * Otherwise, print data from stdin...
138         */
139
140	if (argc == 6) {
141		/*
142	         * Print from Copy stdin to a temporary file...
143	         */
144
145		fp = stdin;
146		copies = 1;
147	} else if ((fp = fopen(argv[6], "rb")) == NULL) {
148		perror("ERROR: Unable to open print file");
149		goto done;
150	} else {
151		copies = atoi(argv[4]);
152	}
153
154	/*
155         * Find the URI...
156         */
157
158	dev_uri = getenv("DEVICE_URI");
159	if (dev_uri) {
160		strncpy(uri, dev_uri, sizeof(uri) - 1);
161	} else if (strncmp(argv[0], "smb://", 6) == 0) {
162		strncpy(uri, argv[0], sizeof(uri) - 1);
163	} else {
164		fputs("ERROR: No device URI found in DEVICE_URI environment variable or argv[0] !\n", stderr);
165		goto done;
166	}
167
168	uri[sizeof(uri) - 1] = '\0';
169
170	/*
171         * Extract the destination from the URI...
172         */
173
174	if ((sep = strrchr_m(uri, '@')) != NULL) {
175		tmp = uri + 6;
176		*sep++ = '\0';
177
178		/* username is in tmp */
179
180		server = sep;
181
182		/*
183	         * Extract password as needed...
184	         */
185
186		if ((tmp2 = strchr_m(tmp, ':')) != NULL) {
187			*tmp2++ = '\0';
188			password = uri_unescape_alloc(tmp2);
189		} else {
190			password = null_str;
191		}
192		username = uri_unescape_alloc(tmp);
193	} else {
194		if ((username = getenv("AUTH_USERNAME")) == NULL) {
195			username = null_str;
196		}
197
198		if ((password = getenv("AUTH_PASSWORD")) == NULL) {
199			password = null_str;
200		}
201
202		server = uri + 6;
203	}
204
205	tmp = server;
206
207	if ((sep = strchr_m(tmp, '/')) == NULL) {
208		fputs("ERROR: Bad URI - need printer name!\n", stderr);
209		goto done;
210	}
211
212	*sep++ = '\0';
213	tmp2 = sep;
214
215	if ((sep = strchr_m(tmp2, '/')) != NULL) {
216		/*
217	         * Convert to smb://[username:password@]workgroup/server/printer...
218	         */
219
220		*sep++ = '\0';
221
222		workgroup = uri_unescape_alloc(tmp);
223		server = uri_unescape_alloc(tmp2);
224		printer = uri_unescape_alloc(sep);
225	} else {
226		workgroup = NULL;
227		server = uri_unescape_alloc(tmp);
228		printer = uri_unescape_alloc(tmp2);
229	}
230
231	if ((sep = strrchr_m(server, ':')) != NULL) {
232		*sep++ = '\0';
233
234		port = atoi(sep);
235	} else {
236		port = 0;
237	}
238
239	/*
240         * Setup the SAMBA server state...
241         */
242
243	setup_logging("smbspool", True);
244
245	lp_set_in_client(True);	/* Make sure that we tell lp_load we are */
246
247	load_case_tables();
248
249	if (!lp_load(get_dyn_CONFIGFILE(), True, False, False, True)) {
250		fprintf(stderr, "ERROR: Can't load %s - run testparm to debug it\n", get_dyn_CONFIGFILE());
251		goto done;
252	}
253
254	if (workgroup == NULL) {
255		workgroup = lp_workgroup();
256	}
257
258	load_interfaces();
259
260	do {
261		cli = smb_connect(workgroup, server, port, printer,
262			username, password, argv[2], &need_auth);
263		if (cli == NULL) {
264			if (need_auth) {
265				exit(2);
266			} else if (getenv("CLASS") == NULL) {
267				fprintf(stderr, "ERROR: Unable to connect to CIFS host, will retry in 60 seconds...\n");
268				sleep(60);
269				tries++;
270			} else {
271				fprintf(stderr, "ERROR: Unable to connect to CIFS host, trying next printer...\n");
272				goto done;
273			}
274		}
275	} while ((cli == NULL) && (tries < MAX_RETRY_CONNECT));
276
277	if (cli == NULL) {
278		fprintf(stderr, "ERROR: Unable to connect to CIFS host after (tried %d times)\n", tries);
279		goto done;
280	}
281
282	/*
283         * Now that we are connected to the server, ignore SIGTERM so that we
284         * can finish out any page data the driver sends (e.g. to eject the
285         * current page...  Only ignore SIGTERM if we are printing data from
286         * stdin (otherwise you can't cancel raw jobs...)
287         */
288
289	if (argc < 7) {
290		CatchSignal(SIGTERM, SIG_IGN);
291	}
292
293	/*
294         * Queue the job...
295         */
296
297	for (i = 0; i < copies; i++) {
298		status = smb_print(cli, argv[3] /* title */ , fp);
299		if (status != 0) {
300			break;
301		}
302	}
303
304	cli_shutdown(cli);
305
306	/*
307         * Return the queue status...
308         */
309
310done:
311
312	TALLOC_FREE(frame);
313	return (status);
314}
315
316
317/*
318 * 'get_exit_code()' - Get the backend exit code based on the current error.
319 */
320
321static int
322get_exit_code(struct cli_state * cli,
323	      NTSTATUS nt_status)
324{
325	int i;
326
327	/* List of NTSTATUS errors that are considered
328	 * authentication errors
329	 */
330	static const NTSTATUS auth_errors[] =
331	{
332		NT_STATUS_ACCESS_DENIED, NT_STATUS_ACCESS_VIOLATION,
333		NT_STATUS_SHARING_VIOLATION, NT_STATUS_PRIVILEGE_NOT_HELD,
334		NT_STATUS_INVALID_ACCOUNT_NAME, NT_STATUS_NO_SUCH_USER,
335		NT_STATUS_WRONG_PASSWORD, NT_STATUS_LOGON_FAILURE,
336		NT_STATUS_ACCOUNT_RESTRICTION, NT_STATUS_INVALID_LOGON_HOURS,
337		NT_STATUS_PASSWORD_EXPIRED, NT_STATUS_ACCOUNT_DISABLED
338	};
339
340
341	fprintf(stderr, "DEBUG: get_exit_code(cli=%p, nt_status=%x)\n",
342		cli, NT_STATUS_V(nt_status));
343
344	for (i = 0; i < ARRAY_SIZE(auth_errors); i++) {
345		if (!NT_STATUS_EQUAL(nt_status, auth_errors[i])) {
346			continue;
347		}
348
349		if (cli) {
350			if (cli->use_kerberos && cli->got_kerberos_mechanism)
351				fputs("ATTR: auth-info-required=negotiate\n", stderr);
352			else
353				fputs("ATTR: auth-info-required=username,password\n", stderr);
354		}
355
356		/*
357		 * 2 = authentication required...
358		 */
359
360		return (2);
361
362	}
363
364	/*
365         * 1 = fail
366         */
367
368	return (1);
369}
370
371
372/*
373 * 'list_devices()' - List the available printers seen on the network...
374 */
375
376static void
377list_devices(void)
378{
379	/*
380         * Eventually, search the local workgroup for available hosts and printers.
381         */
382
383	puts("network smb \"Unknown\" \"Windows Printer via SAMBA\"");
384}
385
386
387static struct cli_state *
388smb_complete_connection(const char *myname,
389			const char *server,
390			int port,
391			const char *username,
392			const char *password,
393			const char *workgroup,
394			const char *share,
395			int flags,
396			bool *need_auth)
397{
398	struct cli_state *cli;	/* New connection */
399	NTSTATUS        nt_status;
400
401	/* Start the SMB connection */
402	*need_auth = false;
403	nt_status = cli_start_connection(&cli, myname, server, NULL, port,
404					 Undefined, flags, NULL);
405	if (!NT_STATUS_IS_OK(nt_status)) {
406		fprintf(stderr, "ERROR: Connection failed: %s\n", nt_errstr(nt_status));
407		return NULL;
408	}
409
410	/*
411	 * We pretty much guarantee password must be valid or a pointer to a
412	 * 0 char.
413	 */
414	if (!password) {
415		*need_auth = true;
416		return NULL;
417	}
418
419	nt_status = cli_session_setup(cli, username,
420				      password, strlen(password) + 1,
421				      password, strlen(password) + 1,
422				      workgroup);
423	if (!NT_STATUS_IS_OK(nt_status)) {
424		fprintf(stderr, "ERROR: Session setup failed: %s\n", nt_errstr(nt_status));
425
426		if (get_exit_code(cli, nt_status) == 2) {
427			*need_auth = true;
428		}
429
430		cli_shutdown(cli);
431
432		return NULL;
433	}
434
435	nt_status = cli_tcon_andx(cli, share, "?????", password,
436				  strlen(password) + 1);
437	if (!NT_STATUS_IS_OK(nt_status)) {
438		fprintf(stderr, "ERROR: Tree connect failed (%s)\n",
439			nt_errstr(nt_status));
440
441		if (get_exit_code(cli, nt_status) == 2) {
442			*need_auth = true;
443		}
444
445		cli_shutdown(cli);
446
447		return NULL;
448	}
449#if 0
450	/* Need to work out how to specify this on the URL. */
451	if (smb_encrypt) {
452		if (!cli_cm_force_encryption(cli,
453					     username,
454					     password,
455					     workgroup,
456					     share)) {
457			fprintf(stderr, "ERROR: encryption setup failed\n");
458			cli_shutdown(cli);
459			return NULL;
460		}
461	}
462#endif
463
464	return cli;
465}
466
467/*
468 * 'smb_connect()' - Return a connection to a server.
469 */
470
471static struct cli_state *	/* O - SMB connection */
472smb_connect(const char *workgroup,	/* I - Workgroup */
473	    const char *server,	/* I - Server */
474	    const int port,	/* I - Port */
475	    const char *share,	/* I - Printer */
476	    const char *username,	/* I - Username */
477	    const char *password,	/* I - Password */
478	    const char *jobusername,	/* I - User who issued the print job */
479	    bool *need_auth)
480{				/* O - Need authentication? */
481	struct cli_state *cli;	/* New connection */
482	char           *myname = NULL;	/* Client name */
483	struct passwd  *pwd;
484
485	/*
486         * Get the names and addresses of the client and server...
487         */
488	myname = get_myname(talloc_tos());
489	if (!myname) {
490		return NULL;
491	}
492
493	/*
494	 * See if we have a username first.  This is for backwards compatible
495	 * behavior with 3.0.14a
496	 */
497
498	if (username && *username && !getenv("KRB5CCNAME")) {
499		cli = smb_complete_connection(myname, server, port, username,
500				    password, workgroup, share, 0, need_auth);
501		if (cli) {
502			fputs("DEBUG: Connected with username/password...\n", stderr);
503			return (cli);
504		}
505	}
506
507	/*
508	 * Try to use the user kerberos credentials (if any) to authenticate
509	 */
510	cli = smb_complete_connection(myname, server, port, jobusername, "",
511				      workgroup, share,
512				 CLI_FULL_CONNECTION_USE_KERBEROS, need_auth);
513
514	if (cli) {
515		fputs("DEBUG: Connected using Kerberos...\n", stderr);
516		return (cli);
517	}
518
519	/* give a chance for a passwordless NTLMSSP session setup */
520	pwd = getpwuid(geteuid());
521	if (pwd == NULL) {
522		return NULL;
523	}
524
525	cli = smb_complete_connection(myname, server, port, pwd->pw_name, "",
526				      workgroup, share, 0, need_auth);
527
528	if (cli) {
529		fputs("DEBUG: Connected with NTLMSSP...\n", stderr);
530		return (cli);
531	}
532
533	/*
534         * last try. Use anonymous authentication
535         */
536
537	cli = smb_complete_connection(myname, server, port, "", "",
538				      workgroup, share, 0, need_auth);
539	/*
540         * Return the new connection...
541         */
542
543	return (cli);
544}
545
546
547/*
548 * 'smb_print()' - Queue a job for printing using the SMB protocol.
549 */
550
551static int			/* O - 0 = success, non-0 = failure */
552smb_print(struct cli_state * cli,	/* I - SMB connection */
553	  char *title,		/* I - Title/job name */
554	  FILE * fp)
555{				/* I - File to print */
556	uint16_t             fnum;	/* File number */
557	int             nbytes,	/* Number of bytes read */
558	                tbytes;	/* Total bytes read */
559	char            buffer[8192],	/* Buffer for copy */
560	               *ptr;	/* Pointer into title */
561
562
563	/*
564         * Sanitize the title...
565         */
566
567	for (ptr = title; *ptr; ptr++) {
568		if (!isalnum((int) *ptr) && !isspace((int) *ptr)) {
569			*ptr = '_';
570		}
571	}
572
573	/*
574         * Open the printer device...
575         */
576
577	if (!NT_STATUS_IS_OK(cli_open(cli, title, O_RDWR | O_CREAT | O_TRUNC, DENY_NONE, &fnum))) {
578		fprintf(stderr, "ERROR: %s opening remote spool %s\n",
579			cli_errstr(cli), title);
580		return (get_exit_code(cli, cli_nt_error(cli)));
581	}
582
583	/*
584         * Copy the file to the printer...
585         */
586
587	if (fp != stdin)
588		rewind(fp);
589
590	tbytes = 0;
591
592	while ((nbytes = fread(buffer, 1, sizeof(buffer), fp)) > 0) {
593		if (cli_write(cli, fnum, 0, buffer, tbytes, nbytes) != nbytes) {
594			int status = get_exit_code(cli, cli_nt_error(cli));
595
596			fprintf(stderr, "ERROR: Error writing spool: %s\n", cli_errstr(cli));
597			fprintf(stderr, "DEBUG: Returning status %d...\n", status);
598			cli_close(cli, fnum);
599
600			return (status);
601		}
602		tbytes += nbytes;
603	}
604
605	if (!NT_STATUS_IS_OK(cli_close(cli, fnum))) {
606		fprintf(stderr, "ERROR: %s closing remote spool %s\n",
607			cli_errstr(cli), title);
608		return (get_exit_code(cli, cli_nt_error(cli)));
609	} else {
610		return (0);
611	}
612}
613
614static char *
615uri_unescape_alloc(const char *uritok)
616{
617	char *ret;
618
619	ret = (char *) SMB_STRDUP(uritok);
620	if (!ret) {
621		return NULL;
622	}
623
624	rfc1738_unescape(ret);
625	return ret;
626}
627