1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 #include <sys/stat.h>
30 #include <stdio.h>
31 #include <syslog.h>
32 #include <fcntl.h>
33 #include <time.h>
34 #include <errno.h>
35 #include <signal.h>
36 #include "packer.h"
37 
38 static int lockfd = -1;
39 static struct flock flock = { 0, 0, 0, 0, 0, 0 };
40 
41 char dblock[PATH_MAX];
42 
43 #define	LOCK_WAIT 60
44 static int timedout = 0;
45 
46 /*ARGSUSED*/
47 void
48 alarm_handler(int sig)
49 {
50 	timedout = 1;
51 }
52 
53 /*
54  * lock_db()
55  *
56  * Create a lockfile to prevent simultaneous access to the database
57  * creation routines. We set a timeout to LOCK_WAIT seconds. If we
58  * haven't obtained a lock by that time, we bail out.
59  *
60  * returns 0 on succes, -1 on (lock) failure.
61  * side effect: the directory "path" will be created if it didn't exist.
62  */
63 int
64 lock_db(char *path)
65 {
66 	void (*oldhandler)(int);
67 	int retval;
68 	struct stat st;
69 
70 	/* create directory "path" if it doesn't exist */
71 	if (stat(path, &st) == -1) {
72 		if (errno != ENOENT ||
73 		    (mkdir(path, 0755) == -1 || chmod(path, 0755) == -1))
74 			return (-1);
75 	}
76 
77 	(void) snprintf(dblock, sizeof (dblock), "%s/authtok_check.lock", path);
78 
79 	if ((lockfd = open(dblock, O_WRONLY|O_CREAT|O_EXCL, 0400)) == -1) {
80 		if (errno == EEXIST)
81 			lockfd = open(dblock, O_WRONLY);
82 		if (lockfd == -1) {
83 			int olderrno = errno;
84 			syslog(LOG_ERR, "pam_authtok_check::pam_sm_chauthtok: "
85 			    "can't open lockfile: %s", strerror(errno));
86 			errno = olderrno;
87 			return (-1);
88 		}
89 	}
90 
91 	flock.l_type = F_WRLCK;
92 	oldhandler = sigset(SIGALRM, alarm_handler);
93 	(void) alarm(LOCK_WAIT);
94 	retval = fcntl(lockfd, F_SETLKW, &flock);
95 	(void) alarm(0);
96 	(void) sigset(SIGALRM, oldhandler);
97 
98 	if (timedout) {
99 		syslog(LOG_ERR, "pam_authtok_check::pam_sm_chauthtok: timeout "
100 		    "waiting for dictionary lock.");
101 		timedout = 0;
102 	}
103 
104 	return (retval);
105 }
106 
107 /*
108  * unlock_db()
109  *
110  * Release the database lock
111  */
112 void
113 unlock_db(void)
114 {
115 	if (lockfd != -1) {
116 		flock.l_type = F_UNLCK;
117 		(void) fcntl(lockfd, F_SETLK, &flock);
118 		(void) close(lockfd);
119 		lockfd = -1;
120 	}
121 }
122 
123 /*
124  * database_present()
125  *
126  * returns 0 if the database files are found, and the database size is
127  * greater than 0
128  */
129 int
130 database_present(char *path)
131 {
132 	struct stat st;
133 	char dict_hwm[PATH_MAX];
134 	char dict_pwd[PATH_MAX];
135 	char dict_pwi[PATH_MAX];
136 
137 	(void) snprintf(dict_hwm, sizeof (dict_hwm), "%s/%s", path,
138 	    DICT_DATABASE_HWM);
139 	(void) snprintf(dict_pwd, sizeof (dict_pwd), "%s/%s", path,
140 	    DICT_DATABASE_PWD);
141 	(void) snprintf(dict_pwi, sizeof (dict_pwi), "%s/%s", path,
142 	    DICT_DATABASE_PWI);
143 
144 	if (stat(dict_hwm, &st) == -1 ||
145 	    (stat(dict_pwd, &st) == -1 || st.st_size == 0) ||
146 	    stat(dict_pwi, &st) == -1)
147 		return (NO_DICTDATABASE);
148 
149 	return (0);
150 }
151 
152 /*
153  * build_dict_database(list, char *path)
154  *
155  * Create the Crack Dictionary Database based on the list of sources
156  * dictionaries specified in "list". Store the database in "path".
157  */
158 int
159 build_dict_database(char *list, char *path)
160 {
161 	return (packer(list, path) == -1 ? DICTDATABASE_BUILD_ERR : 0);
162 }
163 
164 /*
165  * Rebuild the database in "path" if the database is older than one of the
166  * files listed in "list", or older than the config-file PWADMIN.
167  */
168 int
169 update_dict_database(char *list, char *path)
170 {
171 	struct stat st_db;
172 	struct stat st_file;
173 	char *buf;
174 	char *listcopy;
175 	boolean_t update_needed = B_FALSE;
176 	char dbase_pwd[PATH_MAX];
177 
178 	(void) snprintf(dbase_pwd, sizeof (dbase_pwd), "%s/%s", path,
179 	    DICT_DATABASE_PWD);
180 
181 	if (stat(dbase_pwd, &st_db) == -1)
182 		return (DICTFILE_ERR);
183 
184 	if ((listcopy = strdup(list)) == NULL)
185 		return (DICTFILE_ERR);
186 
187 	buf = strtok(listcopy,  "\t ,");
188 
189 	/* Compare mtime of each listed dictionary against DB mtime */
190 	while (update_needed == B_FALSE && buf != NULL) {
191 		if (stat(buf, &st_file) == -1) {
192 			if (errno == ENOENT) {
193 				syslog(LOG_ERR,
194 				    "pam_authtok_check:update_dict_database: "
195 				    "dictionary \"%s\" not present.", buf);
196 			} else {
197 				syslog(LOG_ERR,
198 				    "pam_authtok_check:update_dict_database: "
199 				    "stat(%s) failed: %s", buf,
200 				    strerror(errno));
201 			}
202 			free(listcopy);
203 			return (DICTFILE_ERR);
204 		}
205 		if (st_db.st_mtime < st_file.st_mtime)
206 			update_needed = B_TRUE;	/* database out of date */
207 		buf = strtok(NULL, "\t ,");
208 	}
209 
210 	free(listcopy);
211 
212 	/*
213 	 * If /etc/default/passwd is updated, generate the database.
214 	 * Because this is the only way we can check if files have been
215 	 * added/deleted from DICTIONLIST.
216 	 * Drawback: the database will also be generated when other
217 	 * tunables are changed.
218 	 */
219 	if (stat(PWADMIN, &st_file) != -1 && st_db.st_mtime < st_file.st_mtime)
220 		update_needed = B_TRUE;
221 
222 	if (update_needed) {
223 		/*
224 		 * Since we actually rebuild the database, we need to remove
225 		 * the old database first.
226 		 */
227 		PWRemove(path);
228 		return (build_dict_database(list, path));
229 	}
230 
231 	return (0);
232 }
233 
234 /*
235  * Build or update database, while holding the global lock.
236  */
237 int
238 make_dict_database(char *list, char *path)
239 {
240 	int r = -1;
241 
242 	if (lock_db(path) == 0) {
243 		if (database_present(path) == NO_DICTDATABASE)
244 			r = build_dict_database(list, path);
245 		else
246 			r = update_dict_database(list, path);
247 		unlock_db();
248 	}
249 	return (r);
250 }
251