Annotation of /trunk/mkinitrd-magellan/busybox/libbb/update_passwd.c
Parent Directory | Revision Log
Revision 816 -
(hide annotations)
(download)
Fri Apr 24 18:33:46 2009 UTC (15 years, 5 months ago) by niro
File MIME type: text/plain
File size: 3877 byte(s)
Fri Apr 24 18:33:46 2009 UTC (15 years, 5 months ago) by niro
File MIME type: text/plain
File size: 3877 byte(s)
-updated to busybox-1.13.4
1 | niro | 816 | /* 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 | |||
14 | #if ENABLE_SELINUX | ||
15 | static void check_selinux_update_passwd(const char *username) | ||
16 | { | ||
17 | security_context_t context; | ||
18 | char *seuser; | ||
19 | |||
20 | if (getuid() != (uid_t)0 || is_selinux_enabled() == 0) | ||
21 | return; /* No need to check */ | ||
22 | |||
23 | if (getprevcon_raw(&context) < 0) | ||
24 | bb_perror_msg_and_die("getprevcon failed"); | ||
25 | seuser = strtok(context, ":"); | ||
26 | if (!seuser) | ||
27 | bb_error_msg_and_die("invalid context '%s'", context); | ||
28 | if (strcmp(seuser, username) != 0) { | ||
29 | if (checkPasswdAccess(PASSWD__PASSWD) != 0) | ||
30 | bb_error_msg_and_die("SELinux: access denied"); | ||
31 | } | ||
32 | if (ENABLE_FEATURE_CLEAN_UP) | ||
33 | freecon(context); | ||
34 | } | ||
35 | #else | ||
36 | #define check_selinux_update_passwd(username) ((void)0) | ||
37 | #endif | ||
38 | |||
39 | int FAST_FUNC update_passwd(const char *filename, const char *username, | ||
40 | const char *new_pw) | ||
41 | { | ||
42 | struct stat sb; | ||
43 | struct flock lock; | ||
44 | FILE *old_fp; | ||
45 | FILE *new_fp; | ||
46 | char *fnamesfx; | ||
47 | char *sfx_char; | ||
48 | unsigned user_len; | ||
49 | int old_fd; | ||
50 | int new_fd; | ||
51 | int i; | ||
52 | int cnt = 0; | ||
53 | int ret = -1; /* failure */ | ||
54 | |||
55 | filename = xmalloc_follow_symlinks(filename); | ||
56 | if (filename == NULL) | ||
57 | return -1; | ||
58 | |||
59 | check_selinux_update_passwd(username); | ||
60 | |||
61 | /* New passwd file, "/etc/passwd+" for now */ | ||
62 | fnamesfx = xasprintf("%s+", filename); | ||
63 | sfx_char = &fnamesfx[strlen(fnamesfx)-1]; | ||
64 | username = xasprintf("%s:", username); | ||
65 | user_len = strlen(username); | ||
66 | |||
67 | old_fp = fopen(filename, "r+"); | ||
68 | if (!old_fp) | ||
69 | goto free_mem; | ||
70 | old_fd = fileno(old_fp); | ||
71 | |||
72 | selinux_preserve_fcontext(old_fd); | ||
73 | |||
74 | /* Try to create "/etc/passwd+". Wait if it exists. */ | ||
75 | i = 30; | ||
76 | do { | ||
77 | // FIXME: on last iteration try w/o O_EXCL but with O_TRUNC? | ||
78 | new_fd = open(fnamesfx, O_WRONLY|O_CREAT|O_EXCL, 0600); | ||
79 | if (new_fd >= 0) goto created; | ||
80 | if (errno != EEXIST) break; | ||
81 | usleep(100000); /* 0.1 sec */ | ||
82 | } while (--i); | ||
83 | bb_perror_msg("cannot create '%s'", fnamesfx); | ||
84 | goto close_old_fp; | ||
85 | |||
86 | created: | ||
87 | if (!fstat(old_fd, &sb)) { | ||
88 | fchmod(new_fd, sb.st_mode & 0777); /* ignore errors */ | ||
89 | fchown(new_fd, sb.st_uid, sb.st_gid); | ||
90 | } | ||
91 | new_fp = fdopen(new_fd, "w"); | ||
92 | if (!new_fp) { | ||
93 | close(new_fd); | ||
94 | goto unlink_new; | ||
95 | } | ||
96 | |||
97 | /* Backup file is "/etc/passwd-" */ | ||
98 | *sfx_char = '-'; | ||
99 | /* Delete old backup */ | ||
100 | i = (unlink(fnamesfx) && errno != ENOENT); | ||
101 | /* Create backup as a hardlink to current */ | ||
102 | if (i || link(filename, fnamesfx)) | ||
103 | bb_perror_msg("warning: cannot create backup copy '%s'", fnamesfx); | ||
104 | *sfx_char = '+'; | ||
105 | |||
106 | /* Lock the password file before updating */ | ||
107 | lock.l_type = F_WRLCK; | ||
108 | lock.l_whence = SEEK_SET; | ||
109 | lock.l_start = 0; | ||
110 | lock.l_len = 0; | ||
111 | if (fcntl(old_fd, F_SETLK, &lock) < 0) | ||
112 | bb_perror_msg("warning: cannot lock '%s'", filename); | ||
113 | lock.l_type = F_UNLCK; | ||
114 | |||
115 | /* Read current password file, write updated /etc/passwd+ */ | ||
116 | while (1) { | ||
117 | char *line = xmalloc_fgets(old_fp); | ||
118 | if (!line) break; /* EOF/error */ | ||
119 | if (strncmp(username, line, user_len) == 0) { | ||
120 | /* we have a match with "username:"... */ | ||
121 | const char *cp = line + user_len; | ||
122 | /* now cp -> old passwd, skip it: */ | ||
123 | cp = strchrnul(cp, ':'); | ||
124 | /* now cp -> ':' after old passwd or -> "" */ | ||
125 | fprintf(new_fp, "%s%s%s", username, new_pw, cp); | ||
126 | cnt++; | ||
127 | } else | ||
128 | fputs(line, new_fp); | ||
129 | free(line); | ||
130 | } | ||
131 | fcntl(old_fd, F_SETLK, &lock); | ||
132 | |||
133 | /* We do want all of them to execute, thus | instead of || */ | ||
134 | if ((ferror(old_fp) | fflush(new_fp) | fsync(new_fd) | fclose(new_fp)) | ||
135 | || rename(fnamesfx, filename) | ||
136 | ) { | ||
137 | /* At least one of those failed */ | ||
138 | goto unlink_new; | ||
139 | } | ||
140 | ret = cnt; /* whee, success! */ | ||
141 | |||
142 | unlink_new: | ||
143 | if (ret < 0) unlink(fnamesfx); | ||
144 | |||
145 | close_old_fp: | ||
146 | fclose(old_fp); | ||
147 | |||
148 | free_mem: | ||
149 | free(fnamesfx); | ||
150 | free((char *)filename); | ||
151 | free((char *)username); | ||
152 | return ret; | ||
153 | } |