xref: /openbsd/lib/libc/gen/authenticate.c (revision 610f49f8)
1 /*	$OpenBSD: authenticate.c,v 1.7 2002/02/05 07:51:52 mpech Exp $	*/
2 
3 /*-
4  * Copyright (c) 1997 Berkeley Software Design, Inc. All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  * 3. All advertising materials mentioning features or use of this software
15  *    must display the following acknowledgement:
16  *	This product includes software developed by Berkeley Software Design,
17  *	Inc.
18  * 4. The name of Berkeley Software Design, Inc.  may not be used to endorse
19  *    or promote products derived from this software without specific prior
20  *    written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY BERKELEY SOFTWARE DESIGN, INC. ``AS IS'' AND
23  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25  * ARE DISCLAIMED.  IN NO EVENT SHALL BERKELEY SOFTWARE DESIGN, INC. BE LIABLE
26  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  *
34  *	BSDI $From: authenticate.c,v 2.21 1999/09/08 22:33:26 prb Exp $
35  */
36 #include <sys/param.h>
37 #include <sys/stat.h>
38 
39 #include <ctype.h>
40 #include <err.h>
41 #include <fcntl.h>
42 #include <login_cap.h>
43 #include <paths.h>
44 #include <pwd.h>
45 #include <stdarg.h>
46 #include <stdio.h>
47 #include <stdlib.h>
48 #include <string.h>
49 #include <syslog.h>
50 #include <unistd.h>
51 
52 #include <bsd_auth.h>
53 
54 static int _auth_checknologin(login_cap_t *, int);
55 
56 char *
57 auth_mkvalue(char *value)
58 {
59 	char *big, *p;
60 
61 	big = malloc(strlen(value) * 4 + 1);
62 	if (big == NULL)
63 		return (NULL);
64 	/*
65 	 * XXX - There should be a more standardized
66 	 * routine for doing this sort of thing.
67 	 */
68 	for (p = big; *value; ++value) {
69 		switch (*value) {
70 		case '\r':
71 			*p++ = '\\';
72 			*p++ = 'r';
73 			break;
74 		case '\n':
75 			*p++ = '\\';
76 			*p++ = 'n';
77 			break;
78 		case '\\':
79 			*p++ = '\\';
80 			*p++ = *value;
81 			break;
82 		case '\t':
83 		case ' ':
84 			if (p == big)
85 				*p++ = '\\';
86 			*p++ = *value;
87 			break;
88 		default:
89 			if (!isprint(*value)) {
90 				*p++ = '\\';
91 				*p++ = ((*value >> 6) & 0x3) + '0';
92 				*p++ = ((*value >> 3) & 0x7) + '0';
93 				*p++ = ((*value     ) & 0x7) + '0';
94 			} else
95 				*p++ = *value;
96 			break;
97 		}
98 	}
99 	*p = '\0';
100 	return (big);
101 }
102 
103 void
104 auth_checknologin(login_cap_t *lc)
105 {
106 	if (_auth_checknologin(lc, 1))
107 		exit(1);
108 }
109 
110 static int
111 _auth_checknologin(login_cap_t *lc, int print)
112 {
113 	struct stat sb;
114 	char *nologin;
115        int mustfree;
116 
117        if (login_getcapbool(lc, "ignorenologin", 0))
118                return (0);
119 
120 	/*
121 	 * If we fail to get the nologin file due to a database error,
122 	 * assume there should have been one...
123 	 */
124        nologin = login_getcapstr(lc, "nologin", "", NULL);
125        mustfree = nologin && *nologin != '\0';
126        if (nologin == NULL)
127                goto print_nologin;
128 
129        /* First try the nologin file specified in login.conf. */
130        if (*nologin != '\0' && stat(nologin, &sb) == 0)
131                goto print_nologin;
132        if (mustfree)
133                free(nologin);
134 
135        /* If that doesn't exist try _PATH_NOLOGIN. */
136        if (stat(_PATH_NOLOGIN, &sb) == 0) {
137                nologin = _PATH_NOLOGIN;
138                goto print_nologin;
139 	}
140 
141        /* Couldn't stat any nologin files, must be OK to login. */
142        return (0);
143 
144 print_nologin:
145        if (print) {
146                if (!nologin || *nologin == '\0' || auth_cat(nologin) == 0) {
147                        puts("Logins are not allowed at this time.");
148 			fflush(stdout);
149 		}
150 	}
151        if (mustfree)
152                free(nologin);
153        return (-1);
154 }
155 
156 int
157 auth_cat(char *file)
158 {
159 	int fd, nchars;
160 	char tbuf[8192];
161 
162 	if ((fd = open(file, O_RDONLY, 0)) < 0)
163 		return (0);
164 	while ((nchars = read(fd, tbuf, sizeof(tbuf))) > 0)
165 		(void)write(fileno(stdout), tbuf, nchars);
166 	(void)close(fd);
167 	return (1);
168 }
169 
170 int
171 auth_approval(auth_session_t *as, login_cap_t *lc, char *name, char *type)
172 {
173 	int close_on_exit, close_lc_on_exit;
174 	struct passwd *pwd;
175 	char *approve, *s, path[MAXPATHLEN];
176 
177 	pwd = NULL;
178 	close_on_exit = as == NULL;
179 	close_lc_on_exit = lc == NULL;
180 
181 	if (as != NULL && name == NULL)
182 		name = auth_getitem(as, AUTHV_NAME);
183 
184 	if (as != NULL)
185 		pwd = auth_getpwd(as);
186 
187 	if (pwd == NULL) {
188 		if (name != NULL)
189 			pwd = getpwnam(name);
190 		else {
191 			if ((pwd = getpwuid(getuid())) == NULL) {
192 				syslog(LOG_ERR, "no such user id %d", getuid());
193 				_warnx("cannot approve who we don't recognize");
194 				return (0);
195 			}
196 			name = pwd->pw_name;
197 		}
198 	}
199 
200 	if (name == NULL)
201 		name = pwd->pw_name;
202 
203 	if (lc == NULL) {
204 		if (strlen(name) >= MAXPATHLEN) {
205 			syslog(LOG_ERR, "username to login %.*s...",
206 			    MAXPATHLEN, name);
207 			_warnx("username too long");
208 			return (0);
209 		}
210 		if (pwd == NULL && (approve = strchr(name, '.')) != NULL) {
211 			strcpy(path, name);
212 			path[approve-name] = '\0';
213 			pwd = getpwnam(name);
214 		}
215 		lc = login_getclass(pwd ? pwd->pw_class : NULL);
216 		if (lc == NULL) {
217 			_warnx("unable to classify user");
218 			return (0);
219 		}
220 	}
221 
222 	if (!type)
223 		type = LOGIN_DEFSERVICE;
224 	else {
225 		if (strncmp(type, "approve-", 8) == 0)
226 			type += 8;
227 
228 		snprintf(path, sizeof(path), "approve-%s", type);
229 	}
230 
231 	if ((approve = login_getcapstr(lc, s = path, NULL, NULL)) == NULL)
232 		approve = login_getcapstr(lc, s = "approve", NULL, NULL);
233 
234 	if (approve && approve[0] != '/') {
235 		if (close_lc_on_exit)
236 			login_close(lc);
237 		syslog(LOG_ERR, "Invalid %s script: %s", s, approve);
238 		_warnx("invalid path to approval script");
239 		return (0);
240 	}
241 
242 	if (as == NULL && (as = auth_open()) == NULL) {
243 		if (close_lc_on_exit)
244 			login_close(lc);
245 		syslog(LOG_ERR, "%m");
246 		_warn(NULL);
247 		return (0);
248 	}
249 
250 	auth_setstate(as, AUTH_OKAY);
251 	if (auth_setitem(as, AUTHV_NAME, name) < 0) {
252 		syslog(LOG_ERR, "%m");
253 		_warn(NULL);
254 		goto out;
255 	}
256 	if (auth_check_expire(as) < 0)	/* is this account expired */
257 		goto out;
258 	if (_auth_checknologin(lc,
259 	    auth_getitem(as, AUTHV_INTERACTIVE) != NULL)) {
260 		auth_setstate(as, (auth_getstate(as) & ~AUTH_ALLOW));
261 		goto out;
262 	}
263 	if (login_getcapbool(lc, "requirehome", 0) && pwd && pwd->pw_dir &&
264 	    pwd->pw_dir[0]) {
265 		struct stat sb;
266 
267 		if (stat(pwd->pw_dir, &sb) < 0 ||
268 		    (sb.st_mode & 0170000) != S_IFDIR ||
269 		    (pwd->pw_uid && sb.st_uid == pwd->pw_uid &&
270 		     (sb.st_mode & S_IXUSR) == 0)) {
271 			auth_setstate(as, (auth_getstate(as) & ~AUTH_ALLOW));
272 			goto out;
273 		}
274 	}
275 	if (approve)
276 	    auth_call(as, approve, strrchr(approve, '/') + 1, name,
277 		lc->lc_class, type, 0);
278 
279 out:
280 	if (close_lc_on_exit)
281 		login_close(lc);
282 
283 	if (close_on_exit)
284 		return (auth_close(as));
285 	return (auth_getstate(as) & AUTH_ALLOW);
286 }
287 
288 auth_session_t *
289 auth_usercheck(char *name, char *style, char *type, char *password)
290 {
291 	char namebuf[MAXLOGNAME + 1 + NAME_MAX + 1];
292 	auth_session_t *as;
293 	login_cap_t *lc;
294 	struct passwd *pwd;
295 	char *sep, save;
296 
297 	if (strlen(name) >= sizeof(namebuf))
298 		return (NULL);
299 	strcpy(namebuf, name);
300 	name = namebuf;
301 
302 	/*
303 	 * Split up user:style names if we were not given a style
304 	 */
305 	if (style == NULL && (style = strchr(name, ':')) != NULL)
306 		*style++ = '\0';
307 
308 	/*
309 	 * Cope with user[./]instance.  We are only using this to get
310 	 * the class so it is okay if we strip a root instance
311 	 * The actual login script will pay attention to the instance.
312 	 */
313 	if ((pwd = getpwnam(name)) == NULL) {
314 		if ((sep = strpbrk(name, "./")) != NULL) {
315 			save = *sep;
316 			*sep = '\0';
317 			pwd = getpwnam(name);
318 			*sep = save;
319 		}
320 	}
321 	if ((lc = login_getclass(pwd ? pwd->pw_class : NULL)) == NULL)
322 		return (NULL);
323 
324 	if ((style = login_getstyle(lc, style, type)) == NULL) {
325 		login_close(lc);
326 		return (NULL);
327 	}
328 
329 	if (password) {
330 		if ((as = auth_open()) == NULL) {
331 			login_close(lc);
332 			return (NULL);
333 		}
334 		auth_setitem(as, AUTHV_SERVICE, "response");
335 		auth_setdata(as, "", 1);
336 		auth_setdata(as, password, strlen(password) + 1);
337 	} else
338 		as = NULL;
339 	as = auth_verify(as, style, name, lc->lc_class, NULL);
340 	login_close(lc);
341 	return (as);
342 }
343 
344 int
345 auth_userokay(char *name, char *style, char *type, char *password)
346 {
347 	auth_session_t *as;
348 
349 	as = auth_usercheck(name, style, type, password);
350 
351 	return (as != NULL ? auth_close(as) : 0);
352 }
353 
354 auth_session_t *
355 auth_userchallenge(char *name, char *style, char *type, char **challengep)
356 {
357 	char namebuf[MAXLOGNAME + 1 + NAME_MAX + 1];
358 	auth_session_t *as;
359 	login_cap_t *lc;
360 	struct passwd *pwd;
361 	char *sep, save;
362 
363 	if (strlen(name) >= sizeof(namebuf))
364 		return (NULL);
365 	strcpy(namebuf, name);
366 	name = namebuf;
367 
368 	/*
369 	 * Split up user:style names if we were not given a style
370 	 */
371 	if (style == NULL && (style = strchr(name, ':')) != NULL)
372 		*style++ = '\0';
373 
374 	/*
375 	 * Cope with user[./]instance.  We are only using this to get
376 	 * the class so it is okay if we strip a root instance
377 	 * The actual login script will pay attention to the instance.
378 	 */
379 	if ((pwd = getpwnam(name)) == NULL) {
380 		if ((sep = strpbrk(name, "./")) != NULL) {
381 			save = *sep;
382 			*sep = '\0';
383 			pwd = getpwnam(name);
384 			*sep = save;
385 		}
386 	}
387 	if ((lc = login_getclass(pwd ? pwd->pw_class : NULL)) == NULL)
388 		return (NULL);
389 
390 	if ((style = login_getstyle(lc, style, type)) == NULL ||
391 	    (as = auth_open()) == NULL) {
392 		login_close(lc);
393 		return (NULL);
394 	}
395 	if (auth_setitem(as, AUTHV_STYLE, style) < 0 ||
396 	    auth_setitem(as, AUTHV_NAME, name) < 0 ||
397 	    auth_setitem(as, AUTHV_CLASS, lc->lc_class) < 0) {
398 		auth_close(as);
399 		login_close(lc);
400 		return (NULL);
401 	}
402 	login_close(lc);
403 	*challengep = auth_challenge(as);
404 	return (as);
405 }
406 
407 int
408 auth_userresponse(auth_session_t *as, char *response, int more)
409 {
410 	char path[MAXPATHLEN];
411 	char *style, *name, *challenge, *class;
412 
413 	if (as == NULL)
414 		return (0);
415 
416 	auth_setstate(as, 0);
417 
418 	if ((style = auth_getitem(as, AUTHV_STYLE)) == NULL ||
419 	    (name = auth_getitem(as, AUTHV_NAME)) == NULL) {
420 		if (more == 0)
421 			return (auth_close(as));
422 		return(0);
423 	}
424 	challenge = auth_getitem(as, AUTHV_CHALLENGE);
425 	class = auth_getitem(as, AUTHV_CLASS);
426 
427 	if (challenge)
428 		auth_setdata(as, challenge, strlen(challenge) + 1);
429 	else
430 		auth_setdata(as, "", 1);
431 	if (response)
432 		auth_setdata(as, response, strlen(response) + 1);
433 	else
434 		auth_setdata(as, "", 1);
435 
436 	snprintf(path, sizeof(path), _PATH_AUTHPROG "%s", style);
437 
438 	auth_call(as, path, style, "-s", "response", name, class, NULL);
439 
440 	/*
441 	 * If they authenticated then make sure they did not expire
442 	 */
443 	if (auth_getstate(as) & AUTH_ALLOW)
444 		auth_check_expire(as);
445 	if (more == 0)
446 		return (auth_close(as));
447 	return (auth_getstate(as) & AUTH_ALLOW);
448 }
449 
450 /*
451  * Authenticate name with the specified style.
452  * If ``as'' is NULL a new session is formed with the default service.
453  * Returns NULL only if ``as'' is NULL and we were unable to allocate
454  * a new session.
455  *
456  * Use auth_close() or auth_getstate() to determine if the authentication
457  * worked.
458  */
459 auth_session_t *
460 auth_verify(auth_session_t *as, char *style, char *name, ...)
461 {
462 	va_list ap;
463 	char path[MAXPATHLEN];
464 
465 	if ((name == NULL || style == NULL) && as == NULL)
466 		return (as);
467 
468 	if (as == NULL && (as = auth_open()) == NULL)
469 		return (NULL);
470 	auth_setstate(as, 0);
471 
472 	if (style != NULL && auth_setitem(as, AUTHV_STYLE, style) < 0)
473 		return (as);
474 
475 	if (name != NULL && auth_setitem(as, AUTHV_NAME, name) < 0)
476 		return (as);
477 
478 	style = auth_getitem(as, AUTHV_STYLE);
479 	name = auth_getitem(as, AUTHV_NAME);
480 
481 	snprintf(path, sizeof(path), _PATH_AUTHPROG "%s", style);
482 	va_start(ap, name);
483 	auth_set_va_list(as, ap);
484 	auth_call(as, path, auth_getitem(as, AUTHV_STYLE), "-s",
485 	    auth_getitem(as, AUTHV_SERVICE), name, NULL);
486 	return (as);
487 }
488