/*
* Copyright © 2002 Networks Associates Technology, Inc.
* All rights reserved.
*
* priv_client.cc
* Provides the client half of the process. Should be considered
* untrusted by the privman server.
*
* $Id: priv_client.cc,v 1.39 2003/04/10 00:25:52 dougk Exp $
*/
#include "../config.h"
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <strings.h>
#include <string.h>
#include <unistd.h>
#include <pwd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <assert.h>
#include <sys/wait.h>
#include <syslog.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <map>
#ifdef HAVE_SYS_RESOURCE_H
#include <sys/resource.h>
#endif
#include "privman.h"
#include "msghdr.h"
#include "types.h"
#include "priv_impl.h"
#ifndef UNIX_PATH_MAX
#define UNIX_PATH_MAX 108
#endif
#ifdef HAVE_LIBPAM
/* Used as a cache for get/set_item */
static const void *pam_types[20] = { 0 };
/* // Strings. void* for strings.
* [PAM_SERVICE] = NULL,
* [PAM_USER] = NULL,
* [PAM_TTY] = NULL,
* [PAM_RHOST] = NULL,
* [PAM_CONV] = NULL,
*
* [PAM_RUSER] = NULL,
* [PAM_USER_PROMPT] = NULL,
* [PAM_FAIL_DELAY] = NULL // This is just a function pointer
*/
#endif /* HAVE_LIBPAM */
#ifndef CONFIG_PATH
#define CONFIG_PATH="/etc/privman.d"
#endif
static void readConfig(const char *progname) {
extern FILE *yyin; /* lex's input */
char pathname[PATH_MAX+1] = CONFIG_PATH;
/* Assigning a string to the buffer null-pads it */
strncpy(pathname+sizeof(CONFIG_PATH)-1,progname,
sizeof(pathname)-sizeof(CONFIG_PATH));
/* fopen, cause yyin if a FILE* */
yyin = fopen(pathname, "r");
if (yyin == NULL) {
syslog(LOG_ERR,"Error: missing privmand configuration file\n");
} else if (yyparse() != 0) {
syslog(LOG_ERR,"Error reading privmand configuration file\n");
}
if (yyin != NULL)
fclose(yyin);
}
#ifdef HAVE_LIBPAM
/* When requested, calls the PAM conversion function registered by
* the client.
*/
static void handleConvert(message_t *msg)
{
struct pam_message **messages;
struct pam_response *resp;
int num_msg;
int i, n, rc;
num_msg = msg_getInt(msg);
messages = (struct pam_message**)malloc(sizeof(*messages) * num_msg);
for (i = 0 ; i < num_msg; ++i) {
char buf[PAM_MAX_MSG_SIZE];
messages[i] = (struct pam_message*)malloc(sizeof(**messages));
messages[i]->msg_style = msg_getInt(msg);
msg_getString(msg,buf,sizeof(buf)-1);
buf[sizeof(buf)-1] = '\0';
messages[i]->msg = strdup(buf);
}
rc = ((struct pam_conv*)pam_types[PAM_CONV])->conv(num_msg,
/* C const fun cast */
(PAM_CONV_FUNC_CONST struct pam_message **)messages,
&resp, ((struct pam_conv*)pam_types[PAM_CONV])->appdata_ptr);
msg_clear(msg);
msg_addInt(msg,rc);
for (i = 0; i < num_msg; ++i) {
msg_addString(msg, resp[i].resp);
msg_addInt(msg, resp[i].resp_retcode);
}
n = msg_sendmsg(msg, privmand_fd);
if (n < 0)
boom("handleConvert(sendmsg)");
/* Tear down all the data. */
for (i = 0; i < num_msg; ++i) {
free((char*)(messages[i]->msg)); /* const cast */
free(resp[i].resp);
}
free(messages);
free(resp);
}
#endif /* HAVE_LIBPAM */
static __inline__
void wait_for_debugger(void)
{
#if defined(DEBUG)
/* Block until the debugger unblocks us. Gives you a known
* place to attach the debugger.
*/
volatile int i = 0;
syslog(LOG_ALERT,"waiting for debugger\n");
while (i == 0)
sleep(1);
#endif
}
void socketfun(int sockfds[2], bool server) {
if (server) {
close(sockfds[1]); /* We keep [0] */
privmand_fd = sockfds[0];
} else {
close(sockfds[0]); /* we keep [1] */
privmand_fd = sockfds[1];
}
}
void setup_child(void (*fnptr)(char * const *), char * const args[],
const char *user, const char *root)
{
struct passwd *pwent;
/* Get unpriv_user info, in case chroot changes it.
* chroot,
* setuid.
*/
/* Normalize unpriv_user, root. */
if (user == NULL || (strcmp(user, "") == 0))
user = "nobody";
if (root == NULL || (strcmp(root, "") == 0))
root = "/";
/* getpwnam */
pwent = getpwnam(user);
/* Don't know if its a static pointer, or malloced and I'm allowed
* to clear it, so just leak it.
*/
if (pwent == NULL) {
syslog(LOG_ERR, "getpwnam failed on unpriv user %s", user);
boom("setup_child(getpwnam)");
}
/* chroot */
if (chroot(root) < 0) {
syslog(LOG_ERR, "chroot to %s\n", root);
boom("setup_child(chroot)");
}
if (chdir("/") < 0) {
syslog(LOG_ERR, "chroot to %s\n", root);
boom("setup_child(chdir)");
}
if (setgid(pwent->pw_gid) < 0)
boom("setup_child(setgid)");
if (setuid(pwent->pw_uid) < 0)
boom("setup_child(setuid)");
/* Call provilded function */
if (fnptr != NULL) {
fnptr(args);
}
/* And return to do normal work. Or not.*/
if (privmand_fd == -1)
_exit(0);
}
void priv_sep_init(void (*servfn)(void),
void (*childfn)(char * const *), char * const childfn_args[],
const char *user, const char *root)
{
int sockfds[2];
if (socketpair(AF_LOCAL, SOCK_STREAM, 0, sockfds) < 0)
boom("socketpair");
child_pid = fork();
if (child_pid == 0) {
wait_for_debugger();
socketfun(sockfds, false);
setup_child(childfn, childfn_args, user, root);
} else if (child_pid < 0) {
boom("fork");
} else {
/* Parent process */
socketfun(sockfds, true);
wait_for_debugger();
if (servfn != NULL)
servfn();
/* Fall out. If we even get here, we're actually a second
* child.
*/
}
}
void priv_init(const char *appname)
{
/* Syslog init. */
openlog("privman", LOG_PID, LOG_AUTHPRIV);
/* Read the config now. */
readConfig(appname);
priv_sep_init(privman_serv_init, 0, 0,
config->unpriv_user.c_str(), config->unpriv_jail.c_str());
/* If in the child, close the syslog() fd */
if (child_pid > 0)
closelog();
}
int priv_open(const char *pathname, int flags, ...)
{
va_list ap;/*va_start(pathname,ap);va_arg(ap,type);va_end(ap)*/
message_t *msg = msg_new();
int n, retval;
char cwd[PATH_MAX];
msg_init(msg, CMD_OPEN);
msg_addInt(msg,flags);
if (flags & O_CREAT) {
va_start(ap, flags);
msg_addInt(msg,va_arg(ap,int));
va_end(ap);
} else {
msg_addInt(msg,0);
}
/* We have to canpath the path, else chdir() messes us up. */
if (getcwd(cwd, sizeof(cwd)) == NULL) {
/* Use a token "NFC" value. we won't work right in this
* case, but it might work if you didn't chdir
*/
msg_addString(msg,"");
} else {
msg_addString(msg,cwd);
}
msg_addString(msg,pathname);
/* send the message */
n = msg_sendmsg(msg, privmand_fd);
if (n < 0) {
retval = -1;
goto exit;
}
/* listen for responce */
msg_clear(msg);
n = msg_recvmsg(msg, privmand_fd);
if ( n < 0 ) {
retval = -1;
goto exit;
}
n = msg_getInt(msg);
if ( n < 0 ) {
errno = -n;
retval = -1;
} else {
retval = msg_getFd(msg);
}
exit:
msg_delete(msg);
return retval;
}
/* Done in terms of fopen */
FILE* priv_fopen(const char *pathname, const char *mode)
{
int fd;
int open_mode = 0;
/* First get the extra flags, then the basic open mode. The
* extra flags are purely depending on the base of the fopen
* mode, while the base open mode is based on the + and the
* base fopen mode.
*/
switch(mode[0]) {
case 'r':
open_mode |= 0; /* Nothing here. No creation. */ break;
case 'w':
open_mode |= O_CREAT|O_TRUNC; break;
case 'a':
open_mode |= O_CREAT|O_APPEND; break;
default:
errno = EINVAL;
return NULL;
}
if (mode[1] == '+') /* '+' or '\0' */
open_mode |= O_RDWR;
else
switch (mode[0]) {
case 'w':
case 'a':
open_mode |= O_WRONLY; break;
case 'r':
open_mode |= O_RDONLY; break;
}
/* Punt to previously written code. Easier. */
fd = priv_open(pathname, open_mode);
if (fd < 0)
return NULL; /* errno should already be set */
return fdopen(fd, mode);
}
int priv_unlink(const char *pathname)
{
message_t *msg = msg_new();
int n, retval;
char cwd[PATH_MAX];
msg_init(msg, CMD_UNLINK);
/* We have to canpath the path, else chdir() messes us up. */
if (getcwd(cwd, sizeof(cwd)) == NULL) {
/* Use a token "NFC" value. we won't work right in this
* case, but it might work if you didn't chdir
*/
msg_addString(msg,"");
} else {
msg_addString(msg,cwd);
}
msg_addString(msg,pathname);
/* send the message */
n = msg_sendmsg(msg, privmand_fd);
if (n < 0) {
retval = -1;
goto exit;
}
/* listen for responce */
msg_clear(msg);
n = msg_recvmsg(msg, privmand_fd);
if ( n < 0 ) {
retval = -1;
goto exit;
}
retval = msg_getInt(msg);
if ( retval < 0 ) {
errno = -n;
retval = -1;
}
exit:
msg_delete(msg);
return retval;
}
int priv_bind(int sockfd, struct sockaddr *addr, socklen_t addrlen)
{
message_t *msg = msg_new();
int n;
msg_addInt(msg,CMD_BIND);
msg_setFd(msg, sockfd);
msg_addInt(msg, addrlen);
msg_addData(msg, addr, addrlen);
/* send the message */
msg_sendmsg(msg, privmand_fd, "priv_bind(sendmsg)");
/* listen for responce */
msg_clear(msg);
msg_recvmsg(msg, privmand_fd, "priv_bind(recvmsg)");
n = msg_getInt(msg);
if (n < 0) {
errno = -n;
n = -1;
}
msg_delete(msg);
return n;
}
#ifdef HAVE_LIBPAM
int priv_pam_start(const char *service, const char *user,
const struct pam_conv *conv,
pam_handle_t **pamh_p)
{
/* service: string. Just send.
* user: string. Just send (two strings? eek. strlen:string
* conversion function. Don't bother with. The other side will
* will have to return a call to us. I think any of the priv_pam
* calls will have to listen for it.
* pamh_p. Er, I think we'll just pretend this is an opaque. The
* other side will keep a list.
*/
message_t *msg = msg_new();
int n, retval = PAM_SYSTEM_ERR;
msg_addInt(msg, CMD_PAM_START);
msg_addString(msg, service);
msg_addString(msg, user);
/* Save the conversion function for when we have to use it.
* TBS: handle multiple man sessions/handles.
*/
pam_types[PAM_CONV] = conv;
msg_sendmsg(msg, privmand_fd, "priv_pam_start(sendmsg)");
/* Listen for responce */
msg_clear(msg);
msg_recvmsg(msg, privmand_fd, "priv_pam_start(recvmsg)");
n = msg_getInt(msg);
if (n < 0) {
errno = -retval;
retval = PAM_PERM_DENIED;
} else {
assert( n == PRIV_PAM_RC );
retval = msg_getInt(msg);
*pamh_p = (pam_handle_t*)msg_getPtr(msg);
}
msg_delete(msg);
return retval;
}
static int priv_pam_simple_func(pam_handle_t *pamh, int flags,
const char *function_name, char function_code)
{
message_t *msg = msg_new();
int rc;
enum privman_responces cmd;
msg_addInt(msg, function_code);
msg_addPtr (msg, pamh);
msg_addInt (msg, flags);
/* send the message */
msg_sendmsg(msg, privmand_fd, function_name);
do {
msg_clear(msg);
msg_recvmsg(msg, privmand_fd, function_name);
rc = msg_getInt(msg);
if (rc < 0) {
errno = -rc;
msg_delete(msg);
return PAM_PERM_DENIED;
}
cmd = (enum privman_responces)rc;
switch (cmd) {
case PRIV_PAM_RC:
rc = msg_getInt(msg);
break;
case PRIV_PAM_RUN_CONV:
handleConvert(msg);
break;
case PRIV_NONE:
case PRIV_SET_COE:
default:
boom("priv_pam_simple_func(unexpected responce)");
break;
}
} while (cmd != PRIV_PAM_RC);
msg_delete(msg);
return rc;
}
#define PRIV_PAM_SIMPLE(name,code) \
int priv_##name (pam_handle_t *pamh, int flags) \
{ \
return priv_pam_simple_func(pamh, flags, \
__FUNCTION__, code) ; \
}
#define PRIV_PAM_SIMPLE2(name,code) \
int priv_##name (pam_handle_t *pamh, unsigned int flags) \
{ \
return priv_pam_simple_func(pamh, flags, \
__FUNCTION__, code) ; \
}
PRIV_PAM_SIMPLE(pam_authenticate, CMD_PAM_AUTHENTICATE)
PRIV_PAM_SIMPLE(pam_acct_mgmt, CMD_PAM_ACCT_MGMT)
PRIV_PAM_SIMPLE(pam_end, CMD_PAM_END)
PRIV_PAM_SIMPLE(pam_setcred, CMD_PAM_SETCRED)
PRIV_PAM_SIMPLE(pam_chauthtok, CMD_PAM_CHAUTHTOK)
PRIV_PAM_SIMPLE(pam_open_session, CMD_PAM_OPEN_SESSION)
PRIV_PAM_SIMPLE(pam_close_session, CMD_PAM_CLOSE_SESSION)
#ifdef LO_HAVE_PAM_FAIL_DELAY
PRIV_PAM_SIMPLE2(pam_fail_delay, CMD_PAM_FAIL_DELAY)
#endif
int priv_pam_set_item(pam_handle_t *pamh, int item_type, const void *item)
{
message_t *msg = NULL;
int rc;
assert(item_type != PAM_CONV); /* handled by pam_start */
/* Assume that pam_fail_delay is not dynamically loaded. */
msg = msg_new();
msg_addInt(msg, CMD_PAM_SET_ITEM);
msg_addPtr (msg, pamh);
msg_addInt (msg, item_type);
if (item_type != PAM_FAIL_DELAY) {
msg_addString(msg, (char *)item);
} else {
msg_addPtr(msg, item);
}
/* send the message */
msg_sendmsg(msg, privmand_fd, "priv_pam_set_item(sendmsg)");
msg_clear(msg);
msg_recvmsg(msg, privmand_fd, "priv_pam_set_item(recvmsg)");
rc = msg_getInt(msg); /* were we denied at the gate? */
if (rc < 0) {
errno = -rc;
msg_delete(msg);
return PAM_PERM_DENIED;
}
assert((enum privman_responces)rc == PRIV_PAM_RC);
rc = msg_getInt(msg); /* and the RC of pam_set_item. */
/* Wait until success to set the cache. */
if (rc == PAM_SUCCESS) {
if (item_type != PAM_FAIL_DELAY) {
if (pam_types[item_type])
free(((void*)pam_types[item_type]));
pam_types[item_type] = strdup((char *)item);
} else {
/* FAIL_DELAY */
pam_types[item_type] = item;
}
}
msg_delete(msg);
return rc;
}
int priv_pam_get_item(pam_handle_t *pamh, int item_type, const void **item)
{
message_t *msg = NULL;
int rc;
if (pam_types[item_type] != NULL) {
*item = pam_types[item_type];
return PAM_SUCCESS;
}
assert(item_type != PAM_CONV); /* handled by pam_start */
/* Assume that pam_fail_delay is not dynamically loaded. */
msg = msg_new();
msg_addInt(msg, CMD_PAM_GET_ITEM);
msg_addPtr (msg, pamh);
msg_addInt (msg, item_type);
/* send the message */
msg_sendmsg(msg, privmand_fd, "priv_pam_get_item(sendmsg)");
msg_clear(msg);
msg_recvmsg(msg, privmand_fd, "priv_pam_get_item(recvmsg)");
rc = (enum privman_responces)msg_getInt(msg);
if (rc < 0) {
errno = -rc;
msg_delete(msg);
return PAM_PERM_DENIED;
}
assert((enum privman_responces)rc == PRIV_PAM_RC);
rc = msg_getInt(msg);
if (rc == PAM_SUCCESS) {
if (item_type == PAM_FAIL_DELAY) {
pam_types[item_type] = msg_getPtr(msg);
} else {
#define ITEM_BUF_SIZE 1024
pam_types[item_type] = malloc(ITEM_BUF_SIZE);
msg_getString(msg, (char*)(pam_types[item_type]),ITEM_BUF_SIZE-1);
pam_types[ITEM_BUF_SIZE-1] = '\0';
}
*item = pam_types[item_type];
}
msg_delete(msg);
return rc;
}
int priv_pam_putenv(pam_handle_t *pamh, const char *name_value);
int priv_pam_getenv(pam_handle_t *pamh, const char *name);
/*
PAM:
To do these right, I need a "map" implimentation. I'm seriously
thinking of redoing this in C++ with a C interface, so when I do
that I'll do these function.
pam_putenv(pam_handle, "FOO=bar"); "FOO=" for empty, "FOO" to nuke
pam_getenv(pam_handle, "FOO")
pam_getenvlist(pam_handle)
*/
#endif /* HAVE_LIBPAM */
pid_t priv_fork(void)
{
/* Tell the parent to dup. Parent will fork, and will return the
* fd we now use to talk to it.
* We fork, tell parent our new pid.
* No race condition since parent is ST. I think
* XXX Race condition?
*/
pid_t retval;
int new_fd, n;
message_t *msg = msg_new();
msg_init(msg, CMD_FORK);
n = msg_sendmsg(msg, privmand_fd);
if (n < 0) {
retval = -1;
goto exit;
}
msg_clear(msg);
n = msg_recvmsg(msg, privmand_fd);
if (n < 0) {
retval = -1;
goto exit;
}
n = msg_getInt(msg);
if (n < 0) {
errno = -n;
retval = -1;
goto exit;
} else {
new_fd = msg_getFd(msg);
}
retval = fork();
if (retval > 0) {
/* Parent */
close(new_fd); /* don't need it here */
} else if (retval == 0) {
/* Child */
close(privmand_fd);
privmand_fd = new_fd;
} else {
/* error. Tell new server.*/
msg_init(msg, CMD_EXIT);
msg_addInt(msg, -1);
msg_sendmsg(msg, new_fd); /* Never check an error you can't handle */
close(new_fd);
}
exit:
msg_delete(msg);
return retval;
}
/* "daemon". On the client side, this does some muching with FD's.
* On the server side, it does a fork();exit() pair in the original
* process so that it detacches.
*/
int priv_daemon(int nochdir, int noclose)
{
message_t *msg = msg_new();
int n = 0;
/* First, tell parent to detach */
msg_init(msg, CMD_DAEMON);
msg_sendmsg(msg, privmand_fd, "priv_daemon(sendmsg)");
msg_clear(msg);
msg_recvmsg(msg, privmand_fd, "priv_daemon(sendmsg)");
n = msg_getInt(msg);
msg_delete(msg);
if (n < 0) {
errno = -n;
return -1;
}
#ifdef HAVE_SETSID
n = setsid();
if (n < 0)
return n;
/* Can't fail, as priv_init() makes us a child */
#endif
if (!nochdir)
chdir("/");
if (!noclose) {
freopen("/dev/null", "r", stdin);
freopen("/dev/null", "w", stdout);
freopen("/dev/null", "a", stderr);
}
return 0;
}
/* Exec "filename" as user "user" in chroot jail "root" with args argv,
* and environment envp
* TODO: limits. Feh.
*
* filename as found from the root jail.
* Will be executed with as user, so check permissions.
*
* Actually Executes from the parent, then exit's here.
* priv_rerunas(), with function pointer that demarshalls args,
* tells the monitor to quit, and exec's.
*/
static void priv_execve_impl(char * const arg[]);
int priv_execve(const char *filename, char * const argv[], char * const envp[],
const char *user, const char *root)
{
const char**arg;
int i, j, argc, envc;
char buf[5]; /* "9999\0" */
for (argc = 0; argv[argc] != NULL && argc < 9999; ++argc)
;
for (envc = 0; envp[envc] != NULL && envc < 9999; ++envc)
;
/* arg = { path + "3" + argv[0 .. 2] + "2" + envp[0 .. 1] + 0}; */
arg = (const char **)malloc(sizeof(char *) * (argc + envc + 2 + 1 + 1));
i = 0;
arg[i++] = filename;
snprintf(buf, sizeof(buf)-1, "%d", argc); buf[sizeof(buf)-1] = '\0';
arg[i++] = strdup(buf);
for ( j = i ; i < argc + j; ++i) {
arg[i] = argv[i-j];
}
snprintf(buf, sizeof(buf)-1, "%d", envc); buf[sizeof(buf)-1] = '\0';
arg[i++] = strdup(buf);
for ( j = i ; i < envc + j; ++i) {
arg[i] = envp[i-j];
}
arg[i] = NULL;
i = priv_rerunas(priv_execve_impl, (char * const *)arg, user, root, 0);
if (i < 0) {
free(arg);
return i;
}
/* The exec should happen in the new slave. We want the monitor
* to wait on the new child to exit so that it appears to exit when
* the new child does.
*/
_exit(0);
}
static void priv_execve_impl(char * const arg[])
{
const char *filename;
char **argv;
char **envp;
int argc, envc, i, j;
/* Tell the monitor to exit */
priv_exit(0);
i = 0;
filename = arg[i++];
argc = atoi(arg[i++]);
argv = (char**)malloc(sizeof(char*) * (argc + 1));
for (j = 0; j < argc; ++j) {
argv[j] = arg[i++];
}
argv[j] = NULL;
envc = atoi(arg[i++]);
envp = (char**)malloc(sizeof(char*) * (envc + 1));
for (j = 0; j < envc; ++j) {
envp[j] = arg[i++];
}
envp[j] = NULL;
execve(filename, argv, envp);
perror("priv_execve_impl(execve)");
_exit(EXIT_FAILURE);
}
/* Creates a clean fork() from the privmand server with state equivilent
* to the client state when priv_init() was first called.
*
* Invokes the specified function, with the string arg provided.
* chroots to the directory provided, setuid's to the user provided.
* TODO: limits. Feh.
*
*/
int priv_rerunas(void (*fnptr)(char * const *), char * const arg[],
const char *user, const char *root, int flags)
{
int i;
message_t *msg = msg_new();
msg_init(msg, CMD_RERUN_AS);
msg_addInt(msg, flags);
msg_addPtr(msg, (void*)fnptr);
msg_addArgv(msg, arg);
msg_addString(msg, user != NULL ? user : "");
msg_addString(msg, root != NULL ? root : "");
/* send the message */
msg_sendmsg(msg, privmand_fd, "priv_rerunas(sendmsg)");
/* listen for responce */
msg_clear(msg);
msg_recvmsg(msg, privmand_fd, "priv_rerunas(recvmsg)");
i = msg_getInt(msg);
if (i < 0) {
errno = -i;
i = -1;
} else if (!(flags & PRIV_RR_OLD_SLAVE_MONITORED)) {
/* the "real program" transitioned to the new one. */
close(privmand_fd);
}
msg_delete(msg);
return i;
}
/* Creates a clean fork() from a new privmand server with state equivilent
* to the client state when priv_init() was first called.
*
* Invokes the specified function, with the string arg provided.
* chroots to the directory provided, setuid's to the user provided.
* TODO: limits. Feh.
*
*/
int priv_respawn_as(void (*fnptr)(char * const *), char * const arg[],
const char *user, const char *root)
{
message_t *msg = msg_new();
int i;
msg_init(msg, CMD_RESPAWN_AS);
msg_addPtr(msg, (void*)fnptr);
msg_addArgv(msg, arg);
msg_addString(msg, user != NULL ? user : "");
msg_addString(msg, root != NULL ? root : "");
/* send the message */
msg_sendmsg(msg, privmand_fd, "priv_respawn_as(sendmsg)");
/* listen for responce */
msg_clear(msg);
msg_recvmsg(msg, privmand_fd, "priv_respawn_as(recvmsg)");
i = msg_getInt(msg);
if (i < 0) {
errno = -i;
i = -1;
}
msg_delete(msg);
return i;
}
/* Proxies wait4. Nothing is really privileged about this call, but
* due to the way rerunas et. all work, sometimes the wait has to be
* done from the monitor.
*/
pid_t priv_wait4(pid_t pid, int *status, int options, struct rusage *rus)
{
message_t *msg = msg_new();
pid_t retval;
int flags = 0;
if (status != NULL)
flags |= WANTS_STATUS;
if (rus != NULL)
flags |= WANTS_RUSAGE;
msg_init(msg, CMD_WAIT4);
msg_addInt(msg, pid);
msg_addInt(msg, options);
msg_addInt(msg, flags);
msg_sendmsg(msg, privmand_fd, "priv_wait4(sendmsg)");
msg_clear(msg);
msg_recvmsg(msg, privmand_fd, "priv_wait4(recvmsg)");
retval = msg_getInt(msg);
if (retval < 0) {
errno = -retval;
retval = -1;
} else {
if (status != NULL)
*status = msg_getInt(msg);
if (rus != NULL)
msg_getData(msg, rus, sizeof(*rus));
}
msg_delete(msg);
return retval;
}
static std::map<int, int> fd_handle_map; /* map fd to popen handle */
FILE* priv_popen_as(const char *command, const char *type, const char *user)
{
message_t *msg = msg_new();
FILE *retval;
int fd;
int rc;
/* Stupid checks */
if (command == NULL || type == NULL || type[1] != '\0' ||
(type[0] != 'r' && type[0] != 'w')) {
errno = EINVAL;
return NULL;
}
msg_init(msg, CMD_POPEN);
msg_addString(msg, command);
if (type[1] == 'r') {
msg_addInt(msg, 0);
} else {
msg_addInt(msg, 1);
}
msg_addString(msg, user);
msg_addString(msg, "/");
msg_sendmsg(msg, privmand_fd, "priv_popen(sendmsg)");
msg_clear(msg);
msg_recvmsg(msg, privmand_fd, "priv_popen(recvmsg)");
rc = msg_getInt(msg);
if (rc < 0) {
errno = -rc;
retval = NULL;
} else {
fd = msg_getFd(msg);
retval = fdopen(fd, type);
fd_handle_map[fd] = rc;
}
msg_delete(msg);
return retval;
}
int priv_pclose(FILE *stream)
{
message_t *msg;
int rc;
int fd = fileno(stream);
int handle;
/* Stupid check */
if (fd_handle_map.count(fd) == 0)
return -1; /* EINVAL */
handle = fd_handle_map[fd];
/* Close the stream, maybe causing the other side to keel over. */
fd_handle_map.erase(fd);
pclose(stream);
msg = msg_new();
msg_init(msg, CMD_PCLOSE);
msg_addInt(msg, handle);
msg_sendmsg(msg, privmand_fd, "priv_pclose(sendmsg)");
msg_clear(msg);
msg_recvmsg(msg, privmand_fd, "priv_pclose(recvmsg)");
rc = msg_getInt(msg);
if (rc < 0) {
errno = -rc;
rc = -1;
}
msg_delete(msg);
return rc;
}
/*
* Extension framework.
*/
info_fn_map_t info_fn_map;
cap_fn_map_t cap_fn_map;
/* Prevent the same handle from being in both maps. */
static int handle_counter = 0;
int priv_register_info_fn(char *(*fnptr)(char * const *))
{
int handle;
/* Must be done before the call to priv_init() */
if (geteuid() != 0) {
errno = EPERM;
return -1;
}
handle = handle_counter++;
info_fn_map[handle] = fnptr;
return handle;
}
int priv_register_cap_fn(int (*fnptr)(char * const *))
{
int handle;
/* Must be done before the call to priv_init() */
if (geteuid() != 0) {
errno = EPERM;
return -1;
}
handle = handle_counter++;
cap_fn_map[handle] = fnptr;
return handle;
}
char *priv_invoke_info_fn(int handle, char * const args[])
{
message_t *msg = msg_new();
char *retval;
int rc;
msg_init(msg, CMD_CUSTOM_INFO);
msg_addInt(msg, handle);
msg_addArgv(msg, args);
/* send request */
msg_sendmsg(msg, privmand_fd, "priv_invoke_info_fn(sendmsg)");
/* listen for responce */
msg_clear(msg);
msg_recvmsg(msg, privmand_fd, "priv_invoke_info_fn(recvmsg)");
rc = msg_getInt(msg);
if (rc < 0) {
errno = -rc;
retval = NULL;
} else {
retval = msg_getAllocStr(msg, 4096);
}
msg_delete(msg);
return retval;
}
int priv_invoke_cap_fn(int handle, char * const args[])
{
message_t *msg = msg_new();
int rc;
msg_init(msg, CMD_CUSTOM_CAP);
msg_addInt(msg, handle);
msg_addArgv(msg, args);
/* send request */
msg_sendmsg(msg, privmand_fd, "priv_invoke_cap_fn(sendmsg)");
/* listen for responce */
msg_clear(msg);
msg_recvmsg(msg, privmand_fd, "priv_invoke_cap_fn(recvmsg)");
rc = msg_getInt(msg);
if (rc < 0) {
errno = -rc;
rc = -1;
} else {
rc = msg_getFd(msg);
}
msg_delete(msg);
return rc;
}
/* Doesn't exit, just causes the privman server to. */
void priv_exit(int status)
{
message_t *msg;
msg = msg_new();
msg_init(msg, CMD_EXIT);
msg_addInt(msg, status);
msg_sendmsg(msg, privmand_fd); /* Never check an error you can't handle */
close(privmand_fd);
}
syntax highlighted by Code2HTML, v. 0.9.1