1 /* Author: Mark Goldman	  <mgoldman@tresys.com>
2  * 	   Paul Rosenfeld <prosenfeld@tresys.com>
3  * 	   Todd C. Miller <tmiller@tresys.com>
4  *
5  * Copyright (C) 2007 Tresys Technology, LLC
6  *
7  *  This library is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU Lesser General Public License as
9  *  published by the Free Software Foundation; either version 2.1 of the
10  *  License, or (at your option) any later version.
11  *
12  *  This library is distributed in the hope that it will be useful, but
13  *  WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  *  Lesser General Public License for more details.
16  *
17  *  You should have received a copy of the GNU Lesser General Public
18  *  License along with this library; if not, write to the Free Software
19  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20  *  02110-1301  USA
21  */
22 
23 #include <semanage/handle.h>
24 #include <semanage/seusers_policy.h>
25 #include <semanage/users_policy.h>
26 #include <semanage/user_record.h>
27 #include <semanage/fcontext_record.h>
28 #include <semanage/fcontexts_policy.h>
29 #include <sepol/context.h>
30 #include <sepol/context_record.h>
31 #include "semanage_store.h"
32 #include "seuser_internal.h"
33 #include "debug.h"
34 
35 #include "utilities.h"
36 #include "genhomedircon.h"
37 #include <ustr.h>
38 
39 #include <assert.h>
40 #include <limits.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <sys/types.h>
45 #include <sys/stat.h>
46 #include <fcntl.h>
47 #include <pwd.h>
48 #include <errno.h>
49 #include <unistd.h>
50 #include <regex.h>
51 
52 /* paths used in get_home_dirs() */
53 #define PATH_ETC_USERADD "/etc/default/useradd"
54 #define PATH_ETC_LIBUSER "/etc/libuser.conf"
55 #define PATH_DEFAULT_HOME "/home"
56 #define PATH_EXPORT_HOME "/export/home"
57 #define PATH_ETC_LOGIN_DEFS "/etc/login.defs"
58 
59 /* other paths */
60 #define PATH_SHELLS_FILE "/etc/shells"
61 #define PATH_NOLOGIN_SHELL "/sbin/nologin"
62 
63 /* comments written to context file */
64 #define COMMENT_FILE_CONTEXT_HEADER "#\n#\n# " \
65 			"User-specific file contexts, generated via libsemanage\n" \
66 			"# use semanage command to manage system users to change" \
67 			" the file_context\n#\n#\n"
68 
69 #define COMMENT_USER_HOME_CONTEXT "\n\n#\n# Home Context for user %s" \
70 			"\n#\n\n"
71 
72 /* placeholders used in the template file
73    which are searched for and replaced */
74 #define TEMPLATE_HOME_ROOT "HOME_ROOT"
75 #define TEMPLATE_HOME_DIR "HOME_DIR"
76 #define TEMPLATE_USER "USER"
77 #define TEMPLATE_ROLE "ROLE"
78 #define TEMPLATE_SEUSER "system_u"
79 #define TEMPLATE_LEVEL "s0"
80 
81 #define FALLBACK_USER "user_u"
82 #define FALLBACK_USER_PREFIX "user"
83 #define FALLBACK_USER_LEVEL "s0"
84 #define DEFAULT_LOGIN "__default__"
85 
86 typedef struct {
87 	const char *fcfilepath;
88 	int usepasswd;
89 	const char *homedir_template_path;
90 	char *fallback_user;
91 	char *fallback_user_prefix;
92 	char *fallback_user_level;
93 	semanage_handle_t *h_semanage;
94 	sepol_policydb_t *policydb;
95 } genhomedircon_settings_t;
96 
97 typedef struct user_entry {
98 	char *name;
99 	char *sename;
100 	char *prefix;
101 	char *home;
102 	char *level;
103 	struct user_entry *next;
104 } genhomedircon_user_entry_t;
105 
106 typedef struct {
107 	const char *search_for;
108 	const char *replace_with;
109 } replacement_pair_t;
110 
111 typedef struct {
112 	const char *dir;
113 	int matched;
114 } fc_match_handle_t;
115 
116 typedef struct IgnoreDir {
117 	struct IgnoreDir *next;
118 	char *dir;
119 } ignoredir_t;
120 
121 ignoredir_t *ignore_head = NULL;
122 
ignore_free(void)123 static void ignore_free(void) {
124 	ignoredir_t *next;
125 
126 	while (ignore_head) {
127 		next = ignore_head->next;
128 		free(ignore_head->dir);
129 		free(ignore_head);
130 		ignore_head = next;
131 	}
132 }
133 
ignore_setup(char * ignoredirs)134 static int ignore_setup(char *ignoredirs) {
135 	char *tok;
136 	ignoredir_t *ptr = NULL;
137 
138 	tok = strtok(ignoredirs, ";");
139 	while(tok) {
140 		ptr = calloc(sizeof(ignoredir_t),1);
141 		if (!ptr)
142 			goto err;
143 		ptr->dir = strdup(tok);
144 		if (!ptr->dir)
145 			goto err;
146 
147 		ptr->next = ignore_head;
148 		ignore_head = ptr;
149 
150 		tok = strtok(NULL, ";");
151 	}
152 
153 	return 0;
154 err:
155 	free(ptr);
156 	ignore_free();
157 	return -1;
158 }
159 
ignore(const char * homedir)160 static int ignore(const char *homedir) {
161 	ignoredir_t *ptr = ignore_head;
162 	while (ptr) {
163 		if (strcmp(ptr->dir, homedir) == 0) {
164 			return 1;
165 		}
166 		ptr = ptr->next;
167 	}
168 	return 0;
169 }
170 
default_shell_list(void)171 static semanage_list_t *default_shell_list(void)
172 {
173 	semanage_list_t *list = NULL;
174 
175 	if (semanage_list_push(&list, "/bin/csh")
176 	    || semanage_list_push(&list, "/bin/tcsh")
177 	    || semanage_list_push(&list, "/bin/ksh")
178 	    || semanage_list_push(&list, "/bin/bsh")
179 	    || semanage_list_push(&list, "/bin/ash")
180 	    || semanage_list_push(&list, "/usr/bin/ksh")
181 	    || semanage_list_push(&list, "/usr/bin/pdksh")
182 	    || semanage_list_push(&list, "/bin/zsh")
183 	    || semanage_list_push(&list, "/bin/sh")
184 	    || semanage_list_push(&list, "/bin/bash"))
185 		goto fail;
186 
187 	return list;
188 
189       fail:
190 	semanage_list_destroy(&list);
191 	return NULL;
192 }
193 
get_shell_list(void)194 static semanage_list_t *get_shell_list(void)
195 {
196 	FILE *shells;
197 	char *temp = NULL;
198 	semanage_list_t *list = NULL;
199 	size_t buff_len = 0;
200 	ssize_t len;
201 
202 	shells = fopen(PATH_SHELLS_FILE, "r");
203 	if (!shells)
204 		return default_shell_list();
205 	while ((len = getline(&temp, &buff_len, shells)) > 0) {
206 		if (temp[len-1] == '\n') temp[len-1] = 0;
207 		if (strcmp(temp, PATH_NOLOGIN_SHELL)) {
208 			if (semanage_list_push(&list, temp)) {
209 				free(temp);
210 				semanage_list_destroy(&list);
211 				return default_shell_list();
212 			}
213 		}
214 	}
215 	free(temp);
216 
217 	return list;
218 }
219 
220 /* Helper function called via semanage_fcontext_iterate() */
fcontext_matches(const semanage_fcontext_t * fcontext,void * varg)221 static int fcontext_matches(const semanage_fcontext_t *fcontext, void *varg)
222 {
223 	const char *oexpr = semanage_fcontext_get_expr(fcontext);
224 	fc_match_handle_t *handp = varg;
225 	struct Ustr *expr;
226 	regex_t re;
227 	int type, retval = -1;
228 
229 	/* Only match ALL or DIR */
230 	type = semanage_fcontext_get_type(fcontext);
231 	if (type != SEMANAGE_FCONTEXT_ALL && type != SEMANAGE_FCONTEXT_ALL)
232 		return 0;
233 
234 	/* Convert oexpr into a Ustr and anchor it at the beginning */
235 	expr = ustr_dup_cstr("^");
236 	if (expr == USTR_NULL)
237 		goto done;
238 	if (!ustr_add_cstr(&expr, oexpr))
239 		goto done;
240 
241 	/* Strip off trailing ".+" or ".*" */
242 	if (ustr_cmp_suffix_cstr_eq(expr, ".+") ||
243 	    ustr_cmp_suffix_cstr_eq(expr, ".*")) {
244 		if (!ustr_del(&expr, 2))
245 			goto done;
246 	}
247 
248 	/* Strip off trailing "(/.*)?" */
249 	if (ustr_cmp_suffix_cstr_eq(expr, "(/.*)?")) {
250 		if (!ustr_del(&expr, 6))
251 			goto done;
252 	}
253 
254 	if (ustr_cmp_suffix_cstr_eq(expr, "/")) {
255 		if (!ustr_del(&expr, 1))
256 			goto done;
257 	}
258 
259 	/* Append pattern to eat up trailing slashes */
260 	if (!ustr_add_cstr(&expr, "/*$"))
261 		goto done;
262 
263 	/* Check dir against expr */
264 	if (regcomp(&re, ustr_cstr(expr), REG_EXTENDED) != 0)
265 		goto done;
266 	if (regexec(&re, handp->dir, 0, NULL, 0) == 0)
267 		handp->matched = 1;
268 	regfree(&re);
269 
270 	retval = 0;
271 
272 done:
273 	ustr_free(expr);
274 
275 	return retval;
276 }
277 
get_home_dirs(genhomedircon_settings_t * s)278 static semanage_list_t *get_home_dirs(genhomedircon_settings_t * s)
279 {
280 	semanage_list_t *homedir_list = NULL;
281 	semanage_list_t *shells = NULL;
282 	fc_match_handle_t hand;
283 	char *rbuf = NULL;
284 	char *path = NULL;
285 	long rbuflen;
286 	uid_t temp, minuid = 500, maxuid = 60000;
287 	int minuid_set = 0;
288 	struct passwd pwstorage, *pwbuf;
289 	struct stat buf;
290 	int retval;
291 
292 	path = semanage_findval(PATH_ETC_USERADD, "HOME", "=");
293 	if (path && *path) {
294 		if (semanage_list_push(&homedir_list, path))
295 			goto fail;
296 	}
297 	free(path);
298 
299 	path = semanage_findval(PATH_ETC_LIBUSER, "LU_HOMEDIRECTORY", "=");
300 	if (path && *path) {
301 		if (semanage_list_push(&homedir_list, path))
302 			goto fail;
303 	}
304 	free(path);
305 	path = NULL;
306 
307 	if (!homedir_list) {
308 		if (semanage_list_push(&homedir_list, PATH_DEFAULT_HOME)) {
309 			goto fail;
310 		}
311 	}
312 
313 	if (!stat(PATH_EXPORT_HOME, &buf)) {
314 		if (S_ISDIR(buf.st_mode)) {
315 			if (semanage_list_push(&homedir_list, PATH_EXPORT_HOME)) {
316 				goto fail;
317 			}
318 		}
319 	}
320 
321 	if (!(s->usepasswd))
322 		return homedir_list;
323 
324 	shells = get_shell_list();
325 	assert(shells);
326 
327 	path = semanage_findval(PATH_ETC_LOGIN_DEFS, "UID_MIN", NULL);
328 	if (path && *path) {
329 		temp = atoi(path);
330 		minuid = temp;
331 		minuid_set = 1;
332 	}
333 	free(path);
334 	path = NULL;
335 
336 	path = semanage_findval(PATH_ETC_LOGIN_DEFS, "UID_MAX", NULL);
337 	if (path && *path) {
338 		temp = atoi(path);
339 		maxuid = temp;
340 	}
341 	free(path);
342 	path = NULL;
343 
344 	path = semanage_findval(PATH_ETC_LIBUSER, "LU_UIDNUMBER", "=");
345 	if (path && *path) {
346 		temp = atoi(path);
347 		if (!minuid_set || temp < minuid) {
348 			minuid = temp;
349 			minuid_set = 1;
350 		}
351 	}
352 	free(path);
353 	path = NULL;
354 
355 	rbuflen = sysconf(_SC_GETPW_R_SIZE_MAX);
356 	if (rbuflen <= 0)
357 		goto fail;
358 	rbuf = malloc(rbuflen);
359 	if (rbuf == NULL)
360 		goto fail;
361 	setpwent();
362 	while ((retval = getpwent_r(&pwstorage, rbuf, rbuflen, &pwbuf)) == 0) {
363 		if (pwbuf->pw_uid < minuid || pwbuf->pw_uid > maxuid)
364 			continue;
365 		if (!semanage_list_find(shells, pwbuf->pw_shell))
366 			continue;
367 		int len = strlen(pwbuf->pw_dir) -1;
368 		for(; len > 0 && pwbuf->pw_dir[len] == '/'; len--) {
369 			pwbuf->pw_dir[len] = '\0';
370 		}
371 		if (strcmp(pwbuf->pw_dir, "/") == 0)
372 			continue;
373 		if (ignore(pwbuf->pw_dir))
374 			continue;
375 		if (semanage_str_count(pwbuf->pw_dir, '/') <= 1)
376 			continue;
377 		if (!(path = strdup(pwbuf->pw_dir))) {
378 			break;
379 		}
380 
381 		semanage_rtrim(path, '/');
382 
383 		if (!semanage_list_find(homedir_list, path)) {
384 			/*
385 			 * Now check for an existing file context that matches
386 			 * so we don't label a non-homedir as a homedir.
387 			 */
388 			hand.dir = path;
389 			hand.matched = 0;
390 			if (semanage_fcontext_iterate(s->h_semanage,
391 			    fcontext_matches, &hand) == STATUS_ERR)
392 				goto fail;
393 
394 			/* NOTE: old genhomedircon printed a warning on match */
395 			if (hand.matched) {
396 				WARN(s->h_semanage, "%s homedir %s or its parent directory conflicts with a file context already specified in the policy.  This usually indicates an incorrectly defined system account.  If it is a system account please make sure its uid is less than %u or greater than %u or its login shell is /sbin/nologin.", pwbuf->pw_name, pwbuf->pw_dir, minuid, maxuid);
397 			} else {
398 				if (semanage_list_push(&homedir_list, path))
399 					goto fail;
400 			}
401 		}
402 		free(path);
403 		path = NULL;
404 	}
405 
406 	if (retval && retval != ENOENT) {
407 		WARN(s->h_semanage, "Error while fetching users.  "
408 		     "Returning list so far.");
409 	}
410 
411 	if (semanage_list_sort(&homedir_list))
412 		goto fail;
413 
414 	endpwent();
415 	free(rbuf);
416 	semanage_list_destroy(&shells);
417 
418 	return homedir_list;
419 
420       fail:
421 	endpwent();
422 	free(rbuf);
423 	free(path);
424 	semanage_list_destroy(&homedir_list);
425 	semanage_list_destroy(&shells);
426 	return NULL;
427 }
428 
429 /**
430  * @param	out	the FILE to put all the output in.
431  * @return	0 on success
432  */
write_file_context_header(FILE * out)433 static int write_file_context_header(FILE * out)
434 {
435 	if (fprintf(out, COMMENT_FILE_CONTEXT_HEADER) < 0) {
436 		return STATUS_ERR;
437 	}
438 
439 	return STATUS_SUCCESS;
440 }
441 
442 /* Predicates for use with semanage_slurp_file_filter() the homedir_template
443  * file currently contains lines that serve as the template for a user's
444  * homedir.
445  *
446  * It also contains lines that are the template for the parent of a
447  * user's home directory.
448  *
449  * Currently, the only lines that apply to the the root of a user's home
450  * directory are all prefixed with the string "HOME_ROOT".  All other
451  * lines apply to a user's home directory.  If this changes the
452  * following predicates need to change to reflect that.
453  */
HOME_ROOT_PRED(const char * string)454 static int HOME_ROOT_PRED(const char *string)
455 {
456 	return semanage_is_prefix(string, TEMPLATE_HOME_ROOT);
457 }
458 
HOME_DIR_PRED(const char * string)459 static int HOME_DIR_PRED(const char *string)
460 {
461 	return semanage_is_prefix(string, TEMPLATE_HOME_DIR);
462 }
463 
USER_CONTEXT_PRED(const char * string)464 static int USER_CONTEXT_PRED(const char *string)
465 {
466 	return (int)(strstr(string, TEMPLATE_USER) != NULL);
467 }
468 
469 /* make_tempate
470  * @param	s	  the settings holding the paths to various files
471  * @param	pred	function pointer to function to use as filter for slurp
472  * 					file filter
473  * @return   a list of lines from the template file with inappropriate
474  *	    lines filtered out.
475  */
make_template(genhomedircon_settings_t * s,int (* pred)(const char *))476 static semanage_list_t *make_template(genhomedircon_settings_t * s,
477 				      int (*pred) (const char *))
478 {
479 	FILE *template_file = NULL;
480 	semanage_list_t *template_data = NULL;
481 
482 	template_file = fopen(s->homedir_template_path, "r");
483 	if (!template_file)
484 		return NULL;
485 	template_data = semanage_slurp_file_filter(template_file, pred);
486 	fclose(template_file);
487 
488 	return template_data;
489 }
490 
replace_all(const char * str,const replacement_pair_t * repl)491 static Ustr *replace_all(const char *str, const replacement_pair_t * repl)
492 {
493 	Ustr *retval = USTR_NULL;
494 	int i;
495 
496 	if (!str || !repl)
497 		goto done;
498 	if (!(retval = ustr_dup_cstr(str)))
499 		goto done;
500 
501 	for (i = 0; repl[i].search_for; i++) {
502 		ustr_replace_cstr(&retval, repl[i].search_for,
503 				  repl[i].replace_with, 0);
504 	}
505 	if (ustr_enomem(retval))
506 		ustr_sc_free(&retval);
507 
508       done:
509 	return retval;
510 }
511 
extract_context(Ustr * line)512 static const char * extract_context(Ustr *line)
513 {
514 	const char whitespace[] = " \t\n";
515 	size_t off, len;
516 
517 	/* check for trailing whitespace */
518 	off = ustr_spn_chrs_rev(line, 0, whitespace, strlen(whitespace));
519 
520 	/* find the length of the last field in line */
521 	len = ustr_cspn_chrs_rev(line, off, whitespace, strlen(whitespace));
522 
523 	if (len == 0)
524 		return NULL;
525 	return ustr_cstr(line) + ustr_len(line) - (len + off);
526 }
527 
check_line(genhomedircon_settings_t * s,Ustr * line)528 static int check_line(genhomedircon_settings_t * s, Ustr *line)
529 {
530 	sepol_context_t *ctx_record = NULL;
531 	const char *ctx_str;
532 	int result;
533 
534 	ctx_str = extract_context(line);
535 	if (!ctx_str)
536 		return STATUS_ERR;
537 
538 	result = sepol_context_from_string(s->h_semanage->sepolh,
539 					   ctx_str, &ctx_record);
540 	if (result == STATUS_SUCCESS && ctx_record != NULL) {
541 		sepol_msg_set_callback(s->h_semanage->sepolh, NULL, NULL);
542 		result = sepol_context_check(s->h_semanage->sepolh,
543 					     s->policydb, ctx_record);
544 		sepol_msg_set_callback(s->h_semanage->sepolh,
545 				       semanage_msg_relay_handler, s->h_semanage);
546 		sepol_context_free(ctx_record);
547 	}
548 	return result;
549 }
550 
write_home_dir_context(genhomedircon_settings_t * s,FILE * out,semanage_list_t * tpl,const char * user,const char * seuser,const char * home,const char * role_prefix,const char * level)551 static int write_home_dir_context(genhomedircon_settings_t * s, FILE * out,
552 				  semanage_list_t * tpl, const char *user,
553 				  const char *seuser, const char *home,
554 				  const char *role_prefix, const char *level)
555 {
556 	replacement_pair_t repl[] = {
557 		{.search_for = TEMPLATE_SEUSER,.replace_with = seuser},
558 		{.search_for = TEMPLATE_HOME_DIR,.replace_with = home},
559 		{.search_for = TEMPLATE_ROLE,.replace_with = role_prefix},
560 		{.search_for = TEMPLATE_LEVEL,.replace_with = level},
561 		{NULL, NULL}
562 	};
563 	Ustr *line = USTR_NULL;
564 
565 	if (fprintf(out, COMMENT_USER_HOME_CONTEXT, user) < 0)
566 		return STATUS_ERR;
567 
568 	for (; tpl; tpl = tpl->next) {
569 		line = replace_all(tpl->data, repl);
570 		if (!line)
571 			goto fail;
572 		if (check_line(s, line) == STATUS_SUCCESS) {
573 			if (!ustr_io_putfileline(&line, out))
574 				goto fail;
575 		}
576 		ustr_sc_free(&line);
577 	}
578 	return STATUS_SUCCESS;
579 
580       fail:
581 	ustr_sc_free(&line);
582 	return STATUS_ERR;
583 }
584 
write_home_root_context(genhomedircon_settings_t * s,FILE * out,semanage_list_t * tpl,char * homedir)585 static int write_home_root_context(genhomedircon_settings_t * s, FILE * out,
586 				   semanage_list_t * tpl, char *homedir)
587 {
588 	replacement_pair_t repl[] = {
589 		{.search_for = TEMPLATE_HOME_ROOT,.replace_with = homedir},
590 		{NULL, NULL}
591 	};
592 	Ustr *line = USTR_NULL;
593 
594 	for (; tpl; tpl = tpl->next) {
595 		line = replace_all(tpl->data, repl);
596 		if (!line)
597 			goto fail;
598 		if (check_line(s, line) == STATUS_SUCCESS) {
599 			if (!ustr_io_putfileline(&line, out))
600 				goto fail;
601 		}
602 		ustr_sc_free(&line);
603 	}
604 	return STATUS_SUCCESS;
605 
606       fail:
607 	ustr_sc_free(&line);
608 	return STATUS_ERR;
609 }
610 
write_user_context(genhomedircon_settings_t * s,FILE * out,semanage_list_t * tpl,const char * user,const char * seuser,const char * role_prefix)611 static int write_user_context(genhomedircon_settings_t * s, FILE * out,
612 			      semanage_list_t * tpl, const char *user,
613 			      const char *seuser, const char *role_prefix)
614 {
615 	replacement_pair_t repl[] = {
616 		{.search_for = TEMPLATE_USER,.replace_with = user},
617 		{.search_for = TEMPLATE_ROLE,.replace_with = role_prefix},
618 		{.search_for = TEMPLATE_SEUSER,.replace_with = seuser},
619 		{NULL, NULL}
620 	};
621 	Ustr *line = USTR_NULL;
622 
623 	for (; tpl; tpl = tpl->next) {
624 		line = replace_all(tpl->data, repl);
625 		if (!line)
626 			goto fail;
627 		if (check_line(s, line) == STATUS_SUCCESS) {
628 			if (!ustr_io_putfileline(&line, out))
629 				goto fail;
630 		}
631 		ustr_sc_free(&line);
632 	}
633 	return STATUS_SUCCESS;
634 
635       fail:
636 	ustr_sc_free(&line);
637 	return STATUS_ERR;
638 }
639 
user_sort_func(semanage_user_t ** arg1,semanage_user_t ** arg2)640 static int user_sort_func(semanage_user_t ** arg1, semanage_user_t ** arg2)
641 {
642 	return strcmp(semanage_user_get_name(*arg1),
643 		      semanage_user_get_name(*arg2));
644 }
645 
name_user_cmp(char * key,semanage_user_t ** val)646 static int name_user_cmp(char *key, semanage_user_t ** val)
647 {
648 	return strcmp(key, semanage_user_get_name(*val));
649 }
650 
push_user_entry(genhomedircon_user_entry_t ** list,const char * n,const char * sen,const char * pre,const char * h,const char * l)651 static int push_user_entry(genhomedircon_user_entry_t ** list, const char *n,
652 			   const char *sen, const char *pre, const char *h,
653 			   const char *l)
654 {
655 	genhomedircon_user_entry_t *temp = NULL;
656 	char *name = NULL;
657 	char *sename = NULL;
658 	char *prefix = NULL;
659 	char *home = NULL;
660 	char *level = NULL;
661 
662 	temp = malloc(sizeof(genhomedircon_user_entry_t));
663 	if (!temp)
664 		goto cleanup;
665 	name = strdup(n);
666 	if (!name)
667 		goto cleanup;
668 	sename = strdup(sen);
669 	if (!sename)
670 		goto cleanup;
671 	prefix = strdup(pre);
672 	if (!prefix)
673 		goto cleanup;
674 	home = strdup(h);
675 	if (!home)
676 		goto cleanup;
677 	level = strdup(l);
678 	if (!level)
679 		goto cleanup;
680 
681 	temp->name = name;
682 	temp->sename = sename;
683 	temp->prefix = prefix;
684 	temp->home = home;
685 	temp->level = level;
686 	temp->next = (*list);
687 	(*list) = temp;
688 
689 	return STATUS_SUCCESS;
690 
691       cleanup:
692 	free(name);
693 	free(sename);
694 	free(prefix);
695 	free(home);
696 	free(level);
697 	free(temp);
698 	return STATUS_ERR;
699 }
700 
pop_user_entry(genhomedircon_user_entry_t ** list)701 static void pop_user_entry(genhomedircon_user_entry_t ** list)
702 {
703 	genhomedircon_user_entry_t *temp;
704 
705 	if (!list || !(*list))
706 		return;
707 
708 	temp = *list;
709 	*list = temp->next;
710 	free(temp->name);
711 	free(temp->sename);
712 	free(temp->prefix);
713 	free(temp->home);
714 	free(temp->level);
715 	free(temp);
716 }
717 
set_fallback_user(genhomedircon_settings_t * s,const char * user,const char * prefix,const char * level)718 static int set_fallback_user(genhomedircon_settings_t *s, const char *user,
719 			     const char *prefix, const char *level)
720 {
721 	char *fallback_user = strdup(user);
722 	char *fallback_user_prefix = strdup(prefix);
723 	char *fallback_user_level = NULL;
724 	if (level)
725 		fallback_user_level = strdup(level);
726 
727 	if (fallback_user == NULL || fallback_user_prefix == NULL ||
728 	    (fallback_user_level == NULL && level != NULL)) {
729 		free(fallback_user);
730 		free(fallback_user_prefix);
731 		free(fallback_user_level);
732 		return STATUS_ERR;
733 	}
734 
735 	free(s->fallback_user);
736 	free(s->fallback_user_prefix);
737 	free(s->fallback_user_level);
738 	s->fallback_user = fallback_user;
739 	s->fallback_user_prefix = fallback_user_prefix;
740 	s->fallback_user_level = fallback_user_level;
741 	return STATUS_SUCCESS;
742 }
743 
setup_fallback_user(genhomedircon_settings_t * s)744 static int setup_fallback_user(genhomedircon_settings_t * s)
745 {
746 	semanage_seuser_t **seuser_list = NULL;
747 	unsigned int nseusers = 0;
748 	semanage_user_key_t *key = NULL;
749 	semanage_user_t *u = NULL;
750 	const char *name = NULL;
751 	const char *seuname = NULL;
752 	const char *prefix = NULL;
753 	const char *level = NULL;
754 	unsigned int i;
755 	int retval;
756 	int errors = 0;
757 
758 	retval = semanage_seuser_list(s->h_semanage, &seuser_list, &nseusers);
759 	if (retval < 0 || (nseusers < 1)) {
760 		/* if there are no users, this function can't do any other work */
761 		return errors;
762 	}
763 
764 	for (i = 0; i < nseusers; i++) {
765 		name = semanage_seuser_get_name(seuser_list[i]);
766 		if (strcmp(name, DEFAULT_LOGIN) == 0) {
767 			seuname = semanage_seuser_get_sename(seuser_list[i]);
768 
769 			/* find the user structure given the name */
770 			if (semanage_user_key_create(s->h_semanage, seuname,
771 						     &key) < 0) {
772 				errors = STATUS_ERR;
773 				break;
774 			}
775 			if (semanage_user_query(s->h_semanage, key, &u) < 0)
776 			{
777 				prefix = name;
778 				level = FALLBACK_USER_LEVEL;
779 			}
780 			else
781 			{
782 				prefix = semanage_user_get_prefix(u);
783 				level = semanage_user_get_mlslevel(u);
784 				if (!level)
785 					level = FALLBACK_USER_LEVEL;
786 			}
787 
788 			if (set_fallback_user(s, seuname, prefix, level) != 0)
789 				errors = STATUS_ERR;
790 			semanage_user_key_free(key);
791 			if (u)
792 				semanage_user_free(u);
793 			break;
794 		}
795 	}
796 
797 	for (i = 0; i < nseusers; i++)
798 		semanage_seuser_free(seuser_list[i]);
799 	free(seuser_list);
800 
801 	return errors;
802 }
803 
get_users(genhomedircon_settings_t * s,int * errors)804 static genhomedircon_user_entry_t *get_users(genhomedircon_settings_t * s,
805 					     int *errors)
806 {
807 	genhomedircon_user_entry_t *head = NULL;
808 	semanage_seuser_t **seuser_list = NULL;
809 	unsigned int nseusers = 0;
810 	semanage_user_t **user_list = NULL;
811 	unsigned int nusers = 0;
812 	semanage_user_t **u = NULL;
813 	const char *name = NULL;
814 	const char *seuname = NULL;
815 	const char *prefix = NULL;
816 	const char *level = NULL;
817 	struct passwd pwstorage, *pwent = NULL;
818 	unsigned int i;
819 	long rbuflen;
820 	char *rbuf = NULL;
821 	int retval;
822 
823 	*errors = 0;
824 	retval = semanage_seuser_list(s->h_semanage, &seuser_list, &nseusers);
825 	if (retval < 0 || (nseusers < 1)) {
826 		/* if there are no users, this function can't do any other work */
827 		return NULL;
828 	}
829 
830 	if (semanage_user_list(s->h_semanage, &user_list, &nusers) < 0) {
831 		nusers = 0;
832 	}
833 
834 	qsort(user_list, nusers, sizeof(semanage_user_t *),
835 	      (int (*)(const void *, const void *))&user_sort_func);
836 
837 	/* Allocate space for the getpwnam_r buffer */
838 	rbuflen = sysconf(_SC_GETPW_R_SIZE_MAX);
839 	if (rbuflen <= 0)
840 		goto cleanup;
841 	rbuf = malloc(rbuflen);
842 	if (rbuf == NULL)
843 		goto cleanup;
844 
845 	for (i = 0; i < nseusers; i++) {
846 		seuname = semanage_seuser_get_sename(seuser_list[i]);
847 		name = semanage_seuser_get_name(seuser_list[i]);
848 
849 		if (strcmp(name,"root") && strcmp(seuname, s->fallback_user) == 0)
850 			continue;
851 
852 		if (strcmp(name, DEFAULT_LOGIN) == 0)
853 			continue;
854 
855 		if (strcmp(name, TEMPLATE_SEUSER) == 0)
856 			continue;
857 
858 		/* %groupname syntax */
859 		if (name[0] == '%')
860 			continue;
861 
862 		/* find the user structure given the name */
863 		u = bsearch(seuname, user_list, nusers, sizeof(semanage_user_t *),
864 			    (int (*)(const void *, const void *))
865 			    &name_user_cmp);
866 		if (u) {
867 			prefix = semanage_user_get_prefix(*u);
868 			level = semanage_user_get_mlslevel(*u);
869 			if (!level)
870 				level = FALLBACK_USER_LEVEL;
871 		} else {
872 			prefix = name;
873 			level = FALLBACK_USER_LEVEL;
874 		}
875 
876 		retval = getpwnam_r(name, &pwstorage, rbuf, rbuflen, &pwent);
877 		if (retval != 0 || pwent == NULL) {
878 			if (retval != 0 && retval != ENOENT) {
879 				*errors = STATUS_ERR;
880 				goto cleanup;
881 			}
882 
883 			WARN(s->h_semanage,
884 			     "user %s not in password file", name);
885 			continue;
886 		}
887 
888 		int len = strlen(pwent->pw_dir) -1;
889 		for(; len > 0 && pwent->pw_dir[len] == '/'; len--) {
890 			pwent->pw_dir[len] = '\0';
891 		}
892 
893 		if (strcmp(pwent->pw_dir, "/") == 0) {
894 			/* don't relabel / genhomdircon checked to see if root
895 			 * was the user and if so, set his home directory to
896 			 * /root */
897 			continue;
898 		}
899 		if (ignore(pwent->pw_dir))
900 			continue;
901 		if (push_user_entry(&head, name, seuname,
902 				    prefix, pwent->pw_dir, level) != STATUS_SUCCESS) {
903 			*errors = STATUS_ERR;
904 			break;
905 		}
906 	}
907 
908       cleanup:
909 	free(rbuf);
910 	if (*errors) {
911 		for (; head; pop_user_entry(&head)) {
912 			/* the pop function takes care of all the cleanup
913 			   so the loop body is just empty */
914 		}
915 	}
916 	for (i = 0; i < nseusers; i++) {
917 		semanage_seuser_free(seuser_list[i]);
918 	}
919 	free(seuser_list);
920 
921 	for (i = 0; i < nusers; i++) {
922 		semanage_user_free(user_list[i]);
923 	}
924 	free(user_list);
925 
926 	return head;
927 }
928 
write_gen_home_dir_context(genhomedircon_settings_t * s,FILE * out,semanage_list_t * user_context_tpl,semanage_list_t * homedir_context_tpl)929 static int write_gen_home_dir_context(genhomedircon_settings_t * s, FILE * out,
930 				      semanage_list_t * user_context_tpl,
931 				      semanage_list_t * homedir_context_tpl)
932 {
933 	genhomedircon_user_entry_t *users;
934 	int errors = 0;
935 
936 	users = get_users(s, &errors);
937 	if (!users && errors) {
938 		return STATUS_ERR;
939 	}
940 
941 	for (; users; pop_user_entry(&users)) {
942 		if (write_home_dir_context(s, out, homedir_context_tpl,
943 					   users->name,
944 					   users->sename, users->home,
945 					   users->prefix, users->level))
946 			goto err;
947 		if (write_user_context(s, out, user_context_tpl, users->name,
948 				       users->sename, users->prefix))
949 			goto err;
950 	}
951 
952 	return STATUS_SUCCESS;
953 err:
954 	for (; users; pop_user_entry(&users)) {
955 	/* the pop function takes care of all the cleanup
956 	 * so the loop body is just empty */
957 	}
958 
959 	return STATUS_ERR;
960 }
961 
962 /**
963  * @param	s	settings structure, stores various paths etc. Must never be NULL
964  * @param	out	the FILE to put all the output in.
965  * @return	0 on success
966  */
write_context_file(genhomedircon_settings_t * s,FILE * out)967 static int write_context_file(genhomedircon_settings_t * s, FILE * out)
968 {
969 	semanage_list_t *homedirs = NULL;
970 	semanage_list_t *h = NULL;
971 	semanage_list_t *user_context_tpl = NULL;
972 	semanage_list_t *homedir_context_tpl = NULL;
973 	semanage_list_t *homeroot_context_tpl = NULL;
974 	int retval = STATUS_SUCCESS;
975 
976 	homedir_context_tpl = make_template(s, &HOME_DIR_PRED);
977 	homeroot_context_tpl = make_template(s, &HOME_ROOT_PRED);
978 	user_context_tpl = make_template(s, &USER_CONTEXT_PRED);
979 
980 	if (!homedir_context_tpl && !homeroot_context_tpl && !user_context_tpl)
981 		goto done;
982 
983 	if (write_file_context_header(out) != STATUS_SUCCESS) {
984 		retval = STATUS_ERR;
985 		goto done;
986 	}
987 
988 	if (setup_fallback_user(s) != 0) {
989 		retval = STATUS_ERR;
990 		goto done;
991 	}
992 
993 	if (homedir_context_tpl || homeroot_context_tpl) {
994 		homedirs = get_home_dirs(s);
995 		if (!homedirs) {
996 			WARN(s->h_semanage,
997 			     "no home directories were available, exiting without writing");
998 			goto done;
999 		}
1000 
1001 		for (h = homedirs; h; h = h->next) {
1002 			Ustr *temp = ustr_dup_cstr(h->data);
1003 
1004 			if (!temp || !ustr_add_cstr(&temp, "/[^/]*")) {
1005 				ustr_sc_free(&temp);
1006 				retval = STATUS_ERR;
1007 				goto done;
1008 			}
1009 
1010 			if (write_home_dir_context(s, out,
1011 						   homedir_context_tpl,
1012 						   s->fallback_user, s->fallback_user,
1013 						   ustr_cstr(temp),
1014 						   s->fallback_user_prefix, s->fallback_user_level) !=
1015 			    STATUS_SUCCESS) {
1016 				ustr_sc_free(&temp);
1017 				retval = STATUS_ERR;
1018 				goto done;
1019 			}
1020 			if (write_home_root_context(s, out,
1021 						    homeroot_context_tpl,
1022 						    h->data) != STATUS_SUCCESS) {
1023 				ustr_sc_free(&temp);
1024 				retval = STATUS_ERR;
1025 				goto done;
1026 			}
1027 
1028 			ustr_sc_free(&temp);
1029 		}
1030 	}
1031 	if (user_context_tpl) {
1032 		if (write_user_context(s, out, user_context_tpl,
1033 				       ".*", s->fallback_user,
1034 				       s->fallback_user_prefix) != STATUS_SUCCESS) {
1035 			retval = STATUS_ERR;
1036 			goto done;
1037 		}
1038 
1039 		if (write_gen_home_dir_context(s, out, user_context_tpl,
1040 					       homedir_context_tpl) != STATUS_SUCCESS) {
1041 			retval = STATUS_ERR;
1042 		}
1043 	}
1044 
1045 done:
1046 	/* Cleanup */
1047 	semanage_list_destroy(&homedirs);
1048 	semanage_list_destroy(&user_context_tpl);
1049 	semanage_list_destroy(&homedir_context_tpl);
1050 	semanage_list_destroy(&homeroot_context_tpl);
1051 
1052 	return retval;
1053 }
1054 
semanage_genhomedircon(semanage_handle_t * sh,sepol_policydb_t * policydb,int usepasswd,char * ignoredirs)1055 int semanage_genhomedircon(semanage_handle_t * sh,
1056 			   sepol_policydb_t * policydb,
1057 			   int usepasswd,
1058 			   char *ignoredirs)
1059 {
1060 	genhomedircon_settings_t s;
1061 	FILE *out = NULL;
1062 	int retval = 0;
1063 
1064 	assert(sh);
1065 
1066 	s.homedir_template_path =
1067 	    semanage_path(SEMANAGE_TMP, SEMANAGE_HOMEDIR_TMPL);
1068 	s.fcfilepath = semanage_final_path(SEMANAGE_FINAL_TMP,
1069 					   SEMANAGE_FC_HOMEDIRS);
1070 
1071 	s.fallback_user = strdup(FALLBACK_USER);
1072 	s.fallback_user_prefix = strdup(FALLBACK_USER_PREFIX);
1073 	s.fallback_user_level = strdup(FALLBACK_USER_LEVEL);
1074 	if (s.fallback_user == NULL || s.fallback_user_prefix == NULL || s.fallback_user_level == NULL) {
1075 		retval = STATUS_ERR;
1076 		goto done;
1077 	}
1078 
1079 	if (ignoredirs) ignore_setup(ignoredirs);
1080 
1081 	s.usepasswd = usepasswd;
1082 	s.h_semanage = sh;
1083 	s.policydb = policydb;
1084 
1085 	if (!(out = fopen(s.fcfilepath, "w"))) {
1086 		/* couldn't open output file */
1087 		ERR(sh, "Could not open the file_context file for writing");
1088 		retval = STATUS_ERR;
1089 		goto done;
1090 	}
1091 
1092 	retval = write_context_file(&s, out);
1093 
1094 done:
1095 	if (out != NULL)
1096 		fclose(out);
1097 
1098 	free(s.fallback_user);
1099 	free(s.fallback_user_prefix);
1100 	free(s.fallback_user_level);
1101 	ignore_free();
1102 
1103 	return retval;
1104 }
1105