Browse Source

Clean up password plumbing.

Rob Landley 2 years ago
parent
commit
0f51bfc50c
3 changed files with 79 additions and 121 deletions
  1. 66 108
      lib/password.c
  2. 2 2
      lib/pending.h
  3. 11 11
      toys/lsb/passwd.c

+ 66 - 108
lib/password.c

@@ -6,7 +6,6 @@
  */
 
 #include "toys.h"
-#include <time.h>
 
 // generate ID prefix and random salt for given encryption algorithm.
 int get_salt(char *salt, char *algo)
@@ -27,7 +26,7 @@ int get_salt(char *salt, char *algo)
       xgetrandom(libbuf, ((len*6)+7)/8, 0);
 
       // Grab 6 bit chunks and convert to characters in ./0-9a-zA-Z
-      for (i=0; i<len; i++) {
+      for (i = 0; i<len; i++) {
         int bitpos = i*6, bits = bitpos/8;
 
         bits = ((libbuf[i]+(libbuf[i+1]<<8)) >> (bitpos&7)) & 0x3f;
@@ -50,150 +49,109 @@ int get_salt(char *salt, char *algo)
 int read_password(char *buf, int buflen, char *mesg)
 {
   struct termios oldtermio;
-  struct sigaction sa, oldsa;
+  struct sigaction sa = {.sa_handler = generic_signal}, oldsa;
   int i, tty = tty_fd(), ret = 1;
 
-  // NOP signal handler to return from the read. Use sigaction() instead
-  // of xsignal() because we want to restore the old handler afterwards.
-  memset(&sa, 0, sizeof(sa));
-  sa.sa_handler = generic_signal;
+  // Set NOP signal handler to return from the read.
   sigaction(SIGINT, &sa, &oldsa);
-
   tcflush(tty, TCIFLUSH);
   xset_terminal(tty, 1, 0, &oldtermio);
   dprintf(tty, "%s", mesg);
 
+  // Loop assembling password. (Too long = fail)
   for (i = 0; i<buflen-1; i++) {
-    if ((ret = read(tty, buf+i, 1))<0 || (!ret&&!i) || *buf==4 || buf[i]==3) {
-      i = 0;
-      ret = 1;
-
+    // tty closed, or EOF or ctrl-D at start, or ctrl-C anywhere: fail.
+    if ((ret = read(tty, buf+i, 1))<0 || (!ret&&!i) || *buf==4 || buf[i]==3)
       break;
-    } else if (!ret || buf[i] == '\n' || buf[i] == '\r') {
+    // EOF or newline: return success
+    else if (!ret || buf[i]=='\n' || buf[i]=='\r') {
       ret = 0;
-
       break;
     } else if (buf[i] == 8 || buf[i] == 127) i -= 2-!i;
   }
 
   // Restore terminal/signal state, terminate string
-  sigaction(SIGINT, &oldsa, 0);
   tcsetattr(0, TCSANOW, &oldtermio);
-  buf[i] = 0;
+  sigaction(SIGINT, &oldsa, 0);
   xputc('\n');
+  buf[i*!ret] = 0;
 
   return ret;
 }
 
-static char *get_nextcolon(char *line, int cnt)
-{
-  while (cnt--) {
-    if (!(line = strchr(line, ':'))) error_exit("Invalid Entry\n");
-    line++; //jump past the colon
-  }
-  return line;
-}
-
-/*update_password is used by multiple utilities to update /etc/passwd,
- * /etc/shadow, /etc/group and /etc/gshadow files,
- * which are used as user, group databeses
- * entry can be
- * 1. encrypted password, when updating user password.
- * 2. complete entry for user details, when creating new user
- * 3. group members comma',' separated list, when adding user to group
- * 4. complete entry for group details, when creating new group
- * 5. entry = NULL, delete the named entry user/group
+/* update colon-separated text files ala /etc/{passwd,shadow,group,gshadow}
+ * username = string match for first entry in line
+ * entry = new entry (NULL deletes matching line from file)
+ * pos = which entry to replace with "entry" (0 is first)
  */
-int update_password(char *filename, char* username, char* entry)
+// filename+ = new copy being written, filename- = backup of old version
+// returns 1 for success, 0 for failure
+int update_password(char *filename, char *username, char *entry, int pos)
 {
-  char *filenamesfx = NULL, *namesfx = NULL, *shadow = NULL,
-       *sfx = NULL, *line = NULL;
-  FILE *exfp, *newfp;
-  int ret = -1, found = 0, n;
-  struct flock lock;
-  size_t allocated_length;
-
-  shadow = strstr(filename, "shadow");
-  filenamesfx = xmprintf("%s+", filename);
-  sfx = strchr(filenamesfx, '+');
-
-  exfp = fopen(filename, "r+");
-  if (!exfp) {
-    perror_msg("Couldn't open file %s",filename);
+  char *filenamesfx = xmprintf("%s-", filename), *line = 0, *start, *end;
+  FILE *ofp, *nfp;
+  int ret = 0, found = 0, len = strlen(username)*!strchr(username, ':'), ii;
+  struct flock lock = {.l_type = F_WRLCK};
+  long long ll = 0;
+
+  // Open old filename ("r" won't let us lock), get blocking lock
+  if (!(ofp = fopen(filename, "w+")) || 0>fcntl(fileno(ofp), F_SETLK, &lock)) {
+    perror_msg("%s", filename);
     goto free_storage;
   }
 
-  *sfx = '-';
+  // Delete old backup, link new backup. (Failure here isn't fatal.)
   unlink(filenamesfx);
-  ret = link(filename, filenamesfx);
-  if (ret < 0) error_msg("can't create backup file");
-
-  *sfx = '+';
-  lock.l_type = F_WRLCK;
-  lock.l_whence = SEEK_SET;
-  lock.l_start = 0;
-  lock.l_len = 0;
-
-  ret = fcntl(fileno(exfp), F_SETLK, &lock);
-  if (ret < 0) perror_msg("Couldn't lock file %s",filename);
-
-  lock.l_type = F_UNLCK; //unlocking at a later stage
+  if (0>link(filename, filenamesfx)) perror_msg("%s", filenamesfx);
 
-  newfp = fopen(filenamesfx, "w+");
-  if (!newfp) {
-    error_msg("couldn't open file for writing");
-    ret = -1;
-    fclose(exfp);
+  // Open new file to copy entries to
+  filenamesfx[strlen(filenamesfx)-1] = '+';
+  if (!(nfp = fopen(filenamesfx, "w+"))) {
+    perror_msg("%s", filenamesfx);
     goto free_storage;
   }
 
-  ret = 0;
-  namesfx = xmprintf("%s:",username);
-  while ((n = getline(&line, &allocated_length, exfp)) > 0) {
-    line[n-1] = 0;
-    if (strncmp(line, namesfx, strlen(namesfx)))
-      fprintf(newfp, "%s\n", line);
-    else if (entry) {
-      char *current_ptr = NULL;
-
-      found = 1;
-      if (!strcmp(toys.which->name, "passwd")) {
-        fprintf(newfp, "%s%s:",namesfx, entry);
-        current_ptr = get_nextcolon(line, 2); //past passwd
-        if (shadow) {
-          fprintf(newfp, "%u:",(unsigned)(time(NULL))/(24*60*60));
-          current_ptr = get_nextcolon(current_ptr, 1);
-          fprintf(newfp, "%s\n",current_ptr);
-        } else fprintf(newfp, "%s\n",current_ptr);
-      } else if (!strcmp(toys.which->name, "groupadd") ||
-          !strcmp(toys.which->name, "addgroup") ||
-          !strcmp(toys.which->name, "delgroup") ||
-          !strcmp(toys.which->name, "groupdel")){
-        current_ptr = get_nextcolon(line, 3); //past gid/admin list
-        *current_ptr = '\0';
-        fprintf(newfp, "%s", line);
-        fprintf(newfp, "%s\n", entry);
+  // Loop through lines
+  while (getline(&line, (void *)&ll, ofp)) {
+    // find matching line
+    start = end = line;
+    if (strncmp(chomp(line), username, len) || line[len]!=':') {
+      found++;
+      if (!entry) continue;
+
+      // Find start and end of span to replace
+      for (ii = pos;;) {
+        while (*end != ':') {
+          if (!*end) break;
+          end++;
+        }
+        if (ii) {
+          start = ++end;
+          ii--;
+        } else break;
       }
+      if (ii) start = end = line;
     }
+
+    // Write with replacement (if any)
+    fprintf(nfp, "%*s%s%s\n", (int)(start-line), line,
+            (start==line) ? "" : entry, end);
+    memset(line, 0, strlen(line));
   }
   free(line);
-  free(namesfx);
-  if (!found && entry) fprintf(newfp, "%s\n", entry);
-  fcntl(fileno(exfp), F_SETLK, &lock);
-  fclose(exfp);
-
-  errno = 0;
-  fflush(newfp);
-  fsync(fileno(newfp));
-  fclose(newfp);
-  rename(filenamesfx, filename);
-  if (errno) {
-    perror_msg("File Writing/Saving failed: ");
+  fflush(nfp);
+  fsync(fileno(nfp));
+  fclose(nfp);  // automatically unlocks
+
+  if (!found || rename(filenamesfx, filename)) {
+    if (found) perror_msg("%s -> %s", filenamesfx, filename);
+    else if (entry) fprintf(nfp, "%s\n", entry);
     unlink(filenamesfx);
-    ret = -1;
-  }
+  } else ret = 1;
 
 free_storage:
+  if (ofp) fclose(ofp);
   free(filenamesfx);
+
   return ret;
 }

+ 2 - 2
lib/pending.h

@@ -2,5 +2,5 @@
 
 // password.c
 #define MAX_SALT_LEN  20 //3 for id, 16 for key, 1 for '\0'
-int read_password(char * buff, int buflen, char* mesg);
-int update_password(char *filename, char* username, char* encrypted);
+int read_password(char *buff, int buflen, char *mesg);
+int update_password(char *filename, char *username, char *encrypted, int pos);

+ 11 - 11
toys/lsb/passwd.c

@@ -58,7 +58,6 @@ void passwd_main(void)
   struct passwd *pw = 0;
   struct spwd *sp;
   char *pass, *name, *encrypted = 0, salt[MAX_SALT_LEN];
-  int ret = -1;
 
   // If we're root or not -lud, load specified user. Exit if not allowed.
   if (!(myuid = getuid()) || !(toys.optflags&(FLAG_l|FLAG_u|FLAG_d))) {
@@ -72,19 +71,20 @@ void passwd_main(void)
   name = pw->pw_name;
   if (*(pass = pw->pw_passwd)=='x' && (sp = getspnam(name))) pass = sp->sp_pwdp;
 
-  if (toys.optflags & FLAG_l) {
+  if (FLAG(l)) {
     if (*pass=='!') error_exit("already locked");
     printf("Locking '%s'\n", name);
     encrypted = xmprintf("!%s", pass);
-  } else if (toys.optflags & FLAG_u) {
+  } else if (FLAG(u)) {
     if (*pass!='!') error_exit("already unlocked");
     printf("Unlocking '%s'\n", name);
     encrypted = pass+1;
-  } else if (toys.optflags & FLAG_d) {
+  } else if (FLAG(d)) {
     printf("Deleting password for '%s'\n", name);
-    encrypted = "";
+    *(encrypted = toybuf) = 0;
   } else {
-    if (get_salt(salt, TT.a ? TT.a : "des")<0) error_exit("bad -a '%s'", TT.a);
+    if (!TT.a) TT.a = "des";
+    if (get_salt(salt, TT.a)<0) error_exit("bad -a '%s'", TT.a);
 
     printf("Changing password for %s\n", name);
     if (myuid) {
@@ -105,11 +105,11 @@ void passwd_main(void)
   }
 
   // Update the passwd
-  ret = update_password(*pw->pw_passwd=='x' ? "/etc/shadow" : "/etc/passwd",
-    name, encrypted);
-
-  if (ret) error_msg("Failure");
+  if (update_password(*pw->pw_passwd=='x' ? "/etc/shadow" : "/etc/passwd",
+    name, encrypted, 1)) error_msg("Failure");
   else fprintf(stderr, "Success\n");
 
-  if (CFG_TOYBOX_FREE && (toys.optflags & FLAG_l)) free(encrypted);
+  memset(toybuf, 0, sizeof(toybuf));
+  memset(encrypted, 0, strlen(encrypted));
+  free(encrypted);
 }