move help text from include/usage.src.h to debianutils/*.c e2fsprogs/*.c editors...
[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. If no USER is specified,\n"
10 //usage:       "changes the password for the current user.\n"
11 //usage:     "\nOptions:"
12 //usage:     "\n        -a ALG  Algorithm to use for password (des, md5)" /* ", sha1)" */
13 //usage:     "\n        -d      Delete password for the account"
14 //usage:     "\n        -l      Lock (disable) account"
15 //usage:     "\n        -u      Unlock (re-enable) account"
16
17 #include "libbb.h"
18 #include <syslog.h>
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, int algo)
26 {
27         char salt[sizeof("$N$XXXXXXXX")]; /* "$N$XXXXXXXX" or "XX" */
28         char *orig = (char*)"";
29         char *newp = NULL;
30         char *cp = NULL;
31         char *ret = NULL; /* failure so far */
32
33         if (myuid && 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",
42                                 pw->pw_name);
43                         bb_do_delay(LOGIN_FAIL_DELAY);
44                         puts("Incorrect password");
45                         goto err_ret;
46                 }
47                 if (ENABLE_FEATURE_CLEAN_UP) 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) && myuid)
56                 goto err_ret; /* non-root is not allowed to have weak passwd */
57
58         cp = bb_ask_stdin("Retype password: ");
59         if (!cp)
60                 goto err_ret;
61         if (strcmp(cp, newp)) {
62                 puts("Passwords don't match");
63                 goto err_ret;
64         }
65
66         crypt_make_salt(salt, 1, 0); /* des */
67         if (algo) { /* MD5 */
68                 strcpy(salt, "$1$");
69                 crypt_make_salt(salt + 3, 4, 0);
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         nuke_str(newp);
79         if (ENABLE_FEATURE_CLEAN_UP) free(newp);
80         nuke_str(cp);
81         return ret;
82 }
83
84 int passwd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
85 int passwd_main(int argc UNUSED_PARAM, char **argv)
86 {
87         enum {
88                 OPT_algo = 0x1, /* -a - password algorithm */
89                 OPT_lock = 0x2, /* -l - lock account */
90                 OPT_unlock = 0x4, /* -u - unlock account */
91                 OPT_delete = 0x8, /* -d - delete password */
92                 OPT_lud = 0xe,
93                 STATE_ALGO_md5 = 0x10,
94                 //STATE_ALGO_des = 0x20, not needed yet
95         };
96         unsigned opt;
97         int rc;
98         const char *opt_a = "";
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         if (strcasecmp(opt_a, "des") != 0) /* -a */
120                 opt |= STATE_ALGO_md5;
121         //else
122         //      opt |= STATE_ALGO_des;
123         myuid = getuid();
124         /* -l, -u, -d require root priv and username argument */
125         if ((opt & OPT_lud) && (myuid || !argv[0]))
126                 bb_show_usage();
127
128         /* Will complain and die if username not found */
129         myname = xstrdup(xuid2uname(myuid));
130         name = argv[0] ? argv[0] : myname;
131
132         pw = xgetpwnam(name);
133         if (myuid && pw->pw_uid != myuid) {
134                 /* LOGMODE_BOTH */
135                 bb_error_msg_and_die("%s can't change password for %s", myname, name);
136         }
137
138 #if ENABLE_FEATURE_SHADOWPASSWDS
139         {
140                 /* getspnam_r may return 0 yet set result to NULL.
141                  * At least glibc 2.4 does this. Be extra paranoid here. */
142                 struct spwd *result = NULL;
143                 errno = 0;
144                 if (getspnam_r(pw->pw_name, &spw, buffer, sizeof(buffer), &result) != 0
145                  || !result /* no error, but no record found either */
146                  || strcmp(result->sp_namp, pw->pw_name) != 0 /* paranoia */
147                 ) {
148                         if (errno != ENOENT) {
149                                 /* LOGMODE_BOTH */
150                                 bb_perror_msg("no record of %s in %s, using %s",
151                                         name, bb_path_shadow_file,
152                                         bb_path_passwd_file);
153                         }
154                         /* else: /etc/shadow does not exist,
155                          * apparently we are on a shadow-less system,
156                          * no surprise there */
157                 } else {
158                         pw->pw_passwd = result->sp_pwdp;
159                 }
160         }
161 #endif
162
163         /* Decide what the new password will be */
164         newp = NULL;
165         c = pw->pw_passwd[0] - '!';
166         if (!(opt & OPT_lud)) {
167                 if (myuid && !c) { /* passwd starts with '!' */
168                         /* LOGMODE_BOTH */
169                         bb_error_msg_and_die("can't change "
170                                         "locked password for %s", name);
171                 }
172                 printf("Changing password for %s\n", name);
173                 newp = new_password(pw, myuid, opt & STATE_ALGO_md5);
174                 if (!newp) {
175                         logmode = LOGMODE_STDIO;
176                         bb_error_msg_and_die("password for %s is unchanged", name);
177                 }
178         } else if (opt & OPT_lock) {
179                 if (!c) goto skip; /* passwd starts with '!' */
180                 newp = xasprintf("!%s", pw->pw_passwd);
181         } else if (opt & OPT_unlock) {
182                 if (c) goto skip; /* not '!' */
183                 /* pw->pw_passwd points to static storage,
184                  * strdup'ing to avoid nasty surprizes */
185                 newp = xstrdup(&pw->pw_passwd[1]);
186         } else if (opt & OPT_delete) {
187                 //newp = xstrdup("");
188                 newp = (char*)"";
189         }
190
191         rlimit_fsize.rlim_cur = rlimit_fsize.rlim_max = 512L * 30000;
192         setrlimit(RLIMIT_FSIZE, &rlimit_fsize);
193         bb_signals(0
194                 + (1 << SIGHUP)
195                 + (1 << SIGINT)
196                 + (1 << SIGQUIT)
197                 , SIG_IGN);
198         umask(077);
199         xsetuid(0);
200
201 #if ENABLE_FEATURE_SHADOWPASSWDS
202         filename = bb_path_shadow_file;
203         rc = update_passwd(bb_path_shadow_file, name, newp, NULL);
204         if (rc == 0) /* no lines updated, no errors detected */
205 #endif
206         {
207                 filename = bb_path_passwd_file;
208                 rc = update_passwd(bb_path_passwd_file, name, newp, NULL);
209         }
210         /* LOGMODE_BOTH */
211         if (rc < 0)
212                 bb_error_msg_and_die("can't update password file %s",
213                                 filename);
214         bb_info_msg("Password for %s changed by %s", name, myname);
215
216         //if (ENABLE_FEATURE_CLEAN_UP) free(newp);
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         if (ENABLE_FEATURE_CLEAN_UP) free(myname);
223         return 0;
224 }