Adding the same changes to HEAD as were added to BRANCH_1_9_18.
[samba.git] / source3 / utils / smbpasswd.c
index 167eb2ed5f3695458f584caa5a51cae545454c4c..5b5f86c5e1c510f72819175f86d3e48c721d153d 100644 (file)
@@ -1,8 +1,6 @@
-#ifdef SMB_PASSWD
-
 /*
  * Unix SMB/Netbios implementation. Version 1.9. smbpasswd module. Copyright
- * (C) Jeremy Allison 1995.
+ * (C) Jeremy Allison 1995-1998
  * 
  * 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
  */
 
 #include "includes.h"
-#include "des.h"
 
-/* Static buffers we will return. */
-static struct smb_passwd pw_buf;
-static pstring  user_name;
-static unsigned char smbpwd[16];
-static unsigned char smbntpwd[16];
+/* 
+ * Password changing error codes.
+ */
+
+struct
+{
+  int err;
+  char *message;
+} pw_change_errmap[] =
+{
+  {5,    "User has insufficient privilege" },
+  {86,   "The specified password is invalid" },
+  {2226, "Operation only permitted on a Primary Domain Controller"  },
+  {2243, "The password cannot be changed" },
+  {2246, "The password is too short" },
+  {0, NULL}
+};
 
 static int gethexpwd(char *p, char *pwd)
 {
@@ -51,10 +60,16 @@ static int gethexpwd(char *p, char *pwd)
        return (True);
 }
 
-struct smb_passwd *
+static struct smb_passwd *
 _my_get_smbpwnam(FILE * fp, char *name, BOOL * valid_old_pwd, 
                BOOL *got_valid_nt_entry, long *pwd_seekpos)
 {
+       /* Static buffers we will return. */
+       static struct smb_passwd pw_buf;
+       static pstring  user_name;
+       static unsigned char smbpwd[16];
+       static unsigned char smbntpwd[16];
+
        char            linebuf[256];
        unsigned char   c;
        unsigned char  *p;
@@ -152,8 +167,8 @@ _my_get_smbpwnam(FILE * fp, char *name, BOOL * valid_old_pwd,
                                /* NT Entry was valid - even if 'X' or '*', can be overwritten */
                                *got_valid_nt_entry = True;
                                if (*p != '*' && *p != 'X') {
-                                       if(gethexpwd(p,smbntpwd))
-                                               pw_buf.smb_nt_passwd = smbntpwd;
+                                 if (gethexpwd((char *)p,(char *)smbntpwd))
+                                   pw_buf.smb_nt_passwd = smbntpwd;
                                }
                        }
                        pw_buf.smb_name = user_name;
@@ -167,12 +182,12 @@ _my_get_smbpwnam(FILE * fp, char *name, BOOL * valid_old_pwd,
                if (p[32] != ':')
                        return (False);
 
-               if (!strncasecmp(p, "NO PASSWORD", 11)) {
-                       pw_buf.smb_passwd = NULL;       /* No password */
+               if (!strncasecmp((char *)p, "NO PASSWORD", 11)) {
+                 pw_buf.smb_passwd = NULL;     /* No password */
                } else {
-                       if(!gethexpwd(p,smbpwd))
-                               return False;
-                       pw_buf.smb_passwd = smbpwd;
+                 if(!gethexpwd((char *)p,(char *)smbpwd))
+                   return False;
+                 pw_buf.smb_passwd = smbpwd;
                }
 
                pw_buf.smb_name = user_name;
@@ -189,8 +204,8 @@ _my_get_smbpwnam(FILE * fp, char *name, BOOL * valid_old_pwd,
                        /* NT Entry was valid - even if 'X' or '*', can be overwritten */
                        *got_valid_nt_entry = True;
                        if (*p != '*' && *p != 'X') {
-                               if(gethexpwd(p,smbntpwd))
-                                       pw_buf.smb_nt_passwd = smbntpwd;
+                         if (gethexpwd((char *)p,(char *)smbntpwd))
+                           pw_buf.smb_nt_passwd = smbntpwd;
                        }
                }
                return &pw_buf;
@@ -201,15 +216,21 @@ _my_get_smbpwnam(FILE * fp, char *name, BOOL * valid_old_pwd,
 /*
  * Print command usage on stderr and die.
  */
-void 
-usage(char *name)
+static void usage(char *name, BOOL is_root)
 {
-       fprintf(stderr, "Usage is : %s [username]\n", name);
+       if(is_root)
+               fprintf(stderr, "Usage is : %s [-a] [username] [password]\n\
+%s: [-r machine] [username] [password]\n%s: [-h]", name, name, name);
+       else
+               fprintf(stderr, "Usage is : %s [-h] [-r machine] [password]\n", name);
        exit(1);
 }
 
 int main(int argc, char **argv)
 {
+  extern char *optarg;
+  extern int optind;
+  char *prog_name;
   int             real_uid;
   struct passwd  *pwd;
   fstring         old_passwd;
@@ -222,7 +243,7 @@ int main(int argc, char **argv)
   struct smb_passwd *smb_pwent;
   FILE           *fp;
   BOOL            valid_old_pwd = False;
-  BOOL                         got_valid_nt_entry = False;
+  BOOL          got_valid_nt_entry = False;
   long            seekpos;
   int             pwfd;
   char            ascii_p16[66];
@@ -231,66 +252,242 @@ int main(int argc, char **argv)
   int             lockfd = -1;
   char           *pfile = SMB_PASSWD_FILE;
   char            readbuf[16 * 1024];
-  
-  setup_logging(argv[0],True);
+  BOOL is_root = False;
+  pstring  user_name;
+  char *remote_machine = NULL;
+  BOOL          add_user = False;
+  BOOL          got_new_pass = False;
+  pstring servicesf = CONFIGFILE;
+
+  new_passwd[0] = '\0';
+  user_name[0] = '\0';
+
+  memset(old_passwd, '\0', sizeof(old_passwd));
+  memset(new_passwd, '\0', sizeof(old_passwd));
+
+  prog_name = argv[0];
+
+  TimeInit();
+
+  setup_logging(prog_name,True);
   
   charset_initialise();
-  
-#ifndef DEBUG_PASSWORD
-  /* Check the effective uid */
-  if (geteuid() != 0) {
-    fprintf(stderr, "%s: Must be setuid root.\n", argv[0]);
-    exit(1);
+
+  if (!lp_load(servicesf,True,False,False)) {
+    fprintf(stderr, "%s: Can't load %s - run testparm to debug it\n", prog_name, servicesf);
   }
-#endif
-  
+    
+  codepage_initialise(lp_client_code_page());
+
   /* Get the real uid */
   real_uid = getuid();
   
-  /* Deal with usage problems */
-  if (real_uid == 0) {
-    /* As root we can change anothers password. */
-    if (argc != 1 && argc != 2)
-      usage(argv[0]);
-  } else if (argc != 1)
-    usage(argv[0]);
-  
-  
-  if (real_uid == 0 && argc == 2) {
-    /* If we are root we can change anothers password. */
-    strncpy(user_name, argv[1], sizeof(user_name) - 1);
-    user_name[sizeof(user_name) - 1] = '\0';
-    pwd = getpwnam(user_name);
-  } else {
-    pwd = getpwuid(real_uid);
+  /* Check the effective uid */
+  if ((geteuid() == 0) && (real_uid != 0)) {
+    fprintf(stderr, "%s: Must *NOT* be setuid root.\n", prog_name);
+    exit(1);
   }
-  
-  if (pwd == 0) {
-    fprintf(stderr, "%s: Unable to get UNIX password entry for user.\n", argv[0]);
+
+  is_root = (real_uid == 0);
+
+  while ((c = getopt(argc, argv, "ahr:")) != EOF) {
+    switch(c) {
+    case 'a':
+      add_user = True;
+      break;
+    case 'r':
+      remote_machine = optarg;
+      break;
+    case 'h':
+    default:
+      usage(prog_name, is_root);
+    }
+  }
+
+  argc -= optind;
+  argv += optind;
+
+  /*
+   * Ensure add_user and remote machine are
+   * not both set.
+   */
+  if(add_user && (remote_machine != NULL))
+    usage(prog_name, True);
+
+  if( is_root ) {
+
+    /*
+     * Deal with root - can add a user, but only locally.
+     */
+
+    switch(argc) {
+      case 0:
+        break;
+      case 1:
+        /* If we are root we can change another's password. */
+        pstrcpy(user_name, argv[0]);
+        break;
+      case 2:
+        pstrcpy(user_name, argv[0]);
+        fstrcpy(new_passwd, argv[1]);
+        got_new_pass = True;
+        break;
+      default:
+        usage(prog_name, True);
+    }
+
+    if(*user_name)
+      pwd = getpwnam(user_name);
+    else {
+      if((pwd = getpwuid(real_uid)) != NULL)
+        pstrcpy( user_name, pwd->pw_name);
+    }
+
+  } else {
+
+    if(add_user) {
+      fprintf(stderr, "%s: Only root can set anothers password.\n", prog_name);
+      usage(prog_name, False);
+    }
+
+    if(argc > 1)
+      usage(prog_name, False);
+
+    if(argc == 1) {
+      fstrcpy(new_passwd, argv[0]);
+      got_new_pass = True;
+    }
+
+    if((pwd = getpwuid(real_uid)) != NULL)
+      pstrcpy( user_name, pwd->pw_name);
+
+    /*
+     * A non-root user is always setting a password
+     * via a remote machine (even if that machine is
+     * localhost).
+     */
+
+    if(remote_machine == NULL)
+      remote_machine = "127.0.0.1";
+  }    
+    
+  if (*user_name == '\0') {
+    fprintf(stderr, "%s: Unable to get a user name for password change.\n", prog_name);
     exit(1);
   }
-  /* If we are root we don't ask for the old password. */
-  old_passwd[0] = '\0';
-  if (real_uid != 0) {
+
+  /* 
+   * If we are root we don't ask for the old password (unless it's on a
+   * remote machine.
+   */
+
+  if (remote_machine != NULL) {
     p = getpass("Old SMB password:");
-    strncpy(old_passwd, p, sizeof(fstring));
-    old_passwd[sizeof(fstring)-1] = '\0';
+    fstrcpy(old_passwd, p);
   }
-  new_passwd[0] = '\0';
-  p = getpass("New SMB password:");
-  strncpy(new_passwd, p, sizeof(fstring));
-  new_passwd[sizeof(fstring)-1] = '\0';
-  p = getpass("Retype new SMB password:");
-  if (strcmp(p, new_passwd)) {
-    fprintf(stderr, "%s: Mismatch - password unchanged.\n", argv[0]);
-    exit(1);
+
+  if (!got_new_pass) {
+    new_passwd[0] = '\0';
+
+    p = getpass("New SMB password:");
+
+    strncpy(new_passwd, p, sizeof(fstring));
+    new_passwd[sizeof(fstring)-1] = '\0';
+
+    p = getpass("Retype new SMB password:");
+
+    if (strncmp(p, new_passwd, sizeof(fstring)-1))
+    {
+      fprintf(stderr, "%s: Mismatch - password unchanged.\n", prog_name);
+      exit(1);
+    }
   }
   
   if (new_passwd[0] == '\0') {
     printf("Password not set\n");
     exit(0);
   }
+  /* 
+   * Now do things differently depending on if we're changing the
+   * password on a remote machine. Remember - a normal user is
+   * always using this code, looping back to the local smbd.
+   */
+
+  if(remote_machine != NULL) {
+    struct cli_state cli;
+    struct in_addr ip;
+    fstring myname;
+
+    if(get_myname(myname,NULL) == False) {
+      fprintf(stderr, "%s: unable to get my hostname.\n", prog_name );
+      exit(1);
+    }
+
+    if(!resolve_name( remote_machine, &ip)) {
+      fprintf(stderr, "%s: unable to find an IP address for machine %s.\n",
+              prog_name, remote_machine );
+      exit(1);
+    }
+  
+    if (!cli_initialise(&cli) || !cli_connect(&cli, remote_machine, &ip)) {
+      fprintf(stderr, "%s: unable to connect to SMB server on machine %s.\n",
+              prog_name, remote_machine );
+      exit(1);
+    }
+  
+    if (!cli_session_request(&cli, remote_machine, 0x20, myname)) {
+      fprintf(stderr, "%s: machine %s rejected the session setup.\n",
+              prog_name, remote_machine );
+      cli_shutdown(&cli);
+      exit(1);
+    }
   
+    cli.protocol = PROTOCOL_NT1;
+
+    if (!cli_negprot(&cli)) {
+      fprintf(stderr, "%s: machine %s rejected the negotiate protocol.\n",        
+              prog_name, remote_machine );
+      cli_shutdown(&cli);
+      exit(1);
+    }
+  
+    if (!cli_session_setup(&cli, user_name, old_passwd, strlen(old_passwd),
+                           "", 0, "")) {
+      fprintf(stderr, "%s: machine %s rejected the session setup.\n",        
+              prog_name, remote_machine );
+      cli_shutdown(&cli);
+      exit(1);
+    }               
+
+    if (!cli_send_tconX(&cli, "IPC$", "IPC", "", 1)) {
+      fprintf(stderr, "%s: machine %s rejected the tconX on the IPC$ share.\n",
+              prog_name, remote_machine );
+      cli_shutdown(&cli);
+      exit(1);
+    }
+
+    if(!cli_oem_change_password(&cli, user_name, new_passwd, old_passwd)) {
+      fstring error_message;
+
+      sprintf(error_message, " with code %d", cli.error);
+      
+      for(i = 0; pw_change_errmap[i].message != NULL; i++) {
+        if (pw_change_errmap[i].err == cli.error) {
+          fstrcpy( error_message, pw_change_errmap[i].message);
+          break;
+        }
+      }
+      fprintf(stderr, "%s: machine %s rejected the password change: %s.\n",
+              prog_name, remote_machine, error_message );
+      cli_shutdown(&cli);
+      exit(1);
+    }
+
+    cli_shutdown(&cli);
+    exit(0);
+  }
+
   /* Calculate the MD4 hash (NT compatible) of the old and new passwords */
   memset(old_nt_p16, '\0', 16);
   E_md4hash((uchar *)old_passwd, old_nt_p16);
@@ -318,69 +515,123 @@ int main(int argc, char **argv)
    * Open the smbpaswd file XXXX - we need to parse smb.conf to get the
    * filename
    */
-  if ((fp = fopen(pfile, "r+")) == NULL) {
-    err = errno;
-    fprintf(stderr, "%s: Failed to open password file %s.\n",
-           argv[0], pfile);
-    errno = err;
-    perror(argv[0]);
-    exit(err);
+  fp = fopen(pfile, "r+");
+  if (!fp && errno == ENOENT) {
+         fp = fopen(pfile, "w");
+         if (fp) {
+                 fprintf(fp, "# Samba SMB password file\n");
+                 fclose(fp);
+                 fp = fopen(pfile, "r+");
+         }
   }
+  if (!fp) {
+         err = errno;
+         fprintf(stderr, "%s: Failed to open password file %s.\n",
+                 prog_name, pfile);
+         errno = err;
+         perror(prog_name);
+         exit(err);
+  }
+  
   /* Set read buffer to 16k for effiecient reads */
   setvbuf(fp, readbuf, _IOFBF, sizeof(readbuf));
   
   /* make sure it is only rw by the owner */
   chmod(pfile, 0600);
-  
+
   /* Lock the smbpasswd file for write. */
-  if ((lockfd = pw_file_lock(pfile, F_WRLCK, 5)) < 0) {
+  if ((lockfd = pw_file_lock(fileno(fp), F_WRLCK, 5)) < 0) {
     err = errno;
     fprintf(stderr, "%s: Failed to lock password file %s.\n",
-           argv[0], pfile);
+           prog_name, pfile);
     fclose(fp);
     errno = err;
-    perror(argv[0]);
+    perror(prog_name);
     exit(err);
   }
   /* Get the smb passwd entry for this user */
-  smb_pwent = _my_get_smbpwnam(fp, pwd->pw_name, &valid_old_pwd, 
+  smb_pwent = _my_get_smbpwnam(fp, user_name, &valid_old_pwd, 
                               &got_valid_nt_entry, &seekpos);
   if (smb_pwent == NULL) {
-    fprintf(stderr, "%s: Failed to find entry for user %s in file %s.\n",
-           argv[0], pwd->pw_name, pfile);
-    fclose(fp);
-    pw_file_unlock(lockfd);
-    exit(1);
-  }
-  /* If we are root we don't need to check the old password. */
-  if (real_uid != 0) {
-    if ((valid_old_pwd == False) || (smb_pwent->smb_passwd == NULL)) {
-      fprintf(stderr, "%s: User %s is disabled, plase contact your administrator to enable it.\n", argv[0], pwd->pw_name);
-      fclose(fp);
-      pw_file_unlock(lockfd);
-      exit(1);
-    }
-    /* Check the old Lanman password */
-    if (memcmp(old_p16, smb_pwent->smb_passwd, 16)) {
-      fprintf(stderr, "%s: Couldn't change password.\n", argv[0]);
+    if(add_user == False) {
+      fprintf(stderr, "%s: Failed to find entry for user %s in file %s.\n",
+             prog_name, pwd->pw_name, pfile);
       fclose(fp);
       pw_file_unlock(lockfd);
       exit(1);
     }
-    /* Check the NT password if it exists */
-    if (smb_pwent->smb_nt_passwd != NULL) {
-      if (memcmp(old_nt_p16, smb_pwent->smb_nt_passwd, 16)) {
-       fprintf(stderr, "%s: Couldn't change password.\n", argv[0]);
-       fclose(fp);
-       pw_file_unlock(lockfd);
-       exit(1);
+
+    /* Create a new smb passwd entry and set it to the given password. */
+    {
+      int fd;
+      int new_entry_length;
+      char *new_entry;
+      long offpos;
+
+      /* The add user write needs to be atomic - so get the fd from 
+         the fp and do a raw write() call.
+       */
+      fd = fileno(fp);
+
+      if((offpos = lseek(fd, 0, SEEK_END)) == -1) {
+        fprintf(stderr, "%s: Failed to add entry for user %s to file %s. \
+Error was %s\n", prog_name, pwd->pw_name, pfile, strerror(errno));
+        fclose(fp);
+        pw_file_unlock(lockfd);
+        exit(1);
+      }
+
+      new_entry_length = strlen(pwd->pw_name) + 1 + 15 + 1 + 
+                         32 + 1 + 32 + 1 + strlen(pwd->pw_gecos) + 
+                         1 + strlen(pwd->pw_dir) + 1 + 
+                         strlen(pwd->pw_shell) + 1;
+      if((new_entry = (char *)malloc( new_entry_length )) == 0) {
+        fprintf(stderr, "%s: Failed to add entry for user %s to file %s. \
+Error was %s\n", prog_name, pwd->pw_name, pfile, strerror(errno));
+        fclose(fp);
+        pw_file_unlock(lockfd);
+        exit(1);
       }
+
+      sprintf(new_entry, "%s:%u:", pwd->pw_name, (unsigned)pwd->pw_uid);
+      p = &new_entry[strlen(new_entry)];
+      for( i = 0; i < 16; i++)
+        sprintf(&p[i*2], "%02X", new_p16[i]);
+      p += 32;
+      *p++ = ':';
+      for( i = 0; i < 16; i++)
+        sprintf(&p[i*2], "%02X", new_nt_p16[i]);
+      p += 32;
+      *p++ = ':';
+      sprintf(p, "%s:%s:%s\n", pwd->pw_gecos, 
+              pwd->pw_dir, pwd->pw_shell);
+      if(write(fd, new_entry, strlen(new_entry)) != strlen(new_entry)) {
+        fprintf(stderr, "%s: Failed to add entry for user %s to file %s. \
+Error was %s\n", prog_name, pwd->pw_name, pfile, strerror(errno));
+        /* Remove the entry we just wrote. */
+        if(ftruncate(fd, offpos) == -1) {
+          fprintf(stderr, "%s: ERROR failed to ftruncate file %s. \
+Error was %s. Password file may be corrupt ! Please examine by hand !\n", 
+                   prog_name, pwd->pw_name, strerror(errno));
+        }
+        fclose(fp);
+        pw_file_unlock(lockfd);
+        exit(1);
+      }
+      
+      fclose(fp);  
+      pw_file_unlock(lockfd);  
+      exit(0);
     }
+  } else {
+         /* the entry already existed */
+         add_user = False;
   }
+
   /*
-   * If we get here either we were root or the old password checked out
-   * ok.
+   * We are root - just write the new password.
    */
+
   /* Create the 32 byte representation of the new p16 */
   for (i = 0; i < 16; i++) {
     sprintf(&ascii_p16[i * 2], "%02X", (uchar) new_p16[i]);
@@ -401,10 +652,10 @@ int main(int argc, char **argv)
   if (ret != seekpos - 1) {
     err = errno;
     fprintf(stderr, "%s: seek fail on file %s.\n",
-           argv[0], pfile);
+           prog_name, pfile);
     fclose(fp);
     errno = err;
-    perror(argv[0]);
+    perror(prog_name);
     pw_file_unlock(lockfd);
     exit(1);
   }
@@ -412,16 +663,16 @@ int main(int argc, char **argv)
   if (read(pwfd, &c, 1) != 1) {
     err = errno;
     fprintf(stderr, "%s: read fail on file %s.\n",
-           argv[0], pfile);
+           prog_name, pfile);
     fclose(fp);
     errno = err;
-    perror(argv[0]);
+    perror(prog_name);
     pw_file_unlock(lockfd);
     exit(1);
   }
   if (c != ':') {
     fprintf(stderr, "%s: sanity check on passwd file %s failed.\n",
-           argv[0], pfile);
+           prog_name, pfile);
     fclose(fp);
     pw_file_unlock(lockfd);
     exit(1);
@@ -430,10 +681,10 @@ int main(int argc, char **argv)
   if (write(pwfd, ascii_p16, writelen) != writelen) {
     err = errno;
     fprintf(stderr, "%s: write fail in file %s.\n",
-           argv[0], pfile);
+           prog_name, pfile);
     fclose(fp);
     errno = err;
-    perror(argv[0]);
+    perror(prog_name);
     pw_file_unlock(lockfd);
     exit(err);
   }
@@ -443,14 +694,3 @@ int main(int argc, char **argv)
   return 0;
 }
 
-#else
-
-#include "includes.h"
-
-int 
-main(int argc, char **argv)
-{
-  printf("smb password encryption not selected in Makefile\n");
-  return 0;
-}
-#endif