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