/*
* Copyright © 2002 Networks Associates Technology, Inc.
* All rights reserved.
*
* privman.cc
* Provides the server half of the process. This half retains priviledge
* and should be nei-invulnerable.
*
* $Id: privman.cc,v 1.57 2003/04/10 00:25:52 dougk Exp $
*/
#include "../config.h"
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include <limits.h>
#include <fcntl.h>
#include <string.h>
#include <pwd.h>
#include <assert.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <sys/wait.h>
#include <syslog.h>
#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif
#ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#ifdef HAVE_SYS_RESOURCE_H
#include <sys/resource.h>
#endif
#ifdef HAVE_SIGNAL_H
#include <signal.h>
#endif
#include <set>
#include <map>
#include "privman.h"
#include "msghdr.h"
#include "types.h"
#include "priv_impl.h"
#ifdef __cplusplus
#define UNUSED(p)
#else
#define UNUSED(p) p __attribute((unused))
#endif
extern char **environ;
/* Globals */
int privmand_fd = -1; /* FD to talk to the other process */
pid_t child_pid = 0;
static bool p_wait_on_child = true;
/* The config for this invocation. Not static so that the parser can
* find it to set it. Parser run from priv_init.
*/
config_t *config;
/* Have to wrap wait. Go ahead, ask why... :) */
/* "object" data for mywait and the signal function. */
static int mywait_status_v[4];
static int mywait_pid_v[4];
static struct rusage mywait_rusage_v[4];
static int mywait_i = 0;
#define sizeof_array(a) (sizeof(a) / sizeof(a[0]))
static void sigchld_handler(int UNUSED(i)) {
do {
mywait_pid_v[mywait_i] = wait4(-1, mywait_status_v + mywait_i,
WNOHANG,
mywait_rusage_v + mywait_i);
if (mywait_pid_v[mywait_i] <= 0 )
break;
mywait_i = (mywait_i + 1) % sizeof_array(mywait_status_v);
} while (1);
}
/* Overhead for sigaction */
static struct sigaction child_sigaction;
/* No support for options. */
static pid_t mywait4(pid_t pid, int *status, int options, struct rusage *usage)
{
unsigned i;
int rc;
if (pid < -1) {
errno = EINVAL;
return -1;
}
do {
for (i = 0 ; i < sizeof_array(mywait_status_v) ; ++i) {
if (mywait_pid_v[i] < 1)
continue; /* skip this one. */
if (pid == -1 || mywait_pid_v[i] == pid) {
if (status != NULL)
*status = mywait_status_v[i];
if (usage != NULL)
*usage = mywait_rusage_v[i];
rc = mywait_pid_v[i];
mywait_pid_v[i] = 0;
return rc;
}
}
} while (!(options & WNOHANG) &&
select(0,NULL,NULL,NULL,NULL) == -1 && errno == EINTR);
return -1;
}
/* Dispatch table */
static std::map< enum commands, void(*)(message_t*) > function_map;
static inline
void freeArgv(char * argv[])
{
for (int i = 0; argv[i] != NULL; ++i)
free(argv[i]);
free(argv);
}
static inline
char *msg_getAllocStr(message_t *msg, size_t maxlen, const char *errormsg) {
char *rv = msg_getAllocStr(msg, maxlen);
if (rv == NULL)
boom(errormsg);
return rv;
}
typedef enum {
at_none = -1,
at_read_only,
at_read_write,
at_append_only,
at_unlink
} accessType_t;
static accessType_t openAccessType(int flags) {
if ((flags & 3) == O_RDONLY)
return at_read_only;
else if (((flags & 3) == O_WRONLY) && (flags & O_APPEND))
return at_append_only;
else if (((flags & 3) == O_RDWR) || (flags & 3) == O_WRONLY)
return at_read_write;
else
return at_none;
}
static bool openPerm(const char *path, accessType_t type)
{
/* MAC check here. TBD: use real globs instead of this hack */
/* Hack:
* 1) Path is in set. Go home happy.
* 2) /first/bit/of/path/[*] is in set. Go home happy.
*/
/* Use the "type" enum to index into the list[] array. */
path_list *list[] = {
&(config->open_ro),
&(config->open_rw),
&(config->open_ao),
&(config->unlink)
};
char testpath[MAXPATHLEN+1];
char *offset;
if (type == at_none)
return false;
strncpy(testpath, path, sizeof(testpath)-2);
testpath[sizeof(testpath)-2] = '\0';
offset = testpath + strlen(path); /* char* cause that's what rindex
* returns */
while (offset != NULL) {
memcpy(testpath, path, offset - testpath);
if ( *offset == '/' ) { /* We have a directory. Look for a glob */
offset[1] = '*'; /* See the -2 above for your "space" question */
offset[2] = '\0';
}
if (list[type]->count(testpath) != 0)
break;
/* Chop off the last element, and loop */
*offset = '\0';
offset = rindex(testpath, '/');
}
if (offset == NULL)
return false;
return true;
}
/* True is "user" is mentioned in a runas statement.
* Or if '*' is mentioned, and user is not root.
*/
static bool runasPerm(const char *user)
{
if (user == NULL || user[0] == '\0' || !strcmp(user, "*"))
return false;
if (config->user.count(user) > 0)
return true;
if (config->user.count("*") > 0) {
struct passwd *pw = getpwnam(user);
if (pw == NULL || pw->pw_uid == 0)
return false;
return true;
}
return false;
}
static void sendEPERM(message_t *msg, const char *reason)
{
msg_initResponce(msg, -EPERM);
if (reason != NULL)
syslog(LOG_NOTICE, "%s", reason);
msg_sendmsg(msg, privmand_fd, "sendEPERM(sendmsg)");
}
/* Like "realpath", but can cope with a missing file in a non
* missing directory. Might not handle dangling symlinks right. XXX
*/
static bool myrealpath(const char *path, char *resolved)
{
char *rv;
char buf[PATH_MAX+1];
char last_elm[PATH_MAX+1];
char *last_slash;
int n;
strncpy(buf, path, sizeof(buf)-1);
buf[sizeof(buf)-1] = '\0';
/* deletegate to "realpath" */
rv = realpath(buf, resolved);
if (rv != NULL || errno != ENOENT)
return rv != NULL;
/* Ok, doesn't exist. Chop off the filename and save it.
* Huge buffer way to big. *shrug*
*/
last_slash = rindex(buf, '/');
if (last_slash == NULL)
return false;
strncpy(last_elm, last_slash, sizeof(last_elm)-1);
last_elm[sizeof(last_elm)-1] = '\0';
/* Now chop off the last bit, and try the directory */
*last_slash = '\0';
rv = realpath(buf, resolved);
if (rv == NULL)
return false;
/* Add it back, and call it a day */
n = strlen(resolved);
strncpy(resolved+n, last_elm, PATH_MAX-n);
return true;
}
/* Handle certain requests */
static void unlinkFile(message_t *msg) {
char *path;
char *cwd;
char canpath[MAXPATHLEN+1];
int retval, n;
cwd = msg_getAllocStr(msg, MAXPATHLEN+1, "unlinkFile: bad cwd");
path = msg_getAllocStr(msg, MAXPATHLEN+1, "unlinkFile, path path");
/* Canacolize the path, so that a comparison with the
* allowed list makes sence, and in case the client did a chdir.
*
* The client sends us "getcwd()" output, so no trailing '/'. If
* the client wants to lie to us for this message, no great foul.
* He gets no file.
*/
n = strlen(cwd);
if (path[0] == '/') { /* absolute or not? */
/* Abs: nuke cwd with path, then realpath it. */
strncpy(cwd, path, sizeof(cwd) - n);
} else {
cwd[n++] = '/'; /* Path seperator */
strncpy(cwd + n, path, sizeof(cwd) - n);
}
if (!myrealpath(cwd, canpath)) {
/* Could be lots of reasons. So lets confuse em with whatever
* realpath said.
*/
msg_initResponce(msg, -errno);
msg_sendmsg(msg, privmand_fd, "unlinkFile(sendmsg)");
}
/* MAC check here. TBD: use real globs instead of this hack */
if (!openPerm(canpath, at_unlink)) {
sendEPERM(msg, "Unauthorized attempt to unlink");
} else {
/* Ok, now do it. */
retval = unlink(canpath);
if (retval < 0) {
msg_initResponce(msg, -errno);
syslog(LOG_WARNING, "priv_unlink(unlink): %m");
} else {
msg_initResponce(msg, 0);
}
msg_sendmsg(msg, privmand_fd, "unlinkFile(sendmsg)");
}
free(path); free(cwd);
}
/* Handle an "open file" request */
static void openFile(message_t *msg) {
char *path;
char cwd[MAXPATHLEN+1]; /* Has to be this big. */
char canpath[MAXPATHLEN+1];
int flags, mode;
int rfd = 0;
int n;
/* Open file specified, return the fd. */
/* arg1 = open mode
* arg2 = creation mode (optional)
* arg3 = pathname
*
* Returns
* arg1 = 0 or -1
* arg2 = errno (if arg1 < 0)
*/
flags = msg_getInt(msg);
mode = msg_getInt(msg);
msg_getString(msg, cwd, MAXPATHLEN+1);
path = msg_getAllocStr(msg, MAXPATHLEN+1, "openFile, path path");
/* Canacolize the path, so that a comparison with the
* allowed list makes sence, and in case the client did a chdir.
*
* The client sends us "getcwd()" output, so no trailing '/'. If
* the client wants to lie to us for this message, no great foul.
* He gets no file.
*/
n = strlen(cwd);
if (path[0] == '/') { /* absolute or not? */
/* Abs: nuke cwd with path, then realpath it. */
strncpy(cwd, path, sizeof(cwd) - n);
} else {
cwd[n++] = '/'; /* Path seperator */
strncpy(cwd + n, path, sizeof(cwd) - n);
}
if (!myrealpath(cwd, canpath)) {
/* Could be lots of reasons. So lets confuse em with whatever
* realpath said.
*/
msg_initResponce(msg, -errno);
msg_sendmsg(msg, privmand_fd, "openFile(sendmsg)");
}
/* MAC check here. TBD: use real globs instead of this hack */
if (!openPerm(canpath, openAccessType(flags))) {
sendEPERM(msg, "Unauthorized attempt open");
} else {
/* Ok, now do it. */
rfd = open(canpath,flags,mode);
if (rfd < 0) {
msg_initResponce(msg, -errno);
syslog(LOG_WARNING, "msg_open_file(open): %m");
} else {
msg_initResponce(msg, 0);
msg_setFd(msg,rfd);
}
msg_sendmsg(msg, privmand_fd, "openFile(sendmsg)");
close(rfd); /* prevent the leak */
}
free(path);
}
static void bindPort(message_t *msg) {
int sockfd;
struct sockaddr_in *addr;
socklen_t addrlen;
int retval;
addrlen = msg_getInt(msg);
addr = (struct sockaddr_in*)malloc(addrlen);
msg_getData(msg, addr, addrlen);
sockfd = msg_getFd(msg);
/* Permission check */
if (addr->sin_family != AF_INET
|| sockfd < 0
|| addrlen < sizeof(sockaddr_in)
|| ( config->bind_port.count(ntohs(addr->sin_port)) == 0
&& config->bind_port.count(ntohs(0)) == 0 ) ) { /* wildcard */
/* Permission denied */
sendEPERM(msg, "Unauthorzed attempt to bind to port.");
} else {
retval = bind(sockfd, (struct sockaddr*)addr, addrlen);
if (retval < 0)
retval = -errno; /* since it'll be used for this anyway. */
msg_initResponce(msg, retval);
msg_sendmsg(msg, privmand_fd, "bindPort(sendmsg)");
}
close(sockfd); /* don't leak this. */
}
#ifdef HAVE_LIBPAM
/* TBD: multiple conversion functions. */
static struct pam_conv pconv; /* server side. */
/* PAM conversion function. We pass the work back to the other
* side. The assumption is that the other side is currently
* waiting in a priv_pam_foo() call, and will recoginize the
* message.
*
* Don't bother passing app_ptr, as it won't be right anyway. The other
* side has it. resp is an out value, not an in value.
*/
static int convert_punt(int num_msg,
PAM_CONV_FUNC_CONST struct pam_message **messages,
struct pam_response **resp,
void * UNUSED(app_ptr))
{
message_t *msg = msg_new();
struct pam_response *reply;
int retval, i;
msg_initResponce(msg, PRIV_PAM_RUN_CONV);
msg_addInt(msg,num_msg);
for (i = 0; i < num_msg; ++i) {
msg_addInt(msg,messages[i]->msg_style);
msg_addString(msg,messages[i]->msg);
}
msg_sendmsg(msg, privmand_fd, "convert_punt(sendmsg)");
msg_initResponce(msg, 0);
msg_recvmsg(msg, privmand_fd, "convert_punt(recvmsg)");
retval = msg_getInt(msg);
reply = (struct pam_response*)malloc(sizeof(*reply) * num_msg);
for (i = 0; i < num_msg; ++i) {
reply[i].resp = msg_getAllocStr(msg, PAM_MAX_RESP_SIZE,
"convert_punt: bad responce");
reply[i].resp_retcode = msg_getInt(msg);
}
msg_delete(msg);
*resp = reply;
return retval;
}
static void pamStart(message_t *msg)
{
pam_handle_t *handle;
int retval;
char *service;
char *user;
service = msg_getAllocStr(msg, 128, "pamStart: bad service");
user = msg_getAllocStr(msg, 128, "pamStart: bad user");
/* msghdr combines "" and NULL. pam_start doens't like "",
* so make sure it gets NULL.
*/
if (user[0] == '\0') {
free(user);
user = NULL;
}
pconv = (struct pam_conv){ convert_punt, 0};
retval = pam_start(service, user, &pconv, &handle);
msg_initResponce(msg, PRIV_PAM_RC);
msg_addInt(msg,retval);
msg_addPtr(msg, handle);
msg_sendmsg(msg, privmand_fd, "pamStart(sendmsg)");
free(service);
if (user != NULL)
free(user);
}
static void pamAuthenticate(message_t *msg)
{
pam_handle_t *pamh;
int flags, rc;
pamh = (pam_handle_t*)msg_getPtr(msg);
flags = msg_getInt(msg);
rc = pam_authenticate(pamh, flags);
if (rc == PAM_SUCCESS && config->auth_allow_rerun) {
char *authenticating_user;
int rc2;
rc2 = pam_get_item(pamh, PAM_USER,
(PAM_GET_ITEM_CONST void**)(&authenticating_user));
if (rc2 == PAM_SUCCESS)
config->user.insert(authenticating_user);
}
/* This isn't a call to run the conv func */
msg_initResponce(msg, PRIV_PAM_RC);
msg_addInt(msg, rc);
msg_sendmsg(msg, privmand_fd, "pamSimpleFunc(sendmsg)");
}
static void pamSimpleFunc(message_t *msg, int (*func)(pam_handle_t*,int))
{
pam_handle_t *pamh;
int flags, rc;
pamh = (pam_handle_t*)msg_getPtr(msg);
flags = msg_getInt(msg);
rc = func(pamh, flags);
/* This isn't a call to run the conv func */
msg_initResponce(msg, PRIV_PAM_RC);
msg_addInt(msg, rc);
msg_sendmsg(msg, privmand_fd, "pamSimpleFunc(sendmsg)");
}
static void pamSetItem(message_t *msg)
{
pam_handle_t *pamh;
int type, rc;
pamh = (pam_handle_t*)msg_getPtr(msg);
type = msg_getInt(msg);
assert(type != PAM_CONV);
if (type == PAM_FAIL_DELAY) {
void *item = msg_getPtr(msg);
rc = pam_set_item(pamh, type, item);
} else {
char buf[1024];
msg_getString(msg, buf, sizeof(buf)-1);
buf[sizeof(buf)-1] = '\0';
rc = pam_set_item(pamh, type, buf);
}
msg_clear(msg);
msg_addInt(msg, PRIV_PAM_RC);
msg_addInt(msg, rc);
msg_sendmsg(msg, privmand_fd, "pamSetItem(sendmsg)");
}
static void pamGetItem(message_t *msg)
{
pam_handle_t *pamh;
int type, rc;
void *item;
pamh = (pam_handle_t*)msg_getPtr(msg);
type = msg_getInt(msg);
assert(type != PAM_CONV);
rc = pam_get_item(pamh, type, (PAM_GET_ITEM_CONST void **)(&item));
msg_clear(msg);
msg_addInt(msg, PRIV_PAM_RC);
msg_addInt(msg, rc);
if (rc == PAM_SUCCESS) {
if (type != PAM_FAIL_DELAY) {
msg_addString(msg, (char*)item);
} else {
msg_addPtr(msg, item);
}
}
msg_sendmsg(msg, privmand_fd, "pamGetItem(sendmsg)");
}
#endif
static void exitServer(message_t *UNUSED(msg)) {
if (p_wait_on_child) {
/* This will cause control_loop to break out of the loop */
close(privmand_fd);
} else {
/* Simple. */
_exit(0);
}
}
static void privWait4(message_t *msg) {
pid_t pid;
int options;
int status;
struct rusage ruse;
int *s = NULL;
struct rusage *r = NULL;
int flags;
pid = msg_getInt(msg);
options = msg_getInt(msg);
flags = msg_getInt(msg);
if (flags & WANTS_STATUS)
s = &status;
if (flags & WANTS_RUSAGE)
r = &ruse;
pid = mywait4(pid, s, options, r);
msg_clear(msg);
if (pid < 0)
msg_initResponce(msg, -errno);
else {
msg_initResponce(msg, pid);
if (flags & WANTS_STATUS)
msg_addInt(msg, status);
if (flags & WANTS_RUSAGE)
msg_addData(msg, &ruse, sizeof(ruse));
}
msg_sendmsg(msg, privmand_fd, "privWait4(sendmsg)");
}
static void forkProcess(message_t *msg) {
/* Client requests fork().
* We create new pipe fd, hand back to client.
* Fork.
* Child privmand listens on fd.
* We close fd.
*
* Can't quite do the same as we do in respawnAs, (and thus share code)
* as the child doesn't exit, it fork()s.
*/
int fds[2], n;
if (socketpair(AF_LOCAL, SOCK_STREAM, 0, fds) < 0)
boom("forkProcess(socketpair)");
msg_initResponce(msg, 0);
msg_setFd(msg, fds[0]);
msg_sendmsg(msg, privmand_fd, "forkProcess(sendmsg)");
close(fds[0]);
n = fork();
if (n < 0)
boom("forkProcess(fork)");
else if (n > 0) {
/* Parent. Close the FD's */
close(fds[1]);
} else {
/* child */
close(privmand_fd);
privmand_fd = fds[1];
p_wait_on_child = false;
}
}
/* Table needed for popen. We have to be able to map a handle to
* a File Descriptor.. because CMD_PCLOSE requires the handle that
* POPEN_AS returned so that it knows which process is expected to end.
*
* Maps fd to pid.
*/
static std::map<int, pid_t> file_pid_map;
/* Static function used for rerunas pointer. */
static void priv_popen_impl(char * const arg[])
{
char *argv[] = { "sh", "-c", arg[0], 0 };
/* fd already set up. */
execve("/bin/sh", argv, environ);
_exit(-1);
}
static void popenImpl(message_t *msg) {
char *command;
int type; /* 0 or 1, write, or read */
char *user;
char *root;
int i;
int fds[2];
int pid;
/* 0: Get the args. */
command = msg_getAllocStr(msg, 4096, "popenImpl(bad command)");
type = msg_getInt(msg);
user = msg_getAllocStr(msg, 32, "popenImpl(bad user)");
root = msg_getAllocStr(msg, PATH_MAX+1, "popenImpl(bad chroot)");
if (type < 0 || type > 1)
boom("popenImpl(bad type)");
/* MAC check */
if (!runasPerm(user)) {
sendEPERM(msg, "Unauthorized rerunAs target");
goto cleanup;
}
/* 1: Create the shared fds. */
if (socketpair(AF_LOCAL, SOCK_STREAM, 0, fds) < 0)
boom("popenImpl(socketpair)");
/* 2: Fork. */
pid = fork();
if (pid < 0)
boom("popenImpl(fork)");
else if (pid == 0) {
/* Just to make sure. */
close(privmand_fd);
privmand_fd = -1;
/* Close all non shared fds */
for (i=255 ; i > 3; --i) {
if (i != fds[0])
close(i);
}
if (type)
dup2(fds[0], STDOUT_FILENO);
else
dup2(fds[0], STDIN_FILENO);
/* execute shell . This won't return since priv_popen_impl won't */
setup_child( priv_popen_impl, &command, user, root);
}
/* 4: Parent, save pid and fd in map for future pclose. */
close(fds[0]);
file_pid_map[fds[1]] = pid;
/* return fd */
msg_clear(msg);
msg_initResponce(msg, fds[1]); /* Pass as a handle. */
msg_setFd(msg, fds[1]);
msg_sendmsg(msg, privmand_fd, "popenImpl(sendmsg)");
close(fds[1]); /* We dont' need it anymore */
cleanup:
free(command);
free(user);
free(root);
}
static void pcloseImpl(message_t *msg) {
int fd;
int pid;
int rc;
/* Get args. */
fd = msg_getInt(msg);
msg_clear(msg);
if (file_pid_map.count(fd) == 0) {
msg_initResponce(msg, -EPERM);
syslog(LOG_NOTICE, "%s", "pcloseImpl(bad handle)");
} else {
/* Get pid from map, remove from map. */
pid = file_pid_map[fd];
file_pid_map.erase(fd);
/* Collect the status. ie, Blocking. */
if (wait4(pid, &rc, 0, NULL) < 0)
rc = -EINVAL;
msg_initResponce(msg, rc);
}
msg_sendmsg(msg, privmand_fd, "pcloseImpl(sendmsg)");
}
static void daemonProcess(message_t *msg) {
/* Daemon.
* 1) Fork, parent quits. No mucking with global state
* and the child should be fine.
* 2) Setsid. This detaches us from the controlling terminal,
* so ^C won't work.
* 3) How do I handle waiting on the child? I don't. It quits,
* I go away. So set the p_wait_on_child flag.
*/
int n;
n = fork();
if (n == 0) {
/* child. See above. */
setsid(); /* Detach */
/* Handle stdio. strace for output? */
freopen("/dev/null", "r", stdin);
freopen("/dev/null", "w", stdout);
freopen("/dev/null", "a", stderr);
p_wait_on_child = false;
/* Send a success message */
msg_clear(msg);
msg_initResponce(msg, 0);
msg_sendmsg(msg, privmand_fd, "daemonProcess(sendmsg)");
} else if (n > 0) {
/* Parent. This is easy. */
_exit(0);
} else if (n < 0) {
/* Error. Panic and fall down? */
boom("daemonProcess(fork)");
}
}
static void rerunAsProcess(message_t *msg)
{
/* Flags tells us which child is allowed to talk to the privmand
* server.
*/
char **args;
char *user, *root;
void (*fnptr)(char * const *);
int flags;
flags = msg_getInt(msg);
fnptr = (void (*)(char* const *))msg_getPtr(msg);
args = msg_getArgv(msg);
user = msg_getAllocStr(msg, 32, "rerunAsProcess: bad user");
root = msg_getAllocStr(msg, MAXPATHLEN+1, "rerunAsProcess: bad root");
/* MAC checks. */
if (!runasPerm(user)) {
sendEPERM(msg, "Unauthorized rerunAs target");
goto cleanup;
}
if (flags & PRIV_RR_OLD_SLAVE_MONITORED) {
pid_t child2 = fork();
if (child2 == -1) {
boom("respawnAsProcess(fork2)");
} else if (child2 == 0) {
close(privmand_fd); /* cut connection to original privmand. */
setup_child(fnptr, args, user, root);
} else {
msg_initResponce(msg, child2);
msg_sendmsg(msg, privmand_fd, "respawnAsProcess(sendmsg)");
}
} else {
/* Tell the original slave to go away. */
msg_clear(msg);
msg_initResponce(msg, 0);
msg_sendmsg(msg, privmand_fd, "rerunAsProcess(sendmsg)");
config->unpriv_user = user; /* Assignments, not copies. */
config->unpriv_jail = root; /* Just for consistency sake */
/* This will run the client function, then drop back
* through here, as both the parent and the child.
*/
priv_sep_init(0, fnptr, args, user, root);
}
cleanup:
free(user); free(root);
freeArgv(args);
}
static void respawnAsProcess(message_t *msg)
{
char **args;
char *user, *root;
void (*fnptr)(char * const *);
fnptr = (void (*)(char* const *))msg_getPtr(msg);
args = msg_getArgv(msg);
user = msg_getAllocStr(msg, 32, "rerunAsProcess: bad user");
root = msg_getAllocStr(msg, MAXPATHLEN+1, "rerunAsProcess: bad root");
/* MAC checks. */
if (!runasPerm(user)) {
sendEPERM(msg, "Unauthorized respawnAs target");
goto clean;
}
/* Fork the monitor. Parent returns. The rest of this method takes
* place in the child.
*/
switch(fork()) {
case -1:
boom("respawnAsProcess(fork)");
/* NOTREACHED */
default:
/* Parent. */
msg_initResponce(msg, 0);
msg_sendmsg(msg, privmand_fd, "respawnAsProcess(sendmsg)");
goto clean;
case 0:
/* Child. Fall through. */
/* Close off connection with old monitor & old slave */
close(privmand_fd);
privmand_fd = -1;
break;
}
config->unpriv_user = user; /* Assignments, not copies. */
config->unpriv_jail = root; /* Just for consistency sake */
/* This will run the client function, then drop back
* through here, as both the parent and the child.
*/
priv_sep_init(0, fnptr, args, user, root);
clean:
free(user); free(root);
freeArgv(args);
}
/* populated by priv_register_foo in priv_client.cc. Our copy should
* be protected by the fork() in priv_init, so after that it should
* not change.
*/
static void customInfo(message_t *msg)
{
int handle = msg_getInt(msg);
char **args = msg_getArgv(msg);
char *rv;
info_fn_map_t::iterator it;
it = info_fn_map.find(handle);
if (it != info_fn_map.end()) {
rv = ((*it).second)(args);
} else {
errno = ENOENT;
rv = NULL;
}
msg_initResponce(msg, PRIV_PAM_RC);
if (rv == NULL) {
msg_addInt(msg, -errno);
} else {
msg_addInt(msg, 0);
msg_addString(msg, rv);
}
msg_sendmsg(msg, privmand_fd, "customInfo(sendmsg)");
freeArgv(args);
free(rv);
}
static void customCap(message_t *msg)
{
int handle = msg_getInt(msg);
char **args = msg_getArgv(msg);
int rv;
cap_fn_map_t::iterator it;
it = cap_fn_map.find(handle);
if (it != cap_fn_map.end()) {
rv = ((*it).second)(args);
} else {
rv = -1;
errno = ENOENT;
}
msg_initResponce(msg, PRIV_PAM_RC);
if (rv < 0) {
msg_addInt(msg, -errno);
} else {
msg_addInt(msg, 0);
msg_setFd(msg, rv);
}
msg_sendmsg(msg, privmand_fd, "customCap(sendmsg)");
freeArgv(args);
}
/* Is this client allowed to make this TYPE of request. For
* things with finer grained permissions, do that kind of check
* in the request handler.
*/
static bool validRequest(enum commands c) {
if (config == NULL)
return false;
switch (c) {
case CMD_OPEN: /* Deal with it when we know what. */
case CMD_UNLINK: /* Deal with it when we know what. */
case CMD_EXIT: /* always thus. */
case CMD_WAIT4: /* Seems harmless */
case CMD_CUSTOM_INFO: /* job of other half to validate. */
case CMD_CUSTOM_CAP:
return true;
case CMD_RERUN_AS:
case CMD_RESPAWN_AS:
case CMD_POPEN:
case CMD_PCLOSE:
return config->rerunas;
case CMD_BIND:
return !config->bind_port.empty();
case CMD_PAM_START:
case CMD_PAM_AUTHENTICATE:
case CMD_PAM_ACCT_MGMT:
case CMD_PAM_END:
case CMD_PAM_SETCRED:
case CMD_PAM_OPEN_SESSION:
case CMD_PAM_CLOSE_SESSION:
case CMD_PAM_GET_ITEM:
case CMD_PAM_SET_ITEM:
case CMD_PAM_GETENV:
case CMD_PAM_PUTENV:
case CMD_PAM_CHAUTHTOK:
case CMD_PAM_FAIL_DELAY:
return config->auth;
case CMD_FORK:
case CMD_DAEMON:
return config->pfork;
}
return false;
}
/* Go to the server. This function is the root of the server. It
* initializes things, then goes into a loop listening for client
* requests.
*/
static void control_loop(void) {
message_t *msg = msg_new();
int readlen = 0;
/* Loop on incoming messages
* child_pid test is for "RERUN_AS"
*/
while (child_pid != 0 && (readlen = msg_recvmsg(msg, privmand_fd)) > 0) {
enum commands c;
c = (enum commands)msg_getInt(msg);
if (!validRequest(c)) {
sendEPERM(msg, "Unknown or not permitted request");
continue;
}
/* The handler function for a given request does the responce. */
void (*fnptr)(message_t*) = function_map[c];
if (fnptr == NULL) {
syslog(LOG_ERR, "libprivman: bad command (c = %c)", c);
boom("control_loop(unknown command)");
}
/* Call the handler */
fnptr(msg);
msg_clear(msg);
}
msg_delete(msg);
if (readlen < 0 && errno != EBADF)
boom("recvmsg");
}
#ifdef HAVE_LIBPAM
#define PAM_SIMPLE_HANDLER(name, method) \
static void name(message_t *msg) { \
pamSimpleFunc(msg, method); \
}
PAM_SIMPLE_HANDLER(pamAcctMgmt, pam_acct_mgmt)
PAM_SIMPLE_HANDLER(pamEnd, pam_end)
PAM_SIMPLE_HANDLER(pamSetcred, pam_setcred)
PAM_SIMPLE_HANDLER(pamOpenSession, pam_open_session)
PAM_SIMPLE_HANDLER(pamCloseSession, pam_close_session)
PAM_SIMPLE_HANDLER(pamChauthtok, pam_chauthtok)
#ifdef LO_HAVE_PAM_FAIL_DELAY
PAM_SIMPLE_HANDLER(pamFailDelay, (int(*)(pam_handle_t*,int))pam_fail_delay)
#endif
#endif /* HAVE_LIBPAM */
void privman_serv_init(void)
{
struct sigaction old;
/* Set up signal handler. */
child_sigaction.sa_handler = sigchld_handler;
child_sigaction.sa_flags = SA_NOCLDSTOP;
sigaction(SIGCHLD, &child_sigaction, &old);
function_map[ CMD_OPEN ] = openFile;
function_map[ CMD_UNLINK ] = unlinkFile;
function_map[ CMD_BIND ] = bindPort;
#ifdef HAVE_LIBPAM
function_map[ CMD_PAM_START ] = pamStart;
function_map[ CMD_PAM_GET_ITEM ] = pamGetItem;
function_map[ CMD_PAM_SET_ITEM ] = pamSetItem;
function_map[ CMD_PAM_AUTHENTICATE ] = pamAuthenticate;
function_map[ CMD_PAM_ACCT_MGMT ] = pamAcctMgmt;
function_map[ CMD_PAM_END ] = pamEnd;
function_map[ CMD_PAM_SETCRED ] = pamSetcred;
function_map[ CMD_PAM_OPEN_SESSION ] = pamOpenSession;
function_map[ CMD_PAM_CLOSE_SESSION ] = pamCloseSession;
function_map[ CMD_PAM_CHAUTHTOK ] = pamChauthtok;
#ifdef LO_HAVE_PAM_FAIL_DELAY
function_map[ CMD_PAM_FAIL_DELAY ] = pamFailDelay;
#endif
#endif /* HAVE_LIBPAM */
function_map[ CMD_FORK ] = forkProcess;
function_map[ CMD_EXIT ] = exitServer;
function_map[ CMD_DAEMON ] = daemonProcess;
function_map[ CMD_WAIT4 ] = privWait4;
function_map[ CMD_POPEN ] = popenImpl;
function_map[ CMD_PCLOSE ] = pcloseImpl;
function_map[ CMD_RERUN_AS ] = rerunAsProcess;
function_map[ CMD_RESPAWN_AS ] = respawnAsProcess;
function_map[ CMD_CUSTOM_INFO ] = customInfo;
function_map[ CMD_CUSTOM_CAP ] = customCap;
/* The server spends most of its time in here. */
control_loop();
/* Two ways we get here
* 1) CMD_EXIT
* 2) CMD_RERUN_AS/RESPAWN_AS
*
* RERUN_AS requires magic. This is a second child of the
* privmand server, so we want to drop back out to priv_init()
*/
if (child_pid != 0) {
if (p_wait_on_child) {
/* Other side closed pipe */
int status;
mywait4(child_pid, &status, 0, 0);
if (WIFEXITED(status))
_exit(WEXITSTATUS(status));
else {
_exit(EXIT_FAILURE);
}
} else {
_exit(0);
}
}
}
syntax highlighted by Code2HTML, v. 0.9.1