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