1/* see LICENSE file for copyright and license details */
2#include "passwd.h"
3#include "util.h"
4
5#include <crypt.h>
6#include <pwd.h>
7#include <stdio.h>
8#include <stdlib.h>
9#include <string.h>
10#include <termios.h>
11#include <unistd.h>
12
13static void
14usage(void)
15{
16 eprintf("usage: %s [-l | -u | -d] [name]\n", argv0);
17}
18
19/* reads one line with echo off into a reusable static buffer */
20static char *
21readpass(const char *prompt)
22{
23 static char buf[128];
24 struct termios old, raw;
25 int have_old;
26 size_t n;
27
28 fputs(prompt, stdout);
29 fflush(stdout);
30
31 have_old = tcgetattr(STDIN_FILENO, &old) == 0;
32 if (have_old) {
33 raw = old;
34 raw.c_lflag &= ~ECHO;
35 tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
36 }
37
38 if (!fgets(buf, sizeof(buf), stdin)) {
39 if (have_old)
40 tcsetattr(STDIN_FILENO, TCSAFLUSH, &old);
41 return NULL;
42 }
43
44 if (have_old) {
45 tcsetattr(STDIN_FILENO, TCSAFLUSH, &old);
46 fputc('\n', stdout);
47 }
48
49 n = strlen(buf);
50 if (n && buf[n - 1] == '\n')
51 buf[--n] = '\0';
52
53 return buf;
54}
55
56// ?man passwd: change a user's password
57// ?man arguments: [-l | -u | -d] [name]
58// ?man change name's password (default: the caller's own), or with -l/-u/-d
59// ?man lock, unlock, or clear it instead. only root may target another
60// ?man user, or use -l/-u/-d at all
61int
62main(int argc, char *argv[])
63{
64 struct pwdb_entry ent;
65 struct passwd *self, *target;
66 char *name, *cur, *new1, *new2, *hash;
67 char salt[PW_SALT_MAX];
68 int lflag = 0, uflag = 0, dflag = 0;
69 uid_t uid;
70
71 ARGBEGIN
72 {
73 // ?man -l: lock the account by prefixing its hash with '!'
74 case 'l':
75 lflag = 1;
76 break;
77 // ?man -u: unlock an account previously locked with -l
78 case 'u':
79 uflag = 1;
80 break;
81 // ?man -d: clear the password, allowing a blank password login
82 case 'd':
83 dflag = 1;
84 break;
85 default:
86 usage();
87 }
88 ARGEND
89
90 if (lflag + uflag + dflag > 1)
91 usage();
92
93 uid = getuid();
94 self = getpwuid(uid);
95 if (!self)
96 eprintf("passwd: who are you?\n");
97
98 name = argc > 0 ? argv[0] : self->pw_name;
99 if (uid != 0 && (strcmp(name, self->pw_name) != 0 || lflag || uflag || dflag))
100 eprintf("passwd: permission denied\n");
101
102 target = getpwnam(name);
103 if (!target)
104 eprintf("passwd: unknown user: %s\n", name);
105
106 pw_init();
107
108 if (lflag || uflag || dflag) {
109 if (pwdb_lookup(&ent, name) < 0)
110 eprintf("passwd: cannot look up %s\n", name);
111
112 if (lflag) {
113 if (ent.hash[0] == '!') {
114 hash = ent.hash;
115 } else {
116 hash = emalloc(strlen(ent.hash) + 2);
117 hash[0] = '!';
118 strcpy(hash + 1, ent.hash);
119 }
120 if (pwdb_update(name, hash) < 0)
121 eprintf("passwd: update failed\n");
122 printf("passwd: password for %s locked\n", name);
123 } else if (uflag) {
124 hash = ent.hash[0] == '!' ? ent.hash + 1 : ent.hash;
125 if (pwdb_update(name, hash) < 0)
126 eprintf("passwd: update failed\n");
127 printf("passwd: password for %s unlocked\n", name);
128 } else {
129 if (pwdb_update(name, "") < 0)
130 eprintf("passwd: update failed\n");
131 printf("passwd: password for %s cleared\n", name);
132 }
133 return 0;
134 }
135
136 if (uid != 0) {
137 cur = readpass("Current password: ");
138 if (!cur || pw_check(self, cur) != 1)
139 eprintf("passwd: authentication failed\n");
140 }
141
142 new1 = readpass("New password: ");
143 if (!new1)
144 exit(1);
145 new1 = estrdup(new1);
146
147 new2 = readpass("Retype new password: ");
148 if (!new2 || strcmp(new1, new2) != 0) {
149 free(new1);
150 eprintf("passwd: passwords do not match\n");
151 }
152 if (new1[0] == '\0')
153 weprintf("passwd: warning: empty password\n");
154
155 if (pw_gensalt(salt, sizeof(salt)) < 0)
156 eprintf("passwd: cannot generate salt\n");
157
158 hash = crypt(new1, salt);
159 free(new1);
160 if (!hash)
161 eprintf("passwd: crypt:");
162
163 if (pwdb_update(name, hash) < 0)
164 eprintf("passwd: update failed\n");
165
166 printf("passwd: password for %s updated\n", name);
167 return 0;
168}