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