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