4f312a6d2949efd02537bf73a033977af2b41657
[metze/samba/wip.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 #include "system/filesys.h"
27 #include "system/passwd.h"
28 #include "system/kerberos.h"
29 #include "libsmb/libsmb.h"
30 #include "lib/param/param.h"
31 #include "lib/krb5_wrap/krb5_samba.h"
32
33 /*
34  * Starting with CUPS 1.3, Kerberos support is provided by cupsd including
35  * the forwarding of user credentials via the authenticated session between
36  * user and server and the KRB5CCNAME environment variable which will point
37  * to a temporary file or an in-memory representation depending on the version
38  * of Kerberos you use.  As a result, all of the ticket code that used to
39  * live here has been removed, and we depend on the user session (if you
40  * run smbspool by hand) or cupsd to provide the necessary Kerberos info.
41  *
42  * Also, the AUTH_USERNAME and AUTH_PASSWORD environment variables provide
43  * for per-job authentication for non-Kerberized printing.  We use those
44  * if there is no username and password specified in the device URI.
45  *
46  * Finally, if we have an authentication failure we return exit code 2
47  * which tells CUPS to hold the job for authentication and bug the user
48  * to get the necessary credentials.
49  */
50
51 #define MAX_RETRY_CONNECT        3
52
53
54 /*
55  * Globals...
56  */
57
58
59
60 /*
61  * Local functions...
62  */
63
64 static int      get_exit_code(struct cli_state * cli, NTSTATUS nt_status, bool use_kerberos);
65 static void     list_devices(void);
66 static struct cli_state *smb_complete_connection(const char *, const char *,
67         int, const char *, const char *, const char *, const char *, int, bool *need_auth);
68 static struct cli_state *smb_connect(const char *, const char *, int, const
69         char *, const char *, const char *, const char *, bool *need_auth);
70 static int      smb_print(struct cli_state *, char *, FILE *);
71 static char    *uri_unescape_alloc(const char *);
72 #if 0
73 static bool     smb_encrypt;
74 #endif
75
76 /*
77  * 'main()' - Main entry for SMB backend.
78  */
79
80 int                             /* O - Exit status */
81 main(int argc,                  /* I - Number of command-line arguments */
82      char *argv[])
83 {                               /* I - Command-line arguments */
84         int             i;      /* Looping var */
85         int             copies; /* Number of copies */
86         int             port;   /* Port number */
87         char            uri[1024],      /* URI */
88                        *sep,    /* Pointer to separator */
89                        *tmp, *tmp2,     /* Temp pointers to do escaping */
90                        *password;       /* Password */
91         char           *username,       /* Username */
92                        *server, /* Server name */
93                        *printer;/* Printer name */
94         const char     *workgroup;      /* Workgroup */
95         FILE           *fp;     /* File to print */
96         int             status = 1;     /* Status of LPD job */
97         struct cli_state *cli;  /* SMB interface */
98         char            empty_str[] = "";
99         int             tries = 0;
100         bool            need_auth = true;
101         const char     *dev_uri;
102         const char     *config_file = NULL;
103         TALLOC_CTX     *frame = talloc_stackframe();
104         bool device_uri_cmdline = false;
105         const char *print_file = NULL;
106         const char *print_copies = NULL;
107         int cmp;
108         int len;
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         /*
125          * We need at least 5 options if the DEVICE_URI is passed via an env
126          * variable and printing data comes via stdin.
127          * We don't accept more than 7 options in total, including optional.
128          */
129         if (argc < 5 || argc > 8) {
130                 fprintf(stderr,
131 "Usage: %s [DEVICE_URI] job-id user title copies options [file]\n"
132 "       The DEVICE_URI environment variable can also contain the\n"
133 "       destination printer:\n"
134 "\n"
135 "           smb://[username:password@][workgroup/]server[:port]/printer\n",
136                         argv[0]);
137                 goto done;
138         }
139
140         /*
141          * If we have 6 arguments find out if we have the device_uri from the
142          * command line or the print data
143          */
144         if (argc == 7) {
145                 cmp = strncmp(argv[1], "smb://", 6);
146                 if (cmp == 0) {
147                         device_uri_cmdline = true;
148                 } else {
149                         print_copies = argv[4];
150                         print_file = argv[6];
151                 }
152         } else if (argc == 8) {
153                 device_uri_cmdline = true;
154                 print_copies = argv[5];
155                 print_file = argv[7];
156         }
157
158         if (print_file != NULL) {
159                 char *endp;
160
161                 fp = fopen(print_file, "rb");
162                 if (fp == NULL) {
163                         perror("ERROR: Unable to open print file");
164                         goto done;
165                 }
166
167                 copies = strtol(print_copies, &endp, 10);
168                 if (print_copies == endp) {
169                         perror("ERROR: Unable to determine number of copies");
170                         goto done;
171                 }
172         } else {
173                 fp = stdin;
174                 copies = 1;
175         }
176
177         /*
178          * Find the URI ...
179          */
180         if (device_uri_cmdline) {
181                 dev_uri = argv[1];
182         } else {
183                 dev_uri = getenv("DEVICE_URI");
184                 if (dev_uri == NULL || strlen(dev_uri) == 0) {
185                         dev_uri = "";
186                 }
187         }
188
189         cmp = strncmp(dev_uri, "smb://", 6);
190         if (cmp != 0) {
191                 fprintf(stderr,
192                         "ERROR: No valid device URI has been specified\n");
193                 goto done;
194         }
195         len = snprintf(uri, sizeof(uri), "%s", dev_uri);
196         if (len >= sizeof(uri)) {
197                 fprintf(stderr,
198                         "ERROR: The URI is too long.\n");
199                 goto done;
200         }
201
202         /*
203          * Extract the destination from the URI...
204          */
205
206         if ((sep = strrchr_m(uri, '@')) != NULL) {
207                 tmp = uri + 6;
208                 *sep++ = '\0';
209
210                 /* username is in tmp */
211
212                 server = sep;
213
214                 /*
215                  * Extract password as needed...
216                  */
217
218                 if ((tmp2 = strchr_m(tmp, ':')) != NULL) {
219                         *tmp2++ = '\0';
220                         password = uri_unescape_alloc(tmp2);
221                 } else {
222                         password = empty_str;
223                 }
224                 username = uri_unescape_alloc(tmp);
225         } else {
226                 if ((username = getenv("AUTH_USERNAME")) == NULL) {
227                         username = empty_str;
228                 }
229
230                 if ((password = getenv("AUTH_PASSWORD")) == NULL) {
231                         password = empty_str;
232                 }
233
234                 server = uri + 6;
235         }
236
237         tmp = server;
238
239         if ((sep = strchr_m(tmp, '/')) == NULL) {
240                 fputs("ERROR: Bad URI - need printer name!\n", stderr);
241                 goto done;
242         }
243
244         *sep++ = '\0';
245         tmp2 = sep;
246
247         if ((sep = strchr_m(tmp2, '/')) != NULL) {
248                 /*
249                  * Convert to smb://[username:password@]workgroup/server/printer...
250                  */
251
252                 *sep++ = '\0';
253
254                 workgroup = uri_unescape_alloc(tmp);
255                 server = uri_unescape_alloc(tmp2);
256                 printer = uri_unescape_alloc(sep);
257         } else {
258                 workgroup = NULL;
259                 server = uri_unescape_alloc(tmp);
260                 printer = uri_unescape_alloc(tmp2);
261         }
262
263         if ((sep = strrchr_m(server, ':')) != NULL) {
264                 *sep++ = '\0';
265
266                 port = atoi(sep);
267         } else {
268                 port = 0;
269         }
270
271         /*
272          * Setup the SAMBA server state...
273          */
274
275         setup_logging("smbspool", DEBUG_STDERR);
276
277         smb_init_locale();
278
279         config_file = lp_default_path();
280         if (!lp_load_client(config_file)) {
281                 fprintf(stderr,
282                         "ERROR: Can't load %s - run testparm to debug it\n",
283                         config_file);
284                 goto done;
285         }
286
287         if (workgroup == NULL) {
288                 workgroup = lp_workgroup();
289         }
290
291         load_interfaces();
292
293         do {
294                 cli = smb_connect(workgroup, server, port, printer,
295                         username, password, argv[3], &need_auth);
296                 if (cli == NULL) {
297                         if (need_auth) {
298                                 exit(2);
299                         } else if (getenv("CLASS") == NULL) {
300                                 fprintf(stderr, "ERROR: Unable to connect to CIFS host, will retry in 60 seconds...\n");
301                                 sleep(60);
302                                 tries++;
303                         } else {
304                                 fprintf(stderr, "ERROR: Unable to connect to CIFS host, trying next printer...\n");
305                                 goto done;
306                         }
307                 }
308         } while ((cli == NULL) && (tries < MAX_RETRY_CONNECT));
309
310         if (cli == NULL) {
311                 fprintf(stderr, "ERROR: Unable to connect to CIFS host after (tried %d times)\n", tries);
312                 goto done;
313         }
314
315         /*
316          * Now that we are connected to the server, ignore SIGTERM so that we
317          * can finish out any page data the driver sends (e.g. to eject the
318          * current page...  Only ignore SIGTERM if we are printing data from
319          * stdin (otherwise you can't cancel raw jobs...)
320          */
321
322         if (argc < 7) {
323                 CatchSignal(SIGTERM, SIG_IGN);
324         }
325
326         /*
327          * Queue the job...
328          */
329
330         for (i = 0; i < copies; i++) {
331                 status = smb_print(cli, argv[4] /* title */ , fp);
332                 if (status != 0) {
333                         break;
334                 }
335         }
336
337         cli_shutdown(cli);
338
339         /*
340          * Return the queue status...
341          */
342
343 done:
344
345         TALLOC_FREE(frame);
346         return (status);
347 }
348
349
350 /*
351  * 'get_exit_code()' - Get the backend exit code based on the current error.
352  */
353
354 static int
355 get_exit_code(struct cli_state * cli,
356               NTSTATUS nt_status,
357               bool use_kerberos)
358 {
359         int i;
360
361         /* List of NTSTATUS errors that are considered
362          * authentication errors
363          */
364         static const NTSTATUS auth_errors[] =
365         {
366                 NT_STATUS_ACCESS_DENIED, NT_STATUS_ACCESS_VIOLATION,
367                 NT_STATUS_SHARING_VIOLATION, NT_STATUS_PRIVILEGE_NOT_HELD,
368                 NT_STATUS_INVALID_ACCOUNT_NAME, NT_STATUS_NO_SUCH_USER,
369                 NT_STATUS_WRONG_PASSWORD, NT_STATUS_LOGON_FAILURE,
370                 NT_STATUS_ACCOUNT_RESTRICTION, NT_STATUS_INVALID_LOGON_HOURS,
371                 NT_STATUS_PASSWORD_EXPIRED, NT_STATUS_ACCOUNT_DISABLED
372         };
373
374
375         fprintf(stderr, "DEBUG: get_exit_code(cli=%p, nt_status=%s [%x])\n",
376                 cli, nt_errstr(nt_status), NT_STATUS_V(nt_status));
377
378         for (i = 0; i < ARRAY_SIZE(auth_errors); i++) {
379                 if (!NT_STATUS_EQUAL(nt_status, auth_errors[i])) {
380                         continue;
381                 }
382
383                 if (cli) {
384                         if (use_kerberos)
385                                 fputs("ATTR: auth-info-required=negotiate\n", stderr);
386                         else
387                                 fputs("ATTR: auth-info-required=username,password\n", stderr);
388                 }
389
390                 /*
391                  * 2 = authentication required...
392                  */
393
394                 return (2);
395
396         }
397
398         /*
399          * 1 = fail
400          */
401
402         return (1);
403 }
404
405
406 /*
407  * 'list_devices()' - List the available printers seen on the network...
408  */
409
410 static void
411 list_devices(void)
412 {
413         /*
414          * Eventually, search the local workgroup for available hosts and printers.
415          */
416
417         puts("network smb \"Unknown\" \"Windows Printer via SAMBA\"");
418 }
419
420
421 static struct cli_state *
422 smb_complete_connection(const char *myname,
423                         const char *server,
424                         int port,
425                         const char *username,
426                         const char *password,
427                         const char *workgroup,
428                         const char *share,
429                         int flags,
430                         bool *need_auth)
431 {
432         struct cli_state *cli;  /* New connection */
433         NTSTATUS        nt_status;
434         struct cli_credentials *creds = NULL;
435         bool use_kerberos = false;
436
437         /* Start the SMB connection */
438         *need_auth = false;
439         nt_status = cli_start_connection(&cli, myname, server, NULL, port,
440                                          SMB_SIGNING_DEFAULT, flags);
441         if (!NT_STATUS_IS_OK(nt_status)) {
442                 fprintf(stderr, "ERROR: Connection failed: %s\n", nt_errstr(nt_status));
443                 return NULL;
444         }
445
446         /*
447          * We pretty much guarantee password must be valid or a pointer to a
448          * 0 char.
449          */
450         if (!password) {
451                 *need_auth = true;
452                 return NULL;
453         }
454
455         if (flags & CLI_FULL_CONNECTION_USE_KERBEROS) {
456                 use_kerberos = true;
457         }
458
459         creds = cli_session_creds_init(cli,
460                                        username,
461                                        workgroup,
462                                        NULL, /* realm */
463                                        password,
464                                        use_kerberos,
465                                        false, /* fallback_after_kerberos */
466                                        false, /* use_ccache */
467                                        false); /* password_is_nt_hash */
468         if (creds == NULL) {
469                 fprintf(stderr, "ERROR: cli_session_creds_init failed\n");
470                 cli_shutdown(cli);
471                 return NULL;
472         }
473
474         nt_status = cli_session_setup_creds(cli, creds);
475         if (!NT_STATUS_IS_OK(nt_status)) {
476                 fprintf(stderr, "ERROR: Session setup failed: %s\n", nt_errstr(nt_status));
477
478                 if (get_exit_code(cli, nt_status, use_kerberos) == 2) {
479                         *need_auth = true;
480                 }
481
482                 cli_shutdown(cli);
483
484                 return NULL;
485         }
486
487         nt_status = cli_tree_connect_creds(cli, share, "?????", creds);
488         if (!NT_STATUS_IS_OK(nt_status)) {
489                 fprintf(stderr, "ERROR: Tree connect failed (%s)\n",
490                         nt_errstr(nt_status));
491
492                 if (get_exit_code(cli, nt_status, use_kerberos) == 2) {
493                         *need_auth = true;
494                 }
495
496                 cli_shutdown(cli);
497
498                 return NULL;
499         }
500 #if 0
501         /* Need to work out how to specify this on the URL. */
502         if (smb_encrypt) {
503                 if (!cli_cm_force_encryption_creds(cli, creds, share)) {
504                         fprintf(stderr, "ERROR: encryption setup failed\n");
505                         cli_shutdown(cli);
506                         return NULL;
507                 }
508         }
509 #endif
510
511         return cli;
512 }
513
514 static bool kerberos_ccache_is_valid(void) {
515         krb5_context ctx;
516         const char *ccache_name = NULL;
517         krb5_ccache ccache = NULL;
518         krb5_error_code code;
519
520         code = smb_krb5_init_context_common(&ctx);
521         if (code != 0) {
522                 DBG_ERR("kerberos init context failed (%s)\n",
523                         error_message(code));
524                 return false;
525         }
526
527         ccache_name = krb5_cc_default_name(ctx);
528         if (ccache_name == NULL) {
529                 krb5_free_context(ctx);
530                 return false;
531         }
532
533         code = krb5_cc_resolve(ctx, ccache_name, &ccache);
534         if (code != 0) {
535                 krb5_free_context(ctx);
536                 return false;
537         } else {
538                 krb5_principal default_princ = NULL;
539
540                 code = krb5_cc_get_principal(ctx,
541                                              ccache,
542                                              &default_princ);
543                 if (code != 0) {
544                         krb5_cc_close(ctx, ccache);
545                         krb5_free_context(ctx);
546                         return false;
547                 }
548                 krb5_free_principal(ctx, default_princ);
549         }
550         krb5_cc_close(ctx, ccache);
551         krb5_free_context(ctx);
552
553         return true;
554 }
555
556 /*
557  * 'smb_connect()' - Return a connection to a server.
558  */
559
560 static struct cli_state *       /* O - SMB connection */
561 smb_connect(const char *workgroup,      /* I - Workgroup */
562             const char *server, /* I - Server */
563             const int port,     /* I - Port */
564             const char *share,  /* I - Printer */
565             const char *username,       /* I - Username */
566             const char *password,       /* I - Password */
567             const char *jobusername,    /* I - User who issued the print job */
568             bool *need_auth)
569 {                               /* O - Need authentication? */
570         struct cli_state *cli;  /* New connection */
571         char           *myname = NULL;  /* Client name */
572         struct passwd  *pwd;
573
574         /*
575          * Get the names and addresses of the client and server...
576          */
577         myname = get_myname(talloc_tos());
578         if (!myname) {
579                 return NULL;
580         }
581
582         /*
583          * See if we have a username first.  This is for backwards compatible
584          * behavior with 3.0.14a
585          */
586
587         if (username != NULL && username[0] != '\0') {
588                 if (kerberos_ccache_is_valid()) {
589                         goto kerberos_auth;
590                 }
591         }
592
593         cli = smb_complete_connection(myname,
594                                       server,
595                                       port,
596                                       username,
597                                       password,
598                                       workgroup,
599                                       share,
600                                       0,
601                                       need_auth);
602         if (cli != NULL) {
603                 fputs("DEBUG: Connected with username/password...\n", stderr);
604                 return (cli);
605         }
606
607 kerberos_auth:
608         /*
609          * Try to use the user kerberos credentials (if any) to authenticate
610          */
611         cli = smb_complete_connection(myname, server, port, jobusername, "",
612                                       workgroup, share,
613                                  CLI_FULL_CONNECTION_USE_KERBEROS, need_auth);
614
615         if (cli) {
616                 fputs("DEBUG: Connected using Kerberos...\n", stderr);
617                 return (cli);
618         }
619
620         /* give a chance for a passwordless NTLMSSP session setup */
621         pwd = getpwuid(geteuid());
622         if (pwd == NULL) {
623                 return NULL;
624         }
625
626         cli = smb_complete_connection(myname, server, port, pwd->pw_name, "",
627                                       workgroup, share, 0, need_auth);
628
629         if (cli) {
630                 fputs("DEBUG: Connected with NTLMSSP...\n", stderr);
631                 return (cli);
632         }
633
634         /*
635          * last try. Use anonymous authentication
636          */
637
638         cli = smb_complete_connection(myname, server, port, "", "",
639                                       workgroup, share, 0, need_auth);
640         /*
641          * Return the new connection...
642          */
643
644         return (cli);
645 }
646
647
648 /*
649  * 'smb_print()' - Queue a job for printing using the SMB protocol.
650  */
651
652 static int                      /* O - 0 = success, non-0 = failure */
653 smb_print(struct cli_state * cli,       /* I - SMB connection */
654           char *title,          /* I - Title/job name */
655           FILE * fp)
656 {                               /* I - File to print */
657         uint16_t             fnum;      /* File number */
658         int             nbytes, /* Number of bytes read */
659                         tbytes; /* Total bytes read */
660         char            buffer[8192],   /* Buffer for copy */
661                        *ptr;    /* Pointer into title */
662         NTSTATUS nt_status;
663
664
665         /*
666          * Sanitize the title...
667          */
668
669         for (ptr = title; *ptr; ptr++) {
670                 if (!isalnum((int) *ptr) && !isspace((int) *ptr)) {
671                         *ptr = '_';
672                 }
673         }
674
675         /*
676          * Open the printer device...
677          */
678
679         nt_status = cli_open(cli, title, O_RDWR | O_CREAT | O_TRUNC, DENY_NONE,
680                           &fnum);
681         if (!NT_STATUS_IS_OK(nt_status)) {
682                 fprintf(stderr, "ERROR: %s opening remote spool %s\n",
683                         nt_errstr(nt_status), title);
684                 return get_exit_code(cli, nt_status, false);
685         }
686
687         /*
688          * Copy the file to the printer...
689          */
690
691         if (fp != stdin)
692                 rewind(fp);
693
694         tbytes = 0;
695
696         while ((nbytes = fread(buffer, 1, sizeof(buffer), fp)) > 0) {
697                 NTSTATUS status;
698
699                 status = cli_writeall(cli, fnum, 0, (uint8_t *)buffer,
700                                       tbytes, nbytes, NULL);
701                 if (!NT_STATUS_IS_OK(status)) {
702                         int ret = get_exit_code(cli, status, false);
703                         fprintf(stderr, "ERROR: Error writing spool: %s\n",
704                                 nt_errstr(status));
705                         fprintf(stderr, "DEBUG: Returning status %d...\n",
706                                 ret);
707                         cli_close(cli, fnum);
708
709                         return (ret);
710                 }
711                 tbytes += nbytes;
712         }
713
714         nt_status = cli_close(cli, fnum);
715         if (!NT_STATUS_IS_OK(nt_status)) {
716                 fprintf(stderr, "ERROR: %s closing remote spool %s\n",
717                         nt_errstr(nt_status), title);
718                 return get_exit_code(cli, nt_status, false);
719         } else {
720                 return (0);
721         }
722 }
723
724 static char *
725 uri_unescape_alloc(const char *uritok)
726 {
727         char *ret;
728         char *end;
729         ret = (char *) SMB_STRDUP(uritok);
730         if (!ret) {
731                 return NULL;
732         }
733
734         end = rfc1738_unescape(ret);
735         if (end == NULL) {
736                 free(ret);
737                 return NULL;
738         }
739         return ret;
740 }