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