1/* vi: set sw=4 ts=4: */ 2/* 3 * update_passwd 4 * 5 * update_passwd is a common function for passwd and chpasswd applets; 6 * it is responsible for updating password file (i.e. /etc/passwd or 7 * /etc/shadow) for a given user and password. 8 * 9 * Moved from loginutils/passwd.c by Alexander Shishkin <virtuoso@slind.org> 10 */ 11 12#include "libbb.h" 13 14int update_passwd(const char *filename, const char *username, 15 const char *new_pw) 16{ 17 struct stat sb; 18 struct flock lock; 19 FILE *old_fp; 20 FILE *new_fp; 21 char *fnamesfx; 22 char *sfx_char; 23 unsigned user_len; 24 int old_fd; 25 int new_fd; 26 int i; 27 int cnt = 0; 28 int ret = -1; /* failure */ 29 30 /* New passwd file, "/etc/passwd+" for now */ 31 fnamesfx = xasprintf("%s+", filename); 32 sfx_char = &fnamesfx[strlen(fnamesfx)-1]; 33 username = xasprintf("%s:", username); 34 user_len = strlen(username); 35 36 old_fp = fopen(filename, "r+"); 37 if (!old_fp) 38 goto free_mem; 39 old_fd = fileno(old_fp); 40 41 /* Try to create "/etc/passwd+". Wait if it exists. */ 42 i = 30; 43 do { 44 new_fd = open(fnamesfx, O_WRONLY|O_CREAT|O_EXCL, 0600); 45 if (new_fd >= 0) goto created; 46 if (errno != EEXIST) break; 47 usleep(100000); /* 0.1 sec */ 48 } while (--i); 49 bb_perror_msg("cannot create '%s'", fnamesfx); 50 goto close_old_fp; 51 52 created: 53 if (!fstat(old_fd, &sb)) { 54 fchmod(new_fd, sb.st_mode & 0777); /* ignore errors */ 55 fchown(new_fd, sb.st_uid, sb.st_gid); 56 } 57 new_fp = fdopen(new_fd, "w"); 58 if (!new_fp) { 59 close(new_fd); 60 goto unlink_new; 61 } 62 63 /* Backup file is "/etc/passwd-" */ 64 *sfx_char = '-'; 65 /* Delete old backup */ 66 i = (unlink(fnamesfx) && errno != ENOENT); 67 /* Create backup as a hardlink to current */ 68 if (i || link(filename, fnamesfx)) 69 bb_perror_msg("warning: cannot create backup copy '%s'", fnamesfx); 70 *sfx_char = '+'; 71 72 /* Lock the password file before updating */ 73 lock.l_type = F_WRLCK; 74 lock.l_whence = SEEK_SET; 75 lock.l_start = 0; 76 lock.l_len = 0; 77 if (fcntl(old_fd, F_SETLK, &lock) < 0) 78 bb_perror_msg("warning: cannot lock '%s'", filename); 79 lock.l_type = F_UNLCK; 80 81 /* Read current password file, write updated /etc/passwd+ */ 82 while (1) { 83 char *line = xmalloc_fgets(old_fp); 84 if (!line) break; /* EOF/error */ 85 if (strncmp(username, line, user_len) == 0) { 86 /* we have a match with "username:"... */ 87 const char *cp = line + user_len; 88 /* now cp -> old passwd, skip it: */ 89 cp = strchrnul(cp, ':'); 90 /* now cp -> ':' after old passwd or -> "" */ 91 fprintf(new_fp, "%s%s%s", username, new_pw, cp); 92 cnt++; 93 } else 94 fputs(line, new_fp); 95 free(line); 96 } 97 fcntl(old_fd, F_SETLK, &lock); 98 99 /* We do want all of them to execute, thus | instead of || */ 100 if ((ferror(old_fp) | fflush(new_fp) | fsync(new_fd) | fclose(new_fp)) 101 || rename(fnamesfx, filename) 102 ) { 103 /* At least one of those failed */ 104 goto unlink_new; 105 } 106 ret = cnt; /* whee, success! */ 107 108 unlink_new: 109 if (ret < 0) unlink(fnamesfx); 110 111 close_old_fp: 112 fclose(old_fp); 113 114 free_mem: 115 free(fnamesfx); 116 free((char*)username); 117 return ret; 118} 119