/* vi: set sw=4 ts=4: */ /* * update_passwd * * update_passwd is a common function for passwd and chpasswd applets; * it is responsible for updating password file (i.e. /etc/passwd or * /etc/shadow) for a given user and password. * * Moved from loginutils/passwd.c by Alexander Shishkin */ #include "libbb.h" int update_passwd(const char *filename, const char *username, const char *new_pw) { struct stat sb; struct flock lock; FILE *old_fp; FILE *new_fp; char *fnamesfx; char *sfx_char; unsigned user_len; int old_fd; int new_fd; int i; int cnt = 0; int ret = -1; /* failure */ /* New passwd file, "/etc/passwd+" for now */ fnamesfx = xasprintf("%s+", filename); sfx_char = &fnamesfx[strlen(fnamesfx)-1]; username = xasprintf("%s:", username); user_len = strlen(username); old_fp = fopen(filename, "r+"); if (!old_fp) goto free_mem; old_fd = fileno(old_fp); /* Try to create "/etc/passwd+". Wait if it exists. */ i = 30; do { // FIXME: on last iteration try w/o O_EXCL but with O_TRUNC? new_fd = open(fnamesfx, O_WRONLY|O_CREAT|O_EXCL, 0600); if (new_fd >= 0) goto created; if (errno != EEXIST) break; usleep(100000); /* 0.1 sec */ } while (--i); bb_perror_msg("cannot create '%s'", fnamesfx); goto close_old_fp; created: if (!fstat(old_fd, &sb)) { fchmod(new_fd, sb.st_mode & 0777); /* ignore errors */ fchown(new_fd, sb.st_uid, sb.st_gid); } new_fp = fdopen(new_fd, "w"); if (!new_fp) { close(new_fd); goto unlink_new; } /* Backup file is "/etc/passwd-" */ *sfx_char = '-'; /* Delete old backup */ i = (unlink(fnamesfx) && errno != ENOENT); /* Create backup as a hardlink to current */ if (i || link(filename, fnamesfx)) bb_perror_msg("warning: cannot create backup copy '%s'", fnamesfx); *sfx_char = '+'; /* Lock the password file before updating */ lock.l_type = F_WRLCK; lock.l_whence = SEEK_SET; lock.l_start = 0; lock.l_len = 0; if (fcntl(old_fd, F_SETLK, &lock) < 0) bb_perror_msg("warning: cannot lock '%s'", filename); lock.l_type = F_UNLCK; /* Read current password file, write updated /etc/passwd+ */ while (1) { char *line = xmalloc_fgets(old_fp); if (!line) break; /* EOF/error */ if (strncmp(username, line, user_len) == 0) { /* we have a match with "username:"... */ const char *cp = line + user_len; /* now cp -> old passwd, skip it: */ cp = strchrnul(cp, ':'); /* now cp -> ':' after old passwd or -> "" */ fprintf(new_fp, "%s%s%s", username, new_pw, cp); cnt++; } else fputs(line, new_fp); free(line); } fcntl(old_fd, F_SETLK, &lock); /* We do want all of them to execute, thus | instead of || */ if ((ferror(old_fp) | fflush(new_fp) | fsync(new_fd) | fclose(new_fp)) || rename(fnamesfx, filename) ) { /* At least one of those failed */ goto unlink_new; } ret = cnt; /* whee, success! */ unlink_new: if (ret < 0) unlink(fnamesfx); close_old_fp: fclose(old_fp); free_mem: free(fnamesfx); free((char*)username); return ret; }