1 /* $OpenBSD: auth_subr.c,v 1.30 2004/12/02 20:38:36 millert Exp $ */
2
3 /*
4 * Copyright (c) 2000-2002,2004 Todd C. Miller <Todd.Miller@courtesan.com>
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 #include <sys/param.h>
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 <paths.h>
63 #include <pwd.h>
64 #include <stdarg.h>
65 #include <stdio.h>
66 #include <stdlib.h>
67 #include <string.h>
68 #include <syslog.h>
69 #include <unistd.h>
70
71 #include <login_cap.h>
72
73 #define MAXSPOOLSIZE (8*1024) /* Spool up to 8K of back info */
74
75 struct rmfiles {
76 struct rmfiles *next;
77 char *file;
78 };
79
80 struct authopts {
81 struct authopts *next;
82 char *opt;
83 };
84
85 struct authdata {
86 struct authdata *next;
87 void *ptr;
88 size_t len;
89 };
90
91 struct auth_session_t {
92 char *name; /* name of use being authenticated */
93 char *style; /* style of authentication used */
94 char *class; /* class of user */
95 char *service; /* type of service being performed */
96 char *challenge; /* last challenge issued */
97 int flags; /* see below */
98 struct passwd *pwd; /* password entry for user */
99 struct timeval now; /* time of authentication */
100
101 int state; /* authenticated state */
102
103 struct rmfiles *rmlist; /* list of files to remove on failure */
104 struct authopts *optlist; /* list of options to scripts */
105 struct authdata *data; /* additional data to send to scripts */
106
107 char spool[MAXSPOOLSIZE]; /* data returned from login script */
108 int index; /* how much returned thus far */
109
110 int fd; /* connection to authenticator */
111
112 va_list ap0; /* argument list to auth_call */
113 va_list ap; /* additional arguments to auth_call */
114 };
115
116 /*
117 * Internal flags
118 */
119 #define AF_INTERACTIVE 0x0001 /* This is an interactive session */
120
121 /*
122 * We cannot include bsd_auth.h until we define the above structures
123 */
124 #include <bsd_auth.h>
125
126 /*
127 * Internally used functions
128 */
129 static void _add_rmlist(auth_session_t *, char *);
130 static void _auth_spool(auth_session_t *, int);
131 static void _recv_fd(auth_session_t *, int);
132 static char *_auth_next_arg(auth_session_t *);
133 /*
134 * Set up a known environment for all authentication scripts.
135 */
136 static char *auth_environ[] = {
137 "PATH=" _PATH_DEFPATH,
138 "SHELL=" _PATH_BSHELL,
139 NULL,
140 };
141
142 static char defservice[] = LOGIN_DEFSERVICE;
143
144 static va_list nilap;
145
146 /*
147 * Quick one liners that only exist to keep auth_session_t opaque
148 */
auth_setstate(auth_session_t * as,int s)149 void auth_setstate(auth_session_t *as, int s){ as->state = s; }
auth_set_va_list(auth_session_t * as,va_list ap)150 void auth_set_va_list(auth_session_t *as, va_list ap) {
151 #if defined(__GNUC__) && __GNUC__ >= 3
152 va_copy(as->ap, ap);
153 #else
154 as->ap = ap;
155 #endif
156 }
auth_getstate(auth_session_t * as)157 int auth_getstate(auth_session_t *as) { return (as->state); }
auth_getpwd(auth_session_t * as)158 struct passwd *auth_getpwd(auth_session_t *as) { return (as->pwd); }
159
160 /*
161 * Open a new BSD Authentication session with the default service
162 * (which can be changed later).
163 */
164 auth_session_t *
auth_open(void)165 auth_open(void)
166 {
167 auth_session_t *as;
168
169 if ((as = malloc(sizeof(auth_session_t))) != NULL) {
170 memset(as, 0, sizeof(*as));
171 as->service = defservice;
172 as->fd = -1;
173 }
174
175 return (as);
176 }
177
178 /*
179 * Clean the specified BSD Authentication session.
180 */
181 void
auth_clean(auth_session_t * as)182 auth_clean(auth_session_t *as)
183 {
184 struct rmfiles *rm;
185 struct authdata *data;
186
187 as->state = 0;
188
189 auth_clrenv(as);
190
191 /*
192 * Clean out the rmlist and remove specified files
193 */
194 while ((rm = as->rmlist) != NULL) {
195 as->rmlist = rm->next;
196 unlink(rm->file);
197 free(rm);
198 }
199
200 /*
201 * Clean out data
202 */
203 while ((data = as->data) != NULL) {
204 if (as->data->len)
205 memset(as->data->ptr, 0, as->data->len);
206 as->data = data->next;
207 free(data);
208 }
209
210 auth_setitem(as, AUTHV_ALL, NULL);
211
212 if (as->pwd != NULL) {
213 memset(as->pwd->pw_passwd, 0, strlen(as->pwd->pw_passwd));
214 free(as->pwd);
215 as->pwd = NULL;
216 }
217
218 if (as->fd != -1) {
219 close(as->fd);
220 as->fd = -1;
221 }
222 }
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 memset(as->data->ptr, 0, as->data->len);
272 as->data = data->next;
273 free(data);
274 }
275
276 if (as->pwd != NULL) {
277 memset(as->pwd->pw_passwd, 0, 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 if (as->challenge)
288 free(as->challenge);
289 if (as->class)
290 free(as->class);
291 if (as->style)
292 free(as->style);
293 if (as->name)
294 free(as->name);
295
296 free(as);
297 return (s);
298 }
299
300 /*
301 * Request a challenge for the session.
302 * The name and style must have already been specified
303 */
304 char *
auth_challenge(auth_session_t * as)305 auth_challenge(auth_session_t *as)
306 {
307 char path[MAXPATHLEN];
308
309 if (as == NULL || as->style == NULL || as->name == NULL)
310 return (NULL);
311
312 as->state = 0;
313
314 if (as->challenge) {
315 free(as->challenge);
316 as->challenge = NULL;
317 }
318
319 snprintf(path, sizeof(path), _PATH_AUTHPROG "%s", as->style);
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
329 /*
330 * Set/unset the requested environment variables.
331 * Mark the variables as set so they will not be set a second time.
332 * XXX - should provide a way to detect setenv() failure.
333 */
334 void
auth_setenv(auth_session_t * as)335 auth_setenv(auth_session_t *as)
336 {
337 char *line, *name;
338
339 /*
340 * Set any environment variables we were asked for
341 */
342 for (line = as->spool; line < as->spool + as->index;) {
343 if (!strncasecmp(line, BI_SETENV, sizeof(BI_SETENV)-1)) {
344 if (isblank(line[sizeof(BI_SETENV) - 1])) {
345 /* only do it once! */
346 line[0] = 'd'; line[1] = 'i'; line[2] = 'd';
347 line += sizeof(BI_SETENV) - 1;
348 for (name = line; isblank(*name); ++name)
349 ;
350 for (line = name; *line && !isblank(*line);
351 ++line)
352 ;
353 if (*line)
354 *line++ = '\0';
355 for (; isblank(*line); ++line)
356 ;
357 if (*line != '\0' && setenv(name, line, 1))
358 _warn("setenv(%s, %s)", name, line);
359 }
360 } else
361 if (!strncasecmp(line, BI_UNSETENV, sizeof(BI_UNSETENV)-1)) {
362 if (isblank(line[sizeof(BI_UNSETENV) - 1])) {
363 /* only do it once! */
364 line[2] = 'd'; line[3] = 'i'; line[4] = 'd';
365 line += sizeof(BI_UNSETENV) - 1;
366 for (name = line; isblank(*name); ++name)
367 ;
368 for (line = name; *line && !isblank(*line);
369 ++line)
370 ;
371 if (*line)
372 *line++ = '\0';
373 unsetenv(name);
374 }
375 }
376 while (*line++)
377 ;
378 }
379 }
380
381 /*
382 * Clear out any requested environment variables.
383 */
384 void
auth_clrenv(auth_session_t * as)385 auth_clrenv(auth_session_t *as)
386 {
387 char *line;
388
389 for (line = as->spool; line < as->spool + as->index;) {
390 if (!strncasecmp(line, BI_SETENV, sizeof(BI_SETENV)-1)) {
391 if (isblank(line[sizeof(BI_SETENV) - 1])) {
392 line[0] = 'i'; line[1] = 'g'; line[2] = 'n';
393 }
394 } else
395 if (!strncasecmp(line, BI_UNSETENV, sizeof(BI_UNSETENV)-1)) {
396 if (isblank(line[sizeof(BI_UNSETENV) - 1])) {
397 line[2] = 'i'; line[3] = 'g'; line[4] = 'n';
398 }
399 }
400 while (*line++)
401 ;
402 }
403 }
404
405 char *
auth_getitem(auth_session_t * as,auth_item_t item)406 auth_getitem(auth_session_t *as, auth_item_t item)
407 {
408 if (as != NULL) {
409 switch (item) {
410 case AUTHV_CHALLENGE:
411 return (as->challenge);
412 case AUTHV_CLASS:
413 return (as->class);
414 case AUTHV_NAME:
415 return (as->name);
416 case AUTHV_SERVICE:
417 return (as->service ? as->service : defservice);
418 case AUTHV_STYLE:
419 return (as->style);
420 case AUTHV_INTERACTIVE:
421 return ((as->flags & AF_INTERACTIVE) ? "True" : NULL);
422 default:
423 break;
424 }
425 }
426 return (NULL);
427 }
428
429 int
auth_setitem(auth_session_t * as,auth_item_t item,char * value)430 auth_setitem(auth_session_t *as, auth_item_t item, char *value)
431 {
432 if (as == NULL) {
433 errno = EINVAL;
434 return (-1);
435 }
436
437 switch (item) {
438 case AUTHV_ALL:
439 if (value != NULL) {
440 errno = EINVAL;
441 return (-1);
442 }
443 auth_setitem(as, AUTHV_CHALLENGE, NULL);
444 auth_setitem(as, AUTHV_CLASS, NULL);
445 auth_setitem(as, AUTHV_NAME, NULL);
446 auth_setitem(as, AUTHV_SERVICE, NULL);
447 auth_setitem(as, AUTHV_STYLE, NULL);
448 auth_setitem(as, AUTHV_INTERACTIVE, NULL);
449 return (0);
450
451 case AUTHV_CHALLENGE:
452 if (value == as->challenge)
453 return (0);
454 if (value != NULL && (value = strdup(value)) == NULL)
455 return (-1);
456 if (as->challenge)
457 free(as->challenge);
458 as->challenge = value;
459 return (0);
460
461 case AUTHV_CLASS:
462 if (value == as->class)
463 return (0);
464 if (value != NULL && (value = strdup(value)) == NULL)
465 return (-1);
466 if (as->class)
467 free(as->class);
468 as->class = value;
469 return (0);
470
471 case AUTHV_NAME:
472 if (value == as->name)
473 return (0);
474 if (value != NULL && (value = strdup(value)) == NULL)
475 return (-1);
476 if (as->name)
477 free(as->name);
478 as->name = value;
479 return (0);
480
481 case AUTHV_SERVICE:
482 if (value == as->service)
483 return (0);
484 if (value == NULL || strcmp(value, defservice) == 0)
485 value = defservice;
486 else if ((value = strdup(value)) == NULL)
487 return (-1);
488 if (as->service && as->service != defservice)
489 free(as->service);
490 as->service = value;
491 return (0);
492
493 case AUTHV_STYLE:
494 if (value == as->style)
495 return (0);
496 if (value == NULL || strchr(value, '/') != NULL ||
497 (value = strdup(value)) == NULL)
498 return (-1);
499 if (as->style)
500 free(as->style);
501 as->style = value;
502 return (0);
503
504 case AUTHV_INTERACTIVE:
505 if (value == NULL)
506 as->flags &= ~AF_INTERACTIVE;
507 else
508 as->flags |= ~AF_INTERACTIVE;
509 return (0);
510
511 default:
512 errno = EINVAL;
513 return (-1);
514 }
515 }
516
517 int
auth_setoption(auth_session_t * as,char * n,char * v)518 auth_setoption(auth_session_t *as, char *n, char *v)
519 {
520 struct authopts *opt;
521 int i = strlen(n) + strlen(v) + 2;
522
523 if ((opt = malloc(sizeof(*opt) + i)) == NULL)
524 return (-1);
525
526 opt->opt = (char *)(opt + 1);
527
528 snprintf(opt->opt, i, "%s=%s", n, v);
529 opt->next = as->optlist;
530 as->optlist = opt;
531 return(0);
532 }
533
534 void
auth_clroptions(auth_session_t * as)535 auth_clroptions(auth_session_t *as)
536 {
537 struct authopts *opt;
538
539 while ((opt = as->optlist) != NULL) {
540 as->optlist = opt->next;
541 free(opt);
542 }
543 }
544
545 void
auth_clroption(auth_session_t * as,char * option)546 auth_clroption(auth_session_t *as, char *option)
547 {
548 struct authopts *opt, *oopt;
549 int len;
550
551 len = strlen(option);
552
553 if ((opt = as->optlist) == NULL)
554 return;
555
556 if (strncmp(opt->opt, option, len) == 0 &&
557 (opt->opt[len] == '=' || opt->opt[len] == '\0')) {
558 as->optlist = opt->next;
559 free(opt);
560 return;
561 }
562
563 while ((oopt = opt->next) != NULL) {
564 if (strncmp(oopt->opt, option, len) == 0 &&
565 (oopt->opt[len] == '=' || oopt->opt[len] == '\0')) {
566 opt->next = oopt->next;
567 free(oopt);
568 return;
569 }
570 opt = oopt;
571 }
572 }
573
574 int
auth_setdata(auth_session_t * as,void * ptr,size_t len)575 auth_setdata(auth_session_t *as, void *ptr, size_t len)
576 {
577 struct authdata *data, *dp;
578
579 if (len <= 0)
580 return (0);
581
582 if ((data = malloc(sizeof(*data) + len)) == NULL)
583 return (-1);
584
585 data->next = NULL;
586 data->len = len;
587 data->ptr = data + 1;
588 memcpy(data->ptr, ptr, len);
589
590 if (as->data == NULL)
591 as->data = data;
592 else {
593 for (dp = as->data; dp->next != NULL; dp = dp->next)
594 ;
595 dp->next = data;
596 }
597 return (0);
598 }
599
600 int
auth_setpwd(auth_session_t * as,struct passwd * pwd)601 auth_setpwd(auth_session_t *as, struct passwd *pwd)
602 {
603 char *instance;
604
605 if (pwd == NULL && as->pwd == NULL && as->name == NULL)
606 return (-1); /* true failure */
607
608 if (pwd == NULL) {
609 /*
610 * If we were not passed in a pwd structure we need to
611 * go find one for ourself. Always look up the username
612 * (if it is defined) in the passwd database to see if there
613 * is an entry for the user. If not, either use the current
614 * entry or simply return a 1 which implies there is
615 * no user by that name here. This is not a failure, just
616 * a point of information.
617 */
618 if (as->name == NULL)
619 return (0);
620 if ((pwd = getpwnam(as->name)) == NULL) {
621 instance = strpbrk(as->name, "./");
622 if (instance++ == NULL)
623 return (as->pwd ? 0 : 1);
624 if (strcmp(instance, "root") == 0)
625 pwd = getpwnam(instance);
626 if (pwd == NULL)
627 return (as->pwd ? 0 : 1);
628 }
629 }
630 if ((pwd = pw_dup(pwd)) == NULL)
631 return (-1); /* true failure */
632 if (as->pwd) {
633 memset(as->pwd->pw_passwd, 0, strlen(as->pwd->pw_passwd));
634 free(as->pwd);
635 }
636 as->pwd = pwd;
637 return (0);
638 }
639
640 char *
auth_getvalue(auth_session_t * as,char * what)641 auth_getvalue(auth_session_t *as, char *what)
642 {
643 char *line, *v, *value;
644 int n, len;
645
646 len = strlen(what);
647
648 for (line = as->spool; line < as->spool + as->index;) {
649 if (strncasecmp(line, BI_VALUE, sizeof(BI_VALUE)-1) != 0)
650 goto next;
651 line += sizeof(BI_VALUE) - 1;
652
653 if (!isblank(*line))
654 goto next;
655
656 while (isblank(*++line))
657 ;
658
659 if (strncmp(line, what, len) != 0 ||
660 !isblank(line[len]))
661 goto next;
662 line += len;
663 while (isblank(*++line))
664 ;
665 value = strdup(line);
666 if (value == NULL)
667 return (NULL);
668
669 /*
670 * XXX - There should be a more standardized
671 * routine for doing this sort of thing.
672 */
673 for (line = v = value; *line; ++line) {
674 if (*line == '\\') {
675 switch (*++line) {
676 case 'r':
677 *v++ = '\r';
678 break;
679 case 'n':
680 *v++ = '\n';
681 break;
682 case 't':
683 *v++ = '\t';
684 break;
685 case '0': case '1': case '2':
686 case '3': case '4': case '5':
687 case '6': case '7':
688 n = *line - '0';
689 if (isdigit(line[1])) {
690 ++line;
691 n <<= 3;
692 n |= *line-'0';
693 }
694 if (isdigit(line[1])) {
695 ++line;
696 n <<= 3;
697 n |= *line-'0';
698 }
699 break;
700 default:
701 *v++ = *line;
702 break;
703 }
704 } else
705 *v++ = *line;
706 }
707 *v = '\0';
708 return (value);
709 next:
710 while (*line++)
711 ;
712 }
713 return (NULL);
714 }
715
716 quad_t
auth_check_expire(auth_session_t * as)717 auth_check_expire(auth_session_t *as)
718 {
719 if (as->pwd == NULL && auth_setpwd(as, NULL) < 0) {
720 as->state &= ~AUTH_ALLOW;
721 as->state |= AUTH_EXPIRED; /* XXX */
722 return (-1);
723 }
724
725 if (as->pwd == NULL)
726 return (0);
727
728 if (as->pwd && (quad_t)as->pwd->pw_expire != 0) {
729 if (as->now.tv_sec == 0)
730 gettimeofday(&as->now, (struct timezone *)NULL);
731 if ((quad_t)as->now.tv_sec >= (quad_t)as->pwd->pw_expire) {
732 as->state &= ~AUTH_ALLOW;
733 as->state |= AUTH_EXPIRED;
734 }
735 if ((quad_t)as->now.tv_sec == (quad_t)as->pwd->pw_expire)
736 return (-1);
737 return ((quad_t)as->pwd->pw_expire - (quad_t)as->now.tv_sec);
738 }
739 return (0);
740 }
741
742 quad_t
auth_check_change(auth_session_t * as)743 auth_check_change(auth_session_t *as)
744 {
745 if (as->pwd == NULL && auth_setpwd(as, NULL) < 0) {
746 as->state &= ~AUTH_ALLOW;
747 as->state |= AUTH_PWEXPIRED; /* XXX */
748 return (-1);
749 }
750
751 if (as->pwd == NULL)
752 return (0);
753
754 if (as->pwd && (quad_t)as->pwd->pw_change) {
755 if (as->now.tv_sec == 0)
756 gettimeofday(&as->now, (struct timezone *)NULL);
757 if (as->now.tv_sec >= (quad_t)as->pwd->pw_change) {
758 as->state &= ~AUTH_ALLOW;
759 as->state |= AUTH_PWEXPIRED;
760 }
761 if ((quad_t)as->now.tv_sec == (quad_t)as->pwd->pw_change)
762 return (-1);
763 return ((quad_t)as->pwd->pw_change - (quad_t)as->now.tv_sec);
764 }
765 return (0);
766 }
767
768 /*
769 * The down and dirty call to the login script
770 * okay contains the default return value, typically 0 but
771 * is AUTH_OKAY for approval like scripts.
772 *
773 * Internally additional trailing arguments can be read from as->ap
774 * Options will be placed just after the first argument (not including path).
775 *
776 * Any data will be sent to (and freed by) the script
777 */
778 int
auth_call(auth_session_t * as,char * path,...)779 auth_call(auth_session_t *as, char *path, ...)
780 {
781 char *line;
782 struct authdata *data;
783 struct authopts *opt;
784 pid_t pid;
785 int status;
786 int okay;
787 int pfd[2];
788 int argc;
789 char *argv[64]; /* 64 args should be more than enough */
790 #define Nargc (sizeof(argv)/sizeof(argv[0]))
791
792 va_start(as->ap0, path);
793
794 argc = 0;
795 if ((argv[argc] = _auth_next_arg(as)) != NULL)
796 ++argc;
797
798 if (as->fd != -1) {
799 argv[argc++] = "-v";
800 argv[argc++] = "fd=4"; /* AUTH_FD, see below */
801 }
802 for (opt = as->optlist; opt != NULL; opt = opt->next) {
803 if (argc < Nargc - 2) {
804 argv[argc++] = "-v";
805 argv[argc++] = opt->opt;
806 } else {
807 syslog(LOG_ERR, "too many authentication options");
808 goto fail;
809 }
810 }
811 while (argc < Nargc - 1 && (argv[argc] = _auth_next_arg(as)))
812 ++argc;
813
814 if (argc >= Nargc - 1 && _auth_next_arg(as)) {
815 if (memcmp(&nilap, &(as->ap0), sizeof(nilap)) != 0) {
816 va_end(as->ap0);
817 memset(&(as->ap0), 0, sizeof(as->ap0));
818 }
819 if (memcmp(&nilap, &(as->ap), sizeof(nilap)) != 0) {
820 va_end(as->ap);
821 memset(&(as->ap), 0, sizeof(as->ap));
822 }
823 syslog(LOG_ERR, "too many arguments");
824 goto fail;
825 }
826
827 argv[argc] = NULL;
828
829 if (secure_path(path) < 0) {
830 syslog(LOG_ERR, "%s: path not secure", path);
831 _warnx("invalid script: %s", path);
832 goto fail;
833 }
834
835 if (socketpair(PF_LOCAL, SOCK_STREAM, 0, pfd) < 0) {
836 syslog(LOG_ERR, "unable to create backchannel %m");
837 _warnx("internal resource failure");
838 goto fail;
839 }
840
841 switch (pid = fork()) {
842 case -1:
843 syslog(LOG_ERR, "%s: %m", path);
844 _warnx("internal resource failure");
845 close(pfd[0]);
846 close(pfd[1]);
847 goto fail;
848 case 0:
849 #define COMM_FD 3
850 #define AUTH_FD 4
851 if (dup2(pfd[1], COMM_FD) < 0)
852 err(1, "dup of backchannel");
853 if (as->fd != -1) {
854 if (dup2(as->fd, AUTH_FD) < 0)
855 err(1, "dup of auth fd");
856 closefrom(AUTH_FD + 1);
857 } else
858 closefrom(COMM_FD + 1);
859 execve(path, argv, auth_environ);
860 syslog(LOG_ERR, "%s: %m", path);
861 err(1, "%s", path);
862 default:
863 close(pfd[1]);
864 if (as->fd != -1) {
865 close(as->fd); /* so child has only ref */
866 as->fd = -1;
867 }
868 while ((data = as->data) != NULL) {
869 as->data = data->next;
870 if (data->len > 0) {
871 write(pfd[0], data->ptr, data->len);
872 memset(data->ptr, 0, data->len);
873 }
874 free(data);
875 }
876 as->index = 0;
877 _auth_spool(as, pfd[0]);
878 close(pfd[0]);
879 status = 0;
880 do {
881 pid = waitpid(pid, &status, 0);
882 } while (pid < 0 && errno == EINTR);
883 if (pid < 0) {
884 if (errno != ECHILD) {
885 syslog(LOG_ERR, "%s: waitpid: %m", path);
886 _warnx("internal failure");
887 goto fail;
888 }
889 } else if (!WIFEXITED(status))
890 goto fail;
891 }
892
893 /*
894 * Now scan the spooled data
895 * It is easier to wait for all the data before starting
896 * to scan it.
897 */
898 for (line = as->spool; line < as->spool + as->index;) {
899 if (!strncasecmp(line, BI_REJECT, sizeof(BI_REJECT)-1)) {
900 line += sizeof(BI_REJECT) - 1;
901 if (!*line || *line == ' ' || *line == '\t') {
902 while (*line == ' ' || *line == '\t')
903 ++line;
904 if (!strcasecmp(line, "silent")) {
905 as->state = AUTH_SILENT;
906 break;
907 }
908 if (!strcasecmp(line, "challenge")) {
909 as->state = AUTH_CHALLENGE;
910 break;
911 }
912 if (!strcasecmp(line, "expired")) {
913 as->state = AUTH_EXPIRED;
914 break;
915 }
916 if (!strcasecmp(line, "pwexpired")) {
917 as->state = AUTH_PWEXPIRED;
918 break;
919 }
920 }
921 break;
922 } else if (!strncasecmp(line, BI_AUTH, sizeof(BI_AUTH)-1)) {
923 line += sizeof(BI_AUTH) - 1;
924 if (!*line || *line == ' ' || *line == '\t') {
925 while (*line == ' ' || *line == '\t')
926 ++line;
927 if (*line == '\0')
928 as->state |= AUTH_OKAY;
929 else if (!strcasecmp(line, "root"))
930 as->state |= AUTH_ROOTOKAY;
931 else if (!strcasecmp(line, "secure"))
932 as->state |= AUTH_SECURE;
933 }
934 } else if (!strncasecmp(line, BI_REMOVE, sizeof(BI_REMOVE)-1)) {
935 line += sizeof(BI_REMOVE) - 1;
936 while (*line == ' ' || *line == '\t')
937 ++line;
938 if (*line)
939 _add_rmlist(as, line);
940 }
941 while (*line++)
942 ;
943 }
944
945 if (WEXITSTATUS(status))
946 as->state &= ~AUTH_ALLOW;
947
948 okay = as->state & AUTH_ALLOW;
949
950 if (!okay)
951 auth_clrenv(as);
952
953 if (0) {
954 fail:
955 auth_clrenv(as);
956 as->state = 0;
957 okay = -1;
958 }
959
960 while ((data = as->data) != NULL) {
961 as->data = data->next;
962 free(data);
963 }
964
965 if (memcmp(&nilap, &(as->ap0), sizeof(nilap)) != 0) {
966 va_end(as->ap0);
967 memset(&(as->ap0), 0, sizeof(as->ap0));
968 }
969
970 if (memcmp(&nilap, &(as->ap), sizeof(nilap)) != 0) {
971 va_end(as->ap);
972 memset(&(as->ap), 0, sizeof(as->ap));
973 }
974 return (okay);
975 }
976
977 static void
_recv_fd(auth_session_t * as,int fd)978 _recv_fd(auth_session_t *as, int fd)
979 {
980 struct msghdr msg;
981 struct cmsghdr *cmp;
982 char cmsgbuf[CMSG_SPACE(sizeof(int))];
983
984 memset(&msg, 0, sizeof(msg));
985 msg.msg_control = cmsgbuf;
986 msg.msg_controllen = sizeof(cmsgbuf);
987 if (recvmsg(fd, &msg, 0) < 0)
988 syslog(LOG_ERR, "recvmsg: %m");
989 else if (msg.msg_flags & MSG_TRUNC)
990 syslog(LOG_ERR, "message truncated");
991 else if (msg.msg_flags & MSG_CTRUNC)
992 syslog(LOG_ERR, "control message truncated");
993 else if ((cmp = CMSG_FIRSTHDR(&msg)) == NULL)
994 syslog(LOG_ERR, "missing control message");
995 else {
996 if (cmp->cmsg_level != SOL_SOCKET)
997 syslog(LOG_ERR, "unexpected cmsg_level %d",
998 cmp->cmsg_level);
999 else if (cmp->cmsg_type != SCM_RIGHTS)
1000 syslog(LOG_ERR, "unexpected cmsg_type %d",
1001 cmp->cmsg_type);
1002 else if (cmp->cmsg_len != CMSG_LEN(sizeof(int)))
1003 syslog(LOG_ERR, "bad cmsg_len %d",
1004 cmp->cmsg_len);
1005 else {
1006 if (as->fd != -1)
1007 close(as->fd);
1008 as->fd = *(int *)CMSG_DATA(cmp);
1009 }
1010 }
1011 }
1012
1013 static void
_auth_spool(auth_session_t * as,int fd)1014 _auth_spool(auth_session_t *as, int fd)
1015 {
1016 ssize_t r;
1017 char *b, *s;
1018
1019 for (s = as->spool + as->index; as->index < sizeof(as->spool) - 1; ) {
1020 r = read(fd, as->spool + as->index,
1021 sizeof(as->spool) - as->index);
1022 if (r <= 0) {
1023 as->spool[as->index] = '\0';
1024 return;
1025 }
1026 b = as->spool + as->index;
1027 as->index += r;
1028 /*
1029 * Convert newlines into NULs to allow easy scanning of the
1030 * file and receive an fd if there is a BI_FDPASS message.
1031 * XXX - checking for BI_FDPASS here is annoying but
1032 * we need to avoid the read() slurping in control data.
1033 */
1034 while (r-- > 0) {
1035 if (*b++ == '\n') {
1036 b[-1] = '\0';
1037 if (strcasecmp(s, BI_FDPASS) == 0)
1038 _recv_fd(as, fd);
1039 s = b;
1040 }
1041 }
1042 }
1043
1044 syslog(LOG_ERR, "Overflowed backchannel spool buffer");
1045 errx(1, "System error in authentication program");
1046 }
1047
1048 static void
_add_rmlist(auth_session_t * as,char * file)1049 _add_rmlist(auth_session_t *as, char *file)
1050 {
1051 struct rmfiles *rm;
1052 int i = strlen(file) + 1;
1053
1054 if ((rm = malloc(sizeof(struct rmfiles) + i)) == NULL) {
1055 syslog(LOG_ERR, "Failed to allocate rmfiles: %m");
1056 return;
1057 }
1058 rm->file = (char *)(rm + 1);
1059 rm->next = as->rmlist;
1060 strlcpy(rm->file, file, i);
1061 as->rmlist = rm;
1062 }
1063
1064 static char *
_auth_next_arg(auth_session_t * as)1065 _auth_next_arg(auth_session_t *as)
1066 {
1067 char *arg;
1068
1069 if (memcmp(&nilap, &(as->ap0), sizeof(nilap)) != 0) {
1070 if ((arg = va_arg(as->ap0, char *)) != NULL)
1071 return (arg);
1072 va_end(as->ap0);
1073 memset(&(as->ap0), 0, sizeof(as->ap0));
1074 }
1075 if (memcmp(&nilap, &(as->ap), sizeof(nilap)) != 0) {
1076 if ((arg = va_arg(as->ap, char *)) != NULL)
1077 return (arg);
1078 va_end(as->ap);
1079 memset(&(as->ap), 0, sizeof(as->ap));
1080 }
1081 return (NULL);
1082 }
1083