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
38#include <assert.h>
39#include <ctype.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#include <grp.h>
52#include <search.h>
53
54/* paths used in get_home_dirs() */
55#define PATH_ETC_USERADD "/etc/default/useradd"
56#define PATH_ETC_LIBUSER "/etc/libuser.conf"
57#define PATH_DEFAULT_HOME "/home"
58#define PATH_EXPORT_HOME "/export/home"
59#define PATH_ETC_LOGIN_DEFS "/etc/login.defs"
60
61/* other paths */
62#define PATH_SHELLS_FILE "/etc/shells"
63#define PATH_NOLOGIN_SHELL "/sbin/nologin"
64
65/* comments written to context file */
66#define COMMENT_FILE_CONTEXT_HEADER "#\n#\n# " \
67			"User-specific file contexts, generated via libsemanage\n" \
68			"# use semanage command to manage system users to change" \
69			" the file_context\n#\n#\n"
70
71#define COMMENT_USER_HOME_CONTEXT "\n\n#\n# Home Context for user %s" \
72			"\n#\n\n"
73
74/* placeholders used in the template file
75   which are searched for and replaced */
76#define TEMPLATE_HOME_ROOT "HOME_ROOT"
77#define TEMPLATE_HOME_DIR "HOME_DIR"
78/* these are legacy */
79#define TEMPLATE_USER "USER"
80#define TEMPLATE_ROLE "ROLE"
81/* new names */
82#define TEMPLATE_USERNAME "%{USERNAME}"
83#define TEMPLATE_USERID "%{USERID}"
84
85#define FALLBACK_SENAME "user_u"
86#define FALLBACK_PREFIX "user"
87#define FALLBACK_LEVEL "s0"
88#define FALLBACK_NAME "[^/]+"
89#define FALLBACK_UIDGID "[0-9]+"
90#define DEFAULT_LOGIN "__default__"
91
92#define CONTEXT_NONE "<<none>>"
93
94typedef struct user_entry {
95	char *name;
96	char *uid;
97	char *gid;
98	char *sename;
99	char *prefix;
100	char *home;
101	char *level;
102	char *login;
103	char *homedir_role;
104	struct user_entry *next;
105} genhomedircon_user_entry_t;
106
107typedef struct {
108	const char *fcfilepath;
109	int usepasswd;
110	const char *homedir_template_path;
111	genhomedircon_user_entry_t *fallback;
112	semanage_handle_t *h_semanage;
113	sepol_policydb_t *policydb;
114} genhomedircon_settings_t;
115
116typedef struct {
117	const char *search_for;
118	const char *replace_with;
119} replacement_pair_t;
120
121typedef struct {
122	const char *dir;
123	int matched;
124} fc_match_handle_t;
125
126typedef struct IgnoreDir {
127	struct IgnoreDir *next;
128	char *dir;
129} ignoredir_t;
130
131ignoredir_t *ignore_head = NULL;
132
133static void ignore_free(void) {
134	ignoredir_t *next;
135
136	while (ignore_head) {
137		next = ignore_head->next;
138		free(ignore_head->dir);
139		free(ignore_head);
140		ignore_head = next;
141	}
142}
143
144static int ignore_setup(char *ignoredirs) {
145	char *tok;
146	ignoredir_t *ptr = NULL;
147
148	tok = strtok(ignoredirs, ";");
149	while(tok) {
150		ptr = calloc(sizeof(ignoredir_t),1);
151		if (!ptr)
152			goto err;
153		ptr->dir = strdup(tok);
154		if (!ptr->dir)
155			goto err;
156
157		ptr->next = ignore_head;
158		ignore_head = ptr;
159
160		tok = strtok(NULL, ";");
161	}
162
163	return 0;
164err:
165	free(ptr);
166	ignore_free();
167	return -1;
168}
169
170static int ignore(const char *homedir) {
171	ignoredir_t *ptr = ignore_head;
172	while (ptr) {
173		if (strcmp(ptr->dir, homedir) == 0) {
174			return 1;
175		}
176		ptr = ptr->next;
177	}
178	return 0;
179}
180
181static int prefix_is_homedir_role(const semanage_user_t *user,
182				  const char *prefix)
183{
184	return strcmp(OBJECT_R, prefix) == 0 ||
185		semanage_user_has_role(user, prefix);
186}
187
188static semanage_list_t *default_shell_list(void)
189{
190	semanage_list_t *list = NULL;
191
192	if (semanage_list_push(&list, "/bin/csh")
193	    || semanage_list_push(&list, "/bin/tcsh")
194	    || semanage_list_push(&list, "/bin/ksh")
195	    || semanage_list_push(&list, "/bin/bsh")
196	    || semanage_list_push(&list, "/bin/ash")
197	    || semanage_list_push(&list, "/usr/bin/ksh")
198	    || semanage_list_push(&list, "/usr/bin/pdksh")
199	    || semanage_list_push(&list, "/bin/zsh")
200	    || semanage_list_push(&list, "/bin/sh")
201	    || semanage_list_push(&list, "/bin/bash"))
202		goto fail;
203
204	return list;
205
206      fail:
207	semanage_list_destroy(&list);
208	return NULL;
209}
210
211static semanage_list_t *get_shell_list(void)
212{
213	FILE *shells;
214	char *temp = NULL;
215	semanage_list_t *list = NULL;
216	size_t buff_len = 0;
217	ssize_t len;
218
219	shells = fopen(PATH_SHELLS_FILE, "r");
220	if (!shells)
221		return default_shell_list();
222	while ((len = getline(&temp, &buff_len, shells)) > 0) {
223		if (temp[len-1] == '\n') temp[len-1] = 0;
224		if (strcmp(temp, PATH_NOLOGIN_SHELL)) {
225			if (semanage_list_push(&list, temp)) {
226				free(temp);
227				semanage_list_destroy(&list);
228				return default_shell_list();
229			}
230		}
231	}
232	free(temp);
233
234	return list;
235}
236
237/* Helper function called via semanage_fcontext_iterate() */
238static int fcontext_matches(const semanage_fcontext_t *fcontext, void *varg)
239{
240	const char *oexpr = semanage_fcontext_get_expr(fcontext);
241	fc_match_handle_t *handp = varg;
242	char *expr = NULL;
243	regex_t re;
244	int type, retval = -1;
245	size_t len;
246
247	/* Only match ALL or DIR */
248	type = semanage_fcontext_get_type(fcontext);
249	if (type != SEMANAGE_FCONTEXT_ALL && type != SEMANAGE_FCONTEXT_DIR)
250		return 0;
251
252	len = strlen(oexpr);
253	/* Define a macro to strip a literal string from the end of oexpr */
254#define rstrip_oexpr_len(cstr, cstrlen) \
255	do { \
256		if (len >= (cstrlen) && !strncmp(oexpr + len - (cstrlen), (cstr), (cstrlen))) \
257			len -= (cstrlen); \
258	} while (0)
259#define rstrip_oexpr(cstr) rstrip_oexpr_len(cstr, sizeof(cstr) - 1)
260
261	rstrip_oexpr(".+");
262	rstrip_oexpr(".*");
263	rstrip_oexpr("(/.*)?");
264	rstrip_oexpr("/");
265
266#undef rstrip_oexpr_len
267#undef rstrip_oexpr
268
269	/* Anchor oexpr at the beginning and append pattern to eat up trailing slashes */
270	if (asprintf(&expr, "^%.*s/*$", (int)len, oexpr) < 0)
271		return -1;
272
273	/* Check dir against expr */
274	if (regcomp(&re, expr, REG_EXTENDED) != 0)
275		goto done;
276	if (regexec(&re, handp->dir, 0, NULL, 0) == 0)
277		handp->matched = 1;
278	regfree(&re);
279
280	retval = 0;
281
282done:
283	free(expr);
284
285	return retval;
286}
287
288static semanage_list_t *get_home_dirs(genhomedircon_settings_t * s)
289{
290	semanage_list_t *homedir_list = NULL;
291	semanage_list_t *shells = NULL;
292	fc_match_handle_t hand;
293	char *path = NULL;
294	uid_t temp, minuid = 500, maxuid = 60000;
295	int minuid_set = 0;
296	struct passwd *pwbuf;
297	struct stat buf;
298
299	path = semanage_findval(PATH_ETC_USERADD, "HOME", "=");
300	if (path && *path) {
301		if (semanage_list_push(&homedir_list, path))
302			goto fail;
303	}
304	free(path);
305
306	path = semanage_findval(PATH_ETC_LIBUSER, "LU_HOMEDIRECTORY", "=");
307	if (path && *path) {
308		if (semanage_list_push(&homedir_list, path))
309			goto fail;
310	}
311	free(path);
312	path = NULL;
313
314	if (!homedir_list) {
315		if (semanage_list_push(&homedir_list, PATH_DEFAULT_HOME)) {
316			goto fail;
317		}
318	}
319
320	if (!stat(PATH_EXPORT_HOME, &buf)) {
321		if (S_ISDIR(buf.st_mode)) {
322			if (semanage_list_push(&homedir_list, PATH_EXPORT_HOME)) {
323				goto fail;
324			}
325		}
326	}
327
328	if (!(s->usepasswd))
329		return homedir_list;
330
331	shells = get_shell_list();
332	assert(shells);
333
334	path = semanage_findval(PATH_ETC_LOGIN_DEFS, "UID_MIN", NULL);
335	if (path && *path) {
336		temp = atoi(path);
337		minuid = temp;
338		minuid_set = 1;
339	}
340	free(path);
341	path = NULL;
342
343	path = semanage_findval(PATH_ETC_LOGIN_DEFS, "UID_MAX", NULL);
344	if (path && *path) {
345		temp = atoi(path);
346		maxuid = temp;
347	}
348	free(path);
349	path = NULL;
350
351	path = semanage_findval(PATH_ETC_LIBUSER, "LU_UIDNUMBER", "=");
352	if (path && *path) {
353		temp = atoi(path);
354		if (!minuid_set || temp < minuid) {
355			minuid = temp;
356			minuid_set = 1;
357		}
358	}
359	free(path);
360	path = NULL;
361
362	errno = 0;
363	setpwent();
364	while ((pwbuf = getpwent()) != NULL) {
365		if (pwbuf->pw_uid < minuid || pwbuf->pw_uid > maxuid)
366			continue;
367		if (!semanage_list_find(shells, pwbuf->pw_shell))
368			continue;
369		int len = strlen(pwbuf->pw_dir) -1;
370		for(; len > 0 && pwbuf->pw_dir[len] == '/'; len--) {
371			pwbuf->pw_dir[len] = '\0';
372		}
373		if (strcmp(pwbuf->pw_dir, "/") == 0)
374			continue;
375		if (ignore(pwbuf->pw_dir))
376			continue;
377		if (semanage_str_count(pwbuf->pw_dir, '/') <= 1)
378			continue;
379		if (!(path = strdup(pwbuf->pw_dir))) {
380			break;
381		}
382
383		semanage_rtrim(path, '/');
384
385		if (!semanage_list_find(homedir_list, path)) {
386			/*
387			 * Now check for an existing file context that matches
388			 * so we don't label a non-homedir as a homedir.
389			 */
390			hand.dir = path;
391			hand.matched = 0;
392			if (semanage_fcontext_iterate(s->h_semanage,
393			    fcontext_matches, &hand) == STATUS_ERR)
394				goto fail;
395
396			/* NOTE: old genhomedircon printed a warning on match */
397			if (hand.matched) {
398				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);
399			} else {
400				if (semanage_list_push(&homedir_list, path))
401					goto fail;
402			}
403		}
404		free(path);
405		path = NULL;
406		errno = 0;
407	}
408
409	if (errno) {
410		WARN(s->h_semanage, "Error while fetching users.  "
411		     "Returning list so far.");
412	}
413
414	if (semanage_list_sort(&homedir_list))
415		goto fail;
416
417	endpwent();
418	semanage_list_destroy(&shells);
419
420	return homedir_list;
421
422      fail:
423	endpwent();
424	free(path);
425	semanage_list_destroy(&homedir_list);
426	semanage_list_destroy(&shells);
427	return NULL;
428}
429
430/**
431 * @param	out	the FILE to put all the output in.
432 * @return	0 on success
433 */
434static int write_file_context_header(FILE * out)
435{
436	if (fprintf(out, COMMENT_FILE_CONTEXT_HEADER) < 0) {
437		return STATUS_ERR;
438	}
439
440	return STATUS_SUCCESS;
441}
442
443/* Predicates for use with semanage_slurp_file_filter() the homedir_template
444 * file currently contains lines that serve as the template for a user's
445 * homedir.
446 *
447 * It also contains lines that are the template for the parent of a
448 * user's home directory.
449 *
450 * Currently, the only lines that apply to the the root of a user's home
451 * directory are all prefixed with the string "HOME_ROOT".  All other
452 * lines apply to a user's home directory.  If this changes the
453 * following predicates need to change to reflect that.
454 */
455static int HOME_ROOT_PRED(const char *string)
456{
457	return semanage_is_prefix(string, TEMPLATE_HOME_ROOT);
458}
459
460static int HOME_DIR_PRED(const char *string)
461{
462	return semanage_is_prefix(string, TEMPLATE_HOME_DIR);
463}
464
465/* new names */
466static int USERNAME_CONTEXT_PRED(const char *string)
467{
468	return (int)(
469		(strstr(string, TEMPLATE_USERNAME) != NULL) ||
470		(strstr(string, TEMPLATE_USERID) != NULL)
471	);
472}
473
474/* This will never match USER if USERNAME or USERID are found. */
475static int USER_CONTEXT_PRED(const char *string)
476{
477	if (USERNAME_CONTEXT_PRED(string))
478		return 0;
479
480	return (int)(strstr(string, TEMPLATE_USER) != NULL);
481}
482
483static int STR_COMPARATOR(const void *a, const void *b)
484{
485	return strcmp((const char *) a, (const char *) b);
486}
487
488/* make_tempate
489 * @param	s	  the settings holding the paths to various files
490 * @param	pred	function pointer to function to use as filter for slurp
491 * 					file filter
492 * @return   a list of lines from the template file with inappropriate
493 *	    lines filtered out.
494 */
495static semanage_list_t *make_template(genhomedircon_settings_t * s,
496				      int (*pred) (const char *))
497{
498	FILE *template_file = NULL;
499	semanage_list_t *template_data = NULL;
500
501	template_file = fopen(s->homedir_template_path, "r");
502	if (!template_file)
503		return NULL;
504	template_data = semanage_slurp_file_filter(template_file, pred);
505	fclose(template_file);
506
507	return template_data;
508}
509
510static char *replace_all(const char *str, const replacement_pair_t * repl)
511{
512	char *retval, *retval2;
513	int i;
514
515	if (!str || !repl)
516		return NULL;
517
518	retval = strdup(str);
519	for (i = 0; retval != NULL && repl[i].search_for; i++) {
520		retval2 = semanage_str_replace(repl[i].search_for,
521					       repl[i].replace_with, retval, 0);
522		free(retval);
523		retval = retval2;
524	}
525	return retval;
526}
527
528static const char *extract_context(const char *line)
529{
530	const char *p = line;
531	size_t off;
532
533	off = strlen(p);
534	p += off;
535	/* consider trailing whitespaces */
536	while (off > 0) {
537		p--;
538		off--;
539		if (!isspace(*p))
540			break;
541	}
542	if (off == 0)
543		return NULL;
544
545	/* find the last field in line */
546	while (off > 0 && !isspace(*(p - 1))) {
547		p--;
548		off--;
549	}
550	return p;
551}
552
553static int check_line(genhomedircon_settings_t * s, const char *line)
554{
555	sepol_context_t *ctx_record = NULL;
556	const char *ctx_str;
557	int result;
558
559	ctx_str = extract_context(line);
560	if (!ctx_str)
561		return STATUS_ERR;
562
563	result = sepol_context_from_string(s->h_semanage->sepolh,
564					   ctx_str, &ctx_record);
565	if (result == STATUS_SUCCESS && ctx_record != NULL) {
566		result = sepol_context_check(s->h_semanage->sepolh,
567					     s->policydb, ctx_record);
568		sepol_context_free(ctx_record);
569	}
570	return result;
571}
572
573static int write_replacements(genhomedircon_settings_t * s, FILE * out,
574			      const semanage_list_t * tpl,
575			      const replacement_pair_t *repl)
576{
577	char *line;
578
579	for (; tpl; tpl = tpl->next) {
580		line = replace_all(tpl->data, repl);
581		if (!line)
582			goto fail;
583		if (check_line(s, line) == STATUS_SUCCESS) {
584			if (fprintf(out, "%s\n", line) < 0)
585				goto fail;
586		}
587		free(line);
588	}
589	return STATUS_SUCCESS;
590
591      fail:
592	free(line);
593	return STATUS_ERR;
594}
595
596static int write_contexts(genhomedircon_settings_t *s, FILE *out,
597			  semanage_list_t *tpl, const replacement_pair_t *repl,
598			  const genhomedircon_user_entry_t *user)
599{
600	char *line, *temp;
601	sepol_context_t *context;
602	char *new_context_str;
603
604	for (; tpl; tpl = tpl->next) {
605		context = NULL;
606		new_context_str = NULL;
607		line = replace_all(tpl->data, repl);
608		if (!line) {
609			goto fail;
610		}
611
612		const char *old_context_str = extract_context(line);
613		if (!old_context_str) {
614			goto fail;
615		}
616
617		if (strcmp(old_context_str, CONTEXT_NONE) == 0) {
618			if (check_line(s, line) == STATUS_SUCCESS &&
619			    fprintf(out, "%s\n", line) < 0) {
620				goto fail;
621			}
622			free(line);
623			continue;
624		}
625
626		sepol_handle_t *sepolh = s->h_semanage->sepolh;
627
628		if (sepol_context_from_string(sepolh, old_context_str,
629					      &context) < 0) {
630			goto fail;
631		}
632
633		if (sepol_context_set_user(sepolh, context, user->sename) < 0) {
634			goto fail;
635		}
636
637		if (sepol_policydb_mls_enabled(s->policydb) &&
638		    sepol_context_set_mls(sepolh, context, user->level) < 0) {
639			goto fail;
640		}
641
642		if (user->homedir_role &&
643		    sepol_context_set_role(sepolh, context, user->homedir_role) < 0) {
644			goto fail;
645		}
646
647		if (sepol_context_to_string(sepolh, context,
648					    &new_context_str) < 0) {
649			goto fail;
650		}
651
652		temp = semanage_str_replace(old_context_str, new_context_str,
653					    line, 1);
654		if (!temp) {
655			goto fail;
656		}
657		free(line);
658		line = temp;
659
660		if (check_line(s, line) == STATUS_SUCCESS) {
661			if (fprintf(out, "%s\n", line) < 0)
662				goto fail;
663		}
664
665		free(line);
666		sepol_context_free(context);
667		free(new_context_str);
668	}
669
670	return STATUS_SUCCESS;
671fail:
672	free(line);
673	sepol_context_free(context);
674	free(new_context_str);
675	return STATUS_ERR;
676}
677
678static int write_home_dir_context(genhomedircon_settings_t * s, FILE * out,
679				  semanage_list_t * tpl, const genhomedircon_user_entry_t *user)
680{
681	replacement_pair_t repl[] = {
682		{.search_for = TEMPLATE_HOME_DIR,.replace_with = user->home},
683		{.search_for = TEMPLATE_ROLE,.replace_with = user->prefix},
684		{NULL, NULL}
685	};
686
687	if (strcmp(user->name, FALLBACK_NAME) == 0) {
688		if (fprintf(out, COMMENT_USER_HOME_CONTEXT, FALLBACK_SENAME) < 0)
689			return STATUS_ERR;
690	} else {
691		if (fprintf(out, COMMENT_USER_HOME_CONTEXT, user->name) < 0)
692			return STATUS_ERR;
693	}
694
695	return write_contexts(s, out, tpl, repl, user);
696}
697
698static int write_home_root_context(genhomedircon_settings_t * s, FILE * out,
699				   semanage_list_t * tpl, char *homedir)
700{
701	replacement_pair_t repl[] = {
702		{.search_for = TEMPLATE_HOME_ROOT,.replace_with = homedir},
703		{NULL, NULL}
704	};
705
706	return write_replacements(s, out, tpl, repl);
707}
708
709static int write_username_context(genhomedircon_settings_t * s, FILE * out,
710				  semanage_list_t * tpl,
711				  const genhomedircon_user_entry_t *user)
712{
713	replacement_pair_t repl[] = {
714		{.search_for = TEMPLATE_USERNAME,.replace_with = user->name},
715		{.search_for = TEMPLATE_USERID,.replace_with = user->uid},
716		{.search_for = TEMPLATE_ROLE,.replace_with = user->prefix},
717		{NULL, NULL}
718	};
719
720	return write_contexts(s, out, tpl, repl, user);
721}
722
723static int write_user_context(genhomedircon_settings_t * s, FILE * out,
724			      semanage_list_t * tpl, const genhomedircon_user_entry_t *user)
725{
726	replacement_pair_t repl[] = {
727		{.search_for = TEMPLATE_USER,.replace_with = user->name},
728		{.search_for = TEMPLATE_ROLE,.replace_with = user->prefix},
729		{NULL, NULL}
730	};
731
732	return write_contexts(s, out, tpl, repl, user);
733}
734
735static int seuser_sort_func(const void *arg1, const void *arg2)
736{
737	const semanage_seuser_t **u1 = (const semanage_seuser_t **) arg1;
738	const semanage_seuser_t **u2 = (const semanage_seuser_t **) arg2;;
739	const char *name1 = semanage_seuser_get_name(*u1);
740	const char *name2 = semanage_seuser_get_name(*u2);
741
742	if (name1[0] == '%' && name2[0] == '%') {
743		return 0;
744	} else if (name1[0] == '%') {
745		return 1;
746	} else if (name2[0] == '%') {
747		return -1;
748	}
749
750	return strcmp(name1, name2);
751}
752
753static int user_sort_func(semanage_user_t ** arg1, semanage_user_t ** arg2)
754{
755	return strcmp(semanage_user_get_name(*arg1),
756		      semanage_user_get_name(*arg2));
757}
758
759static int name_user_cmp(char *key, semanage_user_t ** val)
760{
761	return strcmp(key, semanage_user_get_name(*val));
762}
763
764static int push_user_entry(genhomedircon_user_entry_t ** list, const char *n,
765			   const char *u, const char *g, const char *sen,
766			   const char *pre, const char *h, const char *l,
767			   const char *ln, const char *hd_role)
768{
769	genhomedircon_user_entry_t *temp = NULL;
770	char *name = NULL;
771	char *uid = NULL;
772	char *gid = NULL;
773	char *sename = NULL;
774	char *prefix = NULL;
775	char *home = NULL;
776	char *level = NULL;
777	char *lname = NULL;
778	char *homedir_role = NULL;
779
780	temp = malloc(sizeof(genhomedircon_user_entry_t));
781	if (!temp)
782		goto cleanup;
783	name = strdup(n);
784	if (!name)
785		goto cleanup;
786	uid = strdup(u);
787	if (!uid)
788		goto cleanup;
789	gid = strdup(g);
790	if (!gid)
791		goto cleanup;
792	sename = strdup(sen);
793	if (!sename)
794		goto cleanup;
795	prefix = strdup(pre);
796	if (!prefix)
797		goto cleanup;
798	home = strdup(h);
799	if (!home)
800		goto cleanup;
801	level = strdup(l);
802	if (!level)
803		goto cleanup;
804	lname = strdup(ln);
805	if (!lname)
806		goto cleanup;
807	if (hd_role) {
808		homedir_role = strdup(hd_role);
809		if (!homedir_role)
810			goto cleanup;
811	}
812
813	temp->name = name;
814	temp->uid = uid;
815	temp->gid = gid;
816	temp->sename = sename;
817	temp->prefix = prefix;
818	temp->home = home;
819	temp->level = level;
820	temp->login = lname;
821	temp->homedir_role = homedir_role;
822	temp->next = (*list);
823	(*list) = temp;
824
825	return STATUS_SUCCESS;
826
827      cleanup:
828	free(name);
829	free(uid);
830	free(gid);
831	free(sename);
832	free(prefix);
833	free(home);
834	free(level);
835	free(lname);
836	free(homedir_role);
837	free(temp);
838	return STATUS_ERR;
839}
840
841static void pop_user_entry(genhomedircon_user_entry_t ** list)
842{
843	genhomedircon_user_entry_t *temp;
844
845	if (!list || !(*list))
846		return;
847
848	temp = *list;
849	*list = temp->next;
850	free(temp->name);
851	free(temp->uid);
852	free(temp->gid);
853	free(temp->sename);
854	free(temp->prefix);
855	free(temp->home);
856	free(temp->level);
857	free(temp->login);
858	free(temp->homedir_role);
859	free(temp);
860}
861
862static int setup_fallback_user(genhomedircon_settings_t * s)
863{
864	semanage_seuser_t **seuser_list = NULL;
865	unsigned int nseusers = 0;
866	semanage_user_key_t *key = NULL;
867	semanage_user_t *u = NULL;
868	const char *name = NULL;
869	const char *seuname = NULL;
870	const char *prefix = NULL;
871	const char *level = NULL;
872	const char *homedir_role = NULL;
873	unsigned int i;
874	int retval;
875	int errors = 0;
876
877	retval = semanage_seuser_list(s->h_semanage, &seuser_list, &nseusers);
878	if (retval < 0 || (nseusers < 1)) {
879		/* if there are no users, this function can't do any other work */
880		return errors;
881	}
882
883	for (i = 0; i < nseusers; i++) {
884		name = semanage_seuser_get_name(seuser_list[i]);
885		if (strcmp(name, DEFAULT_LOGIN) == 0) {
886			seuname = semanage_seuser_get_sename(seuser_list[i]);
887
888			/* find the user structure given the name */
889			if (semanage_user_key_create(s->h_semanage, seuname,
890						     &key) < 0) {
891				errors = STATUS_ERR;
892				break;
893			}
894			if (semanage_user_query(s->h_semanage, key, &u) < 0)
895			{
896				prefix = name;
897				level = FALLBACK_LEVEL;
898			}
899			else
900			{
901				prefix = semanage_user_get_prefix(u);
902				level = semanage_user_get_mlslevel(u);
903				if (!level)
904					level = FALLBACK_LEVEL;
905			}
906
907			if (prefix_is_homedir_role(u, prefix)) {
908				homedir_role = prefix;
909			}
910
911			if (push_user_entry(&(s->fallback), FALLBACK_NAME,
912					    FALLBACK_UIDGID, FALLBACK_UIDGID,
913					    seuname, prefix, "", level,
914					    FALLBACK_NAME, homedir_role) != 0)
915				errors = STATUS_ERR;
916			semanage_user_key_free(key);
917			if (u)
918				semanage_user_free(u);
919			break;
920		}
921	}
922
923	for (i = 0; i < nseusers; i++)
924		semanage_seuser_free(seuser_list[i]);
925	free(seuser_list);
926
927	return errors;
928}
929
930static genhomedircon_user_entry_t *find_user(genhomedircon_user_entry_t *head,
931					     const char *name)
932{
933	for(; head; head = head->next) {
934		if (strcmp(head->name, name) == 0) {
935			return head;
936		}
937	}
938
939	return NULL;
940}
941
942static int add_user(genhomedircon_settings_t * s,
943		    genhomedircon_user_entry_t **head,
944		    semanage_user_t *user,
945		    const char *name,
946		    const char *sename,
947		    const char *selogin)
948{
949	if (selogin[0] == '%') {
950		genhomedircon_user_entry_t *orig = find_user(*head, name);
951		if (orig != NULL && orig->login[0] == '%') {
952			ERR(s->h_semanage, "User %s is already mapped to"
953			    " group %s, but also belongs to group %s. Add an"
954			    " explicit mapping for this user to"
955			    " override group mappings.",
956			    name, orig->login + 1, selogin + 1);
957			return STATUS_ERR;
958		} else if (orig != NULL) {
959			// user mappings take precedence
960			return STATUS_SUCCESS;
961		}
962	}
963
964	int retval = STATUS_ERR;
965
966	char *rbuf = NULL;
967	long rbuflen;
968	struct passwd pwstorage, *pwent = NULL;
969	const char *prefix = NULL;
970	const char *level = NULL;
971	const char *homedir_role = NULL;
972	char uid[11];
973	char gid[11];
974
975	/* Allocate space for the getpwnam_r buffer */
976	rbuflen = sysconf(_SC_GETPW_R_SIZE_MAX);
977	if (rbuflen <= 0)
978		goto cleanup;
979	rbuf = malloc(rbuflen);
980	if (rbuf == NULL)
981		goto cleanup;
982
983	if (user) {
984		prefix = semanage_user_get_prefix(user);
985		level = semanage_user_get_mlslevel(user);
986
987		if (!level) {
988			level = FALLBACK_LEVEL;
989		}
990	} else {
991		prefix = name;
992		level = FALLBACK_LEVEL;
993	}
994
995	if (prefix_is_homedir_role(user, prefix)) {
996		homedir_role = prefix;
997	}
998
999	retval = getpwnam_r(name, &pwstorage, rbuf, rbuflen, &pwent);
1000	if (retval != 0 || pwent == NULL) {
1001		if (retval != 0 && retval != ENOENT) {
1002			goto cleanup;
1003		}
1004
1005		WARN(s->h_semanage,
1006		     "user %s not in password file", name);
1007		retval = STATUS_SUCCESS;
1008		goto cleanup;
1009	}
1010
1011	int len = strlen(pwent->pw_dir) -1;
1012	for(; len > 0 && pwent->pw_dir[len] == '/'; len--) {
1013		pwent->pw_dir[len] = '\0';
1014	}
1015
1016	if (strcmp(pwent->pw_dir, "/") == 0) {
1017		/* don't relabel / genhomdircon checked to see if root
1018		 * was the user and if so, set his home directory to
1019		 * /root */
1020		retval = STATUS_SUCCESS;
1021		goto cleanup;
1022	}
1023
1024	if (ignore(pwent->pw_dir)) {
1025		retval = STATUS_SUCCESS;
1026		goto cleanup;
1027	}
1028
1029	len = snprintf(uid, sizeof(uid), "%u", pwent->pw_uid);
1030	if (len < 0 || len >= (int)sizeof(uid)) {
1031		goto cleanup;
1032	}
1033
1034	len = snprintf(gid, sizeof(gid), "%u", pwent->pw_gid);
1035	if (len < 0 || len >= (int)sizeof(gid)) {
1036		goto cleanup;
1037	}
1038
1039	retval = push_user_entry(head, name, uid, gid, sename, prefix,
1040				pwent->pw_dir, level, selogin, homedir_role);
1041cleanup:
1042	free(rbuf);
1043	return retval;
1044}
1045
1046static int get_group_users(genhomedircon_settings_t * s,
1047			  genhomedircon_user_entry_t **head,
1048			  semanage_user_t *user,
1049			  const char *sename,
1050			  const char *selogin)
1051{
1052	int retval = STATUS_ERR;
1053	unsigned int i;
1054
1055	long grbuflen;
1056	char *grbuf = NULL;
1057	struct group grstorage, *group = NULL;
1058	struct passwd *pw = NULL;
1059
1060	grbuflen = sysconf(_SC_GETGR_R_SIZE_MAX);
1061	if (grbuflen <= 0)
1062		goto cleanup;
1063	grbuf = malloc(grbuflen);
1064	if (grbuf == NULL)
1065		goto cleanup;
1066
1067	const char *grname = selogin + 1;
1068
1069	if (getgrnam_r(grname, &grstorage, grbuf,
1070			(size_t) grbuflen, &group) != 0) {
1071		goto cleanup;
1072	}
1073
1074	if (group == NULL) {
1075		ERR(s->h_semanage, "Can't find group named %s\n", grname);
1076		goto cleanup;
1077	}
1078
1079	size_t nmembers = 0;
1080	char **members = group->gr_mem;
1081
1082	while (*members != NULL) {
1083		nmembers++;
1084		members++;
1085	}
1086
1087	for (i = 0; i < nmembers; i++) {
1088		const char *uname = group->gr_mem[i];
1089
1090		if (add_user(s, head, user, uname, sename, selogin) < 0) {
1091			goto cleanup;
1092		}
1093	}
1094
1095	setpwent();
1096	while ((pw = getpwent()) != NULL) {
1097		// skip users who also have this group as their
1098		// primary group
1099		if (lfind(pw->pw_name, group->gr_mem, &nmembers,
1100			  sizeof(char *), &STR_COMPARATOR)) {
1101			continue;
1102		}
1103
1104		if (group->gr_gid == pw->pw_gid) {
1105			if (add_user(s, head, user, pw->pw_name,
1106				     sename, selogin) < 0) {
1107				goto cleanup;
1108			}
1109		}
1110	}
1111
1112	retval = STATUS_SUCCESS;
1113cleanup:
1114	endpwent();
1115	free(grbuf);
1116
1117	return retval;
1118}
1119
1120static genhomedircon_user_entry_t *get_users(genhomedircon_settings_t * s,
1121					     int *errors)
1122{
1123	genhomedircon_user_entry_t *head = NULL;
1124	semanage_seuser_t **seuser_list = NULL;
1125	unsigned int nseusers = 0;
1126	semanage_user_t **user_list = NULL;
1127	unsigned int nusers = 0;
1128	semanage_user_t **u = NULL;
1129	const char *name = NULL;
1130	const char *seuname = NULL;
1131	unsigned int i;
1132	int retval;
1133
1134	*errors = 0;
1135	retval = semanage_seuser_list(s->h_semanage, &seuser_list, &nseusers);
1136	if (retval < 0 || (nseusers < 1)) {
1137		/* if there are no users, this function can't do any other work */
1138		return NULL;
1139	}
1140
1141	if (semanage_user_list(s->h_semanage, &user_list, &nusers) < 0) {
1142		nusers = 0;
1143	}
1144
1145	qsort(seuser_list, nseusers, sizeof(semanage_seuser_t *),
1146	      &seuser_sort_func);
1147	qsort(user_list, nusers, sizeof(semanage_user_t *),
1148	      (int (*)(const void *, const void *))&user_sort_func);
1149
1150	for (i = 0; i < nseusers; i++) {
1151		seuname = semanage_seuser_get_sename(seuser_list[i]);
1152		name = semanage_seuser_get_name(seuser_list[i]);
1153
1154		if (strcmp(name, DEFAULT_LOGIN) == 0)
1155			continue;
1156
1157		/* find the user structure given the name */
1158		u = bsearch(seuname, user_list, nusers, sizeof(semanage_user_t *),
1159			    (int (*)(const void *, const void *))
1160			    &name_user_cmp);
1161
1162		/* %groupname syntax */
1163		if (name[0] == '%') {
1164			retval = get_group_users(s, &head, *u, seuname,
1165						name);
1166		} else {
1167			retval = add_user(s, &head, *u, name,
1168					  seuname, name);
1169		}
1170
1171		if (retval != 0) {
1172			*errors = STATUS_ERR;
1173			goto cleanup;
1174		}
1175	}
1176
1177      cleanup:
1178	if (*errors) {
1179		for (; head; pop_user_entry(&head)) {
1180			/* the pop function takes care of all the cleanup
1181			   so the loop body is just empty */
1182		}
1183	}
1184	for (i = 0; i < nseusers; i++) {
1185		semanage_seuser_free(seuser_list[i]);
1186	}
1187	free(seuser_list);
1188
1189	for (i = 0; i < nusers; i++) {
1190		semanage_user_free(user_list[i]);
1191	}
1192	free(user_list);
1193
1194	return head;
1195}
1196
1197static int write_gen_home_dir_context(genhomedircon_settings_t * s, FILE * out,
1198				      semanage_list_t * username_context_tpl,
1199				      semanage_list_t * user_context_tpl,
1200				      semanage_list_t * homedir_context_tpl)
1201{
1202	genhomedircon_user_entry_t *users;
1203	int errors = 0;
1204
1205	users = get_users(s, &errors);
1206	if (!users && errors) {
1207		return STATUS_ERR;
1208	}
1209
1210	for (; users; pop_user_entry(&users)) {
1211		if (write_home_dir_context(s, out, homedir_context_tpl, users))
1212			goto err;
1213		if (write_username_context(s, out, username_context_tpl, users))
1214			goto err;
1215		if (write_user_context(s, out, user_context_tpl, users))
1216			goto err;
1217	}
1218
1219	return STATUS_SUCCESS;
1220err:
1221	for (; users; pop_user_entry(&users)) {
1222	/* the pop function takes care of all the cleanup
1223	 * so the loop body is just empty */
1224	}
1225
1226	return STATUS_ERR;
1227}
1228
1229/**
1230 * @param	s	settings structure, stores various paths etc. Must never be NULL
1231 * @param	out	the FILE to put all the output in.
1232 * @return	0 on success
1233 */
1234static int write_context_file(genhomedircon_settings_t * s, FILE * out)
1235{
1236	semanage_list_t *homedirs = NULL;
1237	semanage_list_t *h = NULL;
1238	semanage_list_t *homedir_context_tpl = NULL;
1239	semanage_list_t *homeroot_context_tpl = NULL;
1240	semanage_list_t *username_context_tpl = NULL;
1241	semanage_list_t *user_context_tpl = NULL;
1242	int retval = STATUS_SUCCESS;
1243
1244	homedir_context_tpl = make_template(s, &HOME_DIR_PRED);
1245	homeroot_context_tpl = make_template(s, &HOME_ROOT_PRED);
1246	username_context_tpl = make_template(s, &USERNAME_CONTEXT_PRED);
1247	user_context_tpl = make_template(s, &USER_CONTEXT_PRED);
1248
1249	if (!homedir_context_tpl
1250	 && !homeroot_context_tpl
1251	 && !username_context_tpl
1252	 && !user_context_tpl)
1253		goto done;
1254
1255	if (write_file_context_header(out) != STATUS_SUCCESS) {
1256		retval = STATUS_ERR;
1257		goto done;
1258	}
1259
1260	if (setup_fallback_user(s) != 0) {
1261		retval = STATUS_ERR;
1262		goto done;
1263	}
1264
1265	if (homedir_context_tpl || homeroot_context_tpl) {
1266		homedirs = get_home_dirs(s);
1267		if (!homedirs) {
1268			WARN(s->h_semanage,
1269			     "no home directories were available, exiting without writing");
1270			goto done;
1271		}
1272
1273		for (h = homedirs; h; h = h->next) {
1274			char *temp = NULL;
1275
1276			if (asprintf(&temp, "%s/%s", h->data, FALLBACK_NAME) < 0) {
1277				retval = STATUS_ERR;
1278				goto done;
1279			}
1280
1281			free(s->fallback->home);
1282			s->fallback->home = temp;
1283
1284			if (write_home_dir_context(s, out, homedir_context_tpl,
1285						   s->fallback) != STATUS_SUCCESS) {
1286				free(temp);
1287				s->fallback->home = NULL;
1288				retval = STATUS_ERR;
1289				goto done;
1290			}
1291			if (write_home_root_context(s, out,
1292						    homeroot_context_tpl,
1293						    h->data) != STATUS_SUCCESS) {
1294				free(temp);
1295				s->fallback->home = NULL;
1296				retval = STATUS_ERR;
1297				goto done;
1298			}
1299
1300			free(temp);
1301			s->fallback->home = NULL;
1302		}
1303	}
1304	if (user_context_tpl || username_context_tpl) {
1305		if (write_username_context(s, out, username_context_tpl,
1306					   s->fallback) != STATUS_SUCCESS) {
1307			retval = STATUS_ERR;
1308			goto done;
1309		}
1310
1311		if (write_user_context(s, out, user_context_tpl,
1312				       s->fallback) != STATUS_SUCCESS) {
1313			retval = STATUS_ERR;
1314			goto done;
1315		}
1316
1317		if (write_gen_home_dir_context(s, out, username_context_tpl,
1318					       user_context_tpl, homedir_context_tpl)
1319				!= STATUS_SUCCESS) {
1320			retval = STATUS_ERR;
1321		}
1322	}
1323
1324done:
1325	/* Cleanup */
1326	semanage_list_destroy(&homedirs);
1327	semanage_list_destroy(&username_context_tpl);
1328	semanage_list_destroy(&user_context_tpl);
1329	semanage_list_destroy(&homedir_context_tpl);
1330	semanage_list_destroy(&homeroot_context_tpl);
1331
1332	return retval;
1333}
1334
1335int semanage_genhomedircon(semanage_handle_t * sh,
1336			   sepol_policydb_t * policydb,
1337			   int usepasswd,
1338			   char *ignoredirs)
1339{
1340	genhomedircon_settings_t s;
1341	FILE *out = NULL;
1342	int retval = 0;
1343
1344	assert(sh);
1345
1346	s.homedir_template_path =
1347	    semanage_path(SEMANAGE_TMP, SEMANAGE_HOMEDIR_TMPL);
1348	s.fcfilepath =
1349		semanage_path(SEMANAGE_TMP, SEMANAGE_STORE_FC_HOMEDIRS);
1350
1351	s.fallback = calloc(1, sizeof(genhomedircon_user_entry_t));
1352	if (s.fallback == NULL) {
1353		retval = STATUS_ERR;
1354		goto done;
1355	}
1356
1357	s.fallback->name = strdup(FALLBACK_NAME);
1358	s.fallback->sename = strdup(FALLBACK_SENAME);
1359	s.fallback->prefix = strdup(FALLBACK_PREFIX);
1360	s.fallback->level = strdup(FALLBACK_LEVEL);
1361	if (s.fallback->name == NULL
1362	 || s.fallback->sename == NULL
1363	 || s.fallback->prefix == NULL
1364	 || s.fallback->level == NULL) {
1365		retval = STATUS_ERR;
1366		goto done;
1367	}
1368
1369	if (ignoredirs) ignore_setup(ignoredirs);
1370
1371	s.usepasswd = usepasswd;
1372	s.h_semanage = sh;
1373	s.policydb = policydb;
1374
1375	if (!(out = fopen(s.fcfilepath, "w"))) {
1376		/* couldn't open output file */
1377		ERR(sh, "Could not open the file_context file for writing");
1378		retval = STATUS_ERR;
1379		goto done;
1380	}
1381
1382	retval = write_context_file(&s, out);
1383
1384done:
1385	if (out != NULL)
1386		fclose(out);
1387
1388	while (s.fallback)
1389		pop_user_entry(&(s.fallback));
1390
1391	ignore_free();
1392
1393	return retval;
1394}
1395