r23784: use the GPLv3 boilerplate as recommended by the FSF and the license text
[tprouty/samba.git] / source / 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    
9    This program is free software; you can redistribute it and/or modify
10    it under the terms of the GNU General Public License as published by
11    the Free Software Foundation; either version 3 of the License, or
12    (at your option) any later version.
13    
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License for more details.
18    
19    You should have received a copy of the GNU General Public License
20    along with this program.  If not, see <http://www.gnu.org/licenses/>.
21 */
22
23 #include "includes.h"
24
25 #define TICKET_CC_DIR            "/tmp"
26 #define CC_PREFIX                "krb5cc_" /* prefix of the ticket cache */
27 #define CC_MAX_FILE_LEN          24   
28 #define CC_MAX_FILE_PATH_LEN     (sizeof(TICKET_CC_DIR)-1)+ CC_MAX_FILE_LEN+2   
29 #define OVERWRITE                1   
30 #define KRB5CCNAME               "KRB5CCNAME"
31 #define MAX_RETRY_CONNECT        3
32
33
34 /*
35  * Globals...
36  */
37
38 extern BOOL             in_client;      /* Boolean for client library */
39
40
41 /*
42  * Local functions...
43  */
44
45 static void             list_devices(void);
46 static struct cli_state *smb_complete_connection(const char *, const char *,int , const char *, const char *, const char *, const char *, int);
47 static struct cli_state *smb_connect(const char *, const char *, int, const char *, const char *, const char *, const char *);
48 static int              smb_print(struct cli_state *, char *, FILE *);
49 static char *           uri_unescape_alloc(const char *);
50
51
52 /*
53  * 'main()' - Main entry for SMB backend.
54  */
55
56  int                            /* O - Exit status */
57  main(int  argc,                        /* I - Number of command-line arguments */
58      char *argv[])              /* I - Command-line arguments */
59 {
60   int           i;              /* Looping var */
61   int           copies;         /* Number of copies */
62   int           port;           /* Port number */
63   char          uri[1024],      /* URI */
64                 *sep,           /* Pointer to separator */
65                 *tmp, *tmp2,    /* Temp pointers to do escaping */
66                 *password;      /* Password */
67   char          *username,      /* Username */
68                 *server,        /* Server name */
69                 *printer;       /* Printer name */
70   const char    *workgroup;     /* Workgroup */
71   FILE          *fp;            /* File to print */
72   int           status=0;               /* Status of LPD job */
73   struct cli_state *cli;        /* SMB interface */
74   char null_str[1];
75   int tries = 0;
76   const char *dev_uri;
77
78   null_str[0] = '\0';
79
80   /* we expect the URI in argv[0]. Detect the case where it is in argv[1] and cope */
81   if (argc > 2 && strncmp(argv[0],"smb://", 6) && !strncmp(argv[1],"smb://", 6)) {
82           argv++;
83           argc--;
84   }
85
86   if (argc == 1)
87   {
88    /*
89     * NEW!  In CUPS 1.1 the backends are run with no arguments to list the
90     *       available devices.  These can be devices served by this backend
91     *       or any other backends (i.e. you can have an SNMP backend that
92     *       is only used to enumerate the available network printers... :)
93     */
94
95     list_devices();
96     return (0);
97   }
98
99   if (argc < 6 || argc > 7)
100   {
101     fprintf(stderr, "Usage: %s [DEVICE_URI] job-id user title copies options [file]\n",
102             argv[0]);
103     fputs("       The DEVICE_URI environment variable can also contain the\n", stderr);
104     fputs("       destination printer:\n", stderr);
105     fputs("\n", stderr);
106     fputs("           smb://[username:password@][workgroup/]server[:port]/printer\n", stderr);
107     return (1);
108   }
109
110  /*
111   * If we have 7 arguments, print the file named on the command-line.
112   * Otherwise, print data from stdin...
113   */
114
115
116   if (argc == 6)
117   {
118    /*
119     * Print from Copy stdin to a temporary file...
120     */
121
122     fp     = stdin;
123     copies = 1;
124   }
125   else if ((fp = fopen(argv[6], "rb")) == NULL)
126   {
127     perror("ERROR: Unable to open print file");
128     return (1);
129   }
130   else
131     copies = atoi(argv[4]);
132
133  /*
134   * Find the URI...
135   */
136
137   dev_uri = getenv("DEVICE_URI");
138   if (dev_uri)
139     strncpy(uri, dev_uri, sizeof(uri) - 1);
140   else if (strncmp(argv[0], "smb://", 6) == 0)
141     strncpy(uri, argv[0], sizeof(uri) - 1);
142   else
143   {
144     fputs("ERROR: No device URI found in DEVICE_URI environment variable or argv[0] !\n", stderr);
145     return (1);
146   }
147
148   uri[sizeof(uri) - 1] = '\0';
149
150  /*
151   * Extract the destination from the URI...
152   */
153
154   if ((sep = strrchr_m(uri, '@')) != NULL)
155   {
156     tmp = uri + 6;
157     *sep++ = '\0';
158
159     /* username is in tmp */
160
161     server = sep;
162
163    /*
164     * Extract password as needed...
165     */
166
167     if ((tmp2 = strchr_m(tmp, ':')) != NULL) {
168       *tmp2++ = '\0';
169       password = uri_unescape_alloc(tmp2);
170     } else {
171       password = null_str;
172     }
173     username = uri_unescape_alloc(tmp);
174   }
175   else
176   {
177     username = null_str;
178     password = null_str;
179     server   = uri + 6;
180   }
181
182   tmp = server;
183
184   if ((sep = strchr_m(tmp, '/')) == NULL)
185   {
186     fputs("ERROR: Bad URI - need printer name!\n", stderr);
187     return (1);
188   }
189
190   *sep++ = '\0';
191   tmp2 = sep;
192
193   if ((sep = strchr_m(tmp2, '/')) != NULL)
194   {
195    /*
196     * Convert to smb://[username:password@]workgroup/server/printer...
197     */
198
199     *sep++ = '\0';
200
201     workgroup = uri_unescape_alloc(tmp);
202     server    = uri_unescape_alloc(tmp2);
203     printer   = uri_unescape_alloc(sep);
204   }
205   else {
206     workgroup = NULL;
207     server = uri_unescape_alloc(tmp);
208     printer = uri_unescape_alloc(tmp2);
209   }
210   
211   if ((sep = strrchr_m(server, ':')) != NULL)
212   {
213     *sep++ = '\0';
214
215     port=atoi(sep);
216   }
217   else 
218         port=0;
219         
220  
221  /*
222   * Setup the SAMBA server state...
223   */
224
225   setup_logging("smbspool", True);
226
227   in_client = True;   /* Make sure that we tell lp_load we are */
228
229   load_case_tables();
230
231   if (!lp_load(dyn_CONFIGFILE, True, False, False, True))
232   {
233     fprintf(stderr, "ERROR: Can't load %s - run testparm to debug it\n", dyn_CONFIGFILE);
234     return (1);
235   }
236
237   if (workgroup == NULL)
238     workgroup = lp_workgroup();
239
240   load_interfaces();
241
242   do
243   {
244     if ((cli = smb_connect(workgroup, server, port, printer, username, password, argv[2])) == NULL)
245     {
246       if (getenv("CLASS") == NULL)
247       {
248         fprintf(stderr, "ERROR: Unable to connect to CIFS host, will retry in 60 seconds...\n");
249         sleep (60); /* should just waiting and retrying fix authentication  ??? */
250         tries++;
251       }
252       else
253       {
254         fprintf(stderr, "ERROR: Unable to connect to CIFS host, trying next printer...\n");
255         return (1);
256       }
257     }
258   }
259   while ((cli == NULL) && (tries < MAX_RETRY_CONNECT));
260
261   if (cli == NULL) {
262         fprintf(stderr, "ERROR: Unable to connect to CIFS host after (tried %d times)\n", tries);
263         return (1);
264   }
265
266  /*
267   * Now that we are connected to the server, ignore SIGTERM so that we
268   * can finish out any page data the driver sends (e.g. to eject the
269   * current page...  Only ignore SIGTERM if we are printing data from
270   * stdin (otherwise you can't cancel raw jobs...)
271   */
272
273   if (argc < 7)
274     CatchSignal(SIGTERM, SIG_IGN);
275
276  /*
277   * Queue the job...
278   */
279
280   for (i = 0; i < copies; i ++)
281     if ((status = smb_print(cli, argv[3] /* title */, fp)) != 0)
282       break;
283
284   cli_shutdown(cli);
285
286  /*
287   * Return the queue status...
288   */
289
290   return (status);
291 }
292
293
294 /*
295  * 'list_devices()' - List the available printers seen on the network...
296  */
297
298 static void
299 list_devices(void)
300 {
301  /*
302   * Eventually, search the local workgroup for available hosts and printers.
303   */
304
305   puts("network smb \"Unknown\" \"Windows Printer via SAMBA\"");
306 }
307
308
309 /*
310  * get the name of the newest ticket cache for the uid user.
311  * pam_krb5 defines a non default ticket cache for each user
312  */
313 static
314 char * get_ticket_cache( uid_t uid )
315 {
316   char *ticket_file = NULL;
317   SMB_STRUCT_DIR *tcdir;                  /* directory where ticket caches are stored */
318   SMB_STRUCT_DIRENT *dirent;   /* directory entry */
319   char *filename = NULL;       /* holds file names on the tmp directory */
320   SMB_STRUCT_STAT buf;        
321   char user_cache_prefix[CC_MAX_FILE_LEN];
322   char file_path[CC_MAX_FILE_PATH_LEN];
323   time_t t = 0;
324
325   snprintf(user_cache_prefix, CC_MAX_FILE_LEN, "%s%d", CC_PREFIX, uid );
326   tcdir = sys_opendir( TICKET_CC_DIR );
327   if ( tcdir == NULL ) 
328     return NULL; 
329   
330   while ( (dirent = sys_readdir( tcdir ) ) ) 
331   { 
332     filename = dirent->d_name;
333     snprintf(file_path, CC_MAX_FILE_PATH_LEN,"%s/%s", TICKET_CC_DIR, filename); 
334     if (sys_stat(file_path, &buf) == 0 ) 
335     {
336       if ( ( buf.st_uid == uid ) && ( S_ISREG(buf.st_mode) ) ) 
337       {
338         /*
339          * check the user id of the file to prevent denial of
340          * service attacks by creating fake ticket caches for the 
341          * user
342          */
343         if ( strstr( filename, user_cache_prefix ) ) 
344         {
345           if ( buf.st_mtime > t ) 
346           { 
347             /*
348              * a newer ticket cache found 
349              */
350             free(ticket_file);
351             ticket_file=SMB_STRDUP(file_path);
352             t = buf.st_mtime;
353           }
354         }
355       }
356     }
357   }
358
359   sys_closedir(tcdir);
360
361   if ( ticket_file == NULL )
362   {
363     /* no ticket cache found */
364     fprintf(stderr, "ERROR: No ticket cache found for userid=%d\n", uid);
365     return NULL;
366   }
367
368   return ticket_file;
369 }
370
371 static struct cli_state 
372 *smb_complete_connection(const char *myname,
373             const char *server,
374             int port,
375             const char *username, 
376             const char *password, 
377             const char *workgroup, 
378             const char *share,
379             int flags)
380 {
381   struct cli_state  *cli;    /* New connection */    
382   NTSTATUS nt_status;
383   
384   /* Start the SMB connection */
385   nt_status = cli_start_connection( &cli, myname, server, NULL, port, 
386                                     Undefined, flags, NULL);
387   if (!NT_STATUS_IS_OK(nt_status)) 
388   {
389     return NULL;      
390   }
391     
392   /* We pretty much guarentee password must be valid or a pointer
393      to a 0 char. */
394   if (!password) {
395     return NULL;
396   }
397   
398   if ( (username) && (*username) && 
399       (strlen(password) == 0 ) && 
400        (cli->use_kerberos) ) 
401   {
402     /* Use kerberos authentication */
403     struct passwd *pw;
404     char *cache_file;
405     
406     
407     if ( !(pw = sys_getpwnam(username)) ) {
408       fprintf(stderr,"ERROR Can not get %s uid\n", username);
409       cli_shutdown(cli);
410       return NULL;
411     }
412
413     /*
414      * Get the ticket cache of the user to set KRB5CCNAME env
415      * variable
416      */
417     cache_file = get_ticket_cache( pw->pw_uid );
418     if ( cache_file == NULL ) 
419     {
420       fprintf(stderr, "ERROR: Can not get the ticket cache for %s\n", username);
421       cli_shutdown(cli);
422       return NULL;
423     }
424
425     if ( setenv(KRB5CCNAME, cache_file, OVERWRITE) < 0 ) 
426     {
427       fprintf(stderr, "ERROR: Can not add KRB5CCNAME to the environment");
428       cli_shutdown(cli);
429       free(cache_file);
430       return NULL;
431     }
432     free(cache_file);
433
434     /*
435      * Change the UID of the process to be able to read the kerberos
436      * ticket cache
437      */
438     setuid(pw->pw_uid);
439
440   }
441    
442    
443   if (!NT_STATUS_IS_OK(cli_session_setup(cli, username,
444                                          password, strlen(password)+1, 
445                                          password, strlen(password)+1,
446                                          workgroup)))
447   {
448     fprintf(stderr,"ERROR: Session setup failed: %s\n", cli_errstr(cli));
449     if (NT_STATUS_V(cli_nt_error(cli)) == 
450         NT_STATUS_V(NT_STATUS_MORE_PROCESSING_REQUIRED))
451     {
452       fprintf(stderr, "did you forget to run kinit?\n");
453     }
454     cli_shutdown(cli);
455
456     return NULL;
457   }
458     
459   if (!cli_send_tconX(cli, share, "?????", password, strlen(password)+1)) 
460   {
461     fprintf(stderr, "ERROR: Tree connect failed (%s)\n", cli_errstr(cli));
462     cli_shutdown(cli);
463     return NULL;
464   }
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 {
482   struct cli_state  *cli;    /* New connection */
483   pstring    myname;    /* Client name */
484   struct passwd *pwd;
485
486  /*
487   * Get the names and addresses of the client and server...
488   */
489
490   get_myname(myname);  
491
492   /* See if we have a username first.  This is for backwards compatible 
493      behavior with 3.0.14a */
494
495   if ( username &&  *username )
496   {
497       cli = smb_complete_connection(myname, server, port, username, 
498                                     password, workgroup, share, 0 );
499       if (cli) 
500         return cli;
501   }
502   
503   /* 
504    * Try to use the user kerberos credentials (if any) to authenticate
505    */
506   cli = smb_complete_connection(myname, server, port, jobusername, "", 
507                                 workgroup, share, 
508                                 CLI_FULL_CONNECTION_USE_KERBEROS );
509
510   if (cli ) { return cli; }
511
512   /* give a chance for a passwordless NTLMSSP session setup */
513
514   pwd = getpwuid(geteuid());
515   if (pwd == NULL) {
516      return NULL;
517   }
518
519   cli = smb_complete_connection(myname, server, port, pwd->pw_name, "", 
520                                 workgroup, share, 0);
521
522   if (cli) { return cli; }
523
524   /*
525    * last try. Use anonymous authentication
526    */
527
528   cli = smb_complete_connection(myname, server, port, "", "", 
529                                 workgroup, share, 0);
530   /*
531    * Return the new connection...
532    */
533   
534   return (cli);
535 }
536
537
538 /*
539  * 'smb_print()' - Queue a job for printing using the SMB protocol.
540  */
541
542 static int                              /* O - 0 = success, non-0 = failure */
543 smb_print(struct cli_state *cli,        /* I - SMB connection */
544           char             *title,      /* I - Title/job name */
545           FILE             *fp)         /* I - File to print */
546 {
547   int   fnum;           /* File number */
548   int   nbytes,         /* Number of bytes read */
549         tbytes;         /* Total bytes read */
550   char  buffer[8192],   /* Buffer for copy */
551         *ptr;           /* Pointer into tile */
552
553
554  /*
555   * Sanitize the title...
556   */
557
558   for (ptr = title; *ptr; ptr ++)
559     if (!isalnum((int)*ptr) && !isspace((int)*ptr))
560       *ptr = '_';
561
562  /*
563   * Open the printer device...
564   */
565
566   if ((fnum = cli_open(cli, title, O_RDWR | O_CREAT | O_TRUNC, DENY_NONE)) == -1)
567   {
568     fprintf(stderr, "ERROR: %s opening remote spool %s\n",
569             cli_errstr(cli), title);
570     return (1);
571   }
572
573  /*
574   * Copy the file to the printer...
575   */
576
577   if (fp != stdin)
578     rewind(fp);
579
580   tbytes = 0;
581
582   while ((nbytes = fread(buffer, 1, sizeof(buffer), fp)) > 0)
583   {
584     if (cli_write(cli, fnum, 0, buffer, tbytes, nbytes) != nbytes)
585     {
586       fprintf(stderr, "ERROR: Error writing spool: %s\n", cli_errstr(cli));
587       break;
588     }
589
590     tbytes += nbytes;
591   } 
592
593   if (!cli_close(cli, fnum))
594   {
595     fprintf(stderr, "ERROR: %s closing remote spool %s\n",
596             cli_errstr(cli), title);
597     return (1);
598   }
599   else
600     return (0);
601 }
602
603 static char *uri_unescape_alloc(const char *uritok)
604 {
605         char *ret;
606
607         ret = (char *)SMB_STRDUP(uritok);
608         if (!ret) return NULL;
609
610         rfc1738_unescape(ret);
611         return ret;
612 }