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 2010 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #include <sys/param.h>
27 #include <string.h>
28 #include <stdlib.h>
29 #include <stdio.h>
30 #include <pwd.h>
31 #include <assert.h>
32 #include <strings.h>
33 #include <sys/stat.h>
34 #include <smbsrv/libsmb.h>
35 #include <smbsrv/libmlsvc.h>
36 #include <smbsrv/smbinfo.h>
37 
38 #define	SMB_AUTOHOME_KEYSIZ	128
39 #define	SMB_AUTOHOME_MAXARG	4
40 #define	SMB_AUTOHOME_BUFSIZ	2048
41 
42 typedef struct smb_autohome_info {
43 	struct smb_autohome_info *magic1;
44 	FILE *fp;
45 	smb_autohome_t autohome;
46 	char buf[SMB_AUTOHOME_BUFSIZ];
47 	char *argv[SMB_AUTOHOME_MAXARG];
48 	int lineno;
49 	struct smb_autohome_info *magic2;
50 } smb_autohome_info_t;
51 
52 static smb_autohome_info_t smb_ai;
53 
54 static smb_autohome_t *smb_autohome_make_entry(smb_autohome_info_t *);
55 static char *smb_autohome_keysub(const char *, char *, int);
56 static smb_autohome_info_t *smb_autohome_getinfo(void);
57 static smb_autohome_t *smb_autohome_lookup(const char *);
58 static void smb_autohome_setent(void);
59 static void smb_autohome_endent(void);
60 static smb_autohome_t *smb_autohome_getent(const char *);
61 static void smb_autohome_parse_options(smb_share_t *);
62 
63 /*
64  * Add an autohome share.  See smb_autohome(4) for details.
65  *
66  * If share directory contains backslash path separators, they will
67  * be converted to forward slash to support NT/DOS path style for
68  * autohome shares.
69  *
70  * We need to serialize calls to smb_autohome_lookup because it
71  * operates on the global smb_ai structure.
72  */
73 void
74 smb_autohome_add(const smb_token_t *token)
75 {
76 	static mutex_t	autohome_mutex;
77 	smb_share_t	si;
78 	smb_autohome_t	*ai;
79 	char		*username = token->tkn_account_name;
80 
81 	assert(username);
82 
83 	if (smb_shr_get((char *)username, &si) == NERR_Success) {
84 		/*
85 		 * A static share with this name already exists
86 		 */
87 		if ((si.shr_flags & SMB_SHRF_AUTOHOME) == 0)
88 			return;
89 
90 		/*
91 		 * autohome shares will be added for each login attempt
92 		 */
93 		(void) smb_shr_add(&si);
94 		return;
95 	}
96 
97 	(void) mutex_lock(&autohome_mutex);
98 
99 	if ((ai = smb_autohome_lookup(username)) == NULL) {
100 		(void) mutex_unlock(&autohome_mutex);
101 		return;
102 	}
103 
104 	bzero(&si, sizeof (smb_share_t));
105 	(void) strlcpy(si.shr_path, ai->ah_path, MAXPATHLEN);
106 	(void) strsubst(si.shr_path, '\\', '/');
107 
108 	(void) strlcpy(si.shr_name, username, MAXNAMELEN);
109 	(void) strlcpy(si.shr_container, ai->ah_container, MAXPATHLEN);
110 	(void) strlcpy(si.shr_cmnt, "Autohome", SMB_SHARE_CMNT_MAX);
111 	smb_autohome_parse_options(&si);
112 	si.shr_flags |= SMB_SHRF_TRANS | SMB_SHRF_AUTOHOME;
113 	si.shr_uid = token->tkn_user.i_id;
114 	si.shr_gid = token->tkn_primary_grp.i_id;
115 
116 	(void) mutex_unlock(&autohome_mutex);
117 
118 	(void) smb_shr_add(&si);
119 }
120 
121 /*
122  * Remove an autohome share.
123  */
124 void
125 smb_autohome_remove(const char *username)
126 {
127 	smb_share_t si;
128 
129 	assert(username);
130 
131 	if (smb_shr_get((char *)username, &si) == NERR_Success) {
132 		if (si.shr_flags & SMB_SHRF_AUTOHOME)
133 			(void) smb_shr_remove((char *)username);
134 	}
135 }
136 
137 /*
138  * Search the autohome database for the specified name. The name cannot
139  * be an empty string or begin with * or +.
140  * 1. Search the file for the specified name.
141  * 2. Check for the wildcard rule and, if present, treat it as a match.
142  * 3. Check for the nsswitch rule and, if present, lookup the name
143  *    via the name services. Note that the nsswitch rule will never
144  *    be applied if the wildcard rule is present.
145  *
146  * Returns a pointer to the entry on success or null on failure.
147  */
148 static smb_autohome_t *
149 smb_autohome_lookup(const char *name)
150 {
151 	struct passwd *pw;
152 	smb_autohome_t *ah = NULL;
153 
154 	if (name == NULL)
155 		return (NULL);
156 
157 	if (*name == '\0' || *name == '*' || *name == '+')
158 		return (NULL);
159 
160 	smb_autohome_setent();
161 
162 	while ((ah = smb_autohome_getent(name)) != NULL) {
163 		if (strcasecmp(ah->ah_name, name) == 0)
164 			break;
165 	}
166 
167 	if (ah == NULL) {
168 		smb_autohome_setent();
169 
170 		while ((ah = smb_autohome_getent(name)) != NULL) {
171 			if (strcasecmp(ah->ah_name, "*") == 0) {
172 				ah->ah_name = (char *)name;
173 				break;
174 			}
175 		}
176 	}
177 
178 	if (ah == NULL) {
179 		smb_autohome_setent();
180 
181 		while ((ah = smb_autohome_getent("+nsswitch")) != NULL) {
182 			if (strcasecmp("+nsswitch", ah->ah_name) != 0)
183 				continue;
184 			if ((pw = getpwnam(name)) == NULL) {
185 				ah = NULL;
186 				break;
187 			}
188 
189 			ah->ah_name = pw->pw_name;
190 
191 			if (ah->ah_path)
192 				ah->ah_container = ah->ah_path;
193 
194 			ah->ah_path = pw->pw_dir;
195 			break;
196 		}
197 	}
198 
199 	smb_autohome_endent();
200 	return (ah);
201 }
202 
203 /*
204  * Open or rewind the autohome database.
205  */
206 static void
207 smb_autohome_setent(void)
208 {
209 	smb_autohome_info_t *si;
210 	char path[MAXNAMELEN];
211 	char filename[MAXNAMELEN];
212 	int rc;
213 
214 	if ((si = smb_autohome_getinfo()) != 0) {
215 		(void) fseek(si->fp, 0L, SEEK_SET);
216 		si->lineno = 0;
217 		return;
218 	}
219 
220 	if ((si = &smb_ai) == 0)
221 		return;
222 
223 	rc = smb_config_getstr(SMB_CI_AUTOHOME_MAP, path, sizeof (path));
224 	if (rc != SMBD_SMF_OK)
225 		return;
226 
227 	(void) snprintf(filename, MAXNAMELEN, "%s/%s", path,
228 	    SMB_AUTOHOME_FILE);
229 
230 	if ((si->fp = fopen(filename, "r")) == NULL)
231 		return;
232 
233 	si->magic1 = si;
234 	si->magic2 = si;
235 	si->lineno = 0;
236 }
237 
238 /*
239  * Close the autohome database and invalidate the autohome info.
240  * We can't zero the whole info structure because the application
241  * should still have access to the data after the file is closed.
242  */
243 static void
244 smb_autohome_endent(void)
245 {
246 	smb_autohome_info_t *si;
247 
248 	if ((si = smb_autohome_getinfo()) != 0) {
249 		(void) fclose(si->fp);
250 		si->fp = 0;
251 		si->magic1 = 0;
252 		si->magic2 = 0;
253 	}
254 }
255 
256 /*
257  * Return the next entry in the autohome database, opening the file
258  * if necessary.  Returns null on EOF or error.
259  *
260  * Note that we are not looking for the specified name. The name is
261  * only used for key substitution, so that the caller sees the entry
262  * in expanded form.
263  */
264 static smb_autohome_t *
265 smb_autohome_getent(const char *name)
266 {
267 	smb_autohome_info_t *si;
268 	char *bp;
269 
270 	if ((si = smb_autohome_getinfo()) == 0) {
271 		smb_autohome_setent();
272 
273 		if ((si = smb_autohome_getinfo()) == 0)
274 			return (0);
275 	}
276 
277 	/*
278 	 * Find the next non-comment, non-empty line.
279 	 * Anything after a # is a comment and can be discarded.
280 	 * Discard a newline to avoid it being included in the parsing
281 	 * that follows.
282 	 * Leading and training whitespace is discarded, and replicated
283 	 * whitespace is compressed to simplify the token parsing,
284 	 * although strsep() deals with that better than strtok().
285 	 */
286 	do {
287 		if (fgets(si->buf, SMB_AUTOHOME_BUFSIZ, si->fp) == 0)
288 			return (0);
289 
290 		++si->lineno;
291 
292 		if ((bp = strpbrk(si->buf, "#\r\n")) != 0)
293 			*bp = '\0';
294 
295 		(void) trim_whitespace(si->buf);
296 		bp = strcanon(si->buf, " \t");
297 	} while (*bp == '\0');
298 
299 	(void) smb_autohome_keysub(name, si->buf, SMB_AUTOHOME_BUFSIZ);
300 	return (smb_autohome_make_entry(si));
301 }
302 
303 /*
304  * Set up an autohome entry from the line buffer. The line should just
305  * contain tokens separated by single whitespace. The line format is:
306  *	<username> <home-dir-path> <ADS container>
307  */
308 static smb_autohome_t *
309 smb_autohome_make_entry(smb_autohome_info_t *si)
310 {
311 	char *bp;
312 	int i;
313 
314 	bp = si->buf;
315 
316 	for (i = 0; i < SMB_AUTOHOME_MAXARG; ++i)
317 		si->argv[i] = NULL;
318 
319 	for (i = 0; i < SMB_AUTOHOME_MAXARG; ++i) {
320 		do {
321 			if ((si->argv[i] = strsep(&bp, " \t")) == NULL)
322 				break;
323 		} while (*(si->argv[i]) == '\0');
324 
325 		if (si->argv[i] == NULL)
326 			break;
327 	}
328 
329 	if ((si->autohome.ah_name = si->argv[0]) == NULL) {
330 		/*
331 		 * Sanity check: the name could be an empty
332 		 * string but it can't be a null pointer.
333 		 */
334 		return (0);
335 	}
336 
337 	if ((si->autohome.ah_path = si->argv[1]) == NULL)
338 		si->autohome.ah_path = "";
339 
340 	if ((si->autohome.ah_container = si->argv[2]) == NULL)
341 		si->autohome.ah_container = "";
342 
343 	return (&si->autohome);
344 }
345 
346 /*
347  * Substitute the ? and & map keys.
348  * ? is replaced by the first character of the name
349  * & is replaced by the whole name.
350  */
351 static char *
352 smb_autohome_keysub(const char *name, char *buf, int buflen)
353 {
354 	char key[SMB_AUTOHOME_KEYSIZ];
355 	char *ampersand;
356 	char *tmp;
357 	int bufsize = buflen;
358 
359 	(void) strlcpy(key, buf, SMB_AUTOHOME_KEYSIZ);
360 
361 	if ((tmp = strpbrk(key, " \t")) == NULL)
362 		return (NULL);
363 
364 	*tmp = '\0';
365 
366 	/*
367 	 * Substitution characters are not allowed in the key.
368 	 */
369 	if (strpbrk(key, "?&") != NULL)
370 		return (NULL);
371 
372 	if (strcmp(key, "*") == 0 && name != NULL)
373 		(void) strlcpy(key, name, SMB_AUTOHOME_KEYSIZ);
374 
375 	(void) strsubst(buf, '?', *key);
376 
377 	while ((ampersand = strchr(buf, '&')) != NULL) {
378 		if ((tmp = strdup(ampersand + 1)) == NULL)
379 			return (0);
380 
381 		bufsize = buflen - (ampersand - buf);
382 		(void) strlcpy(ampersand, key, bufsize);
383 		(void) strlcat(ampersand, tmp, bufsize);
384 		free(tmp);
385 	}
386 
387 	return (buf);
388 }
389 
390 /*
391  * Get a pointer to the context buffer and validate it.
392  */
393 static smb_autohome_info_t *
394 smb_autohome_getinfo(void)
395 {
396 	smb_autohome_info_t *si;
397 
398 	if ((si = &smb_ai) == 0)
399 		return (0);
400 
401 	if ((si->magic1 == si) && (si->magic2 == si) && (si->fp != NULL))
402 		return (si);
403 
404 	return (0);
405 }
406 
407 /*
408  * Parse the options string, which contains a comma separated list of
409  * name-value pairs.  One of the options may be an AD container, which
410  * is also a comma separated list of name-value pairs.  For example,
411  * dn=ad,dn=sun,dn=com,ou=users
412  *
413  * All options other than the AD container will be extracted from
414  * shr_container and used to set share properties.
415  * On return, shr_container will contain the AD container string.
416  */
417 static void
418 smb_autohome_parse_options(smb_share_t *si)
419 {
420 	char buf[MAXPATHLEN];
421 	char **argv;
422 	char **ap;
423 	char *bp;
424 	char *value;
425 	boolean_t separator = B_FALSE;
426 	int argc;
427 	int i;
428 
429 	if (strlcpy(buf, si->shr_container, MAXPATHLEN) == 0)
430 		return;
431 
432 	for (argc = 1, bp = si->shr_container; *bp != '\0'; ++bp)
433 		if (*bp == ',')
434 			++argc;
435 
436 	if ((argv = calloc(argc + 1, sizeof (char *))) == NULL)
437 		return;
438 
439 	ap = argv;
440 	for (bp = buf, i = 0; i < argc; ++i) {
441 		do {
442 			if ((value = strsep(&bp, ",")) == NULL)
443 				break;
444 		} while (*value == '\0');
445 
446 		if (value == NULL)
447 			break;
448 
449 		*ap++ = value;
450 	}
451 	*ap = NULL;
452 
453 	si->shr_container[0] = '\0';
454 	bp = si->shr_container;
455 
456 	for (ap = argv; *ap != NULL; ++ap) {
457 		value = *ap;
458 
459 		if (strncasecmp(value, "catia=", 6) == 0) {
460 			smb_shr_sa_setflag((value + 6), si, SMB_SHRF_CATIA);
461 			continue;
462 		}
463 
464 		if (strncasecmp(value, "csc=", 4) == 0) {
465 			smb_shr_sa_csc_option((value + 4), si);
466 			continue;
467 		}
468 
469 		if (strncasecmp(value, "abe=", 4) == 0) {
470 			smb_shr_sa_setflag((value + 4), si, SMB_SHRF_ABE);
471 			continue;
472 		}
473 
474 		if (strncasecmp(value, "description=", 12) == 0) {
475 			(void) strlcpy(si->shr_cmnt, (value + 12),
476 			    SMB_SHARE_CMNT_MAX);
477 			continue;
478 		}
479 
480 		if (separator)
481 			(void) strlcat(bp, ",", MAXPATHLEN);
482 		(void) strlcat(bp, value, MAXPATHLEN);
483 		separator = B_TRUE;
484 	}
485 
486 	free(argv);
487 }
488