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