1 /* $OpenBSD: auth_subr.c,v 1.56 2020/10/13 04:42:28 guenther Exp $ */
2
3 /*
4 * Copyright (c) 2000-2002,2004 Todd C. Miller <millert@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18 /*-
19 * Copyright (c) 1995,1996,1997 Berkeley Software Design, Inc.
20 * All rights reserved.
21 *
22 * Redistribution and use in source and binary forms, with or without
23 * modification, are permitted provided that the following conditions
24 * are met:
25 * 1. Redistributions of source code must retain the above copyright
26 * notice, this list of conditions and the following disclaimer.
27 * 2. Redistributions in binary form must reproduce the above copyright
28 * notice, this list of conditions and the following disclaimer in the
29 * documentation and/or other materials provided with the distribution.
30 * 3. All advertising materials mentioning features or use of this software
31 * must display the following acknowledgement:
32 * This product includes software developed by Berkeley Software Design,
33 * Inc.
34 * 4. The name of Berkeley Software Design, Inc. may not be used to endorse
35 * or promote products derived from this software without specific prior
36 * written permission.
37 *
38 * THIS SOFTWARE IS PROVIDED BY BERKELEY SOFTWARE DESIGN, INC. ``AS IS'' AND
39 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
40 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
41 * ARE DISCLAIMED. IN NO EVENT SHALL BERKELEY SOFTWARE DESIGN, INC. BE LIABLE
42 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
43 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
44 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
45 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
46 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
47 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
48 * SUCH DAMAGE.
49 *
50 * BSDI $From: auth_subr.c,v 2.4 1999/09/08 04:10:40 prb Exp $
51 */
52
53 #include <sys/time.h>
54 #include <sys/resource.h>
55 #include <sys/socket.h>
56 #include <sys/wait.h>
57
58 #include <ctype.h>
59 #include <err.h>
60 #include <errno.h>
61 #include <fcntl.h>
62 #include <limits.h>
63 #include <paths.h>
64 #include <pwd.h>
65 #include <stdarg.h>
66 #include <stdio.h>
67 #include <stdlib.h>
68 #include <string.h>
69 #include <syslog.h>
70 #include <unistd.h>
71
72 #include <login_cap.h>
73
74 #define MAXSPOOLSIZE (8*1024) /* Spool up to 8K of back info */
75
76 struct rmfiles {
77 struct rmfiles *next;
78 char *file;
79 };
80
81 struct authopts {
82 struct authopts *next;
83 char *opt;
84 };
85
86 struct authdata {
87 struct authdata *next;
88 void *ptr;
89 size_t len;
90 };
91
92 struct auth_session_t {
93 char *name; /* name of use being authenticated */
94 char *style; /* style of authentication used */
95 char *class; /* class of user */
96 char *service; /* type of service being performed */
97 char *challenge; /* last challenge issued */
98 int flags; /* see below */
99 struct passwd *pwd; /* password entry for user */
100 struct timeval now; /* time of authentication */
101
102 int state; /* authenticated state */
103
104 struct rmfiles *rmlist; /* list of files to remove on failure */
105 struct authopts *optlist; /* list of options to scripts */
106 struct authdata *data; /* additional data to send to scripts */
107
108 char spool[MAXSPOOLSIZE]; /* data returned from login script */
109 int index; /* how much returned thus far */
110
111 int fd; /* connection to authenticator */
112
113 va_list ap0; /* argument list to auth_call */
114 va_list ap; /* additional arguments to auth_call */
115 };
116
117 /*
118 * Internal flags
119 */
120 #define AF_INTERACTIVE 0x0001 /* This is an interactive session */
121
122 /*
123 * We cannot include bsd_auth.h until we define the above structures
124 */
125 #include <bsd_auth.h>
126
127 /*
128 * Internally used functions
129 */
130 static void _add_rmlist(auth_session_t *, char *);
131 static void _auth_spool(auth_session_t *, int);
132 static void _recv_fd(auth_session_t *, int);
133 static char *_auth_next_arg(auth_session_t *);
134 /*
135 * Set up a known environment for all authentication scripts.
136 */
137 static char * const auth_environ[] = {
138 "PATH=" _PATH_DEFPATH,
139 "SHELL=" _PATH_BSHELL,
140 NULL,
141 };
142
143 static char defservice[] = LOGIN_DEFSERVICE;
144
145 static va_list nilap;
146
147 /*
148 * Quick one liners that only exist to keep auth_session_t opaque
149 */
auth_setstate(auth_session_t * as,int s)150 void auth_setstate(auth_session_t *as, int s){ as->state = s; }
auth_set_va_list(auth_session_t * as,va_list ap)151 void auth_set_va_list(auth_session_t *as, va_list ap) { va_copy(as->ap, ap); }
auth_getstate(auth_session_t * as)152 int auth_getstate(auth_session_t *as) { return (as->state); }
auth_getpwd(auth_session_t * as)153 struct passwd *auth_getpwd(auth_session_t *as) { return (as->pwd); }
154 DEF_WEAK(auth_setstate);
155 DEF_WEAK(auth_set_va_list);
156 DEF_WEAK(auth_getstate);
157 DEF_WEAK(auth_getpwd);
158
159 /*
160 * Open a new BSD Authentication session with the default service
161 * (which can be changed later).
162 */
163 auth_session_t *
auth_open(void)164 auth_open(void)
165 {
166 auth_session_t *as;
167
168 if ((as = calloc(1, sizeof(auth_session_t))) != NULL) {
169 as->service = defservice;
170 as->fd = -1;
171 }
172
173 return (as);
174 }
175 DEF_WEAK(auth_open);
176
177 /*
178 * Clean the specified BSD Authentication session.
179 */
180 void
auth_clean(auth_session_t * as)181 auth_clean(auth_session_t *as)
182 {
183 struct rmfiles *rm;
184 struct authdata *data;
185
186 as->state = 0;
187
188 auth_clrenv(as);
189
190 /*
191 * Clean out the rmlist and remove specified files
192 */
193 while ((rm = as->rmlist) != NULL) {
194 as->rmlist = rm->next;
195 unlink(rm->file);
196 free(rm);
197 }
198
199 /*
200 * Clean out data
201 */
202 while ((data = as->data) != NULL) {
203 if (as->data->len)
204 explicit_bzero(as->data->ptr, as->data->len);
205 as->data = data->next;
206 free(data);
207 }
208
209 auth_setitem(as, AUTHV_ALL, NULL);
210
211 if (as->pwd != NULL) {
212 explicit_bzero(as->pwd->pw_passwd, strlen(as->pwd->pw_passwd));
213 free(as->pwd);
214 as->pwd = NULL;
215 }
216
217 if (as->fd != -1) {
218 close(as->fd);
219 as->fd = -1;
220 }
221 }
222 DEF_WEAK(auth_clean);
223
224 /*
225 * Close the specified BSD Authentication session.
226 * Return 0 if not authenticated.
227 */
228 int
auth_close(auth_session_t * as)229 auth_close(auth_session_t *as)
230 {
231 struct rmfiles *rm;
232 struct authopts *opt;
233 struct authdata *data;
234 int s;
235
236 /*
237 * Save our return value
238 */
239 s = as->state & AUTH_ALLOW;
240
241 if (s == 0)
242 as->index = 0;
243
244 auth_setenv(as);
245
246
247 /*
248 * Clean out the rmlist and remove specified files if the
249 * authentication failed
250 */
251 while ((rm = as->rmlist) != NULL) {
252 as->rmlist = rm->next;
253 if (s == 0)
254 unlink(rm->file);
255 free(rm);
256 }
257
258 /*
259 * Clean out the opt list
260 */
261 while ((opt = as->optlist) != NULL) {
262 as->optlist = opt->next;
263 free(opt);
264 }
265
266 /*
267 * Clean out data
268 */
269 while ((data = as->data) != NULL) {
270 if (as->data->len)
271 explicit_bzero(as->data->ptr, as->data->len);
272 as->data = data->next;
273 free(data);
274 }
275
276 if (as->pwd != NULL) {
277 explicit_bzero(as->pwd->pw_passwd, strlen(as->pwd->pw_passwd));
278 free(as->pwd);
279 as->pwd = NULL;
280 }
281
282 /*
283 * Clean up random variables
284 */
285 if (as->service && as->service != defservice)
286 free(as->service);
287 free(as->challenge);
288 free(as->class);
289 free(as->style);
290 free(as->name);
291
292 free(as);
293 return (s);
294 }
295 DEF_WEAK(auth_close);
296
297 /*
298 * Request a challenge for the session.
299 * The name and style must have already been specified
300 */
301 char *
auth_challenge(auth_session_t * as)302 auth_challenge(auth_session_t *as)
303 {
304 char path[PATH_MAX];
305 int len;
306
307 if (as == NULL || as->style == NULL || as->name == NULL ||
308 !_auth_validuser(as->name))
309 return (NULL);
310
311 len = snprintf(path, sizeof(path), _PATH_AUTHPROG "%s", as->style);
312 if (len < 0 || len >= sizeof(path))
313 return (NULL);
314
315 as->state = 0;
316
317 free(as->challenge);
318 as->challenge = NULL;
319
320 auth_call(as, path, as->style, "-s", "challenge", "--", as->name,
321 as->class, (char *)NULL);
322 if (as->state & AUTH_CHALLENGE)
323 as->challenge = auth_getvalue(as, "challenge");
324 as->state = 0;
325 as->index = 0; /* toss our data */
326 return (as->challenge);
327 }
328 DEF_WEAK(auth_challenge);
329
330 /*
331 * Set/unset the requested environment variables.
332 * Mark the variables as set so they will not be set a second time.
333 * XXX - should provide a way to detect setenv() failure.
334 */
335 void
auth_setenv(auth_session_t * as)336 auth_setenv(auth_session_t *as)
337 {
338 char *line, *name;
339
340 /*
341 * Set any environment variables we were asked for
342 */
343 for (line = as->spool; line < as->spool + as->index;) {
344 if (!strncasecmp(line, BI_SETENV, sizeof(BI_SETENV)-1)) {
345 if (isblank((unsigned char)line[sizeof(BI_SETENV) - 1])) {
346 /* only do it once! */
347 line[0] = 'd'; line[1] = 'i'; line[2] = 'd';
348 line += sizeof(BI_SETENV) - 1;
349 for (name = line;
350 isblank((unsigned char)*name); ++name)
351 ;
352 for (line = name;
353 *line && !isblank((unsigned char)*line);
354 ++line)
355 ;
356 if (*line)
357 *line++ = '\0';
358 for (; isblank((unsigned char)*line); ++line)
359 ;
360 if (*line != '\0' && setenv(name, line, 1))
361 warn("setenv(%s, %s)", name, line);
362 }
363 } else
364 if (!strncasecmp(line, BI_UNSETENV, sizeof(BI_UNSETENV)-1)) {
365 if (isblank((unsigned char)line[sizeof(BI_UNSETENV) - 1])) {
366 /* only do it once! */
367 line[2] = 'd'; line[3] = 'i'; line[4] = 'd';
368 line += sizeof(BI_UNSETENV) - 1;
369 for (name = line;
370 isblank((unsigned char)*name); ++name)
371 ;
372 for (line = name;
373 *line && !isblank((unsigned char)*line);
374 ++line)
375 ;
376 if (*line)
377 *line++ = '\0';
378 unsetenv(name);
379 }
380 }
381 while (*line++)
382 ;
383 }
384 }
385 DEF_WEAK(auth_setenv);
386
387 /*
388 * Clear out any requested environment variables.
389 */
390 void
auth_clrenv(auth_session_t * as)391 auth_clrenv(auth_session_t *as)
392 {
393 char *line;
394
395 for (line = as->spool; line < as->spool + as->index;) {
396 if (!strncasecmp(line, BI_SETENV, sizeof(BI_SETENV)-1)) {
397 if (isblank((unsigned char)line[sizeof(BI_SETENV) - 1])) {
398 line[0] = 'i'; line[1] = 'g'; line[2] = 'n';
399 }
400 } else
401 if (!strncasecmp(line, BI_UNSETENV, sizeof(BI_UNSETENV)-1)) {
402 if (isblank((unsigned char)line[sizeof(BI_UNSETENV) - 1])) {
403 line[2] = 'i'; line[3] = 'g'; line[4] = 'n';
404 }
405 }
406 while (*line++)
407 ;
408 }
409 }
410 DEF_WEAK(auth_clrenv);
411
412 char *
auth_getitem(auth_session_t * as,auth_item_t item)413 auth_getitem(auth_session_t *as, auth_item_t item)
414 {
415 if (as != NULL) {
416 switch (item) {
417 case AUTHV_CHALLENGE:
418 return (as->challenge);
419 case AUTHV_CLASS:
420 return (as->class);
421 case AUTHV_NAME:
422 return (as->name);
423 case AUTHV_SERVICE:
424 return (as->service ? as->service : defservice);
425 case AUTHV_STYLE:
426 return (as->style);
427 case AUTHV_INTERACTIVE:
428 return ((as->flags & AF_INTERACTIVE) ? "True" : NULL);
429 default:
430 break;
431 }
432 }
433 return (NULL);
434 }
435 DEF_WEAK(auth_getitem);
436
437 int
auth_setitem(auth_session_t * as,auth_item_t item,char * value)438 auth_setitem(auth_session_t *as, auth_item_t item, char *value)
439 {
440 if (as == NULL) {
441 errno = EINVAL;
442 return (-1);
443 }
444
445 switch (item) {
446 case AUTHV_ALL:
447 if (value != NULL) {
448 errno = EINVAL;
449 return (-1);
450 }
451 auth_setitem(as, AUTHV_CHALLENGE, NULL);
452 auth_setitem(as, AUTHV_CLASS, NULL);
453 auth_setitem(as, AUTHV_NAME, NULL);
454 auth_setitem(as, AUTHV_SERVICE, NULL);
455 auth_setitem(as, AUTHV_STYLE, NULL);
456 auth_setitem(as, AUTHV_INTERACTIVE, NULL);
457 return (0);
458
459 case AUTHV_CHALLENGE:
460 if (value == as->challenge)
461 return (0);
462 if (value != NULL && (value = strdup(value)) == NULL)
463 return (-1);
464 free(as->challenge);
465 as->challenge = value;
466 return (0);
467
468 case AUTHV_CLASS:
469 if (value == as->class)
470 return (0);
471 if (value != NULL && (value = strdup(value)) == NULL)
472 return (-1);
473 free(as->class);
474 as->class = value;
475 return (0);
476
477 case AUTHV_NAME:
478 if (value == as->name)
479 return (0);
480 if (value != NULL && !_auth_validuser(value)) {
481 errno = EINVAL;
482 return (-1);
483 }
484 if (value != NULL && (value = strdup(value)) == NULL)
485 return (-1);
486 free(as->name);
487 as->name = value;
488 return (0);
489
490 case AUTHV_SERVICE:
491 if (value == as->service)
492 return (0);
493 if (value == NULL || strcmp(value, defservice) == 0)
494 value = defservice;
495 else if ((value = strdup(value)) == NULL)
496 return (-1);
497 if (as->service && as->service != defservice)
498 free(as->service);
499 as->service = value;
500 return (0);
501
502 case AUTHV_STYLE:
503 if (value == as->style)
504 return (0);
505 if (value == NULL || strchr(value, '/') != NULL ||
506 (value = strdup(value)) == NULL)
507 return (-1);
508 free(as->style);
509 as->style = value;
510 return (0);
511
512 case AUTHV_INTERACTIVE:
513 if (value == NULL)
514 as->flags &= ~AF_INTERACTIVE;
515 else
516 as->flags |= ~AF_INTERACTIVE;
517 return (0);
518
519 default:
520 errno = EINVAL;
521 return (-1);
522 }
523 }
524 DEF_WEAK(auth_setitem);
525
526 int
auth_setoption(auth_session_t * as,char * n,char * v)527 auth_setoption(auth_session_t *as, char *n, char *v)
528 {
529 struct authopts *opt;
530 size_t len = strlen(n) + strlen(v) + 2;
531 int ret;
532
533 if ((opt = malloc(sizeof(*opt) + len)) == NULL)
534 return (-1);
535
536 opt->opt = (char *)(opt + 1);
537
538 ret = snprintf(opt->opt, len, "%s=%s", n, v);
539 if (ret < 0 || ret >= len) {
540 free(opt);
541 errno = ENAMETOOLONG;
542 return (-1);
543 }
544 opt->next = as->optlist;
545 as->optlist = opt;
546 return(0);
547 }
548 DEF_WEAK(auth_setoption);
549
550 void
auth_clroptions(auth_session_t * as)551 auth_clroptions(auth_session_t *as)
552 {
553 struct authopts *opt;
554
555 while ((opt = as->optlist) != NULL) {
556 as->optlist = opt->next;
557 free(opt);
558 }
559 }
560 DEF_WEAK(auth_clroptions);
561
562 void
auth_clroption(auth_session_t * as,char * option)563 auth_clroption(auth_session_t *as, char *option)
564 {
565 struct authopts *opt, *oopt;
566 size_t len;
567
568 len = strlen(option);
569
570 if ((opt = as->optlist) == NULL)
571 return;
572
573 if (strncmp(opt->opt, option, len) == 0 &&
574 (opt->opt[len] == '=' || opt->opt[len] == '\0')) {
575 as->optlist = opt->next;
576 free(opt);
577 return;
578 }
579
580 while ((oopt = opt->next) != NULL) {
581 if (strncmp(oopt->opt, option, len) == 0 &&
582 (oopt->opt[len] == '=' || oopt->opt[len] == '\0')) {
583 opt->next = oopt->next;
584 free(oopt);
585 return;
586 }
587 opt = oopt;
588 }
589 }
590 DEF_WEAK(auth_clroption);
591
592 int
auth_setdata(auth_session_t * as,void * ptr,size_t len)593 auth_setdata(auth_session_t *as, void *ptr, size_t len)
594 {
595 struct authdata *data, *dp;
596
597 if (len <= 0)
598 return (0);
599
600 if ((data = malloc(sizeof(*data) + len)) == NULL)
601 return (-1);
602
603 data->next = NULL;
604 data->len = len;
605 data->ptr = data + 1;
606 memcpy(data->ptr, ptr, len);
607
608 if (as->data == NULL)
609 as->data = data;
610 else {
611 for (dp = as->data; dp->next != NULL; dp = dp->next)
612 ;
613 dp->next = data;
614 }
615 return (0);
616 }
617 DEF_WEAK(auth_setdata);
618
619 int
auth_setpwd(auth_session_t * as,struct passwd * pwd)620 auth_setpwd(auth_session_t *as, struct passwd *pwd)
621 {
622 struct passwd pwstore;
623 char *instance, pwbuf[_PW_BUF_LEN];
624
625 if (pwd == NULL && as->pwd == NULL && as->name == NULL)
626 return (-1); /* true failure */
627
628 if (pwd == NULL) {
629 /*
630 * If we were not passed in a pwd structure we need to
631 * go find one for ourself. Always look up the username
632 * (if it is defined) in the passwd database to see if there
633 * is an entry for the user. If not, either use the current
634 * entry or simply return a 1 which implies there is
635 * no user by that name here. This is not a failure, just
636 * a point of information.
637 */
638 if (as->name == NULL)
639 return (0);
640 getpwnam_r(as->name, &pwstore, pwbuf, sizeof(pwbuf), &pwd);
641 if (pwd == NULL) {
642 instance = strchr(as->name, '/');
643 if (instance == NULL)
644 return (as->pwd ? 0 : 1);
645 if (strcmp(instance, "/root") == 0) {
646 getpwnam_r(instance + 1, &pwstore, pwbuf,
647 sizeof(pwbuf), &pwd);
648 }
649 if (pwd == NULL)
650 return (as->pwd ? 0 : 1);
651 }
652 }
653 if ((pwd = pw_dup(pwd)) == NULL)
654 return (-1); /* true failure */
655 if (as->pwd) {
656 explicit_bzero(as->pwd->pw_passwd, strlen(as->pwd->pw_passwd));
657 free(as->pwd);
658 }
659 as->pwd = pwd;
660 return (0);
661 }
662 DEF_WEAK(auth_setpwd);
663
664 char *
auth_getvalue(auth_session_t * as,char * what)665 auth_getvalue(auth_session_t *as, char *what)
666 {
667 char *line, *v, *value;
668 int n, len;
669
670 len = strlen(what);
671
672 for (line = as->spool; line < as->spool + as->index;) {
673 if (strncasecmp(line, BI_VALUE, sizeof(BI_VALUE)-1) != 0)
674 goto next;
675 line += sizeof(BI_VALUE) - 1;
676
677 if (!isblank((unsigned char)*line))
678 goto next;
679
680 while (isblank((unsigned char)*++line))
681 ;
682
683 if (strncmp(line, what, len) != 0 ||
684 !isblank((unsigned char)line[len]))
685 goto next;
686 line += len;
687 while (isblank((unsigned char)*++line))
688 ;
689 value = strdup(line);
690 if (value == NULL)
691 return (NULL);
692
693 /*
694 * XXX - There should be a more standardized
695 * routine for doing this sort of thing.
696 */
697 for (line = v = value; *line; ++line) {
698 if (*line == '\\') {
699 switch (*++line) {
700 case 'r':
701 *v++ = '\r';
702 break;
703 case 'n':
704 *v++ = '\n';
705 break;
706 case 't':
707 *v++ = '\t';
708 break;
709 case '0': case '1': case '2':
710 case '3': case '4': case '5':
711 case '6': case '7':
712 n = *line - '0';
713 if (isdigit((unsigned char)line[1])) {
714 ++line;
715 n <<= 3;
716 n |= *line-'0';
717 }
718 if (isdigit((unsigned char)line[1])) {
719 ++line;
720 n <<= 3;
721 n |= *line-'0';
722 }
723 break;
724 default:
725 *v++ = *line;
726 break;
727 }
728 } else
729 *v++ = *line;
730 }
731 *v = '\0';
732 return (value);
733 next:
734 while (*line++)
735 ;
736 }
737 return (NULL);
738 }
739 DEF_WEAK(auth_getvalue);
740
741 quad_t
auth_check_expire(auth_session_t * as)742 auth_check_expire(auth_session_t *as)
743 {
744 if (as->pwd == NULL && auth_setpwd(as, NULL) < 0) {
745 as->state &= ~AUTH_ALLOW;
746 as->state |= AUTH_EXPIRED; /* XXX */
747 return (-1);
748 }
749
750 if (as->pwd == NULL)
751 return (0);
752
753 if (as->pwd && (quad_t)as->pwd->pw_expire != 0) {
754 if (as->now.tv_sec == 0)
755 WRAP(gettimeofday)(&as->now, NULL);
756 if ((quad_t)as->now.tv_sec >= (quad_t)as->pwd->pw_expire) {
757 as->state &= ~AUTH_ALLOW;
758 as->state |= AUTH_EXPIRED;
759 }
760 if ((quad_t)as->now.tv_sec == (quad_t)as->pwd->pw_expire)
761 return (-1);
762 return ((quad_t)as->pwd->pw_expire - (quad_t)as->now.tv_sec);
763 }
764 return (0);
765 }
766 DEF_WEAK(auth_check_expire);
767
768 quad_t
auth_check_change(auth_session_t * as)769 auth_check_change(auth_session_t *as)
770 {
771 if (as->pwd == NULL && auth_setpwd(as, NULL) < 0) {
772 as->state &= ~AUTH_ALLOW;
773 as->state |= AUTH_PWEXPIRED; /* XXX */
774 return (-1);
775 }
776
777 if (as->pwd == NULL)
778 return (0);
779
780 if (as->pwd && (quad_t)as->pwd->pw_change) {
781 if (as->now.tv_sec == 0)
782 WRAP(gettimeofday)(&as->now, NULL);
783 if (as->now.tv_sec >= (quad_t)as->pwd->pw_change) {
784 as->state &= ~AUTH_ALLOW;
785 as->state |= AUTH_PWEXPIRED;
786 }
787 if ((quad_t)as->now.tv_sec == (quad_t)as->pwd->pw_change)
788 return (-1);
789 return ((quad_t)as->pwd->pw_change - (quad_t)as->now.tv_sec);
790 }
791 return (0);
792 }
793 DEF_WEAK(auth_check_change);
794
795 /*
796 * The down and dirty call to the login script
797 * okay contains the default return value, typically 0 but
798 * is AUTH_OKAY for approval like scripts.
799 *
800 * Internally additional trailing arguments can be read from as->ap
801 * Options will be placed just after the first argument (not including path).
802 *
803 * Any data will be sent to (and freed by) the script
804 */
805 int
auth_call(auth_session_t * as,char * path,...)806 auth_call(auth_session_t *as, char *path, ...)
807 {
808 char *line;
809 struct authdata *data;
810 struct authopts *opt;
811 pid_t pid;
812 int status;
813 int okay;
814 int pfd[2];
815 int argc;
816 char *argv[64]; /* 64 args should be more than enough */
817 #define Nargc (sizeof(argv)/sizeof(argv[0]))
818
819 va_start(as->ap0, path);
820
821 argc = 0;
822 if ((argv[argc] = _auth_next_arg(as)) != NULL)
823 ++argc;
824
825 if (as->fd != -1) {
826 argv[argc++] = "-v";
827 argv[argc++] = "fd=4"; /* AUTH_FD, see below */
828 }
829 /* XXX - fail if out of space in argv */
830 for (opt = as->optlist; opt != NULL; opt = opt->next) {
831 if (argc < Nargc - 2) {
832 argv[argc++] = "-v";
833 argv[argc++] = opt->opt;
834 } else {
835 syslog(LOG_ERR, "too many authentication options");
836 goto fail;
837 }
838 }
839 while (argc < Nargc - 1 && (argv[argc] = _auth_next_arg(as)))
840 ++argc;
841
842 if (argc >= Nargc - 1 && _auth_next_arg(as)) {
843 if (memcmp(&nilap, &(as->ap0), sizeof(nilap)) != 0) {
844 va_end(as->ap0);
845 explicit_bzero(&(as->ap0), sizeof(as->ap0));
846 }
847 if (memcmp(&nilap, &(as->ap), sizeof(nilap)) != 0) {
848 va_end(as->ap);
849 explicit_bzero(&(as->ap), sizeof(as->ap));
850 }
851 syslog(LOG_ERR, "too many arguments");
852 goto fail;
853 }
854
855 argv[argc] = NULL;
856
857 if (socketpair(PF_LOCAL, SOCK_STREAM, 0, pfd) == -1) {
858 syslog(LOG_ERR, "unable to create backchannel %m");
859 warnx("internal resource failure");
860 goto fail;
861 }
862
863 switch (pid = fork()) {
864 case -1:
865 syslog(LOG_ERR, "%s: %m", path);
866 warnx("internal resource failure");
867 close(pfd[0]);
868 close(pfd[1]);
869 goto fail;
870 case 0:
871 #define COMM_FD 3
872 #define AUTH_FD 4
873 if (dup2(pfd[1], COMM_FD) == -1)
874 err(1, "dup of backchannel");
875 if (as->fd != -1) {
876 if (dup2(as->fd, AUTH_FD) == -1)
877 err(1, "dup of auth fd");
878 closefrom(AUTH_FD + 1);
879 } else
880 closefrom(COMM_FD + 1);
881 execve(path, argv, auth_environ);
882 syslog(LOG_ERR, "%s: %m", path);
883 err(1, "%s", path);
884 default:
885 close(pfd[1]);
886 if (as->fd != -1) {
887 close(as->fd); /* so child has only ref */
888 as->fd = -1;
889 }
890 while ((data = as->data) != NULL) {
891 as->data = data->next;
892 if (data->len > 0) {
893 write(pfd[0], data->ptr, data->len);
894 explicit_bzero(data->ptr, data->len);
895 }
896 free(data);
897 }
898 as->index = 0;
899 _auth_spool(as, pfd[0]);
900 close(pfd[0]);
901 do {
902 if (waitpid(pid, &status, 0) != -1) {
903 if (!WIFEXITED(status))
904 goto fail;
905 break;
906 }
907 /*
908 * could get ECHILD if it was waited for by
909 * another thread or from a signal handler
910 */
911 } while (errno == EINTR);
912 }
913
914 /*
915 * Now scan the spooled data
916 * It is easier to wait for all the data before starting
917 * to scan it.
918 */
919 for (line = as->spool; line < as->spool + as->index;) {
920 if (!strncasecmp(line, BI_REJECT, sizeof(BI_REJECT)-1)) {
921 line += sizeof(BI_REJECT) - 1;
922 if (!*line || *line == ' ' || *line == '\t') {
923 while (*line == ' ' || *line == '\t')
924 ++line;
925 if (!strcasecmp(line, "silent")) {
926 as->state = AUTH_SILENT;
927 break;
928 }
929 if (!strcasecmp(line, "challenge")) {
930 as->state = AUTH_CHALLENGE;
931 break;
932 }
933 if (!strcasecmp(line, "expired")) {
934 as->state = AUTH_EXPIRED;
935 break;
936 }
937 if (!strcasecmp(line, "pwexpired")) {
938 as->state = AUTH_PWEXPIRED;
939 break;
940 }
941 }
942 break;
943 } else if (!strncasecmp(line, BI_AUTH, sizeof(BI_AUTH)-1)) {
944 line += sizeof(BI_AUTH) - 1;
945 if (!*line || *line == ' ' || *line == '\t') {
946 while (*line == ' ' || *line == '\t')
947 ++line;
948 if (*line == '\0')
949 as->state |= AUTH_OKAY;
950 else if (!strcasecmp(line, "root"))
951 as->state |= AUTH_ROOTOKAY;
952 else if (!strcasecmp(line, "secure"))
953 as->state |= AUTH_SECURE;
954 }
955 } else if (!strncasecmp(line, BI_REMOVE, sizeof(BI_REMOVE)-1)) {
956 line += sizeof(BI_REMOVE) - 1;
957 while (*line == ' ' || *line == '\t')
958 ++line;
959 if (*line)
960 _add_rmlist(as, line);
961 }
962 while (*line++)
963 ;
964 }
965
966 if (WEXITSTATUS(status))
967 as->state &= ~AUTH_ALLOW;
968
969 okay = as->state & AUTH_ALLOW;
970
971 if (!okay)
972 auth_clrenv(as);
973
974 if (0) {
975 fail:
976 auth_clrenv(as);
977 as->state = 0;
978 okay = -1;
979 }
980
981 while ((data = as->data) != NULL) {
982 as->data = data->next;
983 free(data);
984 }
985
986 if (memcmp(&nilap, &(as->ap0), sizeof(nilap)) != 0) {
987 va_end(as->ap0);
988 explicit_bzero(&(as->ap0), sizeof(as->ap0));
989 }
990
991 if (memcmp(&nilap, &(as->ap), sizeof(nilap)) != 0) {
992 va_end(as->ap);
993 explicit_bzero(&(as->ap), sizeof(as->ap));
994 }
995 return (okay);
996 }
997 DEF_WEAK(auth_call);
998
999 static void
_recv_fd(auth_session_t * as,int fd)1000 _recv_fd(auth_session_t *as, int fd)
1001 {
1002 struct msghdr msg;
1003 struct cmsghdr *cmp;
1004 union {
1005 struct cmsghdr hdr;
1006 char buf[CMSG_SPACE(sizeof(int))];
1007 } cmsgbuf;
1008
1009 memset(&msg, 0, sizeof(msg));
1010 msg.msg_control = &cmsgbuf.buf;
1011 msg.msg_controllen = sizeof(cmsgbuf.buf);
1012 if (recvmsg(fd, &msg, 0) == -1)
1013 syslog(LOG_ERR, "recvmsg: %m");
1014 else if (msg.msg_flags & MSG_TRUNC)
1015 syslog(LOG_ERR, "message truncated");
1016 else if (msg.msg_flags & MSG_CTRUNC)
1017 syslog(LOG_ERR, "control message truncated");
1018 else if ((cmp = CMSG_FIRSTHDR(&msg)) == NULL)
1019 syslog(LOG_ERR, "missing control message");
1020 else {
1021 if (cmp->cmsg_level != SOL_SOCKET)
1022 syslog(LOG_ERR, "unexpected cmsg_level %d",
1023 cmp->cmsg_level);
1024 else if (cmp->cmsg_type != SCM_RIGHTS)
1025 syslog(LOG_ERR, "unexpected cmsg_type %d",
1026 cmp->cmsg_type);
1027 else if (cmp->cmsg_len != CMSG_LEN(sizeof(int)))
1028 syslog(LOG_ERR, "bad cmsg_len %d",
1029 cmp->cmsg_len);
1030 else {
1031 if (as->fd != -1)
1032 close(as->fd);
1033 as->fd = *(int *)CMSG_DATA(cmp);
1034 }
1035 }
1036 }
1037
1038 static void
_auth_spool(auth_session_t * as,int fd)1039 _auth_spool(auth_session_t *as, int fd)
1040 {
1041 ssize_t r;
1042 char *b, *s;
1043
1044 for (s = as->spool + as->index; as->index < sizeof(as->spool) - 1; ) {
1045 r = read(fd, as->spool + as->index,
1046 sizeof(as->spool) - as->index);
1047 if (r <= 0) {
1048 as->spool[as->index] = '\0';
1049 return;
1050 }
1051 b = as->spool + as->index;
1052 as->index += r;
1053 /*
1054 * Convert newlines into NULs to allow easy scanning of the
1055 * file and receive an fd if there is a BI_FDPASS message.
1056 * XXX - checking for BI_FDPASS here is annoying but
1057 * we need to avoid the read() slurping in control data.
1058 */
1059 while (r-- > 0) {
1060 if (*b++ == '\n') {
1061 b[-1] = '\0';
1062 if (strcasecmp(s, BI_FDPASS) == 0)
1063 _recv_fd(as, fd);
1064 s = b;
1065 }
1066 }
1067 }
1068
1069 syslog(LOG_ERR, "Overflowed backchannel spool buffer");
1070 errx(1, "System error in authentication program");
1071 }
1072
1073 static void
_add_rmlist(auth_session_t * as,char * file)1074 _add_rmlist(auth_session_t *as, char *file)
1075 {
1076 struct rmfiles *rm;
1077 size_t i = strlen(file) + 1;
1078
1079 // XXX should rangecheck i since we are about to add?
1080
1081 if ((rm = malloc(sizeof(struct rmfiles) + i)) == NULL) {
1082 syslog(LOG_ERR, "Failed to allocate rmfiles: %m");
1083 return;
1084 }
1085 rm->file = (char *)(rm + 1);
1086 rm->next = as->rmlist;
1087 strlcpy(rm->file, file, i);
1088 as->rmlist = rm;
1089 }
1090
1091 static char *
_auth_next_arg(auth_session_t * as)1092 _auth_next_arg(auth_session_t *as)
1093 {
1094 char *arg;
1095
1096 if (memcmp(&nilap, &(as->ap0), sizeof(nilap)) != 0) {
1097 if ((arg = va_arg(as->ap0, char *)) != NULL)
1098 return (arg);
1099 va_end(as->ap0);
1100 explicit_bzero(&(as->ap0), sizeof(as->ap0));
1101 }
1102 if (memcmp(&nilap, &(as->ap), sizeof(nilap)) != 0) {
1103 if ((arg = va_arg(as->ap, char *)) != NULL)
1104 return (arg);
1105 va_end(as->ap);
1106 explicit_bzero(&(as->ap), sizeof(as->ap));
1107 }
1108 return (NULL);
1109 }
1110