Blame lib/notify.c

Packit Service 5956c7
/*
Packit Service 5956c7
 * Soft:        Keepalived is a failover program for the LVS project
Packit Service 5956c7
 *              <www.linuxvirtualserver.org>. It monitor & manipulate
Packit Service 5956c7
 *              a loadbalanced server pool using multi-layer checks.
Packit Service 5956c7
 *
Packit Service 5956c7
 * Part:        Forked system call to launch an extra script.
Packit Service 5956c7
 *
Packit Service 5956c7
 * Author:      Alexandre Cassen, <acassen@linux-vs.org>
Packit Service 5956c7
 *
Packit Service 5956c7
 *              This program is distributed in the hope that it will be useful,
Packit Service 5956c7
 *              but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit Service 5956c7
 *              MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Packit Service 5956c7
 *              See the GNU General Public License for more details.
Packit Service 5956c7
 *
Packit Service 5956c7
 *              This program is free software; you can redistribute it and/or
Packit Service 5956c7
 *              modify it under the terms of the GNU General Public License
Packit Service 5956c7
 *              as published by the Free Software Foundation; either version
Packit Service 5956c7
 *              2 of the License, or (at your option) any later version.
Packit Service 5956c7
 *
Packit Service 5956c7
 * Copyright (C) 2001-2017 Alexandre Cassen, <acassen@gmail.com>
Packit Service 5956c7
 */
Packit Service 5956c7
Packit Service 5956c7
#include "config.h"
Packit Service 5956c7
Packit Service 5956c7
#include <unistd.h>
Packit Service 5956c7
#include <stdlib.h>
Packit Service 5956c7
#include <fcntl.h>
Packit Service 5956c7
#include <errno.h>
Packit Service 5956c7
#include <signal.h>
Packit Service 5956c7
#include <grp.h>
Packit Service 5956c7
#include <string.h>
Packit Service 5956c7
#include <sys/stat.h>
Packit Service 5956c7
#include <pwd.h>
Packit Service 5956c7
#include <sys/resource.h>
Packit Service 5956c7
#include <limits.h>
Packit Service 5956c7
#include <sys/prctl.h>
Packit Service 5956c7
Packit Service 5956c7
#include "notify.h"
Packit Service 5956c7
#include "signals.h"
Packit Service 5956c7
#include "logger.h"
Packit Service 5956c7
#include "utils.h"
Packit Service 5956c7
#include "process.h"
Packit Service 5956c7
#include "parser.h"
Packit Service 5956c7
#include "keepalived_magic.h"
Packit Service 5956c7
#include "scheduler.h"
Packit Service 5956c7
Packit Service 5956c7
/* Default user/group for script execution */
Packit Service 5956c7
uid_t default_script_uid;
Packit Service 5956c7
gid_t default_script_gid;
Packit Service 5956c7
Packit Service 5956c7
/* Have we got a default user OK? */
Packit Service 5956c7
static bool default_script_uid_set = false;
Packit Service 5956c7
static bool default_user_fail = false;			/* Set if failed to set default user,
Packit Service 5956c7
							   unless it defaults to root */
Packit Service 5956c7
Packit Service 5956c7
/* Script security enabled */
Packit Service 5956c7
bool script_security = false;
Packit Service 5956c7
Packit Service 5956c7
/* Buffer length needed for getpwnam_r/getgrname_r */
Packit Service 5956c7
static size_t getpwnam_buf_len;
Packit Service 5956c7
Packit Service 5956c7
static char *path;
Packit Service 5956c7
static bool path_is_malloced;
Packit Service 5956c7
Packit Service 5956c7
/* The priority this process is running at */
Packit Service 5956c7
static int cur_prio = INT_MAX;
Packit Service 5956c7
Packit Service 5956c7
/* Buffer for expanding notify script commands */
Packit Service 5956c7
static char cmd_str_buf[MAXBUF];
Packit Service 5956c7
Packit Service 5956c7
static bool
Packit Service 5956c7
set_privileges(uid_t uid, gid_t gid)
Packit Service 5956c7
{
Packit Service 5956c7
	int retval;
Packit Service 5956c7
Packit Service 5956c7
	/* Ensure we receive SIGTERM if our parent process dies */
Packit Service 5956c7
	prctl(PR_SET_PDEATHSIG, SIGTERM);
Packit Service 5956c7
Packit Service 5956c7
	/* If we have increased our priority, set it to default for the script */
Packit Service 5956c7
	if (cur_prio != INT_MAX)
Packit Service 5956c7
		cur_prio = getpriority(PRIO_PROCESS, 0);
Packit Service 5956c7
	if (cur_prio < 0)
Packit Service 5956c7
		setpriority(PRIO_PROCESS, 0, 0);
Packit Service 5956c7
Packit Service 5956c7
	/* Drop our privileges if configured */
Packit Service 5956c7
	if (gid) {
Packit Service 5956c7
		retval = setgid(gid);
Packit Service 5956c7
		if (retval < 0) {
Packit Service 5956c7
			log_message(LOG_ALERT, "Couldn't setgid: %d (%m)", gid);
Packit Service 5956c7
			return true;
Packit Service 5956c7
		}
Packit Service 5956c7
Packit Service 5956c7
		/* Clear any extra supplementary groups */
Packit Service 5956c7
		retval = setgroups(1, &gid;;
Packit Service 5956c7
		if (retval < 0) {
Packit Service 5956c7
			log_message(LOG_ALERT, "Couldn't setgroups: %d (%m)", gid);
Packit Service 5956c7
			return true;
Packit Service 5956c7
		}
Packit Service 5956c7
	}
Packit Service 5956c7
Packit Service 5956c7
	if (uid) {
Packit Service 5956c7
		retval = setuid(uid);
Packit Service 5956c7
		if (retval < 0) {
Packit Service 5956c7
			log_message(LOG_ALERT, "Couldn't setuid: %d (%m)", uid);
Packit Service 5956c7
			return true;
Packit Service 5956c7
		}
Packit Service 5956c7
	}
Packit Service 5956c7
Packit Service 5956c7
	/* Prepare for invoking process/script */
Packit Service 5956c7
	signal_handler_script();
Packit Service 5956c7
	set_std_fd(false);
Packit Service 5956c7
Packit Service 5956c7
	return false;
Packit Service 5956c7
}
Packit Service 5956c7
Packit Service 5956c7
char *
Packit Service 5956c7
cmd_str_r(const notify_script_t *script, char *buf, size_t len)
Packit Service 5956c7
{
Packit Service 5956c7
	char *str_p;
Packit Service 5956c7
	int i;
Packit Service 5956c7
	size_t str_len;
Packit Service 5956c7
Packit Service 5956c7
	str_p = buf;
Packit Service 5956c7
Packit Service 5956c7
	for (i = 0; i < script->num_args; i++) {
Packit Service 5956c7
		/* Check there is enough room for the next word */
Packit Service 5956c7
		str_len = strlen(script->args[i]);
Packit Service 5956c7
		if (str_p + str_len + 2 + (i ? 1 : 0) >= buf + len)
Packit Service 5956c7
			return NULL;
Packit Service 5956c7
Packit Service 5956c7
		if (i)
Packit Service 5956c7
			*str_p++ = ' ';
Packit Service 93d34c
		*str_p++ = '\'';
Packit Service 5956c7
		strcpy(str_p, script->args[i]);
Packit Service 5956c7
		str_p += str_len;
Packit Service 93d34c
		*str_p++ = '\'';
Packit Service 5956c7
	}
Packit Service 5956c7
	*str_p = '\0';
Packit Service 5956c7
Packit Service 5956c7
	return buf;
Packit Service 5956c7
}
Packit Service 5956c7
Packit Service 5956c7
char *
Packit Service 5956c7
cmd_str(const notify_script_t *script)
Packit Service 5956c7
{
Packit Service 5956c7
	size_t len;
Packit Service 5956c7
	int i;
Packit Service 5956c7
Packit Service 5956c7
	for (i = 0, len = 0; i < script->num_args; i++)
Packit Service 5956c7
		len += strlen(script->args[i]) + 3; /* Add two ', and trailing space (or null for last arg) */
Packit Service 5956c7
Packit Service 5956c7
	if (len > sizeof cmd_str_buf)
Packit Service 5956c7
		return NULL;
Packit Service 5956c7
Packit Service 5956c7
	return cmd_str_r(script, cmd_str_buf, sizeof cmd_str_buf);
Packit Service 5956c7
}
Packit Service 5956c7
Packit Service 5956c7
/* Execute external script/program to process FIFO */
Packit Service 5956c7
static pid_t
Packit Service 5956c7
notify_fifo_exec(thread_master_t *m, int (*func) (thread_t *), void *arg, notify_script_t *script)
Packit Service 5956c7
{
Packit Service 5956c7
	pid_t pid;
Packit Service 5956c7
	int retval;
Packit Service 5956c7
	char *scr;
Packit Service 5956c7
Packit Service 5956c7
	pid = local_fork();
Packit Service 5956c7
Packit Service 5956c7
	/* In case of fork is error. */
Packit Service 5956c7
	if (pid < 0) {
Packit Service 5956c7
		log_message(LOG_INFO, "Failed fork process");
Packit Service 5956c7
		return -1;
Packit Service 5956c7
	}
Packit Service 5956c7
Packit Service 5956c7
	/* In case of this is parent process */
Packit Service 5956c7
	if (pid) {
Packit Service 5956c7
		thread_add_child(m, func, arg, pid, TIMER_NEVER);
Packit Service 5956c7
		return 0;
Packit Service 5956c7
	}
Packit Service 5956c7
Packit Service 5956c7
#ifdef _MEM_CHECK_
Packit Service 5956c7
	skip_mem_dump();
Packit Service 5956c7
#endif
Packit Service 5956c7
Packit Service 5956c7
	setpgid(0, 0);
Packit Service 5956c7
	set_privileges(script->uid, script->gid);
Packit Service 5956c7
Packit Service 5956c7
	if (script->flags | SC_EXECABLE) {
Packit Service 5956c7
		/* If keepalived dies, we want the script to die */
Packit Service 5956c7
		prctl(PR_SET_PDEATHSIG, SIGTERM);
Packit Service 5956c7
Packit Service 5956c7
		execve(script->args[0], script->args, environ);
Packit Service 5956c7
Packit Service 5956c7
		if (errno == EACCES)
Packit Service 5956c7
			log_message(LOG_INFO, "FIFO notify script %s is not executable", script->args[0]);
Packit Service 5956c7
		else
Packit Service 5956c7
			log_message(LOG_INFO, "Unable to execute FIFO notify script %s - errno %d - %m", script->args[0], errno);
Packit Service 5956c7
	}
Packit Service 5956c7
	else {
Packit Service 5956c7
		retval = system(scr = cmd_str(script));
Packit Service 5956c7
Packit Service 5956c7
		if (retval == 127) {
Packit Service 5956c7
			/* couldn't exec command */
Packit Service 5956c7
			log_message(LOG_ALERT, "Couldn't exec FIFO command: %s", scr);
Packit Service 5956c7
		}
Packit Service 5956c7
		else if (retval == -1)
Packit Service 5956c7
			log_message(LOG_ALERT, "Error exec-ing FIFO command: %s", scr);
Packit Service 5956c7
Packit Service 5956c7
		exit(0);
Packit Service 5956c7
	}
Packit Service 5956c7
Packit Service 5956c7
	/* unreached unless error */
Packit Service 5956c7
	exit(0);
Packit Service 5956c7
}
Packit Service 5956c7
Packit Service 5956c7
static void
Packit Service 5956c7
fifo_open(notify_fifo_t* fifo, int (*script_exit)(thread_t *), const char *type)
Packit Service 5956c7
{
Packit Service 5956c7
	int ret;
Packit Service 5956c7
	int sav_errno;
Packit Service 5956c7
Packit Service 5956c7
	if (fifo->name) {
Packit Service 5956c7
		sav_errno = 0;
Packit Service 5956c7
Packit Service 5956c7
		if (!(ret = mkfifo(fifo->name, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)))
Packit Service 5956c7
			fifo->created_fifo = true;
Packit Service 5956c7
		else {
Packit Service 5956c7
			sav_errno = errno;
Packit Service 5956c7
Packit Service 5956c7
			if (sav_errno != EEXIST)
Packit Service 5956c7
				log_message(LOG_INFO, "Unable to create %snotify fifo %s", type, fifo->name);
Packit Service 5956c7
		}
Packit Service 5956c7
Packit Service 5956c7
		if (!sav_errno || sav_errno == EEXIST) {
Packit Service 5956c7
			/* Run the notify script if there is one */
Packit Service 5956c7
			if (fifo->script)
Packit Service 5956c7
				notify_fifo_exec(master, script_exit, fifo, fifo->script);
Packit Service 5956c7
Packit Service 5956c7
			/* Now open the fifo */
Packit Service 5956c7
			if ((fifo->fd = open(fifo->name, O_RDWR | O_CLOEXEC | O_NONBLOCK | O_NOFOLLOW)) == -1) {
Packit Service 5956c7
				log_message(LOG_INFO, "Unable to open %snotify fifo %s - errno %d", type, fifo->name, errno);
Packit Service 5956c7
				if (fifo->created_fifo) {
Packit Service 5956c7
					unlink(fifo->name);
Packit Service 5956c7
					fifo->created_fifo = false;
Packit Service 5956c7
				}
Packit Service 5956c7
			}
Packit Service 5956c7
		}
Packit Service 5956c7
Packit Service 5956c7
		if (fifo->fd == -1) {
Packit Service 5956c7
			FREE(fifo->name);
Packit Service 5956c7
			fifo->name = NULL;
Packit Service 5956c7
		}
Packit Service 5956c7
	}
Packit Service 5956c7
}
Packit Service 5956c7
Packit Service 5956c7
void
Packit Service 5956c7
notify_fifo_open(notify_fifo_t* global_fifo, notify_fifo_t* fifo, int (*script_exit)(thread_t *), const char *type)
Packit Service 5956c7
{
Packit Service 5956c7
	/* Open the global FIFO if specified */
Packit Service 5956c7
	if (global_fifo->name)
Packit Service 5956c7
		fifo_open(global_fifo, script_exit, "");
Packit Service 5956c7
Packit Service 5956c7
	/* Now the specific FIFO */
Packit Service 5956c7
	if (fifo->name)
Packit Service 5956c7
		fifo_open(fifo, script_exit, type);
Packit Service 5956c7
}
Packit Service 5956c7
Packit Service 5956c7
static void
Packit Service 5956c7
fifo_close(notify_fifo_t* fifo)
Packit Service 5956c7
{
Packit Service 5956c7
	if (fifo->fd != -1) {
Packit Service 5956c7
		close(fifo->fd);
Packit Service 5956c7
		fifo->fd = -1;
Packit Service 5956c7
	}
Packit Service 5956c7
	if (fifo->created_fifo)
Packit Service 5956c7
		unlink(fifo->name);
Packit Service 5956c7
}
Packit Service 5956c7
Packit Service 5956c7
void
Packit Service 5956c7
notify_fifo_close(notify_fifo_t* global_fifo, notify_fifo_t* fifo)
Packit Service 5956c7
{
Packit Service 5956c7
	if (global_fifo->fd != -1)
Packit Service 5956c7
		fifo_close(global_fifo);
Packit Service 5956c7
Packit Service 5956c7
	fifo_close(fifo);
Packit Service 5956c7
}
Packit Service 5956c7
Packit Service 5956c7
/* perform a system call */
Packit Service 5956c7
static void system_call(const notify_script_t *) __attribute__ ((noreturn));
Packit Service 5956c7
Packit Service 5956c7
static void
Packit Service 5956c7
system_call(const notify_script_t* script)
Packit Service 5956c7
{
Packit Service 5956c7
	char *command_line = NULL;
Packit Service 5956c7
	char *str;
Packit Service 5956c7
	int retval;
Packit Service 5956c7
Packit Service 5956c7
	if (set_privileges(script->uid, script->gid))
Packit Service 5956c7
		exit(0);
Packit Service 5956c7
Packit Service 5956c7
	/* Move us into our own process group, so if the script needs to be killed
Packit Service 5956c7
	 * all its child processes will also be killed. */
Packit Service 5956c7
	setpgid(0, 0);
Packit Service 5956c7
Packit Service 5956c7
	if (script->flags & SC_EXECABLE) {
Packit Service 5956c7
		/* If keepalived dies, we want the script to die */
Packit Service 5956c7
		prctl(PR_SET_PDEATHSIG, SIGTERM);
Packit Service 5956c7
Packit Service 5956c7
		execve(script->args[0], script->args, environ);
Packit Service 5956c7
Packit Service 5956c7
		/* error */
Packit Service 5956c7
		log_message(LOG_ALERT, "Error exec-ing command '%s', error %d: %m", script->args[0], errno);
Packit Service 5956c7
	}
Packit Service 5956c7
	else {
Packit Service 5956c7
		retval = system(str = cmd_str(script));
Packit Service 5956c7
Packit Service 5956c7
		if (retval == -1)
Packit Service 5956c7
			log_message(LOG_ALERT, "Error exec-ing command: %s", str);
Packit Service 5956c7
		else if (WIFEXITED(retval)) {
Packit Service 5956c7
			if (WEXITSTATUS(retval) == 127) {
Packit Service 5956c7
				/* couldn't find command */
Packit Service 5956c7
				log_message(LOG_ALERT, "Couldn't find command: %s", str);
Packit Service 5956c7
			}
Packit Service 5956c7
			else if (WEXITSTATUS(retval) == 126) {
Packit Service 5956c7
				/* couldn't find command */
Packit Service 5956c7
				log_message(LOG_ALERT, "Couldn't execute command: %s", str);
Packit Service 5956c7
			}
Packit Service 5956c7
		}
Packit Service 5956c7
Packit Service 5956c7
		if (command_line)
Packit Service 5956c7
			FREE(command_line);
Packit Service 5956c7
Packit Service 5956c7
		if (retval == -1 ||
Packit Service 5956c7
		    (WIFEXITED(retval) && (WEXITSTATUS(retval) == 126 || WEXITSTATUS(retval) == 127)))
Packit Service 5956c7
			exit(0);
Packit Service 5956c7
		if (WIFEXITED(retval))
Packit Service 5956c7
			exit(WEXITSTATUS(retval));
Packit Service 5956c7
		if (WIFSIGNALED(retval))
Packit Service 5956c7
			kill(getpid(), WTERMSIG(retval));
Packit Service 5956c7
		exit(0);
Packit Service 5956c7
	}
Packit Service 5956c7
Packit Service 5956c7
	exit(0);
Packit Service 5956c7
}
Packit Service 5956c7
Packit Service 5956c7
/* Execute external script/program */
Packit Service 5956c7
int
Packit Service 5956c7
notify_exec(const notify_script_t *script)
Packit Service 5956c7
{
Packit Service 5956c7
	pid_t pid;
Packit Service 5956c7
Packit Service 5956c7
#ifdef ENABLE_LOG_TO_FILE
Packit Service 5956c7
	if (log_file_name)
Packit Service 5956c7
		flush_log_file();
Packit Service 5956c7
#endif
Packit Service 5956c7
Packit Service 5956c7
	pid = local_fork();
Packit Service 5956c7
Packit Service 5956c7
	if (pid < 0) {
Packit Service 5956c7
		/* fork error */
Packit Service 5956c7
		log_message(LOG_INFO, "Failed fork process");
Packit Service 5956c7
		return -1;
Packit Service 5956c7
	}
Packit Service 5956c7
Packit Service 5956c7
	if (pid) {
Packit Service 5956c7
		/* parent process */
Packit Service 5956c7
		return 0;
Packit Service 5956c7
	}
Packit Service 5956c7
Packit Service 5956c7
#ifdef _MEM_CHECK_
Packit Service 5956c7
	skip_mem_dump();
Packit Service 5956c7
#endif
Packit Service 5956c7
Packit Service 5956c7
	system_call(script);
Packit Service 5956c7
Packit Service 5956c7
	/* We should never get here */
Packit Service 5956c7
	exit(0);
Packit Service 5956c7
}
Packit Service 5956c7
Packit Service 5956c7
int
Packit Service 5956c7
system_call_script(thread_master_t *m, int (*func) (thread_t *), void * arg, unsigned long timer, notify_script_t* script)
Packit Service 5956c7
{
Packit Service 5956c7
	pid_t pid;
Packit Service 5956c7
Packit Service 5956c7
	/* Daemonization to not degrade our scheduling timer */
Packit Service 5956c7
#ifdef ENABLE_LOG_TO_FILE
Packit Service 5956c7
	if (log_file_name)
Packit Service 5956c7
		flush_log_file();
Packit Service 5956c7
#endif
Packit Service 5956c7
Packit Service 5956c7
	pid = local_fork();
Packit Service 5956c7
Packit Service 5956c7
	if (pid < 0) {
Packit Service 5956c7
		/* fork error */
Packit Service 5956c7
		log_message(LOG_INFO, "Failed fork process");
Packit Service 5956c7
		return -1;
Packit Service 5956c7
	}
Packit Service 5956c7
Packit Service 5956c7
	if (pid) {
Packit Service 5956c7
		/* parent process */
Packit Service 5956c7
		thread_add_child(m, func, arg, pid, timer);
Packit Service 5956c7
		return 0;
Packit Service 5956c7
	}
Packit Service 5956c7
Packit Service 5956c7
	/* Child process */
Packit Service 5956c7
#ifdef _MEM_CHECK_
Packit Service 5956c7
	skip_mem_dump();
Packit Service 5956c7
#endif
Packit Service 5956c7
Packit Service 5956c7
	system_call(script);
Packit Service 5956c7
Packit Service 5956c7
	exit(0); /* Script errors aren't server errors */
Packit Service 5956c7
}
Packit Service 5956c7
Packit Service 5956c7
int
Packit Service 5956c7
child_killed_thread(thread_t *thread)
Packit Service 5956c7
{
Packit Service 5956c7
	thread_master_t *m = thread->master;
Packit Service 5956c7
Packit Service 5956c7
	/* If the child didn't die, then force it */
Packit Service 5956c7
	if (thread->type == THREAD_CHILD_TIMEOUT)
Packit Service 5956c7
		kill(-getpgid(thread->u.c.pid), SIGKILL);
Packit Service 5956c7
Packit Service 5956c7
	/* If all children have died, we can now complete the
Packit Service 5956c7
	 * termination process */
Packit Service 5956c7
	if (!&m->child.rb_root.rb_node && !m->shutdown_timer_running)
Packit Service 5956c7
		thread_add_terminate_event(m);
Packit Service 5956c7
Packit Service 5956c7
	return 0;
Packit Service 5956c7
}
Packit Service 5956c7
Packit Service 5956c7
void
Packit Service 5956c7
script_killall(thread_master_t *m, int signo, bool requeue)
Packit Service 5956c7
{
Packit Service 5956c7
	thread_t *thread;
Packit Service 5956c7
	pid_t p_pgid, c_pgid;
Packit Service 5956c7
#ifndef HAVE_SIGNALFD
Packit Service 5956c7
	sigset_t old_set, child_wait;
Packit Service 5956c7
Packit Service 5956c7
	sigmask_func(0, NULL, &old_set);
Packit Service 5956c7
	if (!sigismember(&old_set, SIGCHLD)) {
Packit Service 5956c7
		sigemptyset(&child_wait);
Packit Service 5956c7
		sigaddset(&child_wait, SIGCHLD);
Packit Service 5956c7
		sigmask_func(SIG_BLOCK, &child_wait, NULL);
Packit Service 5956c7
	}
Packit Service 5956c7
#endif
Packit Service 5956c7
Packit Service 5956c7
	p_pgid = getpgid(0);
Packit Service 5956c7
Packit Service 5956c7
	rb_for_each_entry_cached(thread, &m->child, n) {
Packit Service 5956c7
		c_pgid = getpgid(thread->u.c.pid);
Packit Service 5956c7
		if (c_pgid != p_pgid)
Packit Service 5956c7
			kill(-c_pgid, signo);
Packit Service 5956c7
		else {
Packit Service 5956c7
			log_message(LOG_INFO, "Child process %d in our process group %d", c_pgid, p_pgid);
Packit Service 5956c7
			kill(thread->u.c.pid, signo);
Packit Service 5956c7
		}
Packit Service 5956c7
	}
Packit Service 5956c7
Packit Service 5956c7
	/* We want to timeout the killed children in 1 second */
Packit Service 5956c7
	if (requeue && signo != SIGKILL)
Packit Service 5956c7
		thread_children_reschedule(m, child_killed_thread, TIMER_HZ);
Packit Service 5956c7
Packit Service 5956c7
#ifndef HAVE_SIGNALFD
Packit Service 5956c7
	if (!sigismember(&old_set, SIGCHLD))
Packit Service 5956c7
		sigmask_func(SIG_UNBLOCK, &child_wait, NULL);
Packit Service 5956c7
#endif
Packit Service 5956c7
}
Packit Service 5956c7
Packit Service 5956c7
static bool
Packit Service 5956c7
is_executable(struct stat *buf, uid_t uid, gid_t gid)
Packit Service 5956c7
{
Packit Service 5956c7
	return (uid == 0 && buf->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) ||
Packit Service 5956c7
	       (uid == buf->st_uid && buf->st_mode & S_IXUSR) ||
Packit Service 5956c7
	       (uid != buf->st_uid &&
Packit Service 5956c7
		((gid == buf->st_gid && buf->st_mode & S_IXGRP) ||
Packit Service 5956c7
		 (gid != buf->st_gid && buf->st_mode & S_IXOTH)));
Packit Service 5956c7
}
Packit Service 5956c7
Packit Service 5956c7
static void
Packit Service 5956c7
replace_cmd_name(notify_script_t *script, char *new_path)
Packit Service 5956c7
{
Packit Service 5956c7
	size_t len;
Packit Service 5956c7
	char **wp = &script->args[1];
Packit Service 5956c7
	size_t num_words = 1;
Packit Service 5956c7
	char **params;
Packit Service 5956c7
	char **word_ptrs;
Packit Service 5956c7
	char *words;
Packit Service 5956c7
Packit Service 5956c7
	len = strlen(new_path) + 1;
Packit Service 5956c7
	while (*wp)
Packit Service 5956c7
		len += strlen(*wp++) + 1;
Packit Service 5956c7
	num_words = ((char **)script->args[0] - &script->args[0]) - 1;
Packit Service 5956c7
Packit Service 5956c7
	params = word_ptrs = MALLOC((num_words + 1) * sizeof(char *) + len);
Packit Service 5956c7
	words = (char *)&params[num_words + 1];
Packit Service 5956c7
Packit Service 5956c7
	strcpy(words, new_path);
Packit Service 5956c7
	*(word_ptrs++) = words;
Packit Service 5956c7
	words += strlen(words) + 1;
Packit Service 5956c7
Packit Service 5956c7
	wp = &script->args[1];
Packit Service 5956c7
	while (*wp) {
Packit Service 5956c7
		strcpy(words, *wp);
Packit Service 5956c7
		*(word_ptrs++) = words;
Packit Service 5956c7
		words += strlen(*wp) + 1;
Packit Service 5956c7
		wp++;
Packit Service 5956c7
	}
Packit Service 5956c7
	*word_ptrs = NULL;
Packit Service 5956c7
Packit Service 5956c7
	FREE(script->args);
Packit Service 5956c7
	script->args = params;
Packit Service 5956c7
}
Packit Service 5956c7
Packit Service 5956c7
/* The following function is essentially __execve() from glibc */
Packit Service 5956c7
static int
Packit Service 5956c7
find_path(notify_script_t *script)
Packit Service 5956c7
{
Packit Service 5956c7
	size_t filename_len;
Packit Service 5956c7
	size_t file_len;
Packit Service 5956c7
	size_t path_len;
Packit Service 5956c7
	char *file = script->args[0];
Packit Service 5956c7
	struct stat buf;
Packit Service 5956c7
	int ret;
Packit Service 5956c7
	int ret_val = ENOENT;
Packit Service 5956c7
	int sgid_num;
Packit Service 5956c7
	gid_t *sgid_list = NULL;
Packit Service 5956c7
	const char *subp;
Packit Service 5956c7
	bool got_eacces = false;
Packit Service 5956c7
	const char *p;
Packit Service 5956c7
Packit Service 5956c7
	/* We check the simple case first. */
Packit Service 5956c7
	if (*file == '\0')
Packit Service 5956c7
		return ENOENT;
Packit Service 5956c7
Packit Service 5956c7
	filename_len = strlen(file);
Packit Service 5956c7
	if (filename_len >= PATH_MAX) {
Packit Service 5956c7
		ret_val = ENAMETOOLONG;
Packit Service 5956c7
		goto exit1;
Packit Service 5956c7
	}
Packit Service 5956c7
Packit Service 5956c7
	/* Don't search when it contains a slash. */
Packit Service 5956c7
	if (strchr (file, '/') != NULL) {
Packit Service 5956c7
		ret_val = 0;
Packit Service 5956c7
		goto exit1;
Packit Service 5956c7
	}
Packit Service 5956c7
Packit Service 5956c7
	/* Get the path if we haven't already done so, and if that doesn't
Packit Service 5956c7
	 * exist, use CS_PATH */
Packit Service 5956c7
	if (!path) {
Packit Service 5956c7
		path = getenv ("PATH");
Packit Service 5956c7
Packit Service 5956c7
		if (!path) {
Packit Service 5956c7
			size_t cs_path_len;
Packit Service 5956c7
			path = MALLOC(cs_path_len = confstr(_CS_PATH, NULL, 0));
Packit Service 5956c7
			confstr(_CS_PATH, path, cs_path_len);
Packit Service 5956c7
			path_is_malloced = true;
Packit Service 5956c7
		}
Packit Service 5956c7
	}
Packit Service 5956c7
Packit Service 5956c7
	/* Although GLIBC does not enforce NAME_MAX, we set it as the maximum
Packit Service 5956c7
	   size to avoid unbounded stack allocation.  Same applies for
Packit Service 5956c7
	   PATH_MAX. */
Packit Service 5956c7
	file_len = strnlen (file, NAME_MAX + 1);
Packit Service 5956c7
	path_len = strnlen (path, PATH_MAX - 1) + 1;
Packit Service 5956c7
Packit Service 5956c7
	if (file_len > NAME_MAX) {
Packit Service 5956c7
		ret_val = ENAMETOOLONG;
Packit Service 5956c7
		goto exit1;
Packit Service 5956c7
	}
Packit Service 5956c7
Packit Service 5956c7
	/* Set file access to the relevant uid/gid */
Packit Service 5956c7
	if (script->gid) {
Packit Service 5956c7
		if (setegid(script->gid)) {
Packit Service 5956c7
			log_message(LOG_INFO, "Unable to set egid to %d (%m)", script->gid);
Packit Service 5956c7
			ret_val = EACCES;
Packit Service 5956c7
			goto exit1;
Packit Service 5956c7
		}
Packit Service 5956c7
Packit Service 5956c7
		/* Get our supplementary groups */
Packit Service 5956c7
		sgid_num = getgroups(0, NULL);
Packit Service 5956c7
		sgid_list = MALLOC(((size_t)sgid_num + 1) * sizeof(gid_t));
Packit Service 5956c7
		sgid_num = getgroups(sgid_num, sgid_list);
Packit Service 5956c7
		sgid_list[sgid_num++] = 0;
Packit Service 5956c7
Packit Service 5956c7
		/* Clear the supplementary group list */
Packit Service 5956c7
		if (setgroups(1, &script->gid)) {
Packit Service 5956c7
			log_message(LOG_INFO, "Unable to set supplementary gids (%m)");
Packit Service 5956c7
			ret_val = EACCES;
Packit Service 5956c7
			goto exit;
Packit Service 5956c7
		}
Packit Service 5956c7
	}
Packit Service 5956c7
	if (script->uid && seteuid(script->uid)) {
Packit Service 5956c7
		log_message(LOG_INFO, "Unable to set euid to %d (%m)", script->uid);
Packit Service 5956c7
		ret_val = EACCES;
Packit Service 5956c7
		goto exit;
Packit Service 5956c7
	}
Packit Service 5956c7
Packit Service 5956c7
	for (p = path; ; p = subp)
Packit Service 5956c7
	{
Packit Service 5956c7
		char buffer[path_len + file_len + 1];
Packit Service 5956c7
Packit Service 5956c7
		subp = strchrnul (p, ':');
Packit Service 5956c7
Packit Service 5956c7
		/* PATH is larger than PATH_MAX and thus potentially larger than
Packit Service 5956c7
		   the stack allocation. */
Packit Service 5956c7
		if (subp >= p + path_len) {
Packit Service 5956c7
			/* There are no more paths, bail out. */
Packit Service 5956c7
			if (*subp == '\0') {
Packit Service 5956c7
				ret_val = ENOENT;
Packit Service 5956c7
				goto exit;
Packit Service 5956c7
			}
Packit Service 5956c7
Packit Service 5956c7
			/* Otherwise skip to next one. */
Packit Service 5956c7
			continue;
Packit Service 5956c7
		}
Packit Service 5956c7
Packit Service 5956c7
		/* Use the current path entry, plus a '/' if nonempty, plus the file to execute. */
Packit Service 5956c7
		char *pend = mempcpy (buffer, p, (size_t)(subp - p));
Packit Service 5956c7
		*pend = '/';
Packit Service 5956c7
		memcpy (pend + (p < subp), file, file_len + 1);
Packit Service 5956c7
Packit Service 5956c7
		ret = stat (buffer, &buf;;
Packit Service 5956c7
		if (!ret) {
Packit Service 5956c7
			if (!S_ISREG(buf.st_mode))
Packit Service 5956c7
				errno = EACCES;
Packit Service 5956c7
			else if (!is_executable(&buf, script->uid, script->gid)) {
Packit Service 5956c7
				errno = EACCES;
Packit Service 5956c7
			} else {
Packit Service 5956c7
				/* Success */
Packit Service 5956c7
				log_message(LOG_INFO, "WARNING - script `%s` resolved by path search to `%s`. Please specify full path.", script->args[0], buffer);
Packit Service 5956c7
Packit Service 5956c7
				/* Copy the found file name, and any parameters */
Packit Service 5956c7
				replace_cmd_name(script, buffer);
Packit Service 5956c7
Packit Service 5956c7
				ret_val = 0;
Packit Service 5956c7
				got_eacces = false;
Packit Service 5956c7
				goto exit;
Packit Service 5956c7
			}
Packit Service 5956c7
		}
Packit Service 5956c7
Packit Service 5956c7
		switch (errno)
Packit Service 5956c7
		{
Packit Service 5956c7
		case ENOEXEC:
Packit Service 5956c7
		case EACCES:
Packit Service 5956c7
			/* Record that we got a 'Permission denied' error.  If we end
Packit Service 5956c7
			   up finding no executable we can use, we want to diagnose
Packit Service 5956c7
			   that we did find one but were denied access. */
Packit Service 5956c7
			if (!ret)
Packit Service 5956c7
				got_eacces = true;
Packit Service 5956c7
		case ENOENT:
Packit Service 5956c7
		case ESTALE:
Packit Service 5956c7
		case ENOTDIR:
Packit Service 5956c7
			/* Those errors indicate the file is missing or not executable
Packit Service 5956c7
			   by us, in which case we want to just try the next path
Packit Service 5956c7
			   directory. */
Packit Service 5956c7
		case ENODEV:
Packit Service 5956c7
		case ETIMEDOUT:
Packit Service 5956c7
			/* Some strange filesystems like AFS return even
Packit Service 5956c7
			   stranger error numbers.  They cannot reasonably mean
Packit Service 5956c7
			   anything else so ignore those, too. */
Packit Service 5956c7
			break;
Packit Service 5956c7
Packit Service 5956c7
		default:
Packit Service 5956c7
			/* Some other error means we found an executable file, but
Packit Service 5956c7
			   something went wrong accessing it; return the error to our
Packit Service 5956c7
			   caller. */
Packit Service 5956c7
			ret_val = -1;
Packit Service 5956c7
			goto exit;
Packit Service 5956c7
		}
Packit Service 5956c7
Packit Service 5956c7
		if (*subp++ == '\0')
Packit Service 5956c7
			break;
Packit Service 5956c7
	}
Packit Service 5956c7
Packit Service 5956c7
exit:
Packit Service 5956c7
	/* Restore root euid/egid */
Packit Service 5956c7
	if (script->uid && seteuid(0))
Packit Service 5956c7
		log_message(LOG_INFO, "Unable to restore euid after script search (%m)");
Packit Service 5956c7
	if (script->gid) {
Packit Service 5956c7
		if (setegid(0))
Packit Service 5956c7
			log_message(LOG_INFO, "Unable to restore egid after script search (%m)");
Packit Service 5956c7
Packit Service 5956c7
		/* restore supplementary groups */
Packit Service 5956c7
		if (sgid_list) {
Packit Service 5956c7
			if (setgroups((size_t)sgid_num, sgid_list))
Packit Service 5956c7
				log_message(LOG_INFO, "Unable to restore supplementary groups after script search (%m)");
Packit Service 5956c7
			FREE(sgid_list);
Packit Service 5956c7
		}
Packit Service 5956c7
	}
Packit Service 5956c7
Packit Service 5956c7
exit1:
Packit Service 5956c7
	/* We tried every element and none of them worked. */
Packit Service 5956c7
	if (got_eacces) {
Packit Service 5956c7
		/* At least one failure was due to permissions, so report that error. */
Packit Service 5956c7
		return EACCES;
Packit Service 5956c7
	}
Packit Service 5956c7
Packit Service 5956c7
	return ret_val;
Packit Service 5956c7
}
Packit Service 5956c7
Packit Service 5956c7
static int
Packit Service 5956c7
check_security(char *filename, bool script_security)
Packit Service 5956c7
{
Packit Service 5956c7
	char *next;
Packit Service 5956c7
	char *slash;
Packit Service 5956c7
	char sav;
Packit Service 5956c7
	int ret;
Packit Service 5956c7
	struct stat buf;
Packit Service 5956c7
	int flags = 0;
Packit Service 5956c7
Packit Service 5956c7
	next = filename;
Packit Service 5956c7
	while (next) {
Packit Service 5956c7
		slash = strchrnul(next, '/');
Packit Service 5956c7
		if (*slash)
Packit Service 5956c7
			next = slash + 1;
Packit Service 5956c7
		else {
Packit Service 5956c7
			slash = NULL;
Packit Service 5956c7
			next = NULL;
Packit Service 5956c7
		}
Packit Service 5956c7
Packit Service 5956c7
		if (slash) {
Packit Service 5956c7
			/* We want to check '/' for first time around */
Packit Service 5956c7
			if (slash == filename)
Packit Service 5956c7
				slash++;
Packit Service 5956c7
			sav = *slash;
Packit Service 5956c7
			*slash = 0;
Packit Service 5956c7
		}
Packit Service 5956c7
Packit Service 5956c7
		ret = fstatat(0, filename, &buf, AT_SYMLINK_NOFOLLOW);
Packit Service 5956c7
Packit Service 5956c7
		/* Restore the full path name */
Packit Service 5956c7
		if (slash)
Packit Service 5956c7
			*slash = sav;
Packit Service 5956c7
Packit Service 5956c7
		if (ret) {
Packit Service 5956c7
			if (errno == EACCES || errno == ELOOP || errno == ENOENT || errno == ENOTDIR)
Packit Service 5956c7
				log_message(LOG_INFO, "check_script_secure could not find script '%s' - disabling", filename);
Packit Service 5956c7
			else
Packit Service 5956c7
				log_message(LOG_INFO, "check_script_secure('%s') returned errno %d - %s - disabling", filename, errno, strerror(errno));
Packit Service 5956c7
			return flags | SC_NOTFOUND;
Packit Service 5956c7
		}
Packit Service 5956c7
Packit Service 5956c7
		/* If it is not the last item, it must be a directory. If it is the last item
Packit Service 5956c7
		 * it must be a file or a symbolic link. */
Packit Service 5956c7
		if ((slash && !S_ISDIR(buf.st_mode)) ||
Packit Service 5956c7
		    (!slash &&
Packit Service 5956c7
		     !S_ISREG(buf.st_mode) &&
Packit Service 5956c7
		     !S_ISLNK(buf.st_mode))) {
Packit Service 5956c7
			log_message(LOG_INFO, "Wrong file type found in script path '%s'.", filename);
Packit Service 5956c7
			return flags | SC_INHIBIT;
Packit Service 5956c7
		}
Packit Service 5956c7
Packit Service 5956c7
		if (buf.st_uid ||				/* Owner is not root */
Packit Service 5956c7
		    (((S_ISDIR(buf.st_mode) &&			/* A directory without the sticky bit set */
Packit Service 5956c7
		       !(buf.st_mode & S_ISVTX)) ||
Packit Service 5956c7
		      S_ISREG(buf.st_mode)) &&			/* This is a file */
Packit Service 5956c7
		     ((buf.st_gid && buf.st_mode & S_IWGRP) ||	/* Group is not root and group write permission */
Packit Service 5956c7
		      buf.st_mode & S_IWOTH))) {		/* World has write permission */
Packit Service 5956c7
			log_message(LOG_INFO, "Unsafe permissions found for script '%s'%s.", filename, script_security ? " - disabling" : "");
Packit Service 5956c7
			flags |= SC_INSECURE;
Packit Service 5956c7
			return flags | (script_security ? SC_INHIBIT : 0);
Packit Service 5956c7
		}
Packit Service 5956c7
	}
Packit Service 5956c7
Packit Service 5956c7
	return flags;
Packit Service 5956c7
}
Packit Service 5956c7
Packit Service 5956c7
int
Packit Service 5956c7
check_script_secure(notify_script_t *script,
Packit Service 5956c7
#ifndef _HAVE_LIBMAGIC_
Packit Service 5956c7
					     __attribute__((unused))
Packit Service 5956c7
#endif
Packit Service 5956c7
								     magic_t magic)
Packit Service 5956c7
{
Packit Service 5956c7
	int flags;
Packit Service 5956c7
	int ret, ret_real, ret_new;
Packit Service 5956c7
	struct stat file_buf, real_buf;
Packit Service 5956c7
	bool need_script_protection = false;
Packit Service 5956c7
	uid_t old_uid = 0;
Packit Service 5956c7
	gid_t old_gid = 0;
Packit Service 5956c7
	char *new_path;
Packit Service 5956c7
	int sav_errno;
Packit Service 5956c7
	char *real_file_path;
Packit Service 5956c7
	char *orig_file_part, *new_file_part;
Packit Service 5956c7
Packit Service 5956c7
	if (!script)
Packit Service 5956c7
		return 0;
Packit Service 5956c7
Packit Service 5956c7
	/* If the script starts "</" (possibly with white space between
Packit Service 5956c7
	 * the '<' and '/'), it is checking for a file being openable,
Packit Service 5956c7
	 * so it won't be executed */
Packit Service 5956c7
	if (script->args[0][0] == '<' &&
Packit Service 5956c7
	    script->args[0][strspn(script->args[0] + 1, " \t") + 1] == '/')
Packit Service 5956c7
		return SC_SYSTEM;
Packit Service 5956c7
Packit Service 5956c7
	if (!strchr(script->args[0], '/')) {
Packit Service 5956c7
		/* It is a bare file name, so do a path search */
Packit Service 5956c7
		if ((ret = find_path(script))) {
Packit Service 5956c7
			if (ret == EACCES)
Packit Service 5956c7
				log_message(LOG_INFO, "Permissions failure for script %s in path - disabling", script->args[0]);
Packit Service 5956c7
			else
Packit Service 5956c7
				log_message(LOG_INFO, "Cannot find script %s in path - disabling", script->args[0]);
Packit Service 5956c7
			return SC_NOTFOUND;
Packit Service 5956c7
		}
Packit Service 5956c7
	}
Packit Service 5956c7
Packit Service 5956c7
	/* Check script accessible by the user running it */
Packit Service 5956c7
	if (script->uid)
Packit Service 5956c7
		old_uid = geteuid();
Packit Service 5956c7
	if (script->gid)
Packit Service 5956c7
		old_gid = getegid();
Packit Service 5956c7
Packit Service 5956c7
	if ((script->gid && setegid(script->gid)) ||
Packit Service 5956c7
	    (script->uid && seteuid(script->uid))) {
Packit Service 5956c7
		log_message(LOG_INFO, "Unable to set uid:gid %d:%d for script %s - disabling", script->uid, script->gid, script->args[0]);
Packit Service 5956c7
Packit Service 5956c7
		if ((script->uid && seteuid(old_uid)) ||
Packit Service 5956c7
		    (script->gid && setegid(old_gid)))
Packit Service 5956c7
			log_message(LOG_INFO, "Unable to restore uid:gid %d:%d after script %s", script->uid, script->gid, script->args[0]);
Packit Service 5956c7
Packit Service 5956c7
		return SC_INHIBIT;
Packit Service 5956c7
	}
Packit Service 5956c7
Packit Service 5956c7
	/* Remove /./, /../, multiple /'s, and resolve symbolic links */
Packit Service 5956c7
	new_path = realpath(script->args[0], NULL);
Packit Service 5956c7
	sav_errno = errno;
Packit Service 5956c7
Packit Service 5956c7
	if ((script->gid && setegid(old_gid)) ||
Packit Service 5956c7
	    (script->uid && seteuid(old_uid)))
Packit Service 5956c7
		log_message(LOG_INFO, "Unable to restore uid:gid %d:%d after checking script %s", script->uid, script->gid, script->args[0]);
Packit Service 5956c7
Packit Service 5956c7
	if (!new_path)
Packit Service 5956c7
	{
Packit Service 5956c7
		log_message(LOG_INFO, "Script %s cannot be accessed - %s", script->args[0], strerror(sav_errno));
Packit Service 5956c7
Packit Service 5956c7
		return SC_NOTFOUND;
Packit Service 5956c7
	}
Packit Service 5956c7
Packit Service 5956c7
	real_file_path = NULL;
Packit Service 5956c7
Packit Service 5956c7
	orig_file_part = strrchr(script->args[0], '/');
Packit Service 5956c7
	new_file_part = strrchr(new_path, '/');
Packit Service 5956c7
	if (strcmp(script->args[0], new_path)) {
Packit Service 5956c7
		/* The path name is different */
Packit Service 5956c7
Packit Service 5956c7
		/* If the file name parts don't match, we need to be careful to
Packit Service 5956c7
		 * ensure that we preserve the file name part since some executables
Packit Service 5956c7
		 * alter their behaviour based on what they are called */
Packit Service 5956c7
		if (strcmp(orig_file_part + 1, new_file_part + 1)) {
Packit Service 5956c7
			real_file_path = new_path;
Packit Service 5956c7
			new_path = MALLOC(new_file_part - real_file_path + 1 + strlen(orig_file_part + 1) + 1);
Packit Service 5956c7
			strncpy(new_path, real_file_path, new_file_part + 1 - real_file_path);
Packit Service 5956c7
			strcpy(new_path + (new_file_part + 1 - real_file_path), orig_file_part + 1);
Packit Service 5956c7
Packit Service 5956c7
			/* Now check this is the same file */
Packit Service 5956c7
			ret_real = stat(real_file_path, &real_buf);
Packit Service 5956c7
			ret_new = stat(new_path, &file_buf);
Packit Service 5956c7
			if (!ret_real &&
Packit Service 5956c7
			    (ret_new ||
Packit Service 5956c7
			     real_buf.st_dev != file_buf.st_dev ||
Packit Service 5956c7
			     real_buf.st_ino != file_buf.st_ino)) {
Packit Service 5956c7
				/* It doesn't resolve to the same file */
Packit Service 5956c7
				FREE(new_path);
Packit Service 5956c7
				new_path = real_file_path;
Packit Service 5956c7
				real_file_path = NULL;
Packit Service 5956c7
			}
Packit Service 5956c7
		}
Packit Service 5956c7
Packit Service 5956c7
		if (strcmp(script->args[0], new_path)) {
Packit Service 5956c7
	 		/* We need to set up all the args again */
Packit Service 5956c7
			replace_cmd_name(script, new_path);
Packit Service 5956c7
		}
Packit Service 5956c7
	}
Packit Service 5956c7
Packit Service 5956c7
	if (!real_file_path)
Packit Service 5956c7
		free(new_path);
Packit Service 5956c7
	else
Packit Service 5956c7
		FREE(new_path);
Packit Service 5956c7
Packit Service 5956c7
	/* Get the permissions for the file itself */
Packit Service 5956c7
	if (stat(real_file_path ? real_file_path : script->args[0], &file_buf)) {
Packit Service 5956c7
		log_message(LOG_INFO, "Unable to access script `%s` - disabling", script->args[0]);
Packit Service 5956c7
		return SC_NOTFOUND;
Packit Service 5956c7
	}
Packit Service 5956c7
Packit Service 5956c7
	flags = SC_ISSCRIPT;
Packit Service 5956c7
Packit Service 5956c7
	/* We have the final file. Check if root is executing it, or it is set uid/gid root. */
Packit Service 5956c7
	if (is_executable(&file_buf, script->uid, script->gid)) {
Packit Service 5956c7
		flags |= SC_EXECUTABLE;
Packit Service 5956c7
		if (script->uid == 0 || script->gid == 0 ||
Packit Service 5956c7
		    (file_buf.st_uid == 0 && (file_buf.st_mode & S_IXUSR) && (file_buf.st_mode & S_ISUID)) ||
Packit Service 5956c7
		    (file_buf.st_gid == 0 && (file_buf.st_mode & S_IXGRP) && (file_buf.st_mode & S_ISGID)))
Packit Service 5956c7
			need_script_protection = true;
Packit Service 5956c7
	} else
Packit Service 5956c7
		log_message(LOG_INFO, "WARNING - script '%s' is not executable for uid:gid %d:%d - disabling.", script->args[0], script->uid, script->gid);
Packit Service 5956c7
Packit Service 5956c7
	/* Default to execable */
Packit Service 5956c7
	script->flags |= SC_EXECABLE;
Packit Service 5956c7
#ifdef _HAVE_LIBMAGIC_
Packit Service 5956c7
	if (magic && flags & SC_EXECUTABLE) {
Packit Service 5956c7
		const char *magic_desc = magic_file(magic, real_file_path ? real_file_path : script->args[0]);
Packit Service 5956c7
		if (!strstr(magic_desc, " executable") &&
Packit Service 5956c7
		    !strstr(magic_desc, " shared object")) {
Packit Service 5956c7
			log_message(LOG_INFO, "Please add a #! shebang to script %s", script->args[0]);
Packit Service 5956c7
			script->flags &= ~SC_EXECABLE;
Packit Service 5956c7
		}
Packit Service 5956c7
	}
Packit Service 5956c7
#endif
Packit Service 5956c7
Packit Service 5956c7
	if (!need_script_protection) {
Packit Service 5956c7
		if (real_file_path)
Packit Service 5956c7
			free(real_file_path);
Packit Service 5956c7
Packit Service 5956c7
		return flags;
Packit Service 5956c7
	}
Packit Service 5956c7
Packit Service 5956c7
	/* Make sure that all parts of the path are not non-root writable */
Packit Service 5956c7
	flags |= check_security(script->args[0], script_security);
Packit Service 5956c7
Packit Service 5956c7
	if (real_file_path) {
Packit Service 5956c7
		flags |= check_security(real_file_path, script_security);
Packit Service 5956c7
		free(real_file_path);
Packit Service 5956c7
	}
Packit Service 5956c7
Packit Service 5956c7
	return flags;
Packit Service 5956c7
}
Packit Service 5956c7
Packit Service 5956c7
int
Packit Service 5956c7
check_notify_script_secure(notify_script_t **script_p, magic_t magic)
Packit Service 5956c7
{
Packit Service 5956c7
	int flags;
Packit Service 5956c7
	notify_script_t *script = *script_p;
Packit Service 5956c7
Packit Service 5956c7
	if (!script)
Packit Service 5956c7
		return 0;
Packit Service 5956c7
Packit Service 5956c7
	flags = check_script_secure(script, magic);
Packit Service 5956c7
Packit Service 5956c7
	/* Mark not to run if needs inhibiting */
Packit Service 5956c7
	if ((flags & (SC_INHIBIT | SC_NOTFOUND)) ||
Packit Service 5956c7
	    !(flags & SC_EXECUTABLE))
Packit Service 5956c7
		free_notify_script(script_p);
Packit Service 5956c7
Packit Service 5956c7
	return flags;
Packit Service 5956c7
}
Packit Service 5956c7
Packit Service 5956c7
static void
Packit Service 5956c7
set_pwnam_buf_len(void)
Packit Service 5956c7
{
Packit Service 5956c7
	long buf_len;
Packit Service 5956c7
Packit Service 5956c7
	/* Get buffer length needed for getpwnam_r/getgrnam_r */
Packit Service 5956c7
	if ((buf_len = sysconf(_SC_GETPW_R_SIZE_MAX)) == -1)
Packit Service 5956c7
		getpwnam_buf_len = 1024;	/* A safe default if no value is returned */
Packit Service 5956c7
	else
Packit Service 5956c7
		getpwnam_buf_len = (size_t)buf_len;
Packit Service 5956c7
	if ((buf_len = sysconf(_SC_GETGR_R_SIZE_MAX)) != -1 &&
Packit Service 5956c7
	    (size_t)buf_len > getpwnam_buf_len)
Packit Service 5956c7
		getpwnam_buf_len = (size_t)buf_len;
Packit Service 5956c7
}
Packit Service 5956c7
Packit Service 5956c7
bool
Packit Service 5956c7
set_uid_gid(const char *username, const char *groupname, uid_t *uid_p, gid_t *gid_p, bool default_user)
Packit Service 5956c7
{
Packit Service 5956c7
	uid_t uid;
Packit Service 5956c7
	gid_t gid;
Packit Service 5956c7
	struct passwd pwd;
Packit Service 5956c7
	struct passwd *pwd_p;
Packit Service 5956c7
	struct group grp;
Packit Service 5956c7
	struct group *grp_p;
Packit Service 5956c7
	int ret;
Packit Service 5956c7
	bool using_default_default_user = false;
Packit Service 5956c7
Packit Service 5956c7
	if (!getpwnam_buf_len)
Packit Service 5956c7
		set_pwnam_buf_len();
Packit Service 5956c7
Packit Service 5956c7
	{
Packit Service 5956c7
		char buf[getpwnam_buf_len];
Packit Service 5956c7
Packit Service 5956c7
		if (default_user && !username) {
Packit Service 5956c7
			using_default_default_user = true;
Packit Service 5956c7
			username = "keepalived_script";
Packit Service 5956c7
		}
Packit Service 5956c7
Packit Service 5956c7
		if ((ret = getpwnam_r(username, &pwd, buf, sizeof(buf), &pwd_p))) {
Packit Service 5956c7
			log_message(LOG_INFO, "Unable to resolve %sscript username '%s' - ignoring", default_user ? "default " : "", username);
Packit Service 5956c7
			return true;
Packit Service 5956c7
		}
Packit Service 5956c7
		if (!pwd_p) {
Packit Service 5956c7
			if (using_default_default_user)
Packit Service 5956c7
				log_message(LOG_INFO, "WARNING - default user '%s' for script execution does not exist - please create.", username);
Packit Service 5956c7
			else
Packit Service 5956c7
				log_message(LOG_INFO, "%script user '%s' does not exist", default_user ? "Default s" : "S", username);
Packit Service 5956c7
			return true;
Packit Service 5956c7
		}
Packit Service 5956c7
Packit Service 5956c7
		uid = pwd.pw_uid;
Packit Service 5956c7
		gid = pwd.pw_gid;
Packit Service 5956c7
Packit Service 5956c7
		if (groupname) {
Packit Service 5956c7
			if ((ret = getgrnam_r(groupname, &grp, buf, sizeof(buf), &grp_p))) {
Packit Service 5956c7
				log_message(LOG_INFO, "Unable to resolve %sscript group name '%s' - ignoring", default_user ? "default " : "", groupname);
Packit Service 5956c7
				return true;
Packit Service 5956c7
			}
Packit Service 5956c7
			if (!grp_p) {
Packit Service 5956c7
				log_message(LOG_INFO, "%script group '%s' does not exist", default_user ? "Default s" : "S", groupname);
Packit Service 5956c7
				return true;
Packit Service 5956c7
			}
Packit Service 5956c7
			gid = grp.gr_gid;
Packit Service 5956c7
		}
Packit Service 5956c7
Packit Service 5956c7
		*uid_p = uid;
Packit Service 5956c7
		*gid_p = gid;
Packit Service 5956c7
	}
Packit Service 5956c7
Packit Service 5956c7
	return false;
Packit Service 5956c7
}
Packit Service 5956c7
Packit Service 5956c7
/* The default script user/group is keepalived_script if it exists, or root otherwise */
Packit Service 5956c7
bool
Packit Service 5956c7
set_default_script_user(const char *username, const char *groupname)
Packit Service 5956c7
{
Packit Service 5956c7
	if (!default_script_uid_set || username) {
Packit Service 5956c7
		/* Even if we fail to set it, there is no point in trying again */
Packit Service 5956c7
		default_script_uid_set = true;
Packit Service 5956c7
Packit Service 5956c7
		if (set_uid_gid(username, groupname, &default_script_uid, &default_script_gid, true)) {
Packit Service 5956c7
			if (username || script_security)
Packit Service 5956c7
				default_user_fail = true;
Packit Service 5956c7
		}
Packit Service 5956c7
		else
Packit Service 5956c7
			default_user_fail = false;
Packit Service 5956c7
	}
Packit Service 5956c7
Packit Service 5956c7
	return default_user_fail;
Packit Service 5956c7
}
Packit Service 5956c7
Packit Service 5956c7
bool
Packit Service 5956c7
set_script_uid_gid(vector_t *strvec, unsigned keyword_offset, uid_t *uid_p, gid_t *gid_p)
Packit Service 5956c7
{
Packit Service 5956c7
	char *username;
Packit Service 5956c7
	char *groupname;
Packit Service 5956c7
Packit Service 5956c7
	username = strvec_slot(strvec, keyword_offset);
Packit Service 5956c7
	if (vector_size(strvec) > keyword_offset + 1)
Packit Service 5956c7
		groupname = strvec_slot(strvec, keyword_offset + 1);
Packit Service 5956c7
	else
Packit Service 5956c7
		groupname = NULL;
Packit Service 5956c7
Packit Service 5956c7
	return set_uid_gid(username, groupname, uid_p, gid_p, false);
Packit Service 5956c7
}
Packit Service 5956c7
Packit Service 5956c7
void
Packit Service 5956c7
set_script_params_array(vector_t *strvec, notify_script_t *script, unsigned extra_params)
Packit Service 5956c7
{
Packit Service 5956c7
	unsigned num_words = 0;
Packit Service 5956c7
	size_t len = 0;
Packit Service 5956c7
	char **word_ptrs;
Packit Service 5956c7
	char *words;
Packit Service 5956c7
	vector_t *strvec_qe = NULL;
Packit Service 5956c7
	unsigned i;
Packit Service 5956c7
Packit Service 5956c7
	/* Count the number of words, and total string length */
Packit Service 5956c7
	if (vector_size(strvec) >= 2)
Packit Service 5956c7
		strvec_qe = alloc_strvec_quoted_escaped(strvec_slot(strvec, 1));
Packit Service 5956c7
Packit Service 5956c7
	if (!strvec_qe)
Packit Service 5956c7
		return;
Packit Service 5956c7
Packit Service 5956c7
	num_words = vector_size(strvec_qe);
Packit Service 5956c7
	for (i = 0; i < num_words; i++)
Packit Service 5956c7
		len += strlen(strvec_slot(strvec_qe, i)) + 1;
Packit Service 5956c7
Packit Service 5956c7
	/* Allocate memory for pointers to words and words themselves */
Packit Service 5956c7
	script->args = word_ptrs = MALLOC((num_words + extra_params + 1) * sizeof(char *) + len);
Packit Service 5956c7
	words = (char *)word_ptrs + (num_words + extra_params + 1) * sizeof(char *);
Packit Service 5956c7
Packit Service 5956c7
	/* Set up pointers to words, and copy the words */
Packit Service 5956c7
	for (i = 0; i < num_words; i++) {
Packit Service 5956c7
		strcpy(words, strvec_slot(strvec_qe, i));
Packit Service 5956c7
		*(word_ptrs++) = words;
Packit Service 5956c7
		words += strlen(words) + 1;
Packit Service 5956c7
	}
Packit Service 5956c7
	*word_ptrs = NULL;
Packit Service 5956c7
Packit Service 5956c7
	script->num_args = num_words;
Packit Service 5956c7
Packit Service 5956c7
	free_strvec(strvec_qe);
Packit Service 5956c7
}
Packit Service 5956c7
Packit Service 5956c7
notify_script_t*
Packit Service 5956c7
notify_script_init(int extra_params, const char *type)
Packit Service 5956c7
{
Packit Service 5956c7
	notify_script_t *script = MALLOC(sizeof(notify_script_t));
Packit Service 5956c7
	vector_t *strvec_qe;
Packit Service 5956c7
Packit Service 5956c7
	/* We need to reparse the command line, allowing for quoted and escaped strings */
Packit Service 5956c7
	strvec_qe = alloc_strvec_quoted_escaped(NULL);
Packit Service 5956c7
Packit Service 5956c7
	if (!strvec_qe) {
Packit Service 5956c7
		log_message(LOG_INFO, "Unable to parse notify script");
Packit Service 5956c7
		FREE(script);
Packit Service 5956c7
		return NULL;
Packit Service 5956c7
	}
Packit Service 5956c7
Packit Service 5956c7
	set_script_params_array(strvec_qe, script, extra_params);
Packit Service 5956c7
	if (!script->args) {
Packit Service 5956c7
		log_message(LOG_INFO, "Unable to parse script '%s' - ignoring", FMT_STR_VSLOT(strvec_qe, 1));
Packit Service 5956c7
		FREE(script);
Packit Service 5956c7
		free_strvec(strvec_qe);
Packit Service 5956c7
		return NULL;
Packit Service 5956c7
	}
Packit Service 5956c7
Packit Service 5956c7
	script->flags = 0;
Packit Service 5956c7
Packit Service 5956c7
	if (vector_size(strvec_qe) > 2) {
Packit Service 5956c7
		if (set_script_uid_gid(strvec_qe, 2, &script->uid, &script->gid)) {
Packit Service 5956c7
			log_message(LOG_INFO, "Invalid user/group for %s script %s - ignoring", type, script->args[0]);
Packit Service 5956c7
			FREE(script->args);
Packit Service 5956c7
			FREE(script);
Packit Service 5956c7
			free_strvec(strvec_qe);
Packit Service 5956c7
			return NULL;
Packit Service 5956c7
		}
Packit Service 5956c7
	}
Packit Service 5956c7
	else {
Packit Service 5956c7
		if (set_default_script_user(NULL, NULL)) {
Packit Service 5956c7
			log_message(LOG_INFO, "Failed to set default user for %s script %s - ignoring", type, script->args[0]);
Packit Service 5956c7
			FREE(script->args);
Packit Service 5956c7
			FREE(script);
Packit Service 5956c7
			free_strvec(strvec_qe);
Packit Service 5956c7
			return NULL;
Packit Service 5956c7
		}
Packit Service 5956c7
Packit Service 5956c7
		script->uid = default_script_uid;
Packit Service 5956c7
		script->gid = default_script_gid;
Packit Service 5956c7
	}
Packit Service 5956c7
Packit Service 5956c7
	free_strvec(strvec_qe);
Packit Service 5956c7
Packit Service 5956c7
	return script;
Packit Service 5956c7
}
Packit Service 5956c7
Packit Service 5956c7
void
Packit Service 5956c7
add_script_param(notify_script_t *script, char *param)
Packit Service 5956c7
{
Packit Service 5956c7
	/* We store the args as an array of pointers to the args, terminated
Packit Service 5956c7
	 * by a NULL pointer, followed by the null terminated strings themselves
Packit Service 5956c7
	 */
Packit Service 5956c7
Packit Service 5956c7
	if (script->args[script->num_args + 1]) {
Packit Service 5956c7
		log_message(LOG_INFO, "notify_fifo_script %s no room to add parameter %s", script->args[0], param);
Packit Service 5956c7
		return;
Packit Service 5956c7
	}
Packit Service 5956c7
Packit Service 5956c7
	/* Add the extra parameter in the pre-reserved slot at the end */
Packit Service 5956c7
	script->args[script->num_args++] = param;
Packit Service 5956c7
}
Packit Service 5956c7
Packit Service 5956c7
void
Packit Service 5956c7
notify_resource_release(void)
Packit Service 5956c7
{
Packit Service 5956c7
	if (path_is_malloced) {
Packit Service 5956c7
		FREE(path);
Packit Service 5956c7
		path_is_malloced = false;
Packit Service 5956c7
		path = NULL;
Packit Service 5956c7
	}
Packit Service 5956c7
}
Packit Service 5956c7
Packit Service 5956c7
bool
Packit Service 5956c7
notify_script_compare(notify_script_t *a, notify_script_t *b)
Packit Service 5956c7
{
Packit Service 5956c7
	int i;
Packit Service 5956c7
Packit Service 5956c7
	if (a->num_args != b->num_args)
Packit Service 5956c7
		return false;
Packit Service 5956c7
	for (i = 0; i < a->num_args; i++) {
Packit Service 5956c7
		if (strcmp(a->args[i], b->args[i]))
Packit Service 5956c7
			return false;
Packit Service 5956c7
	}
Packit Service 5956c7
Packit Service 5956c7
	return true;
Packit Service 5956c7
}
Packit Service 5956c7
Packit Service 5956c7
#ifdef THREAD_DUMP
Packit Service 5956c7
void
Packit Service 5956c7
register_notify_addresses(void)
Packit Service 5956c7
{
Packit Service 5956c7
	register_thread_address("child_killed_thread", child_killed_thread);
Packit Service 5956c7
}
Packit Service 5956c7
#endif