505 lines
11 KiB
C
505 lines
11 KiB
C
/*
|
|
* restorecond
|
|
*
|
|
* Copyright (C) 2006-2009 Red Hat
|
|
* see file 'COPYING' for use and warranty information
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation; either version 2 of
|
|
* the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
.*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
|
* 02111-1307 USA
|
|
*
|
|
* Authors:
|
|
* Dan Walsh <dwalsh@redhat.com>
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* PURPOSE:
|
|
* This daemon program watches for the creation of files listed in a config file
|
|
* and makes sure that there security context matches the systems defaults
|
|
*
|
|
* USAGE:
|
|
* restorecond [-d] [-v]
|
|
*
|
|
* -d Run in debug mode
|
|
* -v Run in verbose mode (Report missing files)
|
|
*
|
|
* EXAMPLE USAGE:
|
|
* restorecond
|
|
*
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
#include <sys/inotify.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <signal.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <ctype.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <syslog.h>
|
|
#include <limits.h>
|
|
#include <fcntl.h>
|
|
|
|
#include "restorecond.h"
|
|
#include "stringslist.h"
|
|
#include "utmpwatcher.h"
|
|
|
|
extern char *dirname(char *path);
|
|
static int master_fd = -1;
|
|
static int master_wd = -1;
|
|
static int terminate = 0;
|
|
|
|
#include <selinux/selinux.h>
|
|
#include <utmp.h>
|
|
|
|
/* size of the event structure, not counting name */
|
|
#define EVENT_SIZE (sizeof (struct inotify_event))
|
|
/* reasonable guess as to size of 1024 events */
|
|
#define BUF_LEN (1024 * (EVENT_SIZE + 16))
|
|
|
|
static int debug_mode = 0;
|
|
static int verbose_mode = 0;
|
|
|
|
static void restore(const char *filename, int exact);
|
|
|
|
struct watchList {
|
|
struct watchList *next;
|
|
int wd;
|
|
char *dir;
|
|
struct stringsList *files;
|
|
};
|
|
struct watchList *firstDir = NULL;
|
|
|
|
/* Compare two contexts to see if their differences are "significant",
|
|
* or whether the only difference is in the user. */
|
|
static int only_changed_user(const char *a, const char *b)
|
|
{
|
|
char *rest_a, *rest_b; /* Rest of the context after the user */
|
|
if (!a || !b)
|
|
return 0;
|
|
rest_a = strchr(a, ':');
|
|
rest_b = strchr(b, ':');
|
|
if (!rest_a || !rest_b)
|
|
return 0;
|
|
return (strcmp(rest_a, rest_b) == 0);
|
|
}
|
|
|
|
/*
|
|
A file was in a direcroty has been created. This function checks to
|
|
see if it is one that we are watching.
|
|
*/
|
|
|
|
static int watch_list_find(int wd, const char *file)
|
|
{
|
|
struct watchList *ptr = NULL;
|
|
ptr = firstDir;
|
|
|
|
if (debug_mode)
|
|
printf("%d: File=%s\n", wd, file);
|
|
while (ptr != NULL) {
|
|
if (ptr->wd == wd) {
|
|
int exact=0;
|
|
if (strings_list_find(ptr->files, file, &exact) == 0) {
|
|
char *path = NULL;
|
|
if (asprintf(&path, "%s/%s", ptr->dir, file) <
|
|
0)
|
|
exitApp("Error allocating memory.");
|
|
restore(path, exact);
|
|
free(path);
|
|
return 0;
|
|
}
|
|
if (debug_mode)
|
|
strings_list_print(ptr->files);
|
|
|
|
/* Not found in this directory */
|
|
return -1;
|
|
}
|
|
ptr = ptr->next;
|
|
}
|
|
/* Did not find a directory */
|
|
return -1;
|
|
}
|
|
|
|
static void watch_list_free(int fd)
|
|
{
|
|
struct watchList *ptr = NULL;
|
|
struct watchList *prev = NULL;
|
|
ptr = firstDir;
|
|
|
|
while (ptr != NULL) {
|
|
inotify_rm_watch(fd, ptr->wd);
|
|
strings_list_free(ptr->files);
|
|
free(ptr->dir);
|
|
prev = ptr;
|
|
ptr = ptr->next;
|
|
free(prev);
|
|
}
|
|
firstDir = NULL;
|
|
}
|
|
|
|
/*
|
|
Set the file context to the default file context for this system.
|
|
Same as restorecon.
|
|
*/
|
|
static void restore(const char *filename, int exact)
|
|
{
|
|
int retcontext = 0;
|
|
security_context_t scontext = NULL;
|
|
security_context_t prev_context = NULL;
|
|
struct stat st;
|
|
int fd = -1;
|
|
if (debug_mode)
|
|
printf("restore %s\n", filename);
|
|
|
|
fd = open(filename, O_NOFOLLOW | O_RDONLY);
|
|
if (fd < 0) {
|
|
if (verbose_mode)
|
|
syslog(LOG_ERR, "Unable to open file (%s) %s\n",
|
|
filename, strerror(errno));
|
|
return;
|
|
}
|
|
|
|
if (fstat(fd, &st) != 0) {
|
|
syslog(LOG_ERR, "Unable to stat file (%s) %s\n", filename,
|
|
strerror(errno));
|
|
close(fd);
|
|
return;
|
|
}
|
|
|
|
if (!(st.st_mode & S_IFDIR) && st.st_nlink > 1) {
|
|
if (exact) {
|
|
syslog(LOG_ERR,
|
|
"Will not restore a file with more than one hard link (%s) %s\n",
|
|
filename, strerror(errno));
|
|
}
|
|
close(fd);
|
|
return;
|
|
}
|
|
|
|
if (matchpathcon(filename, st.st_mode, &scontext) < 0) {
|
|
if (errno == ENOENT)
|
|
return;
|
|
syslog(LOG_ERR, "matchpathcon(%s) failed %s\n", filename,
|
|
strerror(errno));
|
|
return;
|
|
}
|
|
retcontext = fgetfilecon_raw(fd, &prev_context);
|
|
|
|
if (retcontext >= 0 || errno == ENODATA) {
|
|
if (retcontext < 0)
|
|
prev_context = NULL;
|
|
if (retcontext < 0 || (strcmp(prev_context, scontext) != 0)) {
|
|
|
|
if (only_changed_user(scontext, prev_context) != 0) {
|
|
free(scontext);
|
|
free(prev_context);
|
|
close(fd);
|
|
return;
|
|
}
|
|
|
|
if (fsetfilecon(fd, scontext) < 0) {
|
|
if (errno != EOPNOTSUPP)
|
|
syslog(LOG_ERR,
|
|
"set context %s->%s failed:'%s'\n",
|
|
filename, scontext, strerror(errno));
|
|
if (retcontext >= 0)
|
|
free(prev_context);
|
|
free(scontext);
|
|
close(fd);
|
|
return;
|
|
}
|
|
syslog(LOG_WARNING, "Reset file context %s: %s->%s\n",
|
|
filename, prev_context, scontext);
|
|
}
|
|
if (retcontext >= 0)
|
|
free(prev_context);
|
|
} else {
|
|
if (errno != EOPNOTSUPP)
|
|
syslog(LOG_ERR, "get context on %s failed: '%s'\n",
|
|
filename, strerror(errno));
|
|
}
|
|
free(scontext);
|
|
close(fd);
|
|
}
|
|
|
|
static void process_config(int fd, FILE * cfg)
|
|
{
|
|
char *line_buf = NULL;
|
|
size_t len = 0;
|
|
|
|
while (getline(&line_buf, &len, cfg) > 0) {
|
|
char *buffer = line_buf;
|
|
while (isspace(*buffer))
|
|
buffer++;
|
|
if (buffer[0] == '#')
|
|
continue;
|
|
int l = strlen(buffer) - 1;
|
|
if (l <= 0)
|
|
continue;
|
|
buffer[l] = 0;
|
|
if (buffer[0] == '~')
|
|
utmpwatcher_add(fd, &buffer[1]);
|
|
else {
|
|
watch_list_add(fd, buffer);
|
|
}
|
|
}
|
|
free(line_buf);
|
|
}
|
|
|
|
/*
|
|
Read config file ignoring Comment lines
|
|
Files specified one per line. Files with "~" will be expanded to the logged in users
|
|
homedirs.
|
|
*/
|
|
|
|
static void read_config(int fd)
|
|
{
|
|
char *watch_file_path = "/etc/selinux/restorecond.conf";
|
|
|
|
FILE *cfg = NULL;
|
|
if (debug_mode)
|
|
printf("Read Config\n");
|
|
|
|
watch_list_free(fd);
|
|
|
|
cfg = fopen(watch_file_path, "r");
|
|
if (!cfg)
|
|
exitApp("Error reading config file.");
|
|
process_config(fd, cfg);
|
|
fclose(cfg);
|
|
|
|
inotify_rm_watch(fd, master_wd);
|
|
master_wd =
|
|
inotify_add_watch(fd, watch_file_path, IN_MOVED_FROM | IN_MODIFY);
|
|
if (master_wd == -1)
|
|
exitApp("Error watching config file.");
|
|
}
|
|
|
|
/*
|
|
Inotify watch loop
|
|
*/
|
|
static int watch(int fd)
|
|
{
|
|
char buf[BUF_LEN];
|
|
int len, i = 0;
|
|
len = read(fd, buf, BUF_LEN);
|
|
if (len < 0) {
|
|
if (terminate == 0) {
|
|
syslog(LOG_ERR, "Read error (%s)", strerror(errno));
|
|
return 0;
|
|
}
|
|
syslog(LOG_ERR, "terminated");
|
|
return -1;
|
|
} else if (!len)
|
|
/* BUF_LEN too small? */
|
|
return -1;
|
|
while (i < len) {
|
|
struct inotify_event *event;
|
|
event = (struct inotify_event *)&buf[i];
|
|
if (debug_mode)
|
|
printf("wd=%d mask=%u cookie=%u len=%u\n",
|
|
event->wd, event->mask,
|
|
event->cookie, event->len);
|
|
if (event->wd == master_wd)
|
|
read_config(fd);
|
|
else {
|
|
switch (utmpwatcher_handle(fd, event->wd)) {
|
|
case -1: /* Message was not for utmpwatcher */
|
|
if (event->len)
|
|
watch_list_find(event->wd, event->name);
|
|
break;
|
|
|
|
case 1: /* utmp has changed need to reload */
|
|
read_config(fd);
|
|
break;
|
|
|
|
default: /* No users logged in or out */
|
|
break;
|
|
}
|
|
}
|
|
|
|
i += EVENT_SIZE + event->len;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static const char *pidfile = "/var/run/restorecond.pid";
|
|
|
|
static int write_pid_file(void)
|
|
{
|
|
int pidfd, len;
|
|
char val[16];
|
|
|
|
len = snprintf(val, sizeof(val), "%u\n", getpid());
|
|
if (len < 0) {
|
|
syslog(LOG_ERR, "Pid error (%s)", strerror(errno));
|
|
pidfile = 0;
|
|
return 1;
|
|
}
|
|
pidfd = open(pidfile, O_CREAT | O_TRUNC | O_NOFOLLOW | O_WRONLY, 0644);
|
|
if (pidfd < 0) {
|
|
syslog(LOG_ERR, "Unable to set pidfile (%s)", strerror(errno));
|
|
pidfile = 0;
|
|
return 1;
|
|
}
|
|
(void)write(pidfd, val, (unsigned int)len);
|
|
close(pidfd);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* SIGTERM handler
|
|
*/
|
|
static void term_handler()
|
|
{
|
|
terminate = 1;
|
|
/* trigger a failure in the watch */
|
|
close(master_fd);
|
|
}
|
|
|
|
static void usage(char *program)
|
|
{
|
|
printf("%s [-d] [-v] \n", program);
|
|
exit(0);
|
|
}
|
|
|
|
void exitApp(const char *msg)
|
|
{
|
|
perror(msg);
|
|
exit(-1);
|
|
}
|
|
|
|
/*
|
|
Add a file to the watch list. We are watching for file creation, so we actually
|
|
put the watch on the directory and then examine all files created in that directory
|
|
to see if it is one that we are watching.
|
|
*/
|
|
|
|
void watch_list_add(int fd, const char *path)
|
|
{
|
|
struct watchList *ptr = NULL;
|
|
struct watchList *prev = NULL;
|
|
char *x = strdup(path);
|
|
if (!x)
|
|
exitApp("Out of Memory");
|
|
char *dir = dirname(x);
|
|
char *file = basename(path);
|
|
ptr = firstDir;
|
|
|
|
restore(path, 1);
|
|
|
|
while (ptr != NULL) {
|
|
if (strcmp(dir, ptr->dir) == 0) {
|
|
strings_list_add(&ptr->files, file);
|
|
free(x);
|
|
return;
|
|
}
|
|
prev = ptr;
|
|
ptr = ptr->next;
|
|
}
|
|
ptr = calloc(1, sizeof(struct watchList));
|
|
|
|
if (!ptr)
|
|
exitApp("Out of Memory");
|
|
|
|
ptr->wd = inotify_add_watch(fd, dir, IN_CREATE | IN_MOVED_TO);
|
|
if (ptr->wd == -1) {
|
|
free(ptr);
|
|
syslog(LOG_ERR, "Unable to watch (%s) %s\n",
|
|
path, strerror(errno));
|
|
return;
|
|
}
|
|
|
|
ptr->dir = strdup(dir);
|
|
if (!ptr->dir)
|
|
exitApp("Out of Memory");
|
|
|
|
strings_list_add(&ptr->files, file);
|
|
if (prev)
|
|
prev->next = ptr;
|
|
else
|
|
firstDir = ptr;
|
|
|
|
if (debug_mode)
|
|
printf("%d: Dir=%s, File=%s\n", ptr->wd, ptr->dir, file);
|
|
|
|
free(x);
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
int opt;
|
|
struct sigaction sa;
|
|
|
|
#ifndef DEBUG
|
|
/* Make sure we are root */
|
|
if (getuid() != 0) {
|
|
fprintf(stderr, "You must be root to run this program.\n");
|
|
return 1;
|
|
}
|
|
#endif
|
|
/* Make sure we are root */
|
|
if (is_selinux_enabled() != 1) {
|
|
fprintf(stderr, "Daemon requires SELinux be enabled to run.\n");
|
|
return 1;
|
|
}
|
|
|
|
/* Register sighandlers */
|
|
sa.sa_flags = 0;
|
|
sa.sa_handler = term_handler;
|
|
sigemptyset(&sa.sa_mask);
|
|
sigaction(SIGTERM, &sa, NULL);
|
|
|
|
set_matchpathcon_flags(MATCHPATHCON_NOTRANS);
|
|
|
|
master_fd = inotify_init();
|
|
if (master_fd < 0)
|
|
exitApp("inotify_init");
|
|
|
|
while ((opt = getopt(argc, argv, "dv")) > 0) {
|
|
switch (opt) {
|
|
case 'd':
|
|
debug_mode = 1;
|
|
break;
|
|
case 'v':
|
|
verbose_mode = 1;
|
|
break;
|
|
case '?':
|
|
usage(argv[0]);
|
|
}
|
|
}
|
|
read_config(master_fd);
|
|
|
|
if (!debug_mode)
|
|
daemon(0, 0);
|
|
|
|
write_pid_file();
|
|
|
|
while (watch(master_fd) == 0) {
|
|
};
|
|
|
|
watch_list_free(master_fd);
|
|
close(master_fd);
|
|
matchpathcon_fini();
|
|
utmpwatcher_free();
|
|
if (pidfile)
|
|
unlink(pidfile);
|
|
|
|
return 0;
|
|
}
|