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