c80ad42dd914c507379c7efc956cf01a628349dd
[kai/samba.git] / source3 / client / smbspool.c
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
58 static int      get_exit_code(struct cli_state * cli, NTSTATUS nt_status);
59 static void     list_devices(void);
60 static struct cli_state *smb_complete_connection(const char *, const char *,
61         int, const char *, const char *, const char *, const char *, int, bool *need_auth);
62 static struct cli_state *smb_connect(const char *, const char *, int, const
63         char *, const char *, const char *, const char *, bool *need_auth);
64 static int      smb_print(struct cli_state *, char *, FILE *);
65 static char    *uri_unescape_alloc(const char *);
66 #if 0
67 static bool     smb_encrypt;
68 #endif
69
70 /*
71  * 'main()' - Main entry for SMB backend.
72  */
73
74 int                             /* O - Exit status */
75 main(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", DEBUG_STDOUT);
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
310 done:
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
321 static int
322 get_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
376 static void
377 list_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
387 static struct cli_state *
388 smb_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);
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
471 static struct cli_state *       /* O - SMB connection */
472 smb_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
551 static int                      /* O - 0 = success, non-0 = failure */
552 smb_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         NTSTATUS nt_status;
562
563
564         /*
565          * Sanitize the title...
566          */
567
568         for (ptr = title; *ptr; ptr++) {
569                 if (!isalnum((int) *ptr) && !isspace((int) *ptr)) {
570                         *ptr = '_';
571                 }
572         }
573
574         /*
575          * Open the printer device...
576          */
577
578         nt_status = cli_open(cli, title, O_RDWR | O_CREAT | O_TRUNC, DENY_NONE,
579                           &fnum);
580         if (!NT_STATUS_IS_OK(nt_status)) {
581                 fprintf(stderr, "ERROR: %s opening remote spool %s\n",
582                         nt_errstr(nt_status), title);
583                 return get_exit_code(cli, nt_status);
584         }
585
586         /*
587          * Copy the file to the printer...
588          */
589
590         if (fp != stdin)
591                 rewind(fp);
592
593         tbytes = 0;
594
595         while ((nbytes = fread(buffer, 1, sizeof(buffer), fp)) > 0) {
596                 if (cli_write(cli, fnum, 0, buffer, tbytes, nbytes) != nbytes) {
597                         int status = get_exit_code(cli, cli_nt_error(cli));
598
599                         fprintf(stderr, "ERROR: Error writing spool: %s\n", cli_errstr(cli));
600                         fprintf(stderr, "DEBUG: Returning status %d...\n", status);
601                         cli_close(cli, fnum);
602
603                         return (status);
604                 }
605                 tbytes += nbytes;
606         }
607
608         nt_status = cli_close(cli, fnum);
609         if (!NT_STATUS_IS_OK(nt_status)) {
610                 fprintf(stderr, "ERROR: %s closing remote spool %s\n",
611                         nt_errstr(nt_status), title);
612                 return get_exit_code(cli, nt_status);
613         } else {
614                 return (0);
615         }
616 }
617
618 static char *
619 uri_unescape_alloc(const char *uritok)
620 {
621         char *ret;
622
623         ret = (char *) SMB_STRDUP(uritok);
624         if (!ret) {
625                 return NULL;
626         }
627
628         rfc1738_unescape(ret);
629         return ret;
630 }