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