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