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
94 typedef 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
107 typedef 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
116 typedef struct {
117 const char *search_for;
118 const char *replace_with;
119 } replacement_pair_t;
120
121 typedef struct {
122 const char *dir;
123 int matched;
124 } fc_match_handle_t;
125
126 typedef struct IgnoreDir {
127 struct IgnoreDir *next;
128 char *dir;
129 } ignoredir_t;
130
131 ignoredir_t *ignore_head = NULL;
132
ignore_free(void)133 static 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
ignore_setup(char * ignoredirs)144 static 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;
164 err:
165 free(ptr);
166 ignore_free();
167 return -1;
168 }
169
ignore(const char * homedir)170 static 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
prefix_is_homedir_role(const semanage_user_t * user,const char * prefix)181 static 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
default_shell_list(void)188 static 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
get_shell_list(void)211 static 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() */
fcontext_matches(const semanage_fcontext_t * fcontext,void * varg)238 static 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
282 done:
283 free(expr);
284
285 return retval;
286 }
287
get_home_dirs(genhomedircon_settings_t * s)288 static 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 (1) {
365 errno = 0;
366 pwbuf = getpwent();
367 if (pwbuf == NULL)
368 break;
369 if (pwbuf->pw_uid < minuid || pwbuf->pw_uid > maxuid)
370 continue;
371 if (!semanage_list_find(shells, pwbuf->pw_shell))
372 continue;
373 int len = strlen(pwbuf->pw_dir) -1;
374 for(; len > 0 && pwbuf->pw_dir[len] == '/'; len--) {
375 pwbuf->pw_dir[len] = '\0';
376 }
377 if (strcmp(pwbuf->pw_dir, "/") == 0)
378 continue;
379 if (ignore(pwbuf->pw_dir))
380 continue;
381 if (semanage_str_count(pwbuf->pw_dir, '/') <= 1)
382 continue;
383 if (!(path = strdup(pwbuf->pw_dir))) {
384 break;
385 }
386
387 semanage_rtrim(path, '/');
388
389 if (!semanage_list_find(homedir_list, path)) {
390 /*
391 * Now check for an existing file context that matches
392 * so we don't label a non-homedir as a homedir.
393 */
394 hand.dir = path;
395 hand.matched = 0;
396 if (semanage_fcontext_iterate(s->h_semanage,
397 fcontext_matches, &hand) == STATUS_ERR)
398 goto fail;
399
400 /* NOTE: old genhomedircon printed a warning on match */
401 if (hand.matched) {
402 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);
403 } else {
404 if (semanage_list_push(&homedir_list, path))
405 goto fail;
406 }
407 }
408 free(path);
409 path = NULL;
410 }
411
412 if (errno) {
413 WARN(s->h_semanage, "Error while fetching users. "
414 "Returning list so far.");
415 }
416
417 if (semanage_list_sort(&homedir_list))
418 goto fail;
419
420 endpwent();
421 semanage_list_destroy(&shells);
422
423 return homedir_list;
424
425 fail:
426 endpwent();
427 free(path);
428 semanage_list_destroy(&homedir_list);
429 semanage_list_destroy(&shells);
430 return NULL;
431 }
432
433 /**
434 * @param out the FILE to put all the output in.
435 * @return 0 on success
436 */
write_file_context_header(FILE * out)437 static int write_file_context_header(FILE * out)
438 {
439 if (fprintf(out, COMMENT_FILE_CONTEXT_HEADER) < 0) {
440 return STATUS_ERR;
441 }
442
443 return STATUS_SUCCESS;
444 }
445
446 /* Predicates for use with semanage_slurp_file_filter() the homedir_template
447 * file currently contains lines that serve as the template for a user's
448 * homedir.
449 *
450 * It also contains lines that are the template for the parent of a
451 * user's home directory.
452 *
453 * Currently, the only lines that apply to the the root of a user's home
454 * directory are all prefixed with the string "HOME_ROOT". All other
455 * lines apply to a user's home directory. If this changes the
456 * following predicates need to change to reflect that.
457 */
HOME_ROOT_PRED(const char * string)458 static int HOME_ROOT_PRED(const char *string)
459 {
460 return semanage_is_prefix(string, TEMPLATE_HOME_ROOT);
461 }
462
HOME_DIR_PRED(const char * string)463 static int HOME_DIR_PRED(const char *string)
464 {
465 return semanage_is_prefix(string, TEMPLATE_HOME_DIR);
466 }
467
468 /* new names */
USERNAME_CONTEXT_PRED(const char * string)469 static int USERNAME_CONTEXT_PRED(const char *string)
470 {
471 return (int)(
472 (strstr(string, TEMPLATE_USERNAME) != NULL) ||
473 (strstr(string, TEMPLATE_USERID) != NULL)
474 );
475 }
476
477 /* This will never match USER if USERNAME or USERID are found. */
USER_CONTEXT_PRED(const char * string)478 static int USER_CONTEXT_PRED(const char *string)
479 {
480 if (USERNAME_CONTEXT_PRED(string))
481 return 0;
482
483 return (int)(strstr(string, TEMPLATE_USER) != NULL);
484 }
485
STR_COMPARATOR(const void * a,const void * b)486 static int STR_COMPARATOR(const void *a, const void *b)
487 {
488 return strcmp((const char *) a, (const char *) b);
489 }
490
491 /* make_tempate
492 * @param s the settings holding the paths to various files
493 * @param pred function pointer to function to use as filter for slurp
494 * file filter
495 * @return a list of lines from the template file with inappropriate
496 * lines filtered out.
497 */
make_template(genhomedircon_settings_t * s,int (* pred)(const char *))498 static semanage_list_t *make_template(genhomedircon_settings_t * s,
499 int (*pred) (const char *))
500 {
501 FILE *template_file = NULL;
502 semanage_list_t *template_data = NULL;
503
504 template_file = fopen(s->homedir_template_path, "r");
505 if (!template_file)
506 return NULL;
507 template_data = semanage_slurp_file_filter(template_file, pred);
508 fclose(template_file);
509
510 return template_data;
511 }
512
replace_all(const char * str,const replacement_pair_t * repl)513 static char *replace_all(const char *str, const replacement_pair_t * repl)
514 {
515 char *retval, *retval2;
516 int i;
517
518 if (!str || !repl)
519 return NULL;
520
521 retval = strdup(str);
522 for (i = 0; retval != NULL && repl[i].search_for; i++) {
523 retval2 = semanage_str_replace(repl[i].search_for,
524 repl[i].replace_with, retval, 0);
525 free(retval);
526 retval = retval2;
527 }
528 return retval;
529 }
530
extract_context(const char * line)531 static const char *extract_context(const char *line)
532 {
533 const char *p = line;
534 size_t off;
535
536 off = strlen(p);
537 p += off;
538 /* consider trailing whitespaces */
539 while (off > 0) {
540 p--;
541 off--;
542 if (!isspace(*p))
543 break;
544 }
545 if (off == 0)
546 return NULL;
547
548 /* find the last field in line */
549 while (off > 0 && !isspace(*(p - 1))) {
550 p--;
551 off--;
552 }
553 return p;
554 }
555
check_line(genhomedircon_settings_t * s,const char * line)556 static int check_line(genhomedircon_settings_t * s, const char *line)
557 {
558 sepol_context_t *ctx_record = NULL;
559 const char *ctx_str;
560 int result;
561
562 ctx_str = extract_context(line);
563 if (!ctx_str)
564 return STATUS_ERR;
565
566 result = sepol_context_from_string(s->h_semanage->sepolh,
567 ctx_str, &ctx_record);
568 if (result == STATUS_SUCCESS && ctx_record != NULL) {
569 result = sepol_context_check(s->h_semanage->sepolh,
570 s->policydb, ctx_record);
571 sepol_context_free(ctx_record);
572 }
573 return result;
574 }
575
write_replacements(genhomedircon_settings_t * s,FILE * out,const semanage_list_t * tpl,const replacement_pair_t * repl)576 static int write_replacements(genhomedircon_settings_t * s, FILE * out,
577 const semanage_list_t * tpl,
578 const replacement_pair_t *repl)
579 {
580 char *line;
581
582 for (; tpl; tpl = tpl->next) {
583 line = replace_all(tpl->data, repl);
584 if (!line)
585 goto fail;
586 if (check_line(s, line) == STATUS_SUCCESS) {
587 if (fprintf(out, "%s\n", line) < 0)
588 goto fail;
589 }
590 free(line);
591 }
592 return STATUS_SUCCESS;
593
594 fail:
595 free(line);
596 return STATUS_ERR;
597 }
598
write_contexts(genhomedircon_settings_t * s,FILE * out,semanage_list_t * tpl,const replacement_pair_t * repl,const genhomedircon_user_entry_t * user)599 static int write_contexts(genhomedircon_settings_t *s, FILE *out,
600 semanage_list_t *tpl, const replacement_pair_t *repl,
601 const genhomedircon_user_entry_t *user)
602 {
603 char *line, *temp;
604 sepol_context_t *context;
605 char *new_context_str;
606
607 for (; tpl; tpl = tpl->next) {
608 context = NULL;
609 new_context_str = NULL;
610 line = replace_all(tpl->data, repl);
611 if (!line) {
612 goto fail;
613 }
614
615 const char *old_context_str = extract_context(line);
616 if (!old_context_str) {
617 goto fail;
618 }
619
620 if (strcmp(old_context_str, CONTEXT_NONE) == 0) {
621 if (check_line(s, line) == STATUS_SUCCESS &&
622 fprintf(out, "%s\n", line) < 0) {
623 goto fail;
624 }
625 free(line);
626 continue;
627 }
628
629 sepol_handle_t *sepolh = s->h_semanage->sepolh;
630
631 if (sepol_context_from_string(sepolh, old_context_str,
632 &context) < 0) {
633 goto fail;
634 }
635
636 if (sepol_context_set_user(sepolh, context, user->sename) < 0) {
637 goto fail;
638 }
639
640 if (sepol_policydb_mls_enabled(s->policydb) &&
641 sepol_context_set_mls(sepolh, context, user->level) < 0) {
642 goto fail;
643 }
644
645 if (user->homedir_role &&
646 sepol_context_set_role(sepolh, context, user->homedir_role) < 0) {
647 goto fail;
648 }
649
650 if (sepol_context_to_string(sepolh, context,
651 &new_context_str) < 0) {
652 goto fail;
653 }
654
655 temp = semanage_str_replace(old_context_str, new_context_str,
656 line, 1);
657 if (!temp) {
658 goto fail;
659 }
660 free(line);
661 line = temp;
662
663 if (check_line(s, line) == STATUS_SUCCESS) {
664 if (fprintf(out, "%s\n", line) < 0)
665 goto fail;
666 }
667
668 free(line);
669 sepol_context_free(context);
670 free(new_context_str);
671 }
672
673 return STATUS_SUCCESS;
674 fail:
675 free(line);
676 sepol_context_free(context);
677 free(new_context_str);
678 return STATUS_ERR;
679 }
680
write_home_dir_context(genhomedircon_settings_t * s,FILE * out,semanage_list_t * tpl,const genhomedircon_user_entry_t * user)681 static int write_home_dir_context(genhomedircon_settings_t * s, FILE * out,
682 semanage_list_t * tpl, const genhomedircon_user_entry_t *user)
683 {
684 replacement_pair_t repl[] = {
685 {.search_for = TEMPLATE_HOME_DIR,.replace_with = user->home},
686 {.search_for = TEMPLATE_ROLE,.replace_with = user->prefix},
687 {NULL, NULL}
688 };
689
690 if (strcmp(user->name, FALLBACK_NAME) == 0) {
691 if (fprintf(out, COMMENT_USER_HOME_CONTEXT, FALLBACK_SENAME) < 0)
692 return STATUS_ERR;
693 } else {
694 if (fprintf(out, COMMENT_USER_HOME_CONTEXT, user->name) < 0)
695 return STATUS_ERR;
696 }
697
698 return write_contexts(s, out, tpl, repl, user);
699 }
700
write_home_root_context(genhomedircon_settings_t * s,FILE * out,semanage_list_t * tpl,char * homedir)701 static int write_home_root_context(genhomedircon_settings_t * s, FILE * out,
702 semanage_list_t * tpl, char *homedir)
703 {
704 replacement_pair_t repl[] = {
705 {.search_for = TEMPLATE_HOME_ROOT,.replace_with = homedir},
706 {NULL, NULL}
707 };
708
709 return write_replacements(s, out, tpl, repl);
710 }
711
write_username_context(genhomedircon_settings_t * s,FILE * out,semanage_list_t * tpl,const genhomedircon_user_entry_t * user)712 static int write_username_context(genhomedircon_settings_t * s, FILE * out,
713 semanage_list_t * tpl,
714 const genhomedircon_user_entry_t *user)
715 {
716 replacement_pair_t repl[] = {
717 {.search_for = TEMPLATE_USERNAME,.replace_with = user->name},
718 {.search_for = TEMPLATE_USERID,.replace_with = user->uid},
719 {.search_for = TEMPLATE_ROLE,.replace_with = user->prefix},
720 {NULL, NULL}
721 };
722
723 return write_contexts(s, out, tpl, repl, user);
724 }
725
write_user_context(genhomedircon_settings_t * s,FILE * out,semanage_list_t * tpl,const genhomedircon_user_entry_t * user)726 static int write_user_context(genhomedircon_settings_t * s, FILE * out,
727 semanage_list_t * tpl, const genhomedircon_user_entry_t *user)
728 {
729 replacement_pair_t repl[] = {
730 {.search_for = TEMPLATE_USER,.replace_with = user->name},
731 {.search_for = TEMPLATE_ROLE,.replace_with = user->prefix},
732 {NULL, NULL}
733 };
734
735 return write_contexts(s, out, tpl, repl, user);
736 }
737
seuser_sort_func(const void * arg1,const void * arg2)738 static int seuser_sort_func(const void *arg1, const void *arg2)
739 {
740 const semanage_seuser_t **u1 = (const semanage_seuser_t **) arg1;
741 const semanage_seuser_t **u2 = (const semanage_seuser_t **) arg2;;
742 const char *name1 = semanage_seuser_get_name(*u1);
743 const char *name2 = semanage_seuser_get_name(*u2);
744
745 if (name1[0] == '%' && name2[0] == '%') {
746 return 0;
747 } else if (name1[0] == '%') {
748 return 1;
749 } else if (name2[0] == '%') {
750 return -1;
751 }
752
753 return strcmp(name1, name2);
754 }
755
user_sort_func(semanage_user_t ** arg1,semanage_user_t ** arg2)756 static int user_sort_func(semanage_user_t ** arg1, semanage_user_t ** arg2)
757 {
758 return strcmp(semanage_user_get_name(*arg1),
759 semanage_user_get_name(*arg2));
760 }
761
name_user_cmp(char * key,semanage_user_t ** val)762 static int name_user_cmp(char *key, semanage_user_t ** val)
763 {
764 return strcmp(key, semanage_user_get_name(*val));
765 }
766
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)767 static int push_user_entry(genhomedircon_user_entry_t ** list, const char *n,
768 const char *u, const char *g, const char *sen,
769 const char *pre, const char *h, const char *l,
770 const char *ln, const char *hd_role)
771 {
772 genhomedircon_user_entry_t *temp = NULL;
773 char *name = NULL;
774 char *uid = NULL;
775 char *gid = NULL;
776 char *sename = NULL;
777 char *prefix = NULL;
778 char *home = NULL;
779 char *level = NULL;
780 char *lname = NULL;
781 char *homedir_role = NULL;
782
783 temp = malloc(sizeof(genhomedircon_user_entry_t));
784 if (!temp)
785 goto cleanup;
786 name = strdup(n);
787 if (!name)
788 goto cleanup;
789 uid = strdup(u);
790 if (!uid)
791 goto cleanup;
792 gid = strdup(g);
793 if (!gid)
794 goto cleanup;
795 sename = strdup(sen);
796 if (!sename)
797 goto cleanup;
798 prefix = strdup(pre);
799 if (!prefix)
800 goto cleanup;
801 home = strdup(h);
802 if (!home)
803 goto cleanup;
804 level = strdup(l);
805 if (!level)
806 goto cleanup;
807 lname = strdup(ln);
808 if (!lname)
809 goto cleanup;
810 if (hd_role) {
811 homedir_role = strdup(hd_role);
812 if (!homedir_role)
813 goto cleanup;
814 }
815
816 temp->name = name;
817 temp->uid = uid;
818 temp->gid = gid;
819 temp->sename = sename;
820 temp->prefix = prefix;
821 temp->home = home;
822 temp->level = level;
823 temp->login = lname;
824 temp->homedir_role = homedir_role;
825 temp->next = (*list);
826 (*list) = temp;
827
828 return STATUS_SUCCESS;
829
830 cleanup:
831 free(name);
832 free(uid);
833 free(gid);
834 free(sename);
835 free(prefix);
836 free(home);
837 free(level);
838 free(lname);
839 free(homedir_role);
840 free(temp);
841 return STATUS_ERR;
842 }
843
pop_user_entry(genhomedircon_user_entry_t ** list)844 static void pop_user_entry(genhomedircon_user_entry_t ** list)
845 {
846 genhomedircon_user_entry_t *temp;
847
848 if (!list || !(*list))
849 return;
850
851 temp = *list;
852 *list = temp->next;
853 free(temp->name);
854 free(temp->uid);
855 free(temp->gid);
856 free(temp->sename);
857 free(temp->prefix);
858 free(temp->home);
859 free(temp->level);
860 free(temp->login);
861 free(temp->homedir_role);
862 free(temp);
863 }
864
setup_fallback_user(genhomedircon_settings_t * s)865 static int setup_fallback_user(genhomedircon_settings_t * s)
866 {
867 semanage_seuser_t **seuser_list = NULL;
868 unsigned int nseusers = 0;
869 semanage_user_key_t *key = NULL;
870 semanage_user_t *u = NULL;
871 const char *name = NULL;
872 const char *seuname = NULL;
873 const char *prefix = NULL;
874 const char *level = NULL;
875 const char *homedir_role = NULL;
876 unsigned int i;
877 int retval;
878 int errors = 0;
879
880 retval = semanage_seuser_list(s->h_semanage, &seuser_list, &nseusers);
881 if (retval < 0 || (nseusers < 1)) {
882 /* if there are no users, this function can't do any other work */
883 return errors;
884 }
885
886 for (i = 0; i < nseusers; i++) {
887 name = semanage_seuser_get_name(seuser_list[i]);
888 if (strcmp(name, DEFAULT_LOGIN) == 0) {
889 seuname = semanage_seuser_get_sename(seuser_list[i]);
890
891 /* find the user structure given the name */
892 if (semanage_user_key_create(s->h_semanage, seuname,
893 &key) < 0) {
894 errors = STATUS_ERR;
895 break;
896 }
897 if (semanage_user_query(s->h_semanage, key, &u) < 0)
898 {
899 prefix = name;
900 level = FALLBACK_LEVEL;
901 }
902 else
903 {
904 prefix = semanage_user_get_prefix(u);
905 level = semanage_user_get_mlslevel(u);
906 if (!level)
907 level = FALLBACK_LEVEL;
908 }
909
910 if (prefix_is_homedir_role(u, prefix)) {
911 homedir_role = prefix;
912 }
913
914 if (push_user_entry(&(s->fallback), FALLBACK_NAME,
915 FALLBACK_UIDGID, FALLBACK_UIDGID,
916 seuname, prefix, "", level,
917 FALLBACK_NAME, homedir_role) != 0)
918 errors = STATUS_ERR;
919 semanage_user_key_free(key);
920 if (u)
921 semanage_user_free(u);
922 break;
923 }
924 }
925
926 for (i = 0; i < nseusers; i++)
927 semanage_seuser_free(seuser_list[i]);
928 free(seuser_list);
929
930 return errors;
931 }
932
find_user(genhomedircon_user_entry_t * head,const char * name)933 static genhomedircon_user_entry_t *find_user(genhomedircon_user_entry_t *head,
934 const char *name)
935 {
936 for(; head; head = head->next) {
937 if (strcmp(head->name, name) == 0) {
938 return head;
939 }
940 }
941
942 return NULL;
943 }
944
add_user(genhomedircon_settings_t * s,genhomedircon_user_entry_t ** head,semanage_user_t * user,const char * name,const char * sename,const char * selogin)945 static int add_user(genhomedircon_settings_t * s,
946 genhomedircon_user_entry_t **head,
947 semanage_user_t *user,
948 const char *name,
949 const char *sename,
950 const char *selogin)
951 {
952 if (selogin[0] == '%') {
953 genhomedircon_user_entry_t *orig = find_user(*head, name);
954 if (orig != NULL && orig->login[0] == '%') {
955 ERR(s->h_semanage, "User %s is already mapped to"
956 " group %s, but also belongs to group %s. Add an"
957 " explicit mapping for this user to"
958 " override group mappings.",
959 name, orig->login + 1, selogin + 1);
960 return STATUS_ERR;
961 } else if (orig != NULL) {
962 // user mappings take precedence
963 return STATUS_SUCCESS;
964 }
965 }
966
967 int retval = STATUS_ERR;
968
969 char *rbuf = NULL;
970 long rbuflen;
971 struct passwd pwstorage, *pwent = NULL;
972 const char *prefix = NULL;
973 const char *level = NULL;
974 const char *homedir_role = NULL;
975 char uid[11];
976 char gid[11];
977
978 errno = 0;
979 /* Allocate space for the getpwnam_r buffer */
980 rbuflen = sysconf(_SC_GETPW_R_SIZE_MAX);
981 if (rbuflen == -1 && errno == 0)
982 /* sysconf returning -1 with no errno means indeterminate size */
983 rbuflen = 1024;
984 else if (rbuflen <= 0)
985 goto cleanup;
986 rbuf = malloc(rbuflen);
987 if (rbuf == NULL)
988 goto cleanup;
989
990 if (user) {
991 prefix = semanage_user_get_prefix(user);
992 level = semanage_user_get_mlslevel(user);
993
994 if (!level) {
995 level = FALLBACK_LEVEL;
996 }
997 } else {
998 prefix = name;
999 level = FALLBACK_LEVEL;
1000 }
1001
1002 if (prefix_is_homedir_role(user, prefix)) {
1003 homedir_role = prefix;
1004 }
1005
1006 retval = getpwnam_r(name, &pwstorage, rbuf, rbuflen, &pwent);
1007 if (retval != 0 || pwent == NULL) {
1008 if (retval != 0 && retval != ENOENT) {
1009 goto cleanup;
1010 }
1011
1012 WARN(s->h_semanage,
1013 "user %s not in password file", name);
1014 retval = STATUS_SUCCESS;
1015 goto cleanup;
1016 }
1017
1018 int len = strlen(pwent->pw_dir) -1;
1019 for(; len > 0 && pwent->pw_dir[len] == '/'; len--) {
1020 pwent->pw_dir[len] = '\0';
1021 }
1022
1023 if (strcmp(pwent->pw_dir, "/") == 0) {
1024 /* don't relabel / genhomdircon checked to see if root
1025 * was the user and if so, set his home directory to
1026 * /root */
1027 retval = STATUS_SUCCESS;
1028 goto cleanup;
1029 }
1030
1031 if (ignore(pwent->pw_dir)) {
1032 retval = STATUS_SUCCESS;
1033 goto cleanup;
1034 }
1035
1036 len = snprintf(uid, sizeof(uid), "%u", pwent->pw_uid);
1037 if (len < 0 || len >= (int)sizeof(uid)) {
1038 goto cleanup;
1039 }
1040
1041 len = snprintf(gid, sizeof(gid), "%u", pwent->pw_gid);
1042 if (len < 0 || len >= (int)sizeof(gid)) {
1043 goto cleanup;
1044 }
1045
1046 retval = push_user_entry(head, name, uid, gid, sename, prefix,
1047 pwent->pw_dir, level, selogin, homedir_role);
1048 cleanup:
1049 free(rbuf);
1050 return retval;
1051 }
1052
get_group_users(genhomedircon_settings_t * s,genhomedircon_user_entry_t ** head,semanage_user_t * user,const char * sename,const char * selogin)1053 static int get_group_users(genhomedircon_settings_t * s,
1054 genhomedircon_user_entry_t **head,
1055 semanage_user_t *user,
1056 const char *sename,
1057 const char *selogin)
1058 {
1059 int retval = STATUS_ERR;
1060 unsigned int i;
1061
1062 long grbuflen;
1063 char *grbuf = NULL;
1064 struct group grstorage, *group = NULL;
1065 struct passwd *pw = NULL;
1066
1067 errno = 0;
1068 grbuflen = sysconf(_SC_GETGR_R_SIZE_MAX);
1069 if (grbuflen == -1 && errno == 0)
1070 /* sysconf returning -1 with no errno means indeterminate size */
1071 grbuflen = 1024;
1072 else if (grbuflen <= 0)
1073 goto cleanup;
1074 grbuf = malloc(grbuflen);
1075 if (grbuf == NULL)
1076 goto cleanup;
1077
1078 const char *grname = selogin + 1;
1079
1080 errno = 0;
1081 while (
1082 (retval = getgrnam_r(grname, &grstorage, grbuf, (size_t) grbuflen, &group)) != 0 &&
1083 errno == ERANGE
1084 ) {
1085 char *new_grbuf;
1086 grbuflen *= 2;
1087 if (grbuflen < 0)
1088 /* the member list could exceed 2Gb on a system with a 32-bit CPU (where
1089 * sizeof(long) = 4) - if this ever happened, the loop would become infinite. */
1090 goto cleanup;
1091 new_grbuf = realloc(grbuf, grbuflen);
1092 if (new_grbuf == NULL)
1093 goto cleanup;
1094 grbuf = new_grbuf;
1095 }
1096 if (retval != 0)
1097 goto cleanup;
1098
1099 if (group == NULL) {
1100 ERR(s->h_semanage, "Can't find group named %s\n", grname);
1101 goto cleanup;
1102 }
1103
1104 size_t nmembers = 0;
1105 char **members = group->gr_mem;
1106
1107 while (*members != NULL) {
1108 nmembers++;
1109 members++;
1110 }
1111
1112 for (i = 0; i < nmembers; i++) {
1113 const char *uname = group->gr_mem[i];
1114
1115 if (add_user(s, head, user, uname, sename, selogin) < 0) {
1116 goto cleanup;
1117 }
1118 }
1119
1120 setpwent();
1121 while (1) {
1122 errno = 0;
1123 pw = getpwent();
1124 if (pw == NULL)
1125 break;
1126 // skip users who also have this group as their
1127 // primary group
1128 if (lfind(pw->pw_name, group->gr_mem, &nmembers,
1129 sizeof(char *), &STR_COMPARATOR)) {
1130 continue;
1131 }
1132
1133 if (group->gr_gid == pw->pw_gid) {
1134 if (add_user(s, head, user, pw->pw_name,
1135 sename, selogin) < 0) {
1136 goto cleanup;
1137 }
1138 }
1139 }
1140
1141 retval = STATUS_SUCCESS;
1142 cleanup:
1143 endpwent();
1144 free(grbuf);
1145
1146 return retval;
1147 }
1148
get_users(genhomedircon_settings_t * s,int * errors)1149 static genhomedircon_user_entry_t *get_users(genhomedircon_settings_t * s,
1150 int *errors)
1151 {
1152 genhomedircon_user_entry_t *head = NULL;
1153 semanage_seuser_t **seuser_list = NULL;
1154 unsigned int nseusers = 0;
1155 semanage_user_t **user_list = NULL;
1156 unsigned int nusers = 0;
1157 semanage_user_t **u = NULL;
1158 const char *name = NULL;
1159 const char *seuname = NULL;
1160 unsigned int i;
1161 int retval;
1162
1163 *errors = 0;
1164 retval = semanage_seuser_list(s->h_semanage, &seuser_list, &nseusers);
1165 if (retval < 0 || (nseusers < 1)) {
1166 /* if there are no users, this function can't do any other work */
1167 return NULL;
1168 }
1169
1170 if (semanage_user_list(s->h_semanage, &user_list, &nusers) < 0) {
1171 nusers = 0;
1172 }
1173
1174 qsort(seuser_list, nseusers, sizeof(semanage_seuser_t *),
1175 &seuser_sort_func);
1176 qsort(user_list, nusers, sizeof(semanage_user_t *),
1177 (int (*)(const void *, const void *))&user_sort_func);
1178
1179 for (i = 0; i < nseusers; i++) {
1180 seuname = semanage_seuser_get_sename(seuser_list[i]);
1181 name = semanage_seuser_get_name(seuser_list[i]);
1182
1183 if (strcmp(name, DEFAULT_LOGIN) == 0)
1184 continue;
1185
1186 /* find the user structure given the name */
1187 u = bsearch(seuname, user_list, nusers, sizeof(semanage_user_t *),
1188 (int (*)(const void *, const void *))
1189 &name_user_cmp);
1190
1191 /* %groupname syntax */
1192 if (name[0] == '%') {
1193 retval = get_group_users(s, &head, *u, seuname,
1194 name);
1195 } else {
1196 retval = add_user(s, &head, *u, name,
1197 seuname, name);
1198 }
1199
1200 if (retval != 0) {
1201 *errors = STATUS_ERR;
1202 goto cleanup;
1203 }
1204 }
1205
1206 cleanup:
1207 if (*errors) {
1208 for (; head; pop_user_entry(&head)) {
1209 /* the pop function takes care of all the cleanup
1210 so the loop body is just empty */
1211 }
1212 }
1213 for (i = 0; i < nseusers; i++) {
1214 semanage_seuser_free(seuser_list[i]);
1215 }
1216 free(seuser_list);
1217
1218 for (i = 0; i < nusers; i++) {
1219 semanage_user_free(user_list[i]);
1220 }
1221 free(user_list);
1222
1223 return head;
1224 }
1225
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)1226 static int write_gen_home_dir_context(genhomedircon_settings_t * s, FILE * out,
1227 semanage_list_t * username_context_tpl,
1228 semanage_list_t * user_context_tpl,
1229 semanage_list_t * homedir_context_tpl)
1230 {
1231 genhomedircon_user_entry_t *users;
1232 int errors = 0;
1233
1234 users = get_users(s, &errors);
1235 if (!users && errors) {
1236 return STATUS_ERR;
1237 }
1238
1239 for (; users; pop_user_entry(&users)) {
1240 if (write_home_dir_context(s, out, homedir_context_tpl, users))
1241 goto err;
1242 if (write_username_context(s, out, username_context_tpl, users))
1243 goto err;
1244 if (write_user_context(s, out, user_context_tpl, users))
1245 goto err;
1246 }
1247
1248 return STATUS_SUCCESS;
1249 err:
1250 for (; users; pop_user_entry(&users)) {
1251 /* the pop function takes care of all the cleanup
1252 * so the loop body is just empty */
1253 }
1254
1255 return STATUS_ERR;
1256 }
1257
1258 /**
1259 * @param s settings structure, stores various paths etc. Must never be NULL
1260 * @param out the FILE to put all the output in.
1261 * @return 0 on success
1262 */
write_context_file(genhomedircon_settings_t * s,FILE * out)1263 static int write_context_file(genhomedircon_settings_t * s, FILE * out)
1264 {
1265 semanage_list_t *homedirs = NULL;
1266 semanage_list_t *h = NULL;
1267 semanage_list_t *homedir_context_tpl = NULL;
1268 semanage_list_t *homeroot_context_tpl = NULL;
1269 semanage_list_t *username_context_tpl = NULL;
1270 semanage_list_t *user_context_tpl = NULL;
1271 int retval = STATUS_SUCCESS;
1272
1273 homedir_context_tpl = make_template(s, &HOME_DIR_PRED);
1274 homeroot_context_tpl = make_template(s, &HOME_ROOT_PRED);
1275 username_context_tpl = make_template(s, &USERNAME_CONTEXT_PRED);
1276 user_context_tpl = make_template(s, &USER_CONTEXT_PRED);
1277
1278 if (!homedir_context_tpl
1279 && !homeroot_context_tpl
1280 && !username_context_tpl
1281 && !user_context_tpl)
1282 goto done;
1283
1284 if (write_file_context_header(out) != STATUS_SUCCESS) {
1285 retval = STATUS_ERR;
1286 goto done;
1287 }
1288
1289 if (setup_fallback_user(s) != 0) {
1290 retval = STATUS_ERR;
1291 goto done;
1292 }
1293
1294 if (homedir_context_tpl || homeroot_context_tpl) {
1295 homedirs = get_home_dirs(s);
1296 if (!homedirs) {
1297 WARN(s->h_semanage,
1298 "no home directories were available, exiting without writing");
1299 goto done;
1300 }
1301
1302 for (h = homedirs; h; h = h->next) {
1303 char *temp = NULL;
1304
1305 if (asprintf(&temp, "%s/%s", h->data, FALLBACK_NAME) < 0) {
1306 retval = STATUS_ERR;
1307 goto done;
1308 }
1309
1310 free(s->fallback->home);
1311 s->fallback->home = temp;
1312
1313 if (write_home_dir_context(s, out, homedir_context_tpl,
1314 s->fallback) != STATUS_SUCCESS) {
1315 free(temp);
1316 s->fallback->home = NULL;
1317 retval = STATUS_ERR;
1318 goto done;
1319 }
1320 if (write_home_root_context(s, out,
1321 homeroot_context_tpl,
1322 h->data) != STATUS_SUCCESS) {
1323 free(temp);
1324 s->fallback->home = NULL;
1325 retval = STATUS_ERR;
1326 goto done;
1327 }
1328
1329 free(temp);
1330 s->fallback->home = NULL;
1331 }
1332 }
1333 if (user_context_tpl || username_context_tpl) {
1334 if (write_username_context(s, out, username_context_tpl,
1335 s->fallback) != STATUS_SUCCESS) {
1336 retval = STATUS_ERR;
1337 goto done;
1338 }
1339
1340 if (write_user_context(s, out, user_context_tpl,
1341 s->fallback) != STATUS_SUCCESS) {
1342 retval = STATUS_ERR;
1343 goto done;
1344 }
1345
1346 if (write_gen_home_dir_context(s, out, username_context_tpl,
1347 user_context_tpl, homedir_context_tpl)
1348 != STATUS_SUCCESS) {
1349 retval = STATUS_ERR;
1350 }
1351 }
1352
1353 done:
1354 /* Cleanup */
1355 semanage_list_destroy(&homedirs);
1356 semanage_list_destroy(&username_context_tpl);
1357 semanage_list_destroy(&user_context_tpl);
1358 semanage_list_destroy(&homedir_context_tpl);
1359 semanage_list_destroy(&homeroot_context_tpl);
1360
1361 return retval;
1362 }
1363
semanage_genhomedircon(semanage_handle_t * sh,sepol_policydb_t * policydb,int usepasswd,char * ignoredirs)1364 int semanage_genhomedircon(semanage_handle_t * sh,
1365 sepol_policydb_t * policydb,
1366 int usepasswd,
1367 char *ignoredirs)
1368 {
1369 genhomedircon_settings_t s;
1370 FILE *out = NULL;
1371 int retval = 0;
1372
1373 assert(sh);
1374
1375 s.homedir_template_path =
1376 semanage_path(SEMANAGE_TMP, SEMANAGE_HOMEDIR_TMPL);
1377 s.fcfilepath =
1378 semanage_path(SEMANAGE_TMP, SEMANAGE_STORE_FC_HOMEDIRS);
1379
1380 s.fallback = calloc(1, sizeof(genhomedircon_user_entry_t));
1381 if (s.fallback == NULL) {
1382 retval = STATUS_ERR;
1383 goto done;
1384 }
1385
1386 s.fallback->name = strdup(FALLBACK_NAME);
1387 s.fallback->sename = strdup(FALLBACK_SENAME);
1388 s.fallback->prefix = strdup(FALLBACK_PREFIX);
1389 s.fallback->level = strdup(FALLBACK_LEVEL);
1390 if (s.fallback->name == NULL
1391 || s.fallback->sename == NULL
1392 || s.fallback->prefix == NULL
1393 || s.fallback->level == NULL) {
1394 retval = STATUS_ERR;
1395 goto done;
1396 }
1397
1398 if (ignoredirs) ignore_setup(ignoredirs);
1399
1400 s.usepasswd = usepasswd;
1401 s.h_semanage = sh;
1402 s.policydb = policydb;
1403
1404 if (!(out = fopen(s.fcfilepath, "w"))) {
1405 /* couldn't open output file */
1406 ERR(sh, "Could not open the file_context file for writing");
1407 retval = STATUS_ERR;
1408 goto done;
1409 }
1410
1411 retval = write_context_file(&s, out);
1412
1413 done:
1414 if (out != NULL)
1415 fclose(out);
1416
1417 while (s.fallback)
1418 pop_user_entry(&(s.fallback));
1419
1420 ignore_free();
1421
1422 return retval;
1423 }
1424