udhcpc: fix a problem with binary-encoded options #2
[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 source tree.
4  */
5
6 //usage:#define passwd_trivial_usage
7 //usage:       "[OPTIONS] [USER]"
8 //usage:#define passwd_full_usage "\n\n"
9 //usage:       "Change USER's password (default: current user)"
10 //usage:     "\n"
11 //usage:     "\n        -a ALG  Encryption method"
12 //usage:     "\n        -d      Set password to ''"
13 //usage:     "\n        -l      Lock (disable) account"
14 //usage:     "\n        -u      Unlock (enable) account"
15
16 #include "libbb.h"
17 #include <syslog.h>
18 #include <sys/resource.h> /* setrlimit */
19
20 static void nuke_str(char *str)
21 {
22         if (str) memset(str, 0, strlen(str));
23 }
24
25 static char* new_password(const struct passwd *pw, uid_t myuid, const char *algo)
26 {
27         char salt[MAX_PW_SALT_LEN];
28         char *orig = (char*)"";
29         char *newp = NULL;
30         char *cp = NULL;
31         char *ret = NULL; /* failure so far */
32
33         if (myuid != 0 && pw->pw_passwd[0]) {
34                 char *encrypted;
35
36                 orig = bb_ask_stdin("Old password: "); /* returns ptr to static */
37                 if (!orig)
38                         goto err_ret;
39                 encrypted = pw_encrypt(orig, pw->pw_passwd, 1); /* returns malloced str */
40                 if (strcmp(encrypted, pw->pw_passwd) != 0) {
41                         syslog(LOG_WARNING, "incorrect password for %s", pw->pw_name);
42                         bb_do_delay(LOGIN_FAIL_DELAY);
43                         puts("Incorrect password");
44                         goto err_ret;
45                 }
46                 if (ENABLE_FEATURE_CLEAN_UP)
47                         free(encrypted);
48         }
49         orig = xstrdup(orig); /* or else bb_ask_stdin() will destroy it */
50         newp = bb_ask_stdin("New password: "); /* returns ptr to static */
51         if (!newp)
52                 goto err_ret;
53         newp = xstrdup(newp); /* we are going to bb_ask_stdin() again, so save it */
54         if (ENABLE_FEATURE_PASSWD_WEAK_CHECK
55          && obscure(orig, newp, pw)
56          && myuid != 0
57         ) {
58                 goto err_ret; /* non-root is not allowed to have weak passwd */
59         }
60
61         cp = bb_ask_stdin("Retype password: ");
62         if (!cp)
63                 goto err_ret;
64         if (strcmp(cp, newp) != 0) {
65                 puts("Passwords don't match");
66                 goto err_ret;
67         }
68
69         crypt_make_pw_salt(salt, algo);
70
71         /* pw_encrypt returns malloced str */
72         ret = pw_encrypt(newp, salt, 1);
73         /* whee, success! */
74
75  err_ret:
76         nuke_str(orig);
77         if (ENABLE_FEATURE_CLEAN_UP) free(orig);
78
79         nuke_str(newp);
80         if (ENABLE_FEATURE_CLEAN_UP) free(newp);
81
82         nuke_str(cp);
83         return ret;
84 }
85
86 int passwd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
87 int passwd_main(int argc UNUSED_PARAM, char **argv)
88 {
89         enum {
90                 OPT_algo   = (1 << 0), /* -a - password algorithm */
91                 OPT_lock   = (1 << 1), /* -l - lock account */
92                 OPT_unlock = (1 << 2), /* -u - unlock account */
93                 OPT_delete = (1 << 3), /* -d - delete password */
94                 OPT_lud    = OPT_lock | OPT_unlock | OPT_delete,
95         };
96         unsigned opt;
97         int rc;
98         const char *opt_a = CONFIG_FEATURE_DEFAULT_PASSWD_ALGO;
99         const char *filename;
100         char *myname;
101         char *name;
102         char *newp;
103         struct passwd *pw;
104         uid_t myuid;
105         struct rlimit rlimit_fsize;
106         char c;
107 #if ENABLE_FEATURE_SHADOWPASSWDS
108         /* Using _r function to avoid pulling in static buffers */
109         struct spwd spw;
110         char buffer[256];
111 #endif
112
113         logmode = LOGMODE_BOTH;
114         openlog(applet_name, 0, LOG_AUTH);
115         opt = getopt32(argv, "a:lud", &opt_a);
116         //argc -= optind;
117         argv += optind;
118
119         myuid = getuid();
120         /* -l, -u, -d require root priv and username argument */
121         if ((opt & OPT_lud) && (myuid != 0 || !argv[0]))
122                 bb_show_usage();
123
124         /* Will complain and die if username not found */
125         myname = xstrdup(xuid2uname(myuid));
126         name = argv[0] ? argv[0] : myname;
127
128         pw = xgetpwnam(name);
129         if (myuid != 0 && pw->pw_uid != myuid) {
130                 /* LOGMODE_BOTH */
131                 bb_error_msg_and_die("%s can't change password for %s", myname, name);
132         }
133
134 #if ENABLE_FEATURE_SHADOWPASSWDS
135         {
136                 /* getspnam_r may return 0 yet set result to NULL.
137                  * At least glibc 2.4 does this. Be extra paranoid here. */
138                 struct spwd *result = NULL;
139                 errno = 0;
140                 if (getspnam_r(pw->pw_name, &spw, buffer, sizeof(buffer), &result) != 0
141                  || !result /* no error, but no record found either */
142                  || strcmp(result->sp_namp, pw->pw_name) != 0 /* paranoia */
143                 ) {
144                         if (errno != ENOENT) {
145                                 /* LOGMODE_BOTH */
146                                 bb_perror_msg("no record of %s in %s, using %s",
147                                         name, bb_path_shadow_file,
148                                         bb_path_passwd_file);
149                         }
150                         /* else: /etc/shadow does not exist,
151                          * apparently we are on a shadow-less system,
152                          * no surprise there */
153                 } else {
154                         pw->pw_passwd = result->sp_pwdp;
155                 }
156         }
157 #endif
158
159         /* Decide what the new password will be */
160         newp = NULL;
161         c = pw->pw_passwd[0] - '!';
162         if (!(opt & OPT_lud)) {
163                 if (myuid != 0 && !c) { /* passwd starts with '!' */
164                         /* LOGMODE_BOTH */
165                         bb_error_msg_and_die("can't change "
166                                         "locked password for %s", name);
167                 }
168                 printf("Changing password for %s\n", name);
169                 newp = new_password(pw, myuid, opt_a);
170                 if (!newp) {
171                         logmode = LOGMODE_STDIO;
172                         bb_error_msg_and_die("password for %s is unchanged", name);
173                 }
174         } else if (opt & OPT_lock) {
175                 if (!c)
176                         goto skip; /* passwd starts with '!' */
177                 newp = xasprintf("!%s", pw->pw_passwd);
178         } else if (opt & OPT_unlock) {
179                 if (c)
180                         goto skip; /* not '!' */
181                 /* pw->pw_passwd points to static storage,
182                  * strdup'ing to avoid nasty surprizes */
183                 newp = xstrdup(&pw->pw_passwd[1]);
184         } else if (opt & OPT_delete) {
185                 newp = (char*)"";
186         }
187
188         rlimit_fsize.rlim_cur = rlimit_fsize.rlim_max = 512L * 30000;
189         setrlimit(RLIMIT_FSIZE, &rlimit_fsize);
190         bb_signals(0
191                 + (1 << SIGHUP)
192                 + (1 << SIGINT)
193                 + (1 << SIGQUIT)
194                 , SIG_IGN);
195         umask(077);
196         xsetuid(0);
197
198 #if ENABLE_FEATURE_SHADOWPASSWDS
199         filename = bb_path_shadow_file;
200         rc = update_passwd(bb_path_shadow_file, name, newp, NULL);
201         if (rc > 0)
202                 /* password in /etc/shadow was updated */
203                 newp = (char*) "x";
204         if (rc >= 0)
205                 /* 0 = /etc/shadow missing (not an error), >0 = passwd changed in /etc/shadow */
206 #endif
207         {
208                 filename = bb_path_passwd_file;
209                 rc = update_passwd(bb_path_passwd_file, name, newp, NULL);
210         }
211         /* LOGMODE_BOTH */
212         if (rc < 0)
213                 bb_error_msg_and_die("can't update password file %s", filename);
214         bb_info_msg("Password for %s changed by %s", name, myname);
215
216         /*if (ENABLE_FEATURE_CLEAN_UP) free(newp); - can't, it may be non-malloced */
217  skip:
218         if (!newp) {
219                 bb_error_msg_and_die("password for %s is already %slocked",
220                         name, (opt & OPT_unlock) ? "un" : "");
221         }
222
223         if (ENABLE_FEATURE_CLEAN_UP)
224                 free(myname);
225         return 0;
226 }