1 /*        $NetBSD: local_passwd.c,v 1.37 2024/05/18 19:03:31 andvar Exp $       */
2 
3 /*-
4  * Copyright (c) 1990, 1993, 1994
5  *        The Regents of the University of California.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. Neither the name of the University nor the names of its contributors
16  *    may be used to endorse or promote products derived from this software
17  *    without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 
32 #include <sys/cdefs.h>
33 #ifndef lint
34 #if 0
35 static char sccsid[] = "from: @(#)local_passwd.c    8.3 (Berkeley) 4/2/94";
36 #else
37 __RCSID("$NetBSD: local_passwd.c,v 1.37 2024/05/18 19:03:31 andvar Exp $");
38 #endif
39 #endif /* not lint */
40 
41 #include <sys/types.h>
42 #include <sys/stat.h>
43 #include <ctype.h>
44 #include <err.h>
45 #include <errno.h>
46 #include <fcntl.h>
47 #include <pwd.h>
48 #include <stdio.h>
49 #include <stdlib.h>
50 #include <string.h>
51 #include <limits.h>
52 #include <time.h>
53 #include <unistd.h>
54 #include <util.h>
55 #include <login_cap.h>
56 #include <syslog.h>
57 
58 #include "extern.h"
59 
60 static uid_t uid;
61 
62 static char *
getnewpasswd(struct passwd * pw,int min_pw_len)63 getnewpasswd(struct passwd *pw, int min_pw_len)
64 {
65           int tries;
66           char *p, *t;
67           char buf[_PASSWORD_LEN+1], salt[_PASSWORD_LEN+1];
68           char option[LINE_MAX], *key, *opt;
69 
70           (void)printf("Changing local password for %s.\n", pw->pw_name);
71 
72           if (uid && pw->pw_passwd[0] &&
73               strcmp(crypt(getpass("Old password:"), pw->pw_passwd),
74               pw->pw_passwd)) {
75                     errno = EACCES;
76                     syslog(LOG_AUTH | LOG_NOTICE,
77                            "user %s (UID %lu) failed to change the "
78                            "local password of user %s: %m",
79                            pw->pw_name, (unsigned long)uid, pw->pw_name);
80                     pw_error(NULL, 1, 1);
81           }
82 
83           for (buf[0] = '\0', tries = 0;;) {
84                     p = getpass("New password:");
85                     if (!*p) {
86                               (void)printf("Password unchanged.\n");
87                               pw_error(NULL, 0, 0);
88                     }
89                     if (min_pw_len > 0 && (int)strlen(p) < min_pw_len) {
90                               (void) printf("Password is too short.\n");
91                               continue;
92                     }
93                     if (strlen(p) <= 5 && ++tries < 2) {
94                               (void)printf("Please enter a longer password.\n");
95                               continue;
96                     }
97                     for (t = p; *t && islower((unsigned char)*t); ++t);
98                     if (!*t && ++tries < 2) {
99                               (void)printf("Please don't use an all-lower case "
100                                              "password.\nUnusual capitalization, "
101                                              "control characters or digits are "
102                                              "suggested.\n");
103                               continue;
104                     }
105                     (void)strlcpy(buf, p, sizeof(buf));
106                     if (!strcmp(buf, getpass("Retype new password:")))
107                               break;
108                     (void)printf("Mismatch; try again, EOF to quit.\n");
109           }
110 
111           pw_getpwconf(option, sizeof(option), pw, "localcipher");
112           opt = option;
113           key = strsep(&opt, ",");
114           if(pw_gensalt(salt, _PASSWORD_LEN, key, opt) == -1) {
115                     warn("Couldn't generate salt");
116                     pw_error(NULL, 0, 0);
117           }
118           return(crypt(buf, salt));
119 }
120 
121 #ifdef USE_PAM
122 
123 void
pwlocal_usage(const char * prefix)124 pwlocal_usage(const char *prefix)
125 {
126 
127           (void) fprintf(stderr, "%s %s [-d files | -l] [user]\n",
128               prefix, getprogname());
129 }
130 
131 void
pwlocal_process(const char * username,int argc,char ** argv)132 pwlocal_process(const char *username, int argc, char **argv)
133 {
134           struct passwd *pw;
135           struct passwd old_pw;
136           time_t old_change;
137           int pfd, tfd;
138           int min_pw_len = 0;
139           int pw_expiry  = 0;
140           int ch;
141 #ifdef LOGIN_CAP
142           login_cap_t *lc;
143 #endif
144 
145           while ((ch = getopt(argc, argv, "l")) != -1) {
146                     switch (ch) {
147                     case 'l':
148                               /*
149                                * Absorb the -l that may have gotten us here.
150                                */
151                               break;
152 
153                     default:
154                               usage();
155                               /* NOTREACHED */
156                     }
157           }
158 
159           argc -= optind;
160           argv += optind;
161 
162           switch (argc) {
163           case 0:
164                     /* username already provided */
165                     break;
166           case 1:
167                     username = argv[0];
168                     break;
169           default:
170                     usage();
171                     /* NOTREACHED */
172           }
173 
174           if (!(pw = getpwnam(username)))
175                     errx(1, "unknown user %s", username);
176 
177           uid = getuid();
178           if (uid && uid != pw->pw_uid)
179                     errx(1, "%s", strerror(EACCES));
180 
181           /* Save the old pw information for comparing on pw_copy(). */
182           old_pw = *pw;
183 
184           /*
185            * Get class restrictions for this user, then get the new password.
186            */
187 #ifdef LOGIN_CAP
188           if((lc = login_getclass(pw->pw_class)) != NULL) {
189                     min_pw_len = (int) login_getcapnum(lc, "minpasswordlen", 0, 0);
190                     pw_expiry  = (int) login_getcaptime(lc, "passwordtime", 0, 0);
191                     login_close(lc);
192           }
193 #endif
194 
195           pw->pw_passwd = getnewpasswd(pw, min_pw_len);
196           old_change = pw->pw_change;
197           pw->pw_change = pw_expiry ? pw_expiry + time(NULL) : 0;
198 
199           /*
200            * Now that the user has given us a new password, let us
201            * change the database.
202            */
203           pw_init();
204           tfd = pw_lock(0);
205           if (tfd < 0) {
206                     warnx ("The passwd file is busy, waiting...");
207                     tfd = pw_lock(10);
208                     if (tfd < 0)
209                               errx(1, "The passwd file is still busy, "
210                                    "try again later.");
211           }
212 
213           pfd = open(_PATH_MASTERPASSWD, O_RDONLY, 0);
214           if (pfd < 0)
215                     pw_error(_PATH_MASTERPASSWD, 1, 1);
216 
217           pw_copy(pfd, tfd, pw, &old_pw);
218 
219           if (pw_mkdb(username, old_change == pw->pw_change) < 0)
220                     pw_error(NULL, 0, 1);
221 
222           syslog(LOG_AUTH | LOG_INFO,
223                  "user %s (UID %lu) successfully changed "
224                  "the local password of user %s",
225                  uid ? username : "root", (unsigned long)uid, username);
226 }
227 
228 #else /* ! USE_PAM */
229 
230 static int force_local;
231 
232 int
local_init(const char * progname)233 local_init(const char *progname)
234 {
235           force_local = 0;
236           return (0);
237 }
238 
239 int
local_arg(char ch,const char * arg)240 local_arg(char ch, const char *arg)
241 {
242           switch (ch) {
243           case 'l':
244                     force_local = 1;
245                     break;
246           default:
247                     return(0);
248           }
249           return(1);
250 }
251 
252 int
local_arg_end(void)253 local_arg_end(void)
254 {
255           if (force_local)
256                     return(PW_USE_FORCE);
257           return(PW_USE);
258 }
259 
260 void
local_end(void)261 local_end(void)
262 {
263           /* NOOP */
264 }
265 
266 int
local_chpw(const char * uname)267 local_chpw(const char *uname)
268 {
269           struct passwd *pw;
270           struct passwd old_pw;
271           time_t old_change;
272           int pfd, tfd;
273           int min_pw_len = 0;
274           int pw_expiry  = 0;
275 #ifdef LOGIN_CAP
276           login_cap_t *lc;
277 #endif
278 
279           if (!(pw = getpwnam(uname))) {
280                     warnx("unknown user %s", uname);
281                     return (1);
282           }
283 
284           uid = getuid();
285           if (uid && uid != pw->pw_uid) {
286                     warnx("%s", strerror(EACCES));
287                     return (1);
288           }
289 
290           /* Save the old pw information for comparing on pw_copy(). */
291           old_pw = *pw;
292 
293           /*
294            * Get class restrictions for this user, then get the new password.
295            */
296 #ifdef LOGIN_CAP
297           if((lc = login_getclass(pw->pw_class))) {
298                     min_pw_len = (int) login_getcapnum(lc, "minpasswordlen", 0, 0);
299                     pw_expiry  = (int) login_getcaptime(lc, "passwordtime", 0, 0);
300                     login_close(lc);
301           }
302 #endif
303 
304           pw->pw_passwd = getnewpasswd(pw, min_pw_len);
305           old_change = pw->pw_change;
306           pw->pw_change = pw_expiry ? pw_expiry + time(NULL) : 0;
307 
308           /*
309            * Now that the user has given us a new password, let us
310            * change the database.
311            */
312           pw_init();
313           tfd = pw_lock(0);
314           if (tfd < 0) {
315                     warnx ("The passwd file is busy, waiting...");
316                     tfd = pw_lock(10);
317                     if (tfd < 0)
318                               errx(1, "The passwd file is still busy, "
319                                    "try again later.");
320           }
321 
322           pfd = open(_PATH_MASTERPASSWD, O_RDONLY, 0);
323           if (pfd < 0)
324                     pw_error(_PATH_MASTERPASSWD, 1, 1);
325 
326           pw_copy(pfd, tfd, pw, &old_pw);
327 
328           if (pw_mkdb(uname, old_change == pw->pw_change) < 0)
329                     pw_error(NULL, 0, 1);
330 
331           syslog(LOG_AUTH | LOG_INFO,
332                  "user %s (UID %lu) successfully changed "
333                  "the local password of user %s",
334                  uid ? uname : "root", (unsigned long)uid, uname);
335 
336           return (0);
337 }
338 
339 #endif /* USE_PAM */
340