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