3b732c99234dca977a6b7ab9c763c1a3135e6b81
[vlendec/samba-autobuild/.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            null_str[1];
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
104         null_str[0] = '\0';
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
161         dev_uri = getenv("DEVICE_URI");
162         if (dev_uri) {
163                 strncpy(uri, dev_uri, sizeof(uri) - 1);
164         } else if (strncmp(argv[1], "smb://", 6) == 0) {
165                 strncpy(uri, argv[1], sizeof(uri) - 1);
166         } else {
167                 fputs("ERROR: No device URI found in DEVICE_URI environment variable or arg1 !\n", stderr);
168                 goto done;
169         }
170
171         uri[sizeof(uri) - 1] = '\0';
172
173         /*
174          * Extract the destination from the URI...
175          */
176
177         if ((sep = strrchr_m(uri, '@')) != NULL) {
178                 tmp = uri + 6;
179                 *sep++ = '\0';
180
181                 /* username is in tmp */
182
183                 server = sep;
184
185                 /*
186                  * Extract password as needed...
187                  */
188
189                 if ((tmp2 = strchr_m(tmp, ':')) != NULL) {
190                         *tmp2++ = '\0';
191                         password = uri_unescape_alloc(tmp2);
192                 } else {
193                         password = null_str;
194                 }
195                 username = uri_unescape_alloc(tmp);
196         } else {
197                 if ((username = getenv("AUTH_USERNAME")) == NULL) {
198                         username = null_str;
199                 }
200
201                 if ((password = getenv("AUTH_PASSWORD")) == NULL) {
202                         password = null_str;
203                 }
204
205                 server = uri + 6;
206         }
207
208         tmp = server;
209
210         if ((sep = strchr_m(tmp, '/')) == NULL) {
211                 fputs("ERROR: Bad URI - need printer name!\n", stderr);
212                 goto done;
213         }
214
215         *sep++ = '\0';
216         tmp2 = sep;
217
218         if ((sep = strchr_m(tmp2, '/')) != NULL) {
219                 /*
220                  * Convert to smb://[username:password@]workgroup/server/printer...
221                  */
222
223                 *sep++ = '\0';
224
225                 workgroup = uri_unescape_alloc(tmp);
226                 server = uri_unescape_alloc(tmp2);
227                 printer = uri_unescape_alloc(sep);
228         } else {
229                 workgroup = NULL;
230                 server = uri_unescape_alloc(tmp);
231                 printer = uri_unescape_alloc(tmp2);
232         }
233
234         if ((sep = strrchr_m(server, ':')) != NULL) {
235                 *sep++ = '\0';
236
237                 port = atoi(sep);
238         } else {
239                 port = 0;
240         }
241
242         /*
243          * Setup the SAMBA server state...
244          */
245
246         setup_logging("smbspool", DEBUG_STDERR);
247
248         smb_init_locale();
249
250         config_file = lp_default_path();
251         if (!lp_load_client(config_file)) {
252                 fprintf(stderr,
253                         "ERROR: Can't load %s - run testparm to debug it\n",
254                         config_file);
255                 goto done;
256         }
257
258         if (workgroup == NULL) {
259                 workgroup = lp_workgroup();
260         }
261
262         load_interfaces();
263
264         do {
265                 cli = smb_connect(workgroup, server, port, printer,
266                         username, password, argv[3], &need_auth);
267                 if (cli == NULL) {
268                         if (need_auth) {
269                                 exit(2);
270                         } else if (getenv("CLASS") == NULL) {
271                                 fprintf(stderr, "ERROR: Unable to connect to CIFS host, will retry in 60 seconds...\n");
272                                 sleep(60);
273                                 tries++;
274                         } else {
275                                 fprintf(stderr, "ERROR: Unable to connect to CIFS host, trying next printer...\n");
276                                 goto done;
277                         }
278                 }
279         } while ((cli == NULL) && (tries < MAX_RETRY_CONNECT));
280
281         if (cli == NULL) {
282                 fprintf(stderr, "ERROR: Unable to connect to CIFS host after (tried %d times)\n", tries);
283                 goto done;
284         }
285
286         /*
287          * Now that we are connected to the server, ignore SIGTERM so that we
288          * can finish out any page data the driver sends (e.g. to eject the
289          * current page...  Only ignore SIGTERM if we are printing data from
290          * stdin (otherwise you can't cancel raw jobs...)
291          */
292
293         if (argc < 7) {
294                 CatchSignal(SIGTERM, SIG_IGN);
295         }
296
297         /*
298          * Queue the job...
299          */
300
301         for (i = 0; i < copies; i++) {
302                 status = smb_print(cli, argv[4] /* title */ , fp);
303                 if (status != 0) {
304                         break;
305                 }
306         }
307
308         cli_shutdown(cli);
309
310         /*
311          * Return the queue status...
312          */
313
314 done:
315
316         TALLOC_FREE(frame);
317         return (status);
318 }
319
320
321 /*
322  * 'get_exit_code()' - Get the backend exit code based on the current error.
323  */
324
325 static int
326 get_exit_code(struct cli_state * cli,
327               NTSTATUS nt_status,
328               bool use_kerberos)
329 {
330         int i;
331
332         /* List of NTSTATUS errors that are considered
333          * authentication errors
334          */
335         static const NTSTATUS auth_errors[] =
336         {
337                 NT_STATUS_ACCESS_DENIED, NT_STATUS_ACCESS_VIOLATION,
338                 NT_STATUS_SHARING_VIOLATION, NT_STATUS_PRIVILEGE_NOT_HELD,
339                 NT_STATUS_INVALID_ACCOUNT_NAME, NT_STATUS_NO_SUCH_USER,
340                 NT_STATUS_WRONG_PASSWORD, NT_STATUS_LOGON_FAILURE,
341                 NT_STATUS_ACCOUNT_RESTRICTION, NT_STATUS_INVALID_LOGON_HOURS,
342                 NT_STATUS_PASSWORD_EXPIRED, NT_STATUS_ACCOUNT_DISABLED
343         };
344
345
346         fprintf(stderr, "DEBUG: get_exit_code(cli=%p, nt_status=%s [%x])\n",
347                 cli, nt_errstr(nt_status), NT_STATUS_V(nt_status));
348
349         for (i = 0; i < ARRAY_SIZE(auth_errors); i++) {
350                 if (!NT_STATUS_EQUAL(nt_status, auth_errors[i])) {
351                         continue;
352                 }
353
354                 if (cli) {
355                         if (use_kerberos)
356                                 fputs("ATTR: auth-info-required=negotiate\n", stderr);
357                         else
358                                 fputs("ATTR: auth-info-required=username,password\n", stderr);
359                 }
360
361                 /*
362                  * 2 = authentication required...
363                  */
364
365                 return (2);
366
367         }
368
369         /*
370          * 1 = fail
371          */
372
373         return (1);
374 }
375
376
377 /*
378  * 'list_devices()' - List the available printers seen on the network...
379  */
380
381 static void
382 list_devices(void)
383 {
384         /*
385          * Eventually, search the local workgroup for available hosts and printers.
386          */
387
388         puts("network smb \"Unknown\" \"Windows Printer via SAMBA\"");
389 }
390
391
392 static struct cli_state *
393 smb_complete_connection(const char *myname,
394                         const char *server,
395                         int port,
396                         const char *username,
397                         const char *password,
398                         const char *workgroup,
399                         const char *share,
400                         int flags,
401                         bool *need_auth)
402 {
403         struct cli_state *cli;  /* New connection */
404         NTSTATUS        nt_status;
405         struct cli_credentials *creds = NULL;
406         bool use_kerberos = false;
407
408         /* Start the SMB connection */
409         *need_auth = false;
410         nt_status = cli_start_connection(&cli, myname, server, NULL, port,
411                                          SMB_SIGNING_DEFAULT, flags);
412         if (!NT_STATUS_IS_OK(nt_status)) {
413                 fprintf(stderr, "ERROR: Connection failed: %s\n", nt_errstr(nt_status));
414                 return NULL;
415         }
416
417         /*
418          * We pretty much guarantee password must be valid or a pointer to a
419          * 0 char.
420          */
421         if (!password) {
422                 *need_auth = true;
423                 return NULL;
424         }
425
426         if (flags & CLI_FULL_CONNECTION_USE_KERBEROS) {
427                 use_kerberos = true;
428         }
429
430         creds = cli_session_creds_init(cli,
431                                        username,
432                                        workgroup,
433                                        NULL, /* realm */
434                                        password,
435                                        use_kerberos,
436                                        false, /* fallback_after_kerberos */
437                                        false, /* use_ccache */
438                                        false); /* password_is_nt_hash */
439         if (creds == NULL) {
440                 fprintf(stderr, "ERROR: cli_session_creds_init failed\n");
441                 cli_shutdown(cli);
442                 return NULL;
443         }
444
445         nt_status = cli_session_setup_creds(cli, creds);
446         if (!NT_STATUS_IS_OK(nt_status)) {
447                 fprintf(stderr, "ERROR: Session setup failed: %s\n", nt_errstr(nt_status));
448
449                 if (get_exit_code(cli, nt_status, use_kerberos) == 2) {
450                         *need_auth = true;
451                 }
452
453                 cli_shutdown(cli);
454
455                 return NULL;
456         }
457
458         nt_status = cli_tree_connect_creds(cli, share, "?????", creds);
459         if (!NT_STATUS_IS_OK(nt_status)) {
460                 fprintf(stderr, "ERROR: Tree connect failed (%s)\n",
461                         nt_errstr(nt_status));
462
463                 if (get_exit_code(cli, nt_status, use_kerberos) == 2) {
464                         *need_auth = true;
465                 }
466
467                 cli_shutdown(cli);
468
469                 return NULL;
470         }
471 #if 0
472         /* Need to work out how to specify this on the URL. */
473         if (smb_encrypt) {
474                 if (!cli_cm_force_encryption_creds(cli, creds, share)) {
475                         fprintf(stderr, "ERROR: encryption setup failed\n");
476                         cli_shutdown(cli);
477                         return NULL;
478                 }
479         }
480 #endif
481
482         return cli;
483 }
484
485 static bool kerberos_ccache_is_valid(void) {
486         krb5_context ctx;
487         const char *ccache_name = NULL;
488         krb5_ccache ccache = NULL;
489         krb5_error_code code;
490
491         code = krb5_init_context(&ctx);
492         if (code != 0) {
493                 return false;
494         }
495
496         ccache_name = krb5_cc_default_name(ctx);
497         if (ccache_name == NULL) {
498                 return false;
499         }
500
501         code = krb5_cc_resolve(ctx, ccache_name, &ccache);
502         if (code != 0) {
503                 krb5_free_context(ctx);
504                 return false;
505         } else {
506                 krb5_principal default_princ = NULL;
507
508                 code = krb5_cc_get_principal(ctx,
509                                              ccache,
510                                              &default_princ);
511                 if (code != 0) {
512                         krb5_cc_close(ctx, ccache);
513                         krb5_free_context(ctx);
514                         return false;
515                 }
516                 krb5_free_principal(ctx, default_princ);
517         }
518         krb5_cc_close(ctx, ccache);
519         krb5_free_context(ctx);
520
521         return true;
522 }
523
524 /*
525  * 'smb_connect()' - Return a connection to a server.
526  */
527
528 static struct cli_state *       /* O - SMB connection */
529 smb_connect(const char *workgroup,      /* I - Workgroup */
530             const char *server, /* I - Server */
531             const int port,     /* I - Port */
532             const char *share,  /* I - Printer */
533             const char *username,       /* I - Username */
534             const char *password,       /* I - Password */
535             const char *jobusername,    /* I - User who issued the print job */
536             bool *need_auth)
537 {                               /* O - Need authentication? */
538         struct cli_state *cli;  /* New connection */
539         char           *myname = NULL;  /* Client name */
540         struct passwd  *pwd;
541
542         /*
543          * Get the names and addresses of the client and server...
544          */
545         myname = get_myname(talloc_tos());
546         if (!myname) {
547                 return NULL;
548         }
549
550         /*
551          * See if we have a username first.  This is for backwards compatible
552          * behavior with 3.0.14a
553          */
554
555         if (username != NULL && username[0] != '\0') {
556                 if (kerberos_ccache_is_valid()) {
557                         goto kerberos_auth;
558                 }
559         }
560
561         cli = smb_complete_connection(myname,
562                                       server,
563                                       port,
564                                       username,
565                                       password,
566                                       workgroup,
567                                       share,
568                                       0,
569                                       need_auth);
570         if (cli != NULL) {
571                 fputs("DEBUG: Connected with username/password...\n", stderr);
572                 return (cli);
573         }
574
575 kerberos_auth:
576         /*
577          * Try to use the user kerberos credentials (if any) to authenticate
578          */
579         cli = smb_complete_connection(myname, server, port, jobusername, "",
580                                       workgroup, share,
581                                  CLI_FULL_CONNECTION_USE_KERBEROS, need_auth);
582
583         if (cli) {
584                 fputs("DEBUG: Connected using Kerberos...\n", stderr);
585                 return (cli);
586         }
587
588         /* give a chance for a passwordless NTLMSSP session setup */
589         pwd = getpwuid(geteuid());
590         if (pwd == NULL) {
591                 return NULL;
592         }
593
594         cli = smb_complete_connection(myname, server, port, pwd->pw_name, "",
595                                       workgroup, share, 0, need_auth);
596
597         if (cli) {
598                 fputs("DEBUG: Connected with NTLMSSP...\n", stderr);
599                 return (cli);
600         }
601
602         /*
603          * last try. Use anonymous authentication
604          */
605
606         cli = smb_complete_connection(myname, server, port, "", "",
607                                       workgroup, share, 0, need_auth);
608         /*
609          * Return the new connection...
610          */
611
612         return (cli);
613 }
614
615
616 /*
617  * 'smb_print()' - Queue a job for printing using the SMB protocol.
618  */
619
620 static int                      /* O - 0 = success, non-0 = failure */
621 smb_print(struct cli_state * cli,       /* I - SMB connection */
622           char *title,          /* I - Title/job name */
623           FILE * fp)
624 {                               /* I - File to print */
625         uint16_t             fnum;      /* File number */
626         int             nbytes, /* Number of bytes read */
627                         tbytes; /* Total bytes read */
628         char            buffer[8192],   /* Buffer for copy */
629                        *ptr;    /* Pointer into title */
630         NTSTATUS nt_status;
631
632
633         /*
634          * Sanitize the title...
635          */
636
637         for (ptr = title; *ptr; ptr++) {
638                 if (!isalnum((int) *ptr) && !isspace((int) *ptr)) {
639                         *ptr = '_';
640                 }
641         }
642
643         /*
644          * Open the printer device...
645          */
646
647         nt_status = cli_open(cli, title, O_RDWR | O_CREAT | O_TRUNC, DENY_NONE,
648                           &fnum);
649         if (!NT_STATUS_IS_OK(nt_status)) {
650                 fprintf(stderr, "ERROR: %s opening remote spool %s\n",
651                         nt_errstr(nt_status), title);
652                 return get_exit_code(cli, nt_status, false);
653         }
654
655         /*
656          * Copy the file to the printer...
657          */
658
659         if (fp != stdin)
660                 rewind(fp);
661
662         tbytes = 0;
663
664         while ((nbytes = fread(buffer, 1, sizeof(buffer), fp)) > 0) {
665                 NTSTATUS status;
666
667                 status = cli_writeall(cli, fnum, 0, (uint8_t *)buffer,
668                                       tbytes, nbytes, NULL);
669                 if (!NT_STATUS_IS_OK(status)) {
670                         int ret = get_exit_code(cli, status, false);
671                         fprintf(stderr, "ERROR: Error writing spool: %s\n",
672                                 nt_errstr(status));
673                         fprintf(stderr, "DEBUG: Returning status %d...\n",
674                                 ret);
675                         cli_close(cli, fnum);
676
677                         return (ret);
678                 }
679                 tbytes += nbytes;
680         }
681
682         nt_status = cli_close(cli, fnum);
683         if (!NT_STATUS_IS_OK(nt_status)) {
684                 fprintf(stderr, "ERROR: %s closing remote spool %s\n",
685                         nt_errstr(nt_status), title);
686                 return get_exit_code(cli, nt_status, false);
687         } else {
688                 return (0);
689         }
690 }
691
692 static char *
693 uri_unescape_alloc(const char *uritok)
694 {
695         char *ret;
696
697         ret = (char *) SMB_STRDUP(uritok);
698         if (!ret) {
699                 return NULL;
700         }
701
702         rfc1738_unescape(ret);
703         return ret;
704 }