removed a lot of trailing \n in bb_msg() calls. It is added
[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 static char crypt_passwd[128];
10
11 static int create_backup(const char *backup, FILE * fp);
12 static int new_password(const struct passwd *pw, int amroot, int algo);
13 static void set_filesize_limit(int blocks);
14
15
16 static int get_algo(char *a)
17 {
18         int x = 1;                                      /* standard: MD5 */
19
20         if (strcasecmp(a, "des") == 0)
21                 x = 0;
22         return x;
23 }
24
25
26 static int update_passwd(const struct passwd *pw, const char *crypt_pw)
27 {
28         char filename[1024];
29         char buf[1025];
30         char buffer[80];
31         char username[32];
32         char *pw_rest;
33         int mask;
34         int continued;
35         FILE *fp;
36         FILE *out_fp;
37         struct stat sb;
38         struct flock lock;
39
40 #if ENABLE_FEATURE_SHADOWPASSWDS
41         if (access(bb_path_shadow_file, F_OK) == 0) {
42                 snprintf(filename, sizeof filename, "%s", bb_path_shadow_file);
43         } else
44 #endif
45         {
46                 snprintf(filename, sizeof filename, "%s", bb_path_passwd_file);
47         }
48
49         if (((fp = fopen(filename, "r+")) == 0) || (fstat(fileno(fp), &sb))) {
50                 /* return 0; */
51                 return 1;
52         }
53
54         /* Lock the password file before updating */
55         lock.l_type = F_WRLCK;
56         lock.l_whence = SEEK_SET;
57         lock.l_start = 0;
58         lock.l_len = 0;
59         if (fcntl(fileno(fp), F_SETLK, &lock) < 0) {
60                 fprintf(stderr, "%s: %s\n", filename, strerror(errno));
61                 return 1;
62         }
63         lock.l_type = F_UNLCK;
64
65         snprintf(buf, sizeof buf, "%s-", filename);
66         if (create_backup(buf, fp)) {
67                 fcntl(fileno(fp), F_SETLK, &lock);
68                 fclose(fp);
69                 return 1;
70         }
71         snprintf(buf, sizeof buf, "%s+", filename);
72         mask = umask(0777);
73         out_fp = fopen(buf, "w");
74         umask(mask);
75         if ((!out_fp) || (fchmod(fileno(out_fp), sb.st_mode & 0777))
76                 || (fchown(fileno(out_fp), sb.st_uid, sb.st_gid))) {
77                 fcntl(fileno(fp), F_SETLK, &lock);
78                 fclose(fp);
79                 fclose(out_fp);
80                 return 1;
81         }
82
83         continued = 0;
84         snprintf(username, sizeof username, "%s:", pw->pw_name);
85         rewind(fp);
86         while (!feof(fp)) {
87                 fgets(buffer, sizeof buffer, fp);
88                 if (!continued) { /* Check to see if we're updating this line.  */
89                         if (strncmp(username, buffer, strlen(username)) == 0) {
90                                 /* we have a match. */
91                                 pw_rest = strchr(buffer, ':');
92                                 *pw_rest++ = '\0';
93                                 pw_rest = strchr(pw_rest, ':');
94                                 fprintf(out_fp, "%s:%s%s", buffer, crypt_pw, pw_rest);
95                         } else {
96                                 fputs(buffer, out_fp);
97                         }
98                 } else {
99                         fputs(buffer, out_fp);
100                 }
101                 if (buffer[strlen(buffer) - 1] == '\n') {
102                         continued = 0;
103                 } else {
104                         continued = 1;
105                 }
106                 memset(buffer, 0, sizeof buffer);
107         }
108
109         if (fflush(out_fp) || fsync(fileno(out_fp)) || fclose(out_fp)) {
110                 unlink(buf);
111                 fcntl(fileno(fp), F_SETLK, &lock);
112                 fclose(fp);
113                 return 1;
114         }
115         if (rename(buf, filename) < 0) {
116                 fcntl(fileno(fp), F_SETLK, &lock);
117                 fclose(fp);
118                 return 1;
119         } else {
120                 fcntl(fileno(fp), F_SETLK, &lock);
121                 fclose(fp);
122                 return 0;
123         }
124 }
125
126
127 int passwd_main(int argc, char **argv)
128 {
129         int amroot;
130         char *cp;
131         char *np;
132         char *name;
133         char *myname;
134         int flag;
135         int algo = 1;                           /* -a - password algorithm */
136         int lflg = 0;                           /* -l - lock account */
137         int uflg = 0;                           /* -u - unlock account */
138         int dflg = 0;                           /* -d - delete password */
139         const struct passwd *pw;
140
141         amroot = (getuid() == 0);
142         openlog("passwd", LOG_PID | LOG_CONS | LOG_NOWAIT, LOG_AUTH);
143         while ((flag = getopt(argc, argv, "a:dlu")) != EOF) {
144                 switch (flag) {
145                 case 'a':
146                         algo = get_algo(optarg);
147                         break;
148                 case 'd':
149                         dflg++;
150                         break;
151                 case 'l':
152                         lflg++;
153                         break;
154                 case 'u':
155                         uflg++;
156                         break;
157                 default:
158                         bb_show_usage();
159                 }
160         }
161         myname = (char *) xstrdup(bb_getpwuid(NULL, getuid(), -1));
162         /* exits on error */
163         if (optind < argc) {
164                 name = argv[optind];
165         } else {
166                 name = myname;
167         }
168         if ((lflg || uflg || dflg) && (optind >= argc || !amroot)) {
169                 bb_show_usage();
170         }
171         pw = getpwnam(name);
172         if (!pw) {
173                 bb_error_msg_and_die("Unknown user %s", name);
174         }
175         if (!amroot && pw->pw_uid != getuid()) {
176                 syslog(LOG_WARNING, "can't change pwd for `%s'", name);
177                 bb_error_msg_and_die("Permission denied.");
178         }
179         if (ENABLE_FEATURE_SHADOWPASSWDS) {
180                 struct spwd *sp = getspnam(name);
181                 if (!sp) bb_error_msg_and_die("Unknown user %s", name);
182                 cp = sp->sp_pwdp;
183         } else cp = pw->pw_passwd;
184
185         np = name;
186         safe_strncpy(crypt_passwd, cp, sizeof(crypt_passwd));
187         if (!(dflg || lflg || uflg)) {
188                 if (!amroot) {
189                         if (cp[0] == '!') {
190                                 syslog(LOG_WARNING, "password locked for `%s'", np);
191                                 bb_error_msg_and_die( "The password for `%s' cannot be changed.", np);
192                         }
193                 }
194                 printf("Changing password for %s\n", name);
195                 if (new_password(pw, amroot, algo)) {
196                         bb_error_msg_and_die( "The password for %s is unchanged.", name);
197                 }
198         } else if (lflg) {
199                 if (crypt_passwd[0] != '!') {
200                         memmove(&crypt_passwd[1], crypt_passwd,
201                                         sizeof crypt_passwd - 1);
202                         crypt_passwd[sizeof crypt_passwd - 1] = '\0';
203                         crypt_passwd[0] = '!';
204                 }
205         } else if (uflg) {
206                 if (crypt_passwd[0] == '!') {
207                         memmove(crypt_passwd, &crypt_passwd[1],
208                                         sizeof crypt_passwd - 1);
209                 }
210         } else if (dflg) {
211                 crypt_passwd[0] = '\0';
212         }
213         set_filesize_limit(30000);
214         signal(SIGHUP, SIG_IGN);
215         signal(SIGINT, SIG_IGN);
216         signal(SIGQUIT, SIG_IGN);
217         umask(077);
218         xsetuid(0);
219         if (!update_passwd(pw, crypt_passwd)) {
220                 syslog(LOG_INFO, "password for `%s' changed by user `%s'", name,
221                            myname);
222                 printf("Password changed.\n");
223         } else {
224                 syslog(LOG_WARNING, "an error occurred updating the password file");
225                 bb_error_msg_and_die("An error occurred updating the password file.");
226         }
227         if (ENABLE_FEATURE_CLEAN_UP) free(myname);
228         return (0);
229 }
230
231
232
233 static int create_backup(const char *backup, FILE * fp)
234 {
235         struct stat sb;
236         struct utimbuf ub;
237         FILE *bkfp;
238         int c, mask;
239
240         if (fstat(fileno(fp), &sb))
241                 /* return -1; */
242                 return 1;
243
244         mask = umask(077);
245         bkfp = fopen(backup, "w");
246         umask(mask);
247         if (!bkfp)
248                 /* return -1; */
249                 return 1;
250
251         /* TODO: faster copy, not one-char-at-a-time.  --marekm */
252         rewind(fp);
253         while ((c = getc(fp)) != EOF) {
254                 if (putc(c, bkfp) == EOF)
255                         break;
256         }
257         if (c != EOF || fflush(bkfp)) {
258                 fclose(bkfp);
259                 /* return -1; */
260                 return 1;
261         }
262         if (fclose(bkfp))
263                 /* return -1; */
264                 return 1;
265
266         ub.actime = sb.st_atime;
267         ub.modtime = sb.st_mtime;
268         utime(backup, &ub);
269         return 0;
270 }
271
272 static int i64c(int i)
273 {
274         if (i <= 0)
275                 return ('.');
276         if (i == 1)
277                 return ('/');
278         if (i >= 2 && i < 12)
279                 return ('0' - 2 + i);
280         if (i >= 12 && i < 38)
281                 return ('A' - 12 + i);
282         if (i >= 38 && i < 63)
283                 return ('a' - 38 + i);
284         return ('z');
285 }
286
287 static char *crypt_make_salt(void)
288 {
289         time_t now;
290         static unsigned long x;
291         static char result[3];
292
293         time(&now);
294         x += now + getpid() + clock();
295         result[0] = i64c(((x >> 18) ^ (x >> 6)) & 077);
296         result[1] = i64c(((x >> 12) ^ x) & 077);
297         result[2] = '\0';
298         return result;
299 }
300
301
302 static int new_password(const struct passwd *pw, int amroot, int algo)
303 {
304         char *clear;
305         char *cipher;
306         char *cp;
307         char salt[12]; /* "$N$XXXXXXXX" or "XX" */
308         char orig[200];
309         char pass[200];
310
311         if (!amroot && crypt_passwd[0]) {
312                 if (!(clear = bb_askpass(0, "Old password:"))) {
313                         /* return -1; */
314                         return 1;
315                 }
316                 cipher = pw_encrypt(clear, crypt_passwd);
317                 if (strcmp(cipher, crypt_passwd) != 0) {
318                         syslog(LOG_WARNING, "incorrect password for `%s'",
319                                    pw->pw_name);
320                         bb_do_delay(FAIL_DELAY);
321                         fprintf(stderr, "Incorrect password.\n");
322                         /* return -1; */
323                         return 1;
324                 }
325                 safe_strncpy(orig, clear, sizeof(orig));
326                 memset(clear, 0, strlen(clear));
327                 memset(cipher, 0, strlen(cipher));
328         } else {
329                 orig[0] = '\0';
330         }
331         if (! (cp=bb_askpass(0, "Enter the new password (minimum of 5, maximum of 8 characters)\n"
332                                           "Please use a combination of upper and lower case letters and numbers.\n"
333                                           "Enter new password: ")))
334         {
335                 memset(orig, 0, sizeof orig);
336                 /* return -1; */
337                 return 1;
338         }
339         safe_strncpy(pass, cp, sizeof(pass));
340         memset(cp, 0, strlen(cp));
341         /* if (!obscure(orig, pass, pw)) { */
342         if (obscure(orig, pass, pw)) {
343                 if (amroot) {
344                         printf("\nWarning: weak password (continuing).\n");
345                 } else {
346                         /* return -1; */
347                         return 1;
348                 }
349         }
350         if (!(cp = bb_askpass(0, "Re-enter new password: "))) {
351                 memset(orig, 0, sizeof orig);
352                 /* return -1; */
353                 return 1;
354         }
355         if (strcmp(cp, pass)) {
356                 fprintf(stderr, "Passwords do not match.\n");
357                 /* return -1; */
358                 return 1;
359         }
360         memset(cp, 0, strlen(cp));
361         memset(orig, 0, sizeof(orig));
362         memset(salt, 0, sizeof(salt));
363
364         if (algo == 1) {
365                 strcpy(salt, "$1$");
366                 strcat(salt, crypt_make_salt());
367                 strcat(salt, crypt_make_salt());
368                 strcat(salt, crypt_make_salt());
369         }
370
371         strcat(salt, crypt_make_salt());
372         cp = pw_encrypt(pass, salt);
373
374         memset(pass, 0, sizeof pass);
375         safe_strncpy(crypt_passwd, cp, sizeof(crypt_passwd));
376         return 0;
377 }
378
379 static void set_filesize_limit(int blocks)
380 {
381         struct rlimit rlimit_fsize;
382
383         rlimit_fsize.rlim_cur = rlimit_fsize.rlim_max = 512L * blocks;
384         setrlimit(RLIMIT_FSIZE, &rlimit_fsize);
385 }