1#include "restore.h"
2#include <glob.h>
3#include <selinux/context.h>
4
5#define SKIP -2
6#define ERR -1
7#define MAX_EXCLUDES 1000
8
9/*
10 * The hash table of associations, hashed by inode number.
11 * Chaining is used for collisions, with elements ordered
12 * by inode number in each bucket.  Each hash bucket has a dummy
13 * header.
14 */
15#define HASH_BITS 16
16#define HASH_BUCKETS (1 << HASH_BITS)
17#define HASH_MASK (HASH_BUCKETS-1)
18
19/*
20 * An association between an inode and a context.
21 */
22typedef struct file_spec {
23	ino_t ino;		/* inode number */
24	char *con;		/* matched context */
25	char *file;		/* full pathname */
26	struct file_spec *next;	/* next association in hash bucket chain */
27} file_spec_t;
28
29struct edir {
30	char *directory;
31	size_t size;
32};
33
34
35static file_spec_t *fl_head;
36static int filespec_add(ino_t ino, const security_context_t con, const char *file);
37struct restore_opts *r_opts = NULL;
38static void filespec_destroy(void);
39static void filespec_eval(void);
40static int excludeCtr = 0;
41static struct edir excludeArray[MAX_EXCLUDES];
42
43void remove_exclude(const char *directory)
44{
45	int i = 0;
46	for (i = 0; i < excludeCtr; i++) {
47		if (strcmp(directory, excludeArray[i].directory) == 0) {
48			free(excludeArray[i].directory);
49			if (i != excludeCtr-1)
50				excludeArray[i] = excludeArray[excludeCtr-1];
51			excludeCtr--;
52			return;
53		}
54	}
55	return;
56}
57
58void restore_init(struct restore_opts *opts)
59{
60	r_opts = opts;
61	struct selinux_opt selinux_opts[] = {
62		{ SELABEL_OPT_VALIDATE, r_opts->selabel_opt_validate },
63		{ SELABEL_OPT_PATH, r_opts->selabel_opt_path }
64	};
65	r_opts->hnd = selabel_open(SELABEL_CTX_FILE, selinux_opts, 2);
66	if (!r_opts->hnd) {
67		perror(r_opts->selabel_opt_path);
68		exit(1);
69	}
70}
71
72void restore_finish()
73{
74	int i;
75	for (i = 0; i < excludeCtr; i++) {
76		free(excludeArray[i].directory);
77	}
78}
79
80static int match(const char *name, struct stat *sb, char **con)
81{
82	if (!(r_opts->hard_links) && !S_ISDIR(sb->st_mode) && (sb->st_nlink > 1)) {
83		fprintf(stderr, "Warning! %s refers to a file with more than one hard link, not fixing hard links.\n",
84					name);
85		return -1;
86	}
87
88	if (NULL != r_opts->rootpath) {
89		if (0 != strncmp(r_opts->rootpath, name, r_opts->rootpathlen)) {
90			fprintf(stderr, "%s:  %s is not located in %s\n",
91				r_opts->progname, name, r_opts->rootpath);
92			return -1;
93		}
94		name += r_opts->rootpathlen;
95	}
96
97	if (r_opts->rootpath != NULL && name[0] == '\0')
98		/* this is actually the root dir of the alt root */
99		return selabel_lookup_raw(r_opts->hnd, con, "/", sb->st_mode);
100	else
101		return selabel_lookup_raw(r_opts->hnd, con, name, sb->st_mode);
102}
103static int restore(FTSENT *ftsent, int recurse)
104{
105	char *my_file = strdupa(ftsent->fts_path);
106	int ret = -1;
107	security_context_t curcon = NULL, newcon = NULL;
108	float progress;
109	if (match(my_file, ftsent->fts_statp, &newcon) < 0) {
110		if ((errno == ENOENT) && ((!recurse) || (r_opts->verbose)))
111			fprintf(stderr, "%s:  Warning no default label for %s\n", r_opts->progname, my_file);
112
113		/* Check for no matching specification. */
114		return (errno == ENOENT) ? 0 : -1;
115	}
116
117	if (r_opts->progress) {
118		r_opts->count++;
119		if (r_opts->count % STAR_COUNT == 0) {
120			if (r_opts->progress == 1) {
121				fprintf(stdout, "\r%luk", (size_t) r_opts->count / STAR_COUNT );
122			} else {
123				if (r_opts->nfile > 0) {
124					progress = (r_opts->count < r_opts->nfile) ? (100.0 * r_opts->count / r_opts->nfile) : 100;
125					fprintf(stdout, "\r%-.1f%%", progress);
126				}
127			}
128			fflush(stdout);
129		}
130	}
131
132	/*
133	 * Try to add an association between this inode and
134	 * this specification.  If there is already an association
135	 * for this inode and it conflicts with this specification,
136	 * then use the last matching specification.
137	 */
138	if (r_opts->add_assoc) {
139		ret = filespec_add(ftsent->fts_statp->st_ino, newcon, my_file);
140		if (ret < 0)
141			goto err;
142
143		if (ret > 0)
144			/* There was already an association and it took precedence. */
145			goto out;
146	}
147
148	if (r_opts->debug) {
149		printf("%s:  %s matched by %s\n", r_opts->progname, my_file, newcon);
150	}
151
152	/*
153	 * Do not relabel if their is no default specification for this file
154	 */
155
156	if (strcmp(newcon, "<<none>>") == 0) {
157		goto out;
158	}
159
160	/* Get the current context of the file. */
161	ret = lgetfilecon_raw(ftsent->fts_accpath, &curcon);
162	if (ret < 0) {
163		if (errno == ENODATA) {
164			curcon = NULL;
165		} else {
166			fprintf(stderr, "%s get context on %s failed: '%s'\n",
167				r_opts->progname, my_file, strerror(errno));
168			goto err;
169		}
170	}
171
172	/* lgetfilecon returns number of characters and ret needs to be reset
173	 * to 0.
174	 */
175	ret = 0;
176
177	/*
178	 * Do not relabel the file if the file is already labeled according to
179	 * the specification.
180	 */
181	if (curcon && (strcmp(curcon, newcon) == 0)) {
182		goto out;
183	}
184
185	if (!r_opts->force && curcon && (is_context_customizable(curcon) > 0)) {
186		if (r_opts->verbose > 1) {
187			fprintf(stderr,
188				"%s: %s not reset customized by admin to %s\n",
189				r_opts->progname, my_file, curcon);
190		}
191		goto out;
192	}
193
194	/*
195	 *  Do not change label unless this is a force or the type is different
196	 */
197	if (!r_opts->force && curcon) {
198		int types_differ = 0;
199		context_t cona;
200		context_t conb;
201		int err = 0;
202		cona = context_new(curcon);
203		if (! cona) {
204			goto out;
205		}
206		conb = context_new(newcon);
207		if (! conb) {
208			context_free(cona);
209			goto out;
210		}
211
212		types_differ = strcmp(context_type_get(cona), context_type_get(conb));
213		if (types_differ) {
214			err |= context_user_set(conb, context_user_get(cona));
215			err |= context_role_set(conb, context_role_get(cona));
216			err |= context_range_set(conb, context_range_get(cona));
217			if (!err) {
218				freecon(newcon);
219				newcon = strdup(context_str(conb));
220			}
221		}
222		context_free(cona);
223		context_free(conb);
224
225		if (!types_differ || err) {
226			goto out;
227		}
228	}
229
230	if (r_opts->verbose) {
231		printf("%s reset %s context %s->%s\n",
232		       r_opts->progname, my_file, curcon ?: "", newcon);
233	}
234
235	if (r_opts->logging && r_opts->change) {
236		if (curcon)
237			syslog(LOG_INFO, "relabeling %s from %s to %s\n",
238			       my_file, curcon, newcon);
239		else
240			syslog(LOG_INFO, "labeling %s to %s\n",
241			       my_file, newcon);
242	}
243
244	if (r_opts->outfile)
245		fprintf(r_opts->outfile, "%s\n", my_file);
246
247	/*
248	 * Do not relabel the file if -n was used.
249	 */
250	if (!r_opts->change)
251		goto out;
252
253	/*
254	 * Relabel the file to the specified context.
255	 */
256	ret = lsetfilecon(ftsent->fts_accpath, newcon);
257	if (ret) {
258		fprintf(stderr, "%s set context %s->%s failed:'%s'\n",
259			r_opts->progname, my_file, newcon, strerror(errno));
260		goto skip;
261	}
262	ret = 0;
263out:
264	freecon(curcon);
265	freecon(newcon);
266	return ret;
267skip:
268	freecon(curcon);
269	freecon(newcon);
270	return SKIP;
271err:
272	freecon(curcon);
273	freecon(newcon);
274	return ERR;
275}
276/*
277 * Apply the last matching specification to a file.
278 * This function is called by fts on each file during
279 * the directory traversal.
280 */
281static int apply_spec(FTSENT *ftsent, int recurse)
282{
283	if (ftsent->fts_info == FTS_DNR) {
284		fprintf(stderr, "%s:  unable to read directory %s\n",
285			r_opts->progname, ftsent->fts_path);
286		return SKIP;
287	}
288
289	int rc = restore(ftsent, recurse);
290	if (rc == ERR) {
291		if (!r_opts->abort_on_error)
292			return SKIP;
293	}
294	return rc;
295}
296
297#include <sys/statvfs.h>
298
299static int process_one(char *name, int recurse_this_path)
300{
301	int rc = 0;
302	const char *namelist[2] = {name, NULL};
303	dev_t dev_num = 0;
304	FTS *fts_handle = NULL;
305	FTSENT *ftsent = NULL;
306
307	if (r_opts == NULL){
308		fprintf(stderr,
309			"Must call initialize first!");
310		goto err;
311	}
312
313	fts_handle = fts_open((char **)namelist, r_opts->fts_flags, NULL);
314	if (fts_handle  == NULL) {
315		fprintf(stderr,
316			"%s: error while labeling %s:  %s\n",
317			r_opts->progname, namelist[0], strerror(errno));
318		goto err;
319	}
320
321
322	ftsent = fts_read(fts_handle);
323	if (ftsent == NULL) {
324		fprintf(stderr,
325			"%s: error while labeling %s:  %s\n",
326			r_opts->progname, namelist[0], strerror(errno));
327		goto err;
328	}
329
330	/* Keep the inode of the first one. */
331	dev_num = ftsent->fts_statp->st_dev;
332
333	do {
334		rc = 0;
335		/* Skip the post order nodes. */
336		if (ftsent->fts_info == FTS_DP)
337			continue;
338		/* If the XDEV flag is set and the device is different */
339		if (ftsent->fts_statp->st_dev != dev_num &&
340		    FTS_XDEV == (r_opts->fts_flags & FTS_XDEV))
341			continue;
342		if (excludeCtr > 0) {
343			if (exclude(ftsent->fts_path)) {
344				fts_set(fts_handle, ftsent, FTS_SKIP);
345				continue;
346			}
347		}
348
349		rc = apply_spec(ftsent, recurse_this_path);
350		if (rc == SKIP)
351			fts_set(fts_handle, ftsent, FTS_SKIP);
352		if (rc == ERR)
353			goto err;
354		if (!recurse_this_path)
355			break;
356	} while ((ftsent = fts_read(fts_handle)) != NULL);
357
358out:
359	if (r_opts->add_assoc) {
360		if (!r_opts->quiet)
361			filespec_eval();
362		filespec_destroy();
363	}
364	if (fts_handle)
365		fts_close(fts_handle);
366	return rc;
367
368err:
369	rc = -1;
370	goto out;
371}
372
373int process_glob(char *name, int recurse) {
374	glob_t globbuf;
375	size_t i = 0;
376	int errors;
377	memset(&globbuf, 0, sizeof(globbuf));
378	errors = glob(name, GLOB_TILDE | GLOB_PERIOD | GLOB_NOCHECK | GLOB_BRACE, NULL, &globbuf);
379	if (errors)
380		return errors;
381
382	for (i = 0; i < globbuf.gl_pathc; i++) {
383		int len = strlen(globbuf.gl_pathv[i]) -2;
384		if (len > 0 && strcmp(&globbuf.gl_pathv[i][len--], "/.") == 0)
385			continue;
386		if (len > 0 && strcmp(&globbuf.gl_pathv[i][len], "/..") == 0)
387			continue;
388		int rc = process_one_realpath(globbuf.gl_pathv[i], recurse);
389		if (rc < 0)
390			errors = rc;
391	}
392	globfree(&globbuf);
393	return errors;
394}
395
396int process_one_realpath(char *name, int recurse)
397{
398	int rc = 0;
399	char *p;
400	struct stat64 sb;
401
402	if (r_opts == NULL){
403		fprintf(stderr,
404			"Must call initialize first!");
405		return -1;
406	}
407
408	if (!r_opts->expand_realpath) {
409		return process_one(name, recurse);
410	} else {
411		rc = lstat64(name, &sb);
412		if (rc < 0) {
413			if (r_opts->ignore_enoent && errno == ENOENT)
414				return 0;
415			fprintf(stderr, "%s:  lstat(%s) failed:  %s\n",
416				r_opts->progname, name,	strerror(errno));
417			return -1;
418		}
419
420		if (S_ISLNK(sb.st_mode)) {
421			char path[PATH_MAX + 1];
422
423			rc = realpath_not_final(name, path);
424			if (rc < 0)
425				return rc;
426			rc = process_one(path, 0);
427			if (rc < 0)
428				return rc;
429
430			p = realpath(name, NULL);
431			if (p) {
432				rc = process_one(p, recurse);
433				free(p);
434			}
435			return rc;
436		} else {
437			p = realpath(name, NULL);
438			if (!p) {
439				fprintf(stderr, "realpath(%s) failed %s\n", name,
440					strerror(errno));
441				return -1;
442			}
443			rc = process_one(p, recurse);
444			free(p);
445			return rc;
446		}
447	}
448}
449
450int exclude(const char *file)
451{
452	int i = 0;
453	for (i = 0; i < excludeCtr; i++) {
454		if (strncmp
455		    (file, excludeArray[i].directory,
456		     excludeArray[i].size) == 0) {
457			if (file[excludeArray[i].size] == 0
458			    || file[excludeArray[i].size] == '/') {
459				return 1;
460			}
461		}
462	}
463	return 0;
464}
465
466int add_exclude(const char *directory)
467{
468	size_t len = 0;
469
470	if (directory == NULL || directory[0] != '/') {
471		fprintf(stderr, "Full path required for exclude: %s.\n",
472			directory);
473		return 1;
474	}
475	if (excludeCtr == MAX_EXCLUDES) {
476		fprintf(stderr, "Maximum excludes %d exceeded.\n",
477			MAX_EXCLUDES);
478		return 1;
479	}
480
481	len = strlen(directory);
482	while (len > 1 && directory[len - 1] == '/') {
483		len--;
484	}
485	excludeArray[excludeCtr].directory = strndup(directory, len);
486
487	if (excludeArray[excludeCtr].directory == NULL) {
488		fprintf(stderr, "Out of memory.\n");
489		return 1;
490	}
491	excludeArray[excludeCtr++].size = len;
492
493	return 0;
494}
495
496/*
497 * Evaluate the association hash table distribution.
498 */
499static void filespec_eval(void)
500{
501	file_spec_t *fl;
502	int h, used, nel, len, longest;
503
504	if (!fl_head)
505		return;
506
507	used = 0;
508	longest = 0;
509	nel = 0;
510	for (h = 0; h < HASH_BUCKETS; h++) {
511		len = 0;
512		for (fl = fl_head[h].next; fl; fl = fl->next) {
513			len++;
514		}
515		if (len)
516			used++;
517		if (len > longest)
518			longest = len;
519		nel += len;
520	}
521
522	if (r_opts->verbose > 1)
523		printf
524		    ("%s:  hash table stats: %d elements, %d/%d buckets used, longest chain length %d\n",
525		     __FUNCTION__, nel, used, HASH_BUCKETS, longest);
526}
527
528/*
529 * Destroy the association hash table.
530 */
531static void filespec_destroy(void)
532{
533	file_spec_t *fl, *tmp;
534	int h;
535
536	if (!fl_head)
537		return;
538
539	for (h = 0; h < HASH_BUCKETS; h++) {
540		fl = fl_head[h].next;
541		while (fl) {
542			tmp = fl;
543			fl = fl->next;
544			freecon(tmp->con);
545			free(tmp->file);
546			free(tmp);
547		}
548		fl_head[h].next = NULL;
549	}
550	free(fl_head);
551	fl_head = NULL;
552}
553/*
554 * Try to add an association between an inode and a context.
555 * If there is a different context that matched the inode,
556 * then use the first context that matched.
557 */
558static int filespec_add(ino_t ino, const security_context_t con, const char *file)
559{
560	file_spec_t *prevfl, *fl;
561	int h, ret;
562	struct stat64 sb;
563
564	if (!fl_head) {
565		fl_head = malloc(sizeof(file_spec_t) * HASH_BUCKETS);
566		if (!fl_head)
567			goto oom;
568		memset(fl_head, 0, sizeof(file_spec_t) * HASH_BUCKETS);
569	}
570
571	h = (ino + (ino >> HASH_BITS)) & HASH_MASK;
572	for (prevfl = &fl_head[h], fl = fl_head[h].next; fl;
573	     prevfl = fl, fl = fl->next) {
574		if (ino == fl->ino) {
575			ret = lstat64(fl->file, &sb);
576			if (ret < 0 || sb.st_ino != ino) {
577				freecon(fl->con);
578				free(fl->file);
579				fl->file = strdup(file);
580				if (!fl->file)
581					goto oom;
582				fl->con = strdup(con);
583				if (!fl->con)
584					goto oom;
585				return 1;
586			}
587
588			if (strcmp(fl->con, con) == 0)
589				return 1;
590
591			fprintf(stderr,
592				"%s:  conflicting specifications for %s and %s, using %s.\n",
593				__FUNCTION__, file, fl->file, fl->con);
594			free(fl->file);
595			fl->file = strdup(file);
596			if (!fl->file)
597				goto oom;
598			return 1;
599		}
600
601		if (ino > fl->ino)
602			break;
603	}
604
605	fl = malloc(sizeof(file_spec_t));
606	if (!fl)
607		goto oom;
608	fl->ino = ino;
609	fl->con = strdup(con);
610	if (!fl->con)
611		goto oom_freefl;
612	fl->file = strdup(file);
613	if (!fl->file)
614		goto oom_freefl;
615	fl->next = prevfl->next;
616	prevfl->next = fl;
617	return 0;
618      oom_freefl:
619	free(fl);
620      oom:
621	fprintf(stderr,
622		"%s:  insufficient memory for file label entry for %s\n",
623		__FUNCTION__, file);
624	return -1;
625}
626
627#include <sys/utsname.h>
628int file_system_count(char *name) {
629	struct statvfs statvfs_buf;
630	int nfile = 0;
631	memset(&statvfs_buf, 0, sizeof(statvfs_buf));
632	if (!statvfs(name, &statvfs_buf)) {
633		nfile = statvfs_buf.f_files - statvfs_buf.f_ffree;
634	}
635	return nfile;
636}
637
638/*
639   Search /proc/mounts for all file systems that do not support extended
640   attributes and add them to the exclude directory table.  File systems
641   that support security labels have the seclabel option, return total file count
642*/
643int exclude_non_seclabel_mounts()
644{
645	struct utsname uts;
646	FILE *fp;
647	size_t len;
648	ssize_t num;
649	int index = 0, found = 0;
650	char *mount_info[4];
651	char *buf = NULL, *item;
652	int nfile = 0;
653	/* Check to see if the kernel supports seclabel */
654	if (uname(&uts) == 0 && strverscmp(uts.release, "2.6.30") < 0)
655		return 0;
656	if (is_selinux_enabled() <= 0)
657		return 0;
658
659	fp = fopen("/proc/mounts", "r");
660	if (!fp)
661		return 0;
662
663	while ((num = getline(&buf, &len, fp)) != -1) {
664		found = 0;
665		index = 0;
666		item = strtok(buf, " ");
667		while (item != NULL) {
668			mount_info[index] = item;
669			if (index == 3)
670				break;
671			index++;
672			item = strtok(NULL, " ");
673		}
674		if (index < 3) {
675			fprintf(stderr,
676				"/proc/mounts record \"%s\" has incorrect format.\n",
677				buf);
678			continue;
679		}
680
681		/* remove pre-existing entry */
682		remove_exclude(mount_info[1]);
683
684		item = strtok(mount_info[3], ",");
685		while (item != NULL) {
686			if (strcmp(item, "seclabel") == 0) {
687				found = 1;
688				nfile += file_system_count(mount_info[1]);
689				break;
690			}
691			item = strtok(NULL, ",");
692		}
693
694		/* exclude mount points without the seclabel option */
695		if (!found)
696			add_exclude(mount_info[1]);
697	}
698
699	free(buf);
700	fclose(fp);
701	/* return estimated #Files + 5% for directories and hard links */
702	return nfile * 1.05;
703}
704
705