xmalloc_follow_symlinks() -- fix ELOOP issue with absolute paths,
[oweals/busybox.git] / libbb / update_passwd.c
index 5572db54739f17503fc4559cda92cfe6261497c5..d10e863c6c63de728e6c818e54993b5a4de3a4c7 100644 (file)
 
 #include "libbb.h"
 
+#if ENABLE_SELINUX
+static void check_selinux_update_passwd(const char *username)
+{
+       security_context_t context;
+       char *seuser;
+
+       if (getuid() != (uid_t)0 || is_selinux_enabled() == 0)
+               return;         /* No need to check */
+
+       if (getprevcon_raw(&context) < 0)
+               bb_perror_msg_and_die("getprevcon failed");
+       seuser = strtok(context, ":");
+       if (!seuser)
+               bb_error_msg_and_die("invalid context '%s'", context);
+       if (strcmp(seuser, username) != 0) {
+               if (checkPasswdAccess(PASSWD__PASSWD) != 0)
+                       bb_error_msg_and_die("SELinux: access denied");
+       }
+       if (ENABLE_FEATURE_CLEAN_UP)
+               freecon(context);
+}
+#else
+#define check_selinux_update_passwd(username) ((void)0)
+#endif
+
 int update_passwd(const char *filename, const char *username,
                        const char *new_pw)
 {
@@ -18,18 +43,24 @@ int update_passwd(const char *filename, const char *username,
        struct flock lock;
        FILE *old_fp;
        FILE *new_fp;
-       char *new_name;
-       char *last_char;
+       char *fnamesfx;
+       char *sfx_char;
        unsigned user_len;
        int old_fd;
        int new_fd;
        int i;
        int cnt = 0;
-       int ret = 1; /* failure */
+       int ret = -1; /* failure */
+
+       filename = xmalloc_follow_symlinks(filename);
+       if (filename == NULL)
+               return -1;
+
+       check_selinux_update_passwd(username);
 
        /* New passwd file, "/etc/passwd+" for now */
-       new_name = xasprintf("%s+", filename);
-       last_char = &new_name[strlen(new_name)-1];
+       fnamesfx = xasprintf("%s+", filename);
+       sfx_char = &fnamesfx[strlen(fnamesfx)-1];
        username = xasprintf("%s:", username);
        user_len = strlen(username);
 
@@ -38,16 +69,18 @@ int update_passwd(const char *filename, const char *username,
                goto free_mem;
        old_fd = fileno(old_fp);
 
+       selinux_preserve_fcontext(old_fd);
+
        /* 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(new_name, O_WRONLY|O_CREAT|O_EXCL,0600);
+               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'", new_name);
+       bb_perror_msg("cannot create '%s'", fnamesfx);
        goto close_old_fp;
 
  created:
@@ -62,12 +95,13 @@ int update_passwd(const char *filename, const char *username,
        }
 
        /* Backup file is "/etc/passwd-" */
-       last_char[0] = '-';
-       /* Delete old one, create new as a hardlink to current */
-       i = (unlink(new_name) && errno != ENOENT);
-       if (i || link(filename, new_name))
-               bb_perror_msg("warning: cannot create backup copy '%s'", new_name);
-       last_char[0] = '+';
+       *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;
@@ -78,7 +112,7 @@ int update_passwd(const char *filename, const char *username,
                bb_perror_msg("warning: cannot lock '%s'", filename);
        lock.l_type = F_UNLCK;
 
-       /* Read current password file, write updated one */
+       /* Read current password file, write updated /etc/passwd+ */
        while (1) {
                char *line = xmalloc_fgets(old_fp);
                if (!line) break; /* EOF/error */
@@ -86,8 +120,7 @@ int update_passwd(const char *filename, const char *username,
                        /* we have a match with "username:"... */
                        const char *cp = line + user_len;
                        /* now cp -> old passwd, skip it: */
-                       cp = strchr(cp, ':');
-                       if (!cp) cp = "";
+                       cp = strchrnul(cp, ':');
                        /* now cp -> ':' after old passwd or -> "" */
                        fprintf(new_fp, "%s%s%s", username, new_pw, cp);
                        cnt++;
@@ -99,7 +132,7 @@ int update_passwd(const char *filename, const char *username,
 
        /* 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(new_name, filename)
+        || rename(fnamesfx, filename)
        ) {
                /* At least one of those failed */
                goto unlink_new;
@@ -107,13 +140,14 @@ int update_passwd(const char *filename, const char *username,
        ret = cnt; /* whee, success! */
 
  unlink_new:
-       if (ret) unlink(new_name);
+       if (ret < 0) unlink(fnamesfx);
 
  close_old_fp:
        fclose(old_fp);
 
  free_mem:
-       if (ENABLE_FEATURE_CLEAN_UP) free(new_name);
-       if (ENABLE_FEATURE_CLEAN_UP) free((char*)username);
+       free(fnamesfx);
+       free((char *)filename);
+       free((char *)username);
        return ret;
 }