busybox/libbb/update_passwd.c
<<
>>
Prefs
   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 * Modified to be able to add or delete users, groups and users to/from groups
  12 * by Tito Ragusa <farmatito@tiscali.it>
  13 *
  14 * Licensed under GPLv2, see file LICENSE in this source tree.
  15 */
  16#include "libbb.h"
  17
  18#if ENABLE_SELINUX
  19static void check_selinux_update_passwd(const char *username)
  20{
  21        security_context_t context;
  22        char *seuser;
  23
  24        if (getuid() != (uid_t)0 || is_selinux_enabled() == 0)
  25                return;  /* No need to check */
  26
  27        if (getprevcon_raw(&context) < 0)
  28                bb_perror_msg_and_die("getprevcon failed");
  29        seuser = strtok(context, ":");
  30        if (!seuser)
  31                bb_error_msg_and_die("invalid context '%s'", context);
  32        if (strcmp(seuser, username) != 0) {
  33                if (checkPasswdAccess(PASSWD__PASSWD) != 0)
  34                        bb_error_msg_and_die("SELinux: access denied");
  35        }
  36        if (ENABLE_FEATURE_CLEAN_UP)
  37                freecon(context);
  38}
  39#else
  40# define check_selinux_update_passwd(username) ((void)0)
  41#endif
  42
  43/*
  44 1) add a user: update_passwd(FILE, USER, REMAINING_PWLINE, NULL)
  45    only if CONFIG_ADDUSER=y and applet_name[0] == 'a' like in adduser
  46
  47 2) add a group: update_passwd(FILE, GROUP, REMAINING_GRLINE, NULL)
  48    only if CONFIG_ADDGROUP=y and applet_name[0] == 'a' like in addgroup
  49
  50 3) add a user to a group: update_passwd(FILE, GROUP, NULL, MEMBER)
  51    only if CONFIG_FEATURE_ADDUSER_TO_GROUP=y, applet_name[0] == 'a'
  52    like in addgroup and member != NULL
  53
  54 4) delete a user: update_passwd(FILE, USER, NULL, NULL)
  55
  56 5) delete a group: update_passwd(FILE, GROUP, NULL, NULL)
  57
  58 6) delete a user from a group: update_passwd(FILE, GROUP, NULL, MEMBER)
  59    only if CONFIG_FEATURE_DEL_USER_FROM_GROUP=y and member != NULL
  60
  61 7) change user's password: update_passwd(FILE, USER, NEW_PASSWD, NULL)
  62    only if CONFIG_PASSWD=y and applet_name[0] == 'p' like in passwd
  63    or if CONFIG_CHPASSWD=y and applet_name[0] == 'c' like in chpasswd
  64
  65 This function does not validate the arguments fed to it
  66 so the calling program should take care of that.
  67
  68 Returns number of lines changed, or -1 on error.
  69*/
  70int FAST_FUNC update_passwd(const char *filename,
  71                const char *name,
  72                const char *new_passwd,
  73                const char *member)
  74{
  75#if !(ENABLE_FEATURE_ADDUSER_TO_GROUP || ENABLE_FEATURE_DEL_USER_FROM_GROUP)
  76#define member NULL
  77#endif
  78        struct stat sb;
  79        struct flock lock;
  80        FILE *old_fp;
  81        FILE *new_fp;
  82        char *fnamesfx;
  83        char *sfx_char;
  84        char *name_colon;
  85        unsigned user_len;
  86        int old_fd;
  87        int new_fd;
  88        int i;
  89        int changed_lines;
  90        int ret = -1; /* failure */
  91        /* used as a bool: "are we modifying /etc/shadow?" */
  92#if ENABLE_FEATURE_SHADOWPASSWDS
  93        const char *shadow = strstr(filename, "shadow");
  94#else
  95# define shadow NULL
  96#endif
  97
  98        filename = xmalloc_follow_symlinks(filename);
  99        if (filename == NULL)
 100                return ret;
 101
 102        check_selinux_update_passwd(name);
 103
 104        /* New passwd file, "/etc/passwd+" for now */
 105        fnamesfx = xasprintf("%s+", filename);
 106        sfx_char = &fnamesfx[strlen(fnamesfx)-1];
 107        name_colon = xasprintf("%s:", name);
 108        user_len = strlen(name_colon);
 109
 110        if (shadow)
 111                old_fp = fopen(filename, "r+");
 112        else
 113                old_fp = fopen_or_warn(filename, "r+");
 114        if (!old_fp) {
 115                if (shadow)
 116                        ret = 0; /* missing shadow is not an error */
 117                goto free_mem;
 118        }
 119        old_fd = fileno(old_fp);
 120
 121        selinux_preserve_fcontext(old_fd);
 122
 123        /* Try to create "/etc/passwd+". Wait if it exists. */
 124        i = 30;
 125        do {
 126                // FIXME: on last iteration try w/o O_EXCL but with O_TRUNC?
 127                new_fd = open(fnamesfx, O_WRONLY|O_CREAT|O_EXCL, 0600);
 128                if (new_fd >= 0) goto created;
 129                if (errno != EEXIST) break;
 130                usleep(100000); /* 0.1 sec */
 131        } while (--i);
 132        bb_perror_msg("can't create '%s'", fnamesfx);
 133        goto close_old_fp;
 134
 135 created:
 136        if (fstat(old_fd, &sb) == 0) {
 137                fchmod(new_fd, sb.st_mode & 0777); /* ignore errors */
 138                fchown(new_fd, sb.st_uid, sb.st_gid);
 139        }
 140        errno = 0;
 141        new_fp = xfdopen_for_write(new_fd);
 142
 143        /* Backup file is "/etc/passwd-" */
 144        *sfx_char = '-';
 145        /* Delete old backup */
 146        i = (unlink(fnamesfx) && errno != ENOENT);
 147        /* Create backup as a hardlink to current */
 148        if (i || link(filename, fnamesfx))
 149                bb_perror_msg("warning: can't create backup copy '%s'",
 150                                fnamesfx);
 151        *sfx_char = '+';
 152
 153        /* Lock the password file before updating */
 154        lock.l_type = F_WRLCK;
 155        lock.l_whence = SEEK_SET;
 156        lock.l_start = 0;
 157        lock.l_len = 0;
 158        if (fcntl(old_fd, F_SETLK, &lock) < 0)
 159                bb_perror_msg("warning: can't lock '%s'", filename);
 160        lock.l_type = F_UNLCK;
 161
 162        /* Read current password file, write updated /etc/passwd+ */
 163        changed_lines = 0;
 164        while (1) {
 165                char *cp, *line;
 166
 167                line = xmalloc_fgetline(old_fp);
 168                if (!line) /* EOF/error */
 169                        break;
 170                if (strncmp(name_colon, line, user_len) != 0) {
 171                        fprintf(new_fp, "%s\n", line);
 172                        goto next;
 173                }
 174
 175                /* We have a match with "name:"... */
 176                cp = line + user_len; /* move past name: */
 177
 178#if ENABLE_FEATURE_ADDUSER_TO_GROUP || ENABLE_FEATURE_DEL_USER_FROM_GROUP
 179                if (member) {
 180                        /* It's actually /etc/group+, not /etc/passwd+ */
 181                        if (ENABLE_FEATURE_ADDUSER_TO_GROUP
 182                         && applet_name[0] == 'a'
 183                        ) {
 184                                /* Add user to group */
 185                                fprintf(new_fp, "%s%s%s\n", line,
 186                                        last_char_is(line, ':') ? "" : ",",
 187                                        member);
 188                                changed_lines++;
 189                        } else if (ENABLE_FEATURE_DEL_USER_FROM_GROUP
 190                        /* && applet_name[0] == 'd' */
 191                        ) {
 192                                /* Delete user from group */
 193                                char *tmp;
 194                                const char *fmt = "%s";
 195
 196                                /* find the start of the member list: last ':' */
 197                                cp = strrchr(line, ':');
 198                                /* cut it */
 199                                *cp++ = '\0';
 200                                /* write the cut line name:passwd:gid:
 201                                 * or name:!:: */
 202                                fprintf(new_fp, "%s:", line);
 203                                /* parse the tokens of the member list */
 204                                tmp = cp;
 205                                while ((cp = strsep(&tmp, ",")) != NULL) {
 206                                        if (strcmp(member, cp) != 0) {
 207                                                fprintf(new_fp, fmt, cp);
 208                                                fmt = ",%s";
 209                                        } else {
 210                                                /* found member, skip it */
 211                                                changed_lines++;
 212                                        }
 213                                }
 214                                fprintf(new_fp, "\n");
 215                        }
 216                } else
 217#endif
 218                if ((ENABLE_PASSWD && applet_name[0] == 'p')
 219                 || (ENABLE_CHPASSWD && applet_name[0] == 'c')
 220                ) {
 221                        /* Change passwd */
 222                        cp = strchrnul(cp, ':'); /* move past old passwd */
 223
 224                        if (shadow && *cp == ':') {
 225                                /* /etc/shadow's field 3 (passwd change date) needs updating */
 226                                /* move past old change date */
 227                                cp = strchrnul(cp + 1, ':');
 228                                /* "name:" + "new_passwd" + ":" + "change date" + ":rest of line" */
 229                                fprintf(new_fp, "%s%s:%u%s\n", name_colon, new_passwd,
 230                                        (unsigned)(time(NULL)) / (24*60*60), cp);
 231                        } else {
 232                                /* "name:" + "new_passwd" + ":rest of line" */
 233                                fprintf(new_fp, "%s%s%s\n", name_colon, new_passwd, cp);
 234                        }
 235                        changed_lines++;
 236                } /* else delete user or group: skip the line */
 237 next:
 238                free(line);
 239        }
 240
 241        if (changed_lines == 0) {
 242#if ENABLE_FEATURE_ADDUSER_TO_GROUP || ENABLE_FEATURE_DEL_USER_FROM_GROUP
 243                if (member) {
 244                        if (ENABLE_ADDGROUP && applet_name[0] == 'a')
 245                                bb_error_msg("can't find %s in %s", name, filename);
 246                        if (ENABLE_DELGROUP && applet_name[0] == 'd')
 247                                bb_error_msg("can't find %s in %s", member, filename);
 248                }
 249#endif
 250                if ((ENABLE_ADDUSER || ENABLE_ADDGROUP)
 251                 && applet_name[0] == 'a' && !member
 252                ) {
 253                        /* add user or group */
 254                        fprintf(new_fp, "%s%s\n", name_colon, new_passwd);
 255                        changed_lines++;
 256                }
 257        }
 258
 259        fcntl(old_fd, F_SETLK, &lock);
 260
 261        /* We do want all of them to execute, thus | instead of || */
 262        errno = 0;
 263        if ((ferror(old_fp) | fflush(new_fp) | fsync(new_fd) | fclose(new_fp))
 264         || rename(fnamesfx, filename)
 265        ) {
 266                /* At least one of those failed */
 267                bb_perror_nomsg();
 268                goto unlink_new;
 269        }
 270        /* Success: ret >= 0 */
 271        ret = changed_lines;
 272
 273 unlink_new:
 274        if (ret < 0)
 275                unlink(fnamesfx);
 276
 277 close_old_fp:
 278        fclose(old_fp);
 279
 280 free_mem:
 281        free(fnamesfx);
 282        free((char *)filename);
 283        free(name_colon);
 284        return ret;
 285}
 286