Replace cli_rpc_pipe_close by a talloc destructor on rpc_pipe_struct
[kai/samba.git] / source / client / smbspool.c
index ce920d1a58dc497a4019991186956ff276c6dc3f..e827df4b09f8d59eb83231db5769a11ee89fc435 100644 (file)
@@ -1,13 +1,14 @@
 /* 
-   Unix SMB/Netbios implementation.
-   Version 2.0.
+   Unix SMB/CIFS implementation.
    SMB backend for the Common UNIX Printing System ("CUPS")
    Copyright 1999 by Easy Software Products
    Copyright Andrew Tridgell 1994-1998
+   Copyright Andrew Bartlett 2002
+   Copyright Rodrigo Fernandez-Vizarra 2005 
    
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 2 of the License, or
+   the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.
    
    This program is distributed in the hope that it will be useful,
    GNU General Public License for more details.
    
    You should have received a copy of the GNU General Public License
-   along with this program; if not, write to the Free Software
-   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-#define NO_SYSLOG
-
 #include "includes.h"
 
+#define TICKET_CC_DIR            "/tmp"
+#define CC_PREFIX                "krb5cc_" /* prefix of the ticket cache */
+#define CC_MAX_FILE_LEN          24   
+#define CC_MAX_FILE_PATH_LEN     (sizeof(TICKET_CC_DIR)-1)+ CC_MAX_FILE_LEN+2   
+#define OVERWRITE                1   
+#define KRB5CCNAME               "KRB5CCNAME"
+#define MAX_RETRY_CONNECT        3
+
+
 /*
  * Globals...
  */
 
-extern BOOL            in_client;      /* Boolean for client library */
-extern struct in_addr  ipzero;         /* Any address */
 
 
 /*
@@ -37,9 +42,13 @@ extern struct in_addr        ipzero;         /* Any address */
  */
 
 static void            list_devices(void);
-static struct cli_state        *smb_connect(char *, char *, char *, char *, char *);
+static struct cli_state *smb_complete_connection(const char *, const char *,int , const char *, const char *, const char *, const char *, int);
+static struct cli_state        *smb_connect(const char *, const char *, int, const char *, const char *, const char *, const char *);
 static int             smb_print(struct cli_state *, char *, FILE *);
-
+static char *          uri_unescape_alloc(const char *);
+#if 0
+static bool smb_encrypt;
+#endif
 
 /*
  * 'main()' - Main entry for SMB backend.
@@ -51,16 +60,24 @@ static int          smb_print(struct cli_state *, char *, FILE *);
 {
   int          i;              /* Looping var */
   int          copies;         /* Number of copies */
+  int          port;           /* Port number */
   char         uri[1024],      /* URI */
                *sep,           /* Pointer to separator */
-               *username,      /* Username */
-               *password,      /* Password */
-               *workgroup,     /* Workgroup */
+               *tmp, *tmp2,    /* Temp pointers to do escaping */
+               *password;      /* Password */
+  char         *username,      /* Username */
                *server,        /* Server name */
                *printer;       /* Printer name */
+  const char   *workgroup;     /* Workgroup */
   FILE         *fp;            /* File to print */
-  int          status=0;               /* Status of LPD job */
+  int          status=1;               /* Status of LPD job */
   struct cli_state *cli;       /* SMB interface */
+  char null_str[1];
+  int tries = 0;
+  const char *dev_uri;
+  TALLOC_CTX *frame = talloc_stackframe();
+
+  null_str[0] = '\0';
 
   /* we expect the URI in argv[0]. Detect the case where it is in argv[1] and cope */
   if (argc > 2 && strncmp(argv[0],"smb://", 6) && !strncmp(argv[1],"smb://", 6)) {
@@ -78,7 +95,8 @@ static int            smb_print(struct cli_state *, char *, FILE *);
     */
 
     list_devices();
-    return (0);
+    status = 0;
+    goto done;
   }
 
   if (argc < 6 || argc > 7)
@@ -88,8 +106,8 @@ static int           smb_print(struct cli_state *, char *, FILE *);
     fputs("       The DEVICE_URI environment variable can also contain the\n", stderr);
     fputs("       destination printer:\n", stderr);
     fputs("\n", stderr);
-    fputs("           smb://[username:password@][workgroup/]server/printer\n", stderr);
-    return (1);
+    fputs("           smb://[username:password@][workgroup/]server[:port]/printer\n", stderr);
+    goto done;
   }
 
  /*
@@ -97,6 +115,7 @@ static int           smb_print(struct cli_state *, char *, FILE *);
   * Otherwise, print data from stdin...
   */
 
+
   if (argc == 6)
   {
    /*
@@ -109,7 +128,7 @@ static int          smb_print(struct cli_state *, char *, FILE *);
   else if ((fp = fopen(argv[6], "rb")) == NULL)
   {
     perror("ERROR: Unable to open print file");
-    return (1);
+    goto done;
   }
   else
     copies = atoi(argv[4]);
@@ -118,14 +137,15 @@ static int                smb_print(struct cli_state *, char *, FILE *);
   * Find the URI...
   */
 
-  if (strncmp(argv[0], "smb://", 6) == 0)
+  dev_uri = getenv("DEVICE_URI");
+  if (dev_uri)
+    strncpy(uri, dev_uri, sizeof(uri) - 1);
+  else if (strncmp(argv[0], "smb://", 6) == 0)
     strncpy(uri, argv[0], sizeof(uri) - 1);
-  else if (getenv("DEVICE_URI") != NULL)
-    strncpy(uri, getenv("DEVICE_URI"), sizeof(uri) - 1);
   else
   {
-    fputs("ERROR: No device URI found in argv[0] or DEVICE_URI environment variable!\n", stderr);
-    return (1);
+    fputs("ERROR: No device URI found in DEVICE_URI environment variable or argv[0] !\n", stderr);
+    goto done;
   }
 
   uri[sizeof(uri) - 1] = '\0';
@@ -134,39 +154,46 @@ static int                smb_print(struct cli_state *, char *, FILE *);
   * Extract the destination from the URI...
   */
 
-  if ((sep = strrchr(uri, '@')) != NULL)
+  if ((sep = strrchr_m(uri, '@')) != NULL)
   {
-    username = uri + 6;
+    tmp = uri + 6;
     *sep++ = '\0';
 
+    /* username is in tmp */
+
     server = sep;
 
    /*
     * Extract password as needed...
     */
 
-    if ((password = strchr(username, ':')) != NULL)
-      *password++ = '\0';
-    else
-      password = "";
+    if ((tmp2 = strchr_m(tmp, ':')) != NULL) {
+      *tmp2++ = '\0';
+      password = uri_unescape_alloc(tmp2);
+    } else {
+      password = null_str;
+    }
+    username = uri_unescape_alloc(tmp);
   }
   else
   {
-    username = "";
-    password = "";
+    username = null_str;
+    password = null_str;
     server   = uri + 6;
   }
 
-  if ((sep = strchr(server, '/')) == NULL)
+  tmp = server;
+
+  if ((sep = strchr_m(tmp, '/')) == NULL)
   {
     fputs("ERROR: Bad URI - need printer name!\n", stderr);
-    return (1);
+    goto done;
   }
 
   *sep++ = '\0';
-  printer = sep;
+  tmp2 = sep;
 
-  if ((sep = strchr(printer, '/')) != NULL)
+  if ((sep = strchr_m(tmp2, '/')) != NULL)
   {
    /*
     * Convert to smb://[username:password@]workgroup/server/printer...
@@ -174,54 +201,70 @@ static int                smb_print(struct cli_state *, char *, FILE *);
 
     *sep++ = '\0';
 
-    workgroup = server;
-    server    = printer;
-    printer   = sep;
+    workgroup = uri_unescape_alloc(tmp);
+    server    = uri_unescape_alloc(tmp2);
+    printer   = uri_unescape_alloc(sep);
   }
-  else
+  else {
     workgroup = NULL;
+    server = uri_unescape_alloc(tmp);
+    printer = uri_unescape_alloc(tmp2);
+  }
+  
+  if ((sep = strrchr_m(server, ':')) != NULL)
+  {
+    *sep++ = '\0';
 
+    port=atoi(sep);
+  }
+  else 
+       port=0;
+       
  /*
   * Setup the SAMBA server state...
   */
 
   setup_logging("smbspool", True);
 
-  TimeInit();
-  charset_initialise();
+  lp_set_in_client(True);       /* Make sure that we tell lp_load we are */
 
-  in_client = True;   /* Make sure that we tell lp_load we are */
+  load_case_tables();
 
-  if (!lp_load(CONFIGFILE, True, False, False))
+  if (!lp_load(get_dyn_CONFIGFILE(), True, False, False, True))
   {
-    fprintf(stderr, "ERROR: Can't load %s - run testparm to debug it\n", CONFIGFILE);
-    return (1);
+    fprintf(stderr, "ERROR: Can't load %s - run testparm to debug it\n", get_dyn_CONFIGFILE());
+    goto done;
   }
 
   if (workgroup == NULL)
     workgroup = lp_workgroup();
 
-  codepage_initialise(lp_client_code_page());
-
   load_interfaces();
 
   do
   {
-    if ((cli = smb_connect(workgroup, server, printer, username, password)) == NULL)
+    if ((cli = smb_connect(workgroup, server, port, printer, username, password, argv[2])) == NULL)
     {
       if (getenv("CLASS") == NULL)
       {
-        perror("ERROR: Unable to connect to SAMBA host, will retry in 60 seconds...");
-        sleep (60);
+        fprintf(stderr, "ERROR: Unable to connect to CIFS host, will retry in 60 seconds...\n");
+        sleep (60); /* should just waiting and retrying fix authentication  ??? */
+        tries++;
       }
       else
       {
-        perror("ERROR: Unable to connect to SAMBA host, trying next printer...");
-        return (1);
+        fprintf(stderr, "ERROR: Unable to connect to CIFS host, trying next printer...\n");
+       goto done;
       }
     }
   }
-  while (cli == NULL);
+  while ((cli == NULL) && (tries < MAX_RETRY_CONNECT));
+
+  if (cli == NULL) {
+        fprintf(stderr, "ERROR: Unable to connect to CIFS host after (tried %d times)\n", tries);
+       goto done;
+  }
 
  /*
   * Now that we are connected to the server, ignore SIGTERM so that we
@@ -247,6 +290,9 @@ static int          smb_print(struct cli_state *, char *, FILE *);
   * Return the queue status...
   */
 
+  done:
+
+  TALLOC_FREE(frame);
   return (status);
 }
 
@@ -267,95 +313,251 @@ list_devices(void)
 
 
 /*
- * 'smb_connect()' - Return a connection to a server.
+ * get the name of the newest ticket cache for the uid user.
+ * pam_krb5 defines a non default ticket cache for each user
  */
-
-static struct cli_state *              /* O - SMB connection */
-smb_connect(char *workgroup,           /* I - Workgroup */
-            char *server,              /* I - Server */
-            char *share,               /* I - Printer */
-            char *username,            /* I - Username */
-            char *password)            /* I - Password */
+static
+char * get_ticket_cache( uid_t uid )
 {
-  struct cli_state     *c;             /* New connection */
-  struct nmb_name      called,         /* NMB name of server */
-                       calling;        /* NMB name of client */
-  struct in_addr       ip;             /* IP address of server */
-  pstring              myname;         /* Client name */
+  char *ticket_file = NULL;
+  SMB_STRUCT_DIR *tcdir;                  /* directory where ticket caches are stored */
+  SMB_STRUCT_DIRENT *dirent;   /* directory entry */
+  char *filename = NULL;       /* holds file names on the tmp directory */
+  SMB_STRUCT_STAT buf;        
+  char user_cache_prefix[CC_MAX_FILE_LEN];
+  char file_path[CC_MAX_FILE_PATH_LEN];
+  time_t t = 0;
+
+  snprintf(user_cache_prefix, CC_MAX_FILE_LEN, "%s%d", CC_PREFIX, uid );
+  tcdir = sys_opendir( TICKET_CC_DIR );
+  if ( tcdir == NULL ) 
+    return NULL; 
+  
+  while ( (dirent = sys_readdir( tcdir ) ) ) 
+  { 
+    filename = dirent->d_name;
+    snprintf(file_path, CC_MAX_FILE_PATH_LEN,"%s/%s", TICKET_CC_DIR, filename); 
+    if (sys_stat(file_path, &buf) == 0 ) 
+    {
+      if ( ( buf.st_uid == uid ) && ( S_ISREG(buf.st_mode) ) ) 
+      {
+        /*
+         * check the user id of the file to prevent denial of
+         * service attacks by creating fake ticket caches for the 
+         * user
+         */
+        if ( strstr( filename, user_cache_prefix ) ) 
+        {
+          if ( buf.st_mtime > t ) 
+          { 
+            /*
+             * a newer ticket cache found 
+             */
+            free(ticket_file);
+            ticket_file=SMB_STRDUP(file_path);
+            t = buf.st_mtime;
+          }
+        }
+      }
+    }
+  }
 
+  sys_closedir(tcdir);
 
- /*
-  * Get the names and addresses of the client and server...
-  */
+  if ( ticket_file == NULL )
+  {
+    /* no ticket cache found */
+    fprintf(stderr, "ERROR: No ticket cache found for userid=%d\n", uid);
+    return NULL;
+  }
+
+  return ticket_file;
+}
 
-  get_myname(myname);  
+static struct cli_state 
+*smb_complete_connection(const char *myname,
+            const char *server,
+            int port,
+            const char *username, 
+            const char *password, 
+            const char *workgroup, 
+            const char *share,
+            int flags)
+{
+  struct cli_state  *cli;    /* New connection */    
+  NTSTATUS nt_status;
+  
+  /* Start the SMB connection */
+  nt_status = cli_start_connection( &cli, myname, server, NULL, port, 
+                                    Undefined, flags, NULL);
+  if (!NT_STATUS_IS_OK(nt_status)) 
+  {
+    return NULL;      
+  }
+    
+  /* We pretty much guarentee password must be valid or a pointer
+     to a 0 char. */
+  if (!password) {
+    return NULL;
+  }
+  
+  if ( (username) && (*username) && 
+      (strlen(password) == 0 ) && 
+       (cli->use_kerberos) ) 
+  {
+    /* Use kerberos authentication */
+    struct passwd *pw;
+    char *cache_file;
+    
+    
+    if ( !(pw = sys_getpwnam(username)) ) {
+      fprintf(stderr,"ERROR Can not get %s uid\n", username);
+      cli_shutdown(cli);
+      return NULL;
+    }
 
-  ip = ipzero;
+    /*
+     * Get the ticket cache of the user to set KRB5CCNAME env
+     * variable
+     */
+    cache_file = get_ticket_cache( pw->pw_uid );
+    if ( cache_file == NULL ) 
+    {
+      fprintf(stderr, "ERROR: Can not get the ticket cache for %s\n", username);
+      cli_shutdown(cli);
+      return NULL;
+    }
 
-  make_nmb_name(&calling, myname, 0x0);
-  make_nmb_name(&called, server, 0x20);
+    if ( setenv(KRB5CCNAME, cache_file, OVERWRITE) < 0 ) 
+    {
+      fprintf(stderr, "ERROR: Can not add KRB5CCNAME to the environment");
+      cli_shutdown(cli);
+      free(cache_file);
+      return NULL;
+    }
+    free(cache_file);
 
- /*
-  * Open a new connection to the SMB server...
-  */
+    /*
+     * Change the UID of the process to be able to read the kerberos
+     * ticket cache
+     */
+    setuid(pw->pw_uid);
 
-  if ((c = cli_initialise(NULL)) == NULL)
-  {
-    fputs("ERROR: cli_initialize() failed...\n", stderr);
-    return (NULL);
   }
-
-  if (!cli_set_port(c, SMB_PORT))
+   
+   
+  if (!NT_STATUS_IS_OK(cli_session_setup(cli, username,
+                                        password, strlen(password)+1, 
+                                        password, strlen(password)+1,
+                                        workgroup)))
   {
-    fputs("ERROR: cli_set_port() failed...\n", stderr);
-    return (NULL);
-  }
+    fprintf(stderr,"ERROR: Session setup failed: %s\n", cli_errstr(cli));
+    if (NT_STATUS_V(cli_nt_error(cli)) == 
+        NT_STATUS_V(NT_STATUS_MORE_PROCESSING_REQUIRED))
+    {
+      fprintf(stderr, "did you forget to run kinit?\n");
+    }
+    cli_shutdown(cli);
 
-  if (!cli_connect(c, server, &ip))
-  {
-    fputs("ERROR: cli_connect() failed...\n", stderr);
-    return (NULL);
+    return NULL;
   }
-
-  if (!cli_session_request(c, &calling, &called))
+    
+  if (!cli_send_tconX(cli, share, "?????", password, strlen(password)+1)) 
   {
-    fputs("ERROR: cli_session_request() failed...\n", stderr);
-    return (NULL);
+    fprintf(stderr, "ERROR: Tree connect failed (%s)\n", cli_errstr(cli));
+    cli_shutdown(cli);
+    return NULL;
   }
-
-  if (!cli_negprot(c))
+    
+#if 0
+  /* Need to work out how to specify this on the URL. */
+  if (smb_encrypt)
   {
-    fputs("ERROR: SMB protocol negotiation failed\n", stderr);
-    cli_shutdown(c);
-    return (NULL);
+    if (!cli_cm_force_encryption(cli,
+                       username,
+                       password,
+                       workgroup,
+                       share))
+    {
+      fprintf(stderr, "ERROR: encryption setup failed\n");
+      cli_shutdown(cli);
+      return NULL;
+    }
   }
+#endif
+
+  return cli;
+}
+
+/*
+ * 'smb_connect()' - Return a connection to a server.
+ */
+
+static struct cli_state *    /* O - SMB connection */
+smb_connect(const char *workgroup,    /* I - Workgroup */
+            const char *server,    /* I - Server */
+            const int port,    /* I - Port */
+            const char *share,    /* I - Printer */
+            const char *username,    /* I - Username */
+            const char *password,    /* I - Password */
+      const char *jobusername)   /* I - User who issued the print job */
+{
+  struct cli_state  *cli;    /* New connection */
+  char *myname = NULL;    /* Client name */
+  struct passwd *pwd;
 
  /*
-  * Do password stuff...
+  * Get the names and addresses of the client and server...
   */
 
-  if (!cli_session_setup(c, username, 
-                        password, strlen(password),
-                        password, strlen(password),
-                        workgroup))
-  {
-    fprintf(stderr, "ERROR: SMB session setup failed: %s\n", cli_errstr(c));
-    return (NULL);
+  myname = get_myname(talloc_tos());
+  if (!myname) {
+       return NULL;
   }
 
-  if (!cli_send_tconX(c, share, "?????",
-                     password, strlen(password)+1))
+  /* See if we have a username first.  This is for backwards compatible 
+     behavior with 3.0.14a */
+
+  if ( username &&  *username )
   {
-    fprintf(stderr, "ERROR: SMB tree connect failed: %s\n", cli_errstr(c));
-    cli_shutdown(c);
-    return (NULL);
+      cli = smb_complete_connection(myname, server, port, username, 
+                                    password, workgroup, share, 0 );
+      if (cli) 
+        return cli;
   }
+  
+  /* 
+   * Try to use the user kerberos credentials (if any) to authenticate
+   */
+  cli = smb_complete_connection(myname, server, port, jobusername, "", 
+                                workgroup, share, 
+                                CLI_FULL_CONNECTION_USE_KERBEROS );
 
- /*
-  * Return the new connection...
-  */
+  if (cli ) { return cli; }
+
+  /* give a chance for a passwordless NTLMSSP session setup */
 
-  return (c);
+  pwd = getpwuid(geteuid());
+  if (pwd == NULL) {
+     return NULL;
+  }
+
+  cli = smb_complete_connection(myname, server, port, pwd->pw_name, "", 
+                                workgroup, share, 0);
+
+  if (cli) { return cli; }
+
+  /*
+   * last try. Use anonymous authentication
+   */
+
+  cli = smb_complete_connection(myname, server, port, "", "", 
+                                workgroup, share, 0);
+  /*
+   * Return the new connection...
+   */
+  
+  return (cli);
 }
 
 
@@ -389,7 +591,7 @@ smb_print(struct cli_state *cli,    /* I - SMB connection */
 
   if ((fnum = cli_open(cli, title, O_RDWR | O_CREAT | O_TRUNC, DENY_NONE)) == -1)
   {
-    fprintf(stderr, "ERROR: %s opening remote file %s\n",
+    fprintf(stderr, "ERROR: %s opening remote spool %s\n",
             cli_errstr(cli), title);
     return (1);
   }
@@ -407,7 +609,7 @@ smb_print(struct cli_state *cli,    /* I - SMB connection */
   {
     if (cli_write(cli, fnum, 0, buffer, tbytes, nbytes) != nbytes)
     {
-      fprintf(stderr, "ERROR: Error writing file: %s\n", cli_errstr(cli));
+      fprintf(stderr, "ERROR: Error writing spool: %s\n", cli_errstr(cli));
       break;
     }
 
@@ -416,10 +618,21 @@ smb_print(struct cli_state *cli,  /* I - SMB connection */
 
   if (!cli_close(cli, fnum))
   {
-    fprintf(stderr, "ERROR: %s closing remote file %s\n",
+    fprintf(stderr, "ERROR: %s closing remote spool %s\n",
             cli_errstr(cli), title);
     return (1);
   }
   else
     return (0);
 }
+
+static char *uri_unescape_alloc(const char *uritok)
+{
+       char *ret;
+
+       ret = (char *)SMB_STRDUP(uritok);
+       if (!ret) return NULL;
+
+       rfc1738_unescape(ret);
+       return ret;
+}