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