41033e51a2f9f94a4974540792ebeb7bb8404187
[oweals/busybox.git] / loginutils / passwd.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
4  */
5
6 #include "busybox.h"
7 #include <syslog.h>
8
9
10 static void nuke_str(char *str)
11 {
12         if (str) memset(str, 0, strlen(str));
13 }
14
15
16 static int i64c(int i)
17 {
18         if (i <= 0)
19                 return '.';
20         if (i == 1)
21                 return '/';
22         if (i >= 2 && i < 12)
23                 return ('0' - 2 + i);
24         if (i >= 12 && i < 38)
25                 return ('A' - 12 + i);
26         if (i >= 38 && i < 63)
27                 return ('a' - 38 + i);
28         return 'z';
29 }
30
31
32 static char *crypt_make_salt(void)
33 {
34         time_t now;
35         static unsigned long x;
36         static char result[3];
37
38         time(&now);
39         x += now + getpid() + clock();
40         result[0] = i64c(((x >> 18) ^ (x >> 6)) & 077);
41         result[1] = i64c(((x >> 12) ^ x) & 077);
42         result[2] = '\0';
43         return result;
44 }
45
46
47 static char* new_password(const struct passwd *pw, const char *old_crypted,
48                         uid_t myuid, int algo)
49 {
50         char salt[sizeof("$N$XXXXXXXX")]; /* "$N$XXXXXXXX" or "XX" */
51         char *orig = "";
52         char *newp = NULL;
53         char *cipher = NULL;
54         char *cp = NULL;
55         char *ret = NULL; /* failure so far */
56
57         if (myuid && old_crypted[0]) {
58                 orig = bb_askpass(0, "Old password:"); /* returns ptr to static */
59                 if (!orig)
60                         goto err_ret;
61                 cipher = pw_encrypt(orig, old_crypted); /* returns ptr to static */
62                 if (strcmp(cipher, old_crypted) != 0) {
63                         syslog(LOG_WARNING, "incorrect password for '%s'",
64                                 pw->pw_name);
65                         bb_do_delay(FAIL_DELAY);
66                         puts("Incorrect password");
67                         goto err_ret;
68                 }
69         }
70         orig = xstrdup(orig); /* or else bb_askpass() will destroy it */
71         newp = bb_askpass(0, "Enter the new password (minimum of 5 characters).\n"
72                            "Please use a combination of upper and lower case letters and numbers.\n"
73                            "Enter new password:"); /* returns ptr to static */
74         if (!newp)
75                 goto err_ret;
76         newp = xstrdup(newp); /* we are going to bb_askpass() again, so save it */
77         if (obscure(orig, newp, pw)) {
78                 if (myuid)
79                         goto err_ret; /* non-root is not allowed to have weak passwd */
80                 puts("\nWarning: weak password (continuing)");
81         }
82
83         cp = bb_askpass(0, "Re-enter new password:");
84         if (!cp)
85                 goto err_ret;
86         if (strcmp(cp, newp)) {
87                 puts("Passwords do not match");
88                 goto err_ret;
89         }
90
91         memset(salt, 0, sizeof(salt));
92         if (algo == 1) { /* MD5 */
93                 strcpy(salt, "$1$");
94                 strcat(salt, crypt_make_salt());
95                 strcat(salt, crypt_make_salt());
96                 strcat(salt, crypt_make_salt());
97         }
98         strcat(salt, crypt_make_salt());
99         ret = xstrdup(pw_encrypt(newp, salt)); /* returns ptr to static */
100         /* whee, success! */
101
102  err_ret:
103         nuke_str(orig);
104         if (ENABLE_FEATURE_CLEAN_UP) free(orig);
105         nuke_str(newp);
106         if (ENABLE_FEATURE_CLEAN_UP) free(newp);
107         nuke_str(cipher);
108         nuke_str(cp);
109         return ret;
110 }
111
112
113 static void set_filesize_limit(int blocks)
114 {
115         struct rlimit rlimit_fsize;
116
117         rlimit_fsize.rlim_cur = rlimit_fsize.rlim_max = 512L * blocks;
118         setrlimit(RLIMIT_FSIZE, &rlimit_fsize);
119 }
120
121
122 #if 0
123 static int get_algo(char *a)
124 {
125         /* standard: MD5 */
126         int x = 1;
127         if (strcasecmp(a, "des") == 0)
128                 x = 0;
129         return x;
130 }
131 #endif
132
133
134 static int update_passwd(const char *filename, const char *username,
135                         const char *new_pw)
136 {
137         struct stat sb;
138         struct flock lock;
139         FILE *old_fp;
140         FILE *new_fp;
141         char *new_name;
142         char *last_char;
143         unsigned user_len;
144         int old_fd;
145         int new_fd;
146         int i;
147         int ret = 1; /* failure */
148
149         /* New passwd file, "/etc/passwd+" for now */
150         new_name = xasprintf("%s+", filename);
151         last_char = &new_name[strlen(new_name)-1];
152         username = xasprintf("%s:", username);
153         user_len = strlen(username);
154
155         old_fp = fopen(filename, "r+");
156         if (!old_fp)
157                 goto free_mem;
158         old_fd = fileno(old_fp);
159
160         /* Try to create "/etc/passwd+". Wait if it exists. */
161         i = 30;
162         do {
163                 // FIXME: on last iteration try w/o O_EXCL but with O_TRUNC?
164                 new_fd = open(new_name, O_WRONLY|O_CREAT|O_EXCL,0600);
165                 if (new_fd >= 0) goto created;
166                 if (errno != EEXIST) break;
167                 usleep(100000); /* 0.1 sec */
168         } while (--i);
169         bb_perror_msg("cannot create '%s'", new_name);
170         goto close_old_fp;
171  created:
172         if (!fstat(old_fd, &sb)) {
173                 fchmod(new_fd, sb.st_mode & 0777); /* ignore errors */
174                 fchown(new_fd, sb.st_uid, sb.st_gid);
175         }
176         new_fp = fdopen(new_fd, "w");
177         if (!new_fp) {
178                 close(new_fd);
179                 goto unlink_new;
180         }
181
182         /* Backup file is "/etc/passwd-" */
183         last_char[0] = '-';
184         /* Delete old one, create new as a hardlink to current */
185         i = (unlink(new_name) && errno != ENOENT);
186         if (i || link(filename, new_name))
187                 bb_perror_msg("warning: cannot create backup copy '%s'", new_name);
188         last_char[0] = '+';
189
190         /* Lock the password file before updating */
191         lock.l_type = F_WRLCK;
192         lock.l_whence = SEEK_SET;
193         lock.l_start = 0;
194         lock.l_len = 0;
195         if (fcntl(old_fd, F_SETLK, &lock) < 0)
196                 bb_perror_msg("warning: cannot lock '%s'", filename);
197         lock.l_type = F_UNLCK;
198
199         /* Read current password file, write updated one */
200         while (1) {
201                 char *line = xmalloc_fgets(old_fp);
202                 if (!line) break; /* EOF/error */
203                 if (strncmp(username, line, user_len) == 0) {
204                         /* we have a match with "username:"... */
205                         const char *cp = line + user_len;
206                         /* now cp -> old passwd, skip it: */
207                         cp = strchr(cp, ':');
208                         if (!cp) cp = "";
209                         /* now cp -> ':' after old passwd or -> "" */
210                         fprintf(new_fp, "%s%s%s", username, new_pw, cp);
211                         /* Erase password in memory */
212                 } else
213                         fputs(line, new_fp);
214                 free(line);
215         }
216         fcntl(old_fd, F_SETLK, &lock);
217
218         /* We do want all of them to execute, thus | instead of || */
219         if ((ferror(old_fp) | fflush(new_fp) | fsync(new_fd) | fclose(new_fp))
220          || rename(new_name, filename)
221         ) {
222                 /* At least one of those failed */
223                 goto unlink_new;
224         }
225         ret = 0; /* whee, success! */
226
227  unlink_new:
228         if (ret) unlink(new_name);
229
230  close_old_fp:
231         fclose(old_fp);
232
233  free_mem:
234         if (ENABLE_FEATURE_CLEAN_UP) free(new_name);
235         if (ENABLE_FEATURE_CLEAN_UP) free((char*)username);
236         return ret;
237 }
238
239
240 int passwd_main(int argc, char **argv)
241 {
242         enum {
243                 OPT_algo = 0x1, /* -a - password algorithm */
244                 OPT_lock = 0x2, /* -l - lock account */
245                 OPT_unlock = 0x4, /* -u - unlock account */
246                 OPT_delete = 0x8, /* -d - delete password */
247                 OPT_lud = 0xe,
248                 STATE_ALGO_md5 = 0x10,
249                 STATE_ALGO_des = 0x20,
250         };
251         unsigned opt;
252         char *opt_a = "";
253         const char *filename;
254         char *myname;
255         char *name;
256         char *oldp;
257         char *newp = NULL; /* gcc happiness */
258         const struct passwd *pw;
259         uid_t myuid;
260
261         openlog("passwd", LOG_NOWAIT, LOG_AUTH);
262         opt = getopt32(argc, argv, "a:lud", &opt_a);
263         argc -= optind;
264         argv += optind;
265
266         if (strcasecmp(opt_a, "des") == 0) /* -a */
267                 opt |= STATE_ALGO_des;
268         else
269                 opt |= STATE_ALGO_md5;
270         myuid = getuid();
271         if ((opt & OPT_lud) && (!argc || myuid))
272                 bb_show_usage();
273
274         myname = xstrdup(bb_getpwuid(NULL, myuid, -1));
275         name = myname;
276         if (argc) name = argv[0];
277
278         pw = getpwnam(name);
279         if (!pw) bb_error_msg_and_die("unknown user %s", name);
280         if (myuid && pw->pw_uid != myuid) {
281                 syslog(LOG_WARNING, "can't change pwd for '%s'", name);
282                 bb_error_msg_and_die("permission denied");
283         }
284
285         filename = bb_path_passwd_file;
286         oldp = pw->pw_passwd;
287         if (ENABLE_FEATURE_SHADOWPASSWDS) {
288                 struct spwd *sp = getspnam(name);
289                 if (!sp) {
290                         bb_error_msg("no shadow record for user %s found, "
291                                 "changing ordinary password instead", name);
292                 } else {
293                         filename = bb_path_shadow_file;
294                         oldp = sp->sp_pwdp;
295                 }
296         }
297
298         /* Decide what the new password will be */
299         if (!(opt & OPT_lud)) {
300                 if (myuid) {
301                         if (oldp[0] == '!') {
302                                 syslog(LOG_WARNING, "password locked for '%s'", name);
303                                 bb_error_msg_and_die("the password for %s cannot be changed", name);
304                         }
305                 }
306                 printf("Changing password for %s\n", name);
307                 newp = new_password(pw, oldp,
308                                         myuid,
309                                         opt & STATE_ALGO_md5);
310                 if (!newp) {
311                         bb_error_msg_and_die("the password for %s is unchanged", name);
312                 }
313         } else if (opt & OPT_lock) {
314                 if (oldp[0] == '!') goto skip;
315                 newp = xasprintf("!%s", oldp);
316         } else if (opt & OPT_unlock) {
317                 if (oldp[0] != '!') goto skip;
318                 newp = xstrdup(oldp + 1);
319         } else if (opt & OPT_delete) {
320                 newp = xstrdup("");
321         }
322
323         set_filesize_limit(30000);
324         signal(SIGHUP, SIG_IGN);
325         signal(SIGINT, SIG_IGN);
326         signal(SIGQUIT, SIG_IGN);
327         umask(077);
328         xsetuid(0);
329         if (update_passwd(filename, name, newp) == 0) {
330                 syslog(LOG_INFO, "password for '%s' changed by user '%s'", name,
331                                 myname);
332                 puts("Password changed");
333         } else {
334                 syslog(LOG_WARNING, "cannot update password file");
335                 bb_error_msg_and_die("cannot update password file");
336         }
337
338         if (ENABLE_FEATURE_CLEAN_UP) free(newp);
339 skip:
340         if (ENABLE_FEATURE_CLEAN_UP) free(myname);
341         return 0;
342 }