-
Notifications
You must be signed in to change notification settings - Fork 5
/
nipasswd.c
398 lines (328 loc) · 8.82 KB
/
nipasswd.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
/*
* nipasswd.c - Non-interactive password utility.
*
* Part of the "web-chpass" package.
* https://github.com/chip-rosenthal/web-chpass
*
* Chip Rosenthal
*/
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdarg.h>
#include <pwd.h>
#include <errno.h>
#include <assert.h>
#include <security/pam_appl.h>
#define USAGE "usage: %s [-Da]"
#define NIPASSWD_PAM_SERVICE "nipasswd"
#define FAIL_DELAY 5 /* secs */
#define BUFLEN 512
#ifndef MIN_AUTH_UID
# define MIN_AUTH_UID 100 /* do not auth users below this uid */
#endif
#ifndef MIN_CHANGE_UID
# define MIN_CHANGE_UID 100 /* do not change users below this uid */
#endif
#define EX_SUCCESS 0 /* password successfully changed */
#define EX_ERROR 1 /* failed due to an error */
#define EX_DENIED 2 /* failed due to username/password auth */
#define EX_BADPW 3 /* failed due to bad password checks */
#define Dprintf if (!Debug) ; else fprintf
int Debug = 0; /* enable debugging messages */
int Do_auth_only = 0; /* authenticate but don't change passwd */
/*
* This information is global so it can be accessed by die().
*/
pam_handle_t *pam_h = NULL;
int pam_rc;
/*
* This information is global so it can be accessed by pam_conv_func().
*/
int pam_conv_resp_count = 0;
char username[BUFLEN];
char old_password[BUFLEN];
char new_password[BUFLEN];
int user_ok(const char *username, struct passwd *save_pw);
int pam_conv_func(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr);
void fgetline(char *buf, size_t buflen, FILE *fp);
char *xstrdup(const char *s);
void die(int exitstat, const char *fmt, ...);
const char *pam_msg_style_str(int n);
int main(int argc, char *argv[])
{
struct pam_conv pam_conv = { pam_conv_func, NULL };
int i;
while ((i = getopt(argc, argv, "Da")) != EOF) {
switch (i) {
case 'D':
Debug = 1;
break;
case 'a':
Do_auth_only = 1;
break;
default:
die(EX_ERROR, USAGE, argv[0]);
}
}
if (argc-optind != 0) {
die(EX_ERROR, USAGE, argv[0]);
}
fgetline(username, sizeof(username), stdin);
fgetline(old_password, sizeof(old_password), stdin);
if (!Do_auth_only) {
fgetline(new_password, sizeof(new_password), stdin);
}
if (getc(stdin) != EOF) {
die(EX_ERROR, "Excess input.");
}
struct passwd pw;
if (!user_ok(username, &pw)) {
(void) sleep(FAIL_DELAY);
die(EX_DENIED, "Access denied.");
}
/*
* Make sure our real UID matches the account that's being changed.
* On Debian with pam_ldap, this is required else there is
* a prompt for the LDAP admin passwd.
*/
if (setreuid(pw.pw_uid, 0) != 0) {
die(EX_ERROR, "Cannot set user id: %m");
}
pam_rc = pam_start(NIPASSWD_PAM_SERVICE, username, &pam_conv, &pam_h);
if (pam_rc != PAM_SUCCESS) {
die(EX_ERROR, "Error initializing PAM subsystem: %s",
pam_strerror(pam_h, pam_rc));
}
#ifdef PAM_FAIL_DELAY
pam_fail_delay(pam_h, FAIL_DELAY*1000);
#endif
if (Do_auth_only) {
/*
* Attempt to authenticate the user.
*/
pam_rc = pam_authenticate(pam_h, 0);
switch (pam_rc) {
case PAM_USER_UNKNOWN:
case PAM_AUTH_ERR:
die(EX_DENIED, "Access denied.");
case PAM_SUCCESS:
break;
default:
die(EX_ERROR, "PAM error authenticating user: %s",
pam_strerror(pam_h, pam_rc));
}
} else {
/*
* Attempt to change the password.
*/
pam_rc = pam_chauthtok(pam_h, 0);
if (pam_rc != PAM_SUCCESS) {
die(EX_ERROR, "Error setting new password: %s:",
pam_strerror(pam_h, pam_rc));
}
}
Dprintf(stderr, "main: terminating with success exit status\n");
(void) pam_end(pam_h, PAM_SUCCESS);
exit(EX_SUCCESS);
}
/*
* user_ok() - Verify it is alright to handle this user.
*
* The main purpose of this procedure is to enforce the min UID checks.
* But so long as we are at it, we can bounce unknown users without the
* overhead of stoking up PAM.
*/
int user_ok(const char *username, struct passwd *save_pw)
{
struct passwd *pw;
if ((pw = getpwnam(username)) == NULL) {
return 0;
}
if (pw->pw_uid < (Do_auth_only ? MIN_AUTH_UID : MIN_CHANGE_UID)) {
return 0;
}
if (save_pw != NULL) {
*save_pw = *pw;
}
return 1;
}
int pam_conv_func(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr)
{
struct pam_response *resp_buf;
int handled_this, i;
Dprintf(stderr,
"pam_conv_func: entered, pam_conv_resp_count=%d num_msg=%d\n",
pam_conv_resp_count, num_msg);
resp_buf = calloc(num_msg, sizeof(struct pam_response));
if (resp_buf == NULL) {
die(EX_ERROR, "System error: malloc failed.");
}
for (i = 0 ; i < num_msg ; ++i) {
handled_this = -1;
resp_buf[i].resp = NULL;
resp_buf[i].resp_retcode = PAM_SUCCESS;
/*
* pam_conv_resp_count counts the number of prompted
* responses we've sent back to PAM. There should
* be three: old password, new password, and confirm
* new password.
*/
switch (pam_conv_resp_count) {
case 0: /* prompt for old password */
switch (msg[i]->msg_style) {
case PAM_PROMPT_ECHO_OFF:
resp_buf[i].resp = xstrdup(old_password);
++pam_conv_resp_count;
handled_this = 1;
break;
case PAM_TEXT_INFO:
/* "Changing password for ..." */
handled_this = 1;
break;
default:
handled_this = 0;
break;
}
break;
case 1: /* prompt for new password */
switch (msg[i]->msg_style) {
case PAM_PROMPT_ECHO_OFF:
resp_buf[i].resp = xstrdup(new_password);
++pam_conv_resp_count;
handled_this = 1;
break;
case PAM_TEXT_INFO:
/* "Changing password for ..." */
handled_this = 1;
break;
#define EXPMSSG "You are required to change your password immediately"
case PAM_ERROR_MSG:
if (strncmp(msg[i]->msg, EXPMSSG, strlen(EXPMSSG)) == 0) {
handled_this = 1;
break;
}
/* else fall thru */
default:
handled_this = 0;
break;
}
break;
case 2: /* confirm new password */
switch (msg[i]->msg_style) {
case PAM_PROMPT_ECHO_OFF:
resp_buf[i].resp = xstrdup(new_password);
++pam_conv_resp_count;
handled_this = 1;
break;
case PAM_ERROR_MSG:
pam_rc = PAM_PERM_DENIED;
die(EX_BADPW, "%s", msg[i]->msg);
break;
default:
handled_this = 0;
break;
}
break;
case 3: /* possible additional message */
switch (msg[i]->msg_style) {
case PAM_TEXT_INFO:
/* "Password changed." */
handled_this = 1;
break;
case PAM_ERROR_MSG:
pam_rc = PAM_PERM_DENIED;
die(EX_BADPW, "%s", msg[i]->msg);
break;
default:
handled_this = 0;
break;
}
break;
default: /* don't know what this is */
handled_this = 0;
break;
}
assert(handled_this >= 0);
Dprintf(stderr,
"pam_conv_func: msg_style=\"%s\" msg=\"%s\" resp=\"%s\"\n",
pam_msg_style_str(msg[i]->msg_style),
msg[i]->msg, resp_buf[i].resp);
if (!handled_this) {
die(EX_ERROR, "System Error - Unexpected PAM message (%s): %s",
pam_msg_style_str(msg[i]->msg_style), msg[i]->msg);
}
}
*resp = resp_buf;
return PAM_SUCCESS;
}
void fgetline(char *buf, size_t buflen, FILE *fp)
{
int n;
if (fgets(buf, buflen, fp) == NULL) {
if (feof(fp)) {
die(EX_ERROR, "premature end of input");
} else {
die(EX_ERROR, "error reading input: %m");
}
}
n = strlen(buf);
if (n > 0 && buf[n-1] != '\n') {
die(EX_ERROR, "input rejected: buffer overflow");
}
buf[n-1] = '\0';
}
char *xstrdup(const char *s)
{
char *s1;
if ((s1 = strdup(s)) == NULL) {
die(EX_ERROR, "System error: malloc failed.");
}
return s1;
}
void die(int exitstat, const char *fmt, ...)
{
va_list ap;
int save_errno = errno;
char mssgbuf[1024], *s;
va_start(ap, fmt);
vsnprintf(mssgbuf, sizeof(mssgbuf), fmt, ap);
if ((s = strstr(mssgbuf, "%m")) != NULL) {
*s = '\0';
s += 2;
fputs(mssgbuf, stderr);
fputs(strerror(save_errno), stderr);
fputs(s, stderr);
} else {
fputs(mssgbuf, stderr);
}
putc('\n', stderr);
if (pam_h != NULL) {
Dprintf(stderr, "die: terminating with PAM status: %s\n",
pam_strerror(pam_h, pam_rc));
(void) pam_end(pam_h, pam_rc);
pam_h = NULL;
}
exit(exitstat);
/*NOTREACHED*/
}
const char *pam_msg_style_str(int n)
{
static char smbuf[64];
switch (n) {
case PAM_PROMPT_ECHO_OFF:
return "PAM_PROMPT_ECHO_OFF";
case PAM_PROMPT_ECHO_ON:
return "PAM_PROMPT_ECHO_ON";
case PAM_ERROR_MSG:
return "PAM_ERROR_MSG";
case PAM_TEXT_INFO:
return "PAM_TEXT_INFO";
default:
sprintf(smbuf, "<code %d>", n);
return smbuf;
}
}