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