1 /* Author: Mark Goldman <[email protected]>
2 * Paul Rosenfeld <[email protected]>
3 * Todd C. Miller <[email protected]>
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(1, sizeof(ignoredir_t));
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_template
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
989 if (user) {
990 prefix = semanage_user_get_prefix(user);
991 level = semanage_user_get_mlslevel(user);
992
993 if (!level) {
994 level = FALLBACK_LEVEL;
995 }
996 } else {
997 prefix = name;
998 level = FALLBACK_LEVEL;
999 }
1000
1001 if (prefix_is_homedir_role(user, prefix)) {
1002 homedir_role = prefix;
1003 }
1004
1005 retry:
1006 rbuf = malloc(rbuflen);
1007 if (rbuf == NULL)
1008 goto cleanup;
1009
1010 retval = getpwnam_r(name, &pwstorage, rbuf, rbuflen, &pwent);
1011 if (retval == ERANGE && rbuflen < LONG_MAX / 2) {
1012 free(rbuf);
1013 rbuflen *= 2;
1014 goto retry;
1015 }
1016 if (retval != 0 || pwent == NULL) {
1017 if (retval != 0 && retval != ENOENT) {
1018 goto cleanup;
1019 }
1020
1021 WARN(s->h_semanage,
1022 "user %s not in password file", name);
1023 retval = STATUS_SUCCESS;
1024 goto cleanup;
1025 }
1026
1027 int len = strlen(pwent->pw_dir) -1;
1028 for(; len > 0 && pwent->pw_dir[len] == '/'; len--) {
1029 pwent->pw_dir[len] = '\0';
1030 }
1031
1032 if (strcmp(pwent->pw_dir, "/") == 0) {
1033 /* don't relabel / genhomdircon checked to see if root
1034 * was the user and if so, set his home directory to
1035 * /root */
1036 retval = STATUS_SUCCESS;
1037 goto cleanup;
1038 }
1039
1040 if (ignore(pwent->pw_dir)) {
1041 retval = STATUS_SUCCESS;
1042 goto cleanup;
1043 }
1044
1045 len = snprintf(uid, sizeof(uid), "%u", pwent->pw_uid);
1046 if (len < 0 || len >= (int)sizeof(uid)) {
1047 goto cleanup;
1048 }
1049
1050 len = snprintf(gid, sizeof(gid), "%u", pwent->pw_gid);
1051 if (len < 0 || len >= (int)sizeof(gid)) {
1052 goto cleanup;
1053 }
1054
1055 retval = push_user_entry(head, name, uid, gid, sename, prefix,
1056 pwent->pw_dir, level, selogin, homedir_role);
1057 cleanup:
1058 free(rbuf);
1059 return retval;
1060 }
1061
get_group_users(genhomedircon_settings_t * s,genhomedircon_user_entry_t ** head,semanage_user_t * user,const char * sename,const char * selogin)1062 static int get_group_users(genhomedircon_settings_t * s,
1063 genhomedircon_user_entry_t **head,
1064 semanage_user_t *user,
1065 const char *sename,
1066 const char *selogin)
1067 {
1068 int retval = STATUS_ERR;
1069 unsigned int i;
1070
1071 long grbuflen;
1072 char *grbuf = NULL;
1073 struct group grstorage, *group = NULL;
1074 struct passwd *pw = NULL;
1075
1076 errno = 0;
1077 grbuflen = sysconf(_SC_GETGR_R_SIZE_MAX);
1078 if (grbuflen == -1 && errno == 0)
1079 /* sysconf returning -1 with no errno means indeterminate size */
1080 grbuflen = 1024;
1081 else if (grbuflen <= 0)
1082 goto cleanup;
1083 grbuf = malloc(grbuflen);
1084 if (grbuf == NULL)
1085 goto cleanup;
1086
1087 const char *grname = selogin + 1;
1088
1089 errno = 0;
1090 while (
1091 (retval = getgrnam_r(grname, &grstorage, grbuf, (size_t) grbuflen, &group)) != 0 &&
1092 errno == ERANGE
1093 ) {
1094 char *new_grbuf;
1095 grbuflen *= 2;
1096 if (grbuflen < 0)
1097 /* the member list could exceed 2Gb on a system with a 32-bit CPU (where
1098 * sizeof(long) = 4) - if this ever happened, the loop would become infinite. */
1099 goto cleanup;
1100 new_grbuf = realloc(grbuf, grbuflen);
1101 if (new_grbuf == NULL)
1102 goto cleanup;
1103 grbuf = new_grbuf;
1104 }
1105 if (retval != 0)
1106 goto cleanup;
1107
1108 if (group == NULL) {
1109 ERR(s->h_semanage, "Can't find group named %s\n", grname);
1110 goto cleanup;
1111 }
1112
1113 size_t nmembers = 0;
1114 char **members = group->gr_mem;
1115
1116 while (*members != NULL) {
1117 nmembers++;
1118 members++;
1119 }
1120
1121 for (i = 0; i < nmembers; i++) {
1122 const char *uname = group->gr_mem[i];
1123
1124 if (add_user(s, head, user, uname, sename, selogin) < 0) {
1125 goto cleanup;
1126 }
1127 }
1128
1129 setpwent();
1130 while (1) {
1131 errno = 0;
1132 pw = getpwent();
1133 if (pw == NULL)
1134 break;
1135 // skip users who also have this group as their
1136 // primary group
1137 if (lfind(pw->pw_name, group->gr_mem, &nmembers,
1138 sizeof(char *), &STR_COMPARATOR)) {
1139 continue;
1140 }
1141
1142 if (group->gr_gid == pw->pw_gid) {
1143 if (add_user(s, head, user, pw->pw_name,
1144 sename, selogin) < 0) {
1145 goto cleanup;
1146 }
1147 }
1148 }
1149
1150 retval = STATUS_SUCCESS;
1151 cleanup:
1152 endpwent();
1153 free(grbuf);
1154
1155 return retval;
1156 }
1157
get_users(genhomedircon_settings_t * s,int * errors)1158 static genhomedircon_user_entry_t *get_users(genhomedircon_settings_t * s,
1159 int *errors)
1160 {
1161 genhomedircon_user_entry_t *head = NULL;
1162 semanage_seuser_t **seuser_list = NULL;
1163 unsigned int nseusers = 0;
1164 semanage_user_t **user_list = NULL;
1165 unsigned int nusers = 0;
1166 semanage_user_t **u = NULL;
1167 const char *name = NULL;
1168 const char *seuname = NULL;
1169 unsigned int i;
1170 int retval;
1171
1172 *errors = 0;
1173 retval = semanage_seuser_list(s->h_semanage, &seuser_list, &nseusers);
1174 if (retval < 0 || (nseusers < 1)) {
1175 /* if there are no users, this function can't do any other work */
1176 return NULL;
1177 }
1178
1179 if (semanage_user_list(s->h_semanage, &user_list, &nusers) < 0) {
1180 nusers = 0;
1181 }
1182
1183 qsort(seuser_list, nseusers, sizeof(semanage_seuser_t *),
1184 &seuser_sort_func);
1185 qsort(user_list, nusers, sizeof(semanage_user_t *),
1186 (int (*)(const void *, const void *))&user_sort_func);
1187
1188 for (i = 0; i < nseusers; i++) {
1189 seuname = semanage_seuser_get_sename(seuser_list[i]);
1190 name = semanage_seuser_get_name(seuser_list[i]);
1191
1192 if (strcmp(name, DEFAULT_LOGIN) == 0)
1193 continue;
1194
1195 /* find the user structure given the name */
1196 u = bsearch(seuname, user_list, nusers, sizeof(semanage_user_t *),
1197 (int (*)(const void *, const void *))
1198 &name_user_cmp);
1199
1200 /* %groupname syntax */
1201 if (name[0] == '%') {
1202 retval = get_group_users(s, &head, *u, seuname,
1203 name);
1204 } else {
1205 retval = add_user(s, &head, *u, name,
1206 seuname, name);
1207 }
1208
1209 if (retval != 0) {
1210 *errors = STATUS_ERR;
1211 goto cleanup;
1212 }
1213 }
1214
1215 cleanup:
1216 if (*errors) {
1217 for (; head; pop_user_entry(&head)) {
1218 /* the pop function takes care of all the cleanup
1219 so the loop body is just empty */
1220 }
1221 }
1222 for (i = 0; i < nseusers; i++) {
1223 semanage_seuser_free(seuser_list[i]);
1224 }
1225 free(seuser_list);
1226
1227 for (i = 0; i < nusers; i++) {
1228 semanage_user_free(user_list[i]);
1229 }
1230 free(user_list);
1231
1232 return head;
1233 }
1234
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)1235 static int write_gen_home_dir_context(genhomedircon_settings_t * s, FILE * out,
1236 semanage_list_t * username_context_tpl,
1237 semanage_list_t * user_context_tpl,
1238 semanage_list_t * homedir_context_tpl)
1239 {
1240 genhomedircon_user_entry_t *users;
1241 int errors = 0;
1242
1243 users = get_users(s, &errors);
1244 if (!users && errors) {
1245 return STATUS_ERR;
1246 }
1247
1248 for (; users; pop_user_entry(&users)) {
1249 if (write_home_dir_context(s, out, homedir_context_tpl, users))
1250 goto err;
1251 if (write_username_context(s, out, username_context_tpl, users))
1252 goto err;
1253 if (write_user_context(s, out, user_context_tpl, users))
1254 goto err;
1255 }
1256
1257 return STATUS_SUCCESS;
1258 err:
1259 for (; users; pop_user_entry(&users)) {
1260 /* the pop function takes care of all the cleanup
1261 * so the loop body is just empty */
1262 }
1263
1264 return STATUS_ERR;
1265 }
1266
1267 /**
1268 * @param s settings structure, stores various paths etc. Must never be NULL
1269 * @param out the FILE to put all the output in.
1270 * @return 0 on success
1271 */
write_context_file(genhomedircon_settings_t * s,FILE * out)1272 static int write_context_file(genhomedircon_settings_t * s, FILE * out)
1273 {
1274 semanage_list_t *homedirs = NULL;
1275 semanage_list_t *h = NULL;
1276 semanage_list_t *homedir_context_tpl = NULL;
1277 semanage_list_t *homeroot_context_tpl = NULL;
1278 semanage_list_t *username_context_tpl = NULL;
1279 semanage_list_t *user_context_tpl = NULL;
1280 int retval = STATUS_SUCCESS;
1281
1282 homedir_context_tpl = make_template(s, &HOME_DIR_PRED);
1283 homeroot_context_tpl = make_template(s, &HOME_ROOT_PRED);
1284 username_context_tpl = make_template(s, &USERNAME_CONTEXT_PRED);
1285 user_context_tpl = make_template(s, &USER_CONTEXT_PRED);
1286
1287 if (!homedir_context_tpl
1288 && !homeroot_context_tpl
1289 && !username_context_tpl
1290 && !user_context_tpl)
1291 goto done;
1292
1293 if (write_file_context_header(out) != STATUS_SUCCESS) {
1294 retval = STATUS_ERR;
1295 goto done;
1296 }
1297
1298 if (setup_fallback_user(s) != 0) {
1299 retval = STATUS_ERR;
1300 goto done;
1301 }
1302
1303 if (homedir_context_tpl || homeroot_context_tpl) {
1304 homedirs = get_home_dirs(s);
1305 if (!homedirs) {
1306 WARN(s->h_semanage,
1307 "no home directories were available, exiting without writing");
1308 goto done;
1309 }
1310
1311 for (h = homedirs; h; h = h->next) {
1312 char *temp = NULL;
1313
1314 if (asprintf(&temp, "%s/%s", h->data, FALLBACK_NAME) < 0) {
1315 retval = STATUS_ERR;
1316 goto done;
1317 }
1318
1319 free(s->fallback->home);
1320 s->fallback->home = temp;
1321
1322 if (write_home_dir_context(s, out, homedir_context_tpl,
1323 s->fallback) != STATUS_SUCCESS) {
1324 free(temp);
1325 s->fallback->home = NULL;
1326 retval = STATUS_ERR;
1327 goto done;
1328 }
1329 if (write_home_root_context(s, out,
1330 homeroot_context_tpl,
1331 h->data) != STATUS_SUCCESS) {
1332 free(temp);
1333 s->fallback->home = NULL;
1334 retval = STATUS_ERR;
1335 goto done;
1336 }
1337
1338 free(temp);
1339 s->fallback->home = NULL;
1340 }
1341 }
1342 if (user_context_tpl || username_context_tpl) {
1343 if (write_username_context(s, out, username_context_tpl,
1344 s->fallback) != STATUS_SUCCESS) {
1345 retval = STATUS_ERR;
1346 goto done;
1347 }
1348
1349 if (write_user_context(s, out, user_context_tpl,
1350 s->fallback) != STATUS_SUCCESS) {
1351 retval = STATUS_ERR;
1352 goto done;
1353 }
1354
1355 if (write_gen_home_dir_context(s, out, username_context_tpl,
1356 user_context_tpl, homedir_context_tpl)
1357 != STATUS_SUCCESS) {
1358 retval = STATUS_ERR;
1359 }
1360 }
1361
1362 done:
1363 /* Cleanup */
1364 semanage_list_destroy(&homedirs);
1365 semanage_list_destroy(&username_context_tpl);
1366 semanage_list_destroy(&user_context_tpl);
1367 semanage_list_destroy(&homedir_context_tpl);
1368 semanage_list_destroy(&homeroot_context_tpl);
1369
1370 return retval;
1371 }
1372
semanage_genhomedircon(semanage_handle_t * sh,sepol_policydb_t * policydb,int usepasswd,char * ignoredirs)1373 int semanage_genhomedircon(semanage_handle_t * sh,
1374 sepol_policydb_t * policydb,
1375 int usepasswd,
1376 char *ignoredirs)
1377 {
1378 genhomedircon_settings_t s;
1379 FILE *out = NULL;
1380 int retval = 0;
1381
1382 assert(sh);
1383
1384 s.homedir_template_path =
1385 semanage_path(SEMANAGE_TMP, SEMANAGE_HOMEDIR_TMPL);
1386 s.fcfilepath =
1387 semanage_path(SEMANAGE_TMP, SEMANAGE_STORE_FC_HOMEDIRS);
1388
1389 s.fallback = calloc(1, sizeof(genhomedircon_user_entry_t));
1390 if (s.fallback == NULL) {
1391 retval = STATUS_ERR;
1392 goto done;
1393 }
1394
1395 s.fallback->name = strdup(FALLBACK_NAME);
1396 s.fallback->sename = strdup(FALLBACK_SENAME);
1397 s.fallback->prefix = strdup(FALLBACK_PREFIX);
1398 s.fallback->level = strdup(FALLBACK_LEVEL);
1399 if (s.fallback->name == NULL
1400 || s.fallback->sename == NULL
1401 || s.fallback->prefix == NULL
1402 || s.fallback->level == NULL) {
1403 retval = STATUS_ERR;
1404 goto done;
1405 }
1406
1407 if (ignoredirs) ignore_setup(ignoredirs);
1408
1409 s.usepasswd = usepasswd;
1410 s.h_semanage = sh;
1411 s.policydb = policydb;
1412
1413 if (!(out = fopen(s.fcfilepath, "w"))) {
1414 /* couldn't open output file */
1415 ERR(sh, "Could not open the file_context file for writing");
1416 retval = STATUS_ERR;
1417 goto done;
1418 }
1419
1420 retval = write_context_file(&s, out);
1421
1422 done:
1423 if (out != NULL)
1424 fclose(out);
1425
1426 while (s.fallback)
1427 pop_user_entry(&(s.fallback));
1428
1429 ignore_free();
1430
1431 return retval;
1432 }
1433