/*
* Copyright (c) 2002, 2003, 2004 Niels Provos <provos@citi.umich.edu>
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <sys/types.h>
#include <sys/param.h>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <sys/stat.h>
#include <sys/tree.h>
#include <sys/queue.h>
#include <sys/socket.h>
#include <sys/resource.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dnet.h>
#include <syslog.h>
#include <grp.h>
#undef timeout_pending
#undef timeout_initialized
#include <event.h>
#include "honeyd.h"
#include "template.h"
#include "personality.h"
#include "subsystem.h"
#include "tcp.h"
#include "udp.h"
#include "fdpass.h"
#include "osfp.h"
#include "pyextend.h"
#include "honeyd_overload.h"
#include "util.h"
ssize_t atomicio(ssize_t (*)(), int, void *, size_t);
extern struct callback cb_tcp;
extern struct callback cb_udp;
void
cmd_trigger_read(struct command *cmd, int size)
{
if (cmd->pfd == -1 || !cmd->fdconnected)
return;
if (size)
TRACE(cmd->pread.ev_fd, event_add(&cmd->pread, NULL));
}
void
cmd_trigger_write(struct command *cmd, int size)
{
if (cmd->pfd == -1 || !cmd->fdconnected)
return;
if (size)
TRACE(cmd->pwrite.ev_fd, event_add(&cmd->pwrite, NULL));
}
void
cmd_free(struct command *cmd)
{
TRACE(cmd->pread.ev_fd, event_del(&cmd->pread));
TRACE(cmd->pwrite.ev_fd, event_del(&cmd->pwrite));
TRACE_RESET(cmd->pfd, close(cmd->pfd));
cmd->pfd = -1;
cmd->pid = -1;
if (cmd->perrfd != -1) {
TRACE(cmd->peread.ev_fd, event_del(&cmd->peread));
TRACE_RESET(cmd->perrfd, close(cmd->perrfd));
cmd->perrfd = -1;
}
#ifdef HAVE_PYTHON
if (cmd->state != NULL)
pyextend_connection_end(cmd->state);
#endif
}
void
cmd_ready_fd(struct command *cmd, struct callback *cb, void *con)
{
TRACE(cmd->pfd,
event_set(&cmd->pread, cmd->pfd, EV_READ, cb->cb_read, con));
TRACE(cmd->pfd,
event_set(&cmd->pwrite, cmd->pfd, EV_WRITE, cb->cb_write, con));
cmd->fdconnected = 1;
if (cmd->perrfd != -1) {
TRACE(cmd->perrfd,
event_set(&cmd->peread, cmd->perrfd, EV_READ, cb->cb_eread,
con));
}
}
struct addrinfo *
cmd_proxy_getinfo(char *address, int type, short port)
{
struct addrinfo ai, *aitop;
char strport[NI_MAXSERV];
memset(&ai, 0, sizeof (ai));
ai.ai_family = AF_INET;
ai.ai_socktype = type;
ai.ai_flags = 0;
snprintf(strport, sizeof (strport), "%d", port);
if (getaddrinfo(address, strport, &ai, &aitop) != 0) {
warn("getaddrinfo: %s:%d", address, port);
return (NULL);
}
return (aitop);
}
int
cmd_proxy_connect(struct tuple *hdr, struct command *cmd, struct addrinfo *ai,
void *con)
{
char ntop[NI_MAXHOST], strport[NI_MAXSERV];
char *host = ntop, *port = strport;
struct callback *cb;
struct timeval tv = {10, 0};
int fd;
if (hdr->type == SOCK_STREAM)
cb = &cb_tcp;
else
cb = &cb_udp;
fd = socket(AF_INET, hdr->type, 0);
if (fd == -1) {
warn("socket");
return (-1);
}
if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1)
warn("fcntl(O_NONBLOCK)");
if (fcntl(fd, F_SETFD, 1) == -1)
warn("fcntl(F_SETFD)");
TRACE(fd, cmd->pfd = fd);
if (connect(fd, ai->ai_addr, ai->ai_addrlen) == 0) {
(*cb->cb_connect)(fd, EV_WRITE, con);
return (0);
}
if (errno != EINPROGRESS) {
warn("connect");
cmd->pfd = -1;
TRACE_RESET(fd, close(fd));
return (-1);
}
TRACE(fd, event_set(&cmd->pwrite, fd, EV_WRITE, cb->cb_connect, con));
TRACE(cmd->pwrite.ev_fd, event_add(&cmd->pwrite, &tv));
if (getnameinfo(ai->ai_addr, ai->ai_addrlen,
ntop, sizeof(ntop), strport, sizeof(strport),
NI_NUMERICHOST|NI_NUMERICSERV) != 0) {
host = "<hosterror>";
port = "<porterror>";
}
syslog(LOG_INFO, "Connection established: %s -> proxy to %s:%s",
honeyd_contoa(hdr), host, port);
return (0);
}
void
cmd_environment(struct template *tmpl, struct tuple *hdr)
{
char line[256];
struct addr addr;
struct ip_hdr ip;
char *os_name;
if (tmpl->person != NULL) {
snprintf(line, sizeof(line), "%s", tmpl->person->name);
setenv("HONEYD_PERSONALITY", line, 1);
}
if (hdr == NULL)
return;
/* Determine the remote operating system */
ip.ip_src = hdr->ip_src;
os_name = honeyd_osfp_name(&ip);
if (os_name != NULL) {
setenv("HONEYD_REMOTE_OS", os_name, 1);
}
addr_pack(&addr, ADDR_TYPE_IP, IP_ADDR_BITS, &hdr->ip_src,IP_ADDR_LEN);
snprintf(line, sizeof(line), "%s", addr_ntoa(&addr));
setenv("HONEYD_IP_SRC", line, 1);
addr_pack(&addr, ADDR_TYPE_IP, IP_ADDR_BITS, &hdr->ip_dst,IP_ADDR_LEN);
snprintf(line, sizeof(line), "%s", addr_ntoa(&addr));
setenv("HONEYD_IP_DST", line, 1);
snprintf(line, sizeof(line), "%d", hdr->sport);
setenv("HONEYD_SRC_PORT", line, 1);
snprintf(line, sizeof(line), "%d", hdr->dport);
setenv("HONEYD_DST_PORT", line, 1);
}
#define SETERROR(x) do { \
snprintf x; \
strlcat(error, errline, sizeof(error)); \
} while (0)
/* Drop the privileges and verify that they got dropped */
void
cmd_droppriv(uid_t uid, gid_t gid)
{
static char error[1024];
static char errline[256];
error[0] = '\0';
/* Lower privileges */
#ifdef HAVE_SETGROUPS
if (setgroups(1, &gid) == -1)
SETERROR((errline, sizeof(errline),
"%s: setgroups(%d) failed\n", __func__, gid));
#endif
#ifdef HAVE_SETREGID
if (setregid(gid, gid) == -1)
SETERROR((errline, sizeof(errline),
"%s: setregid(%d) failed\n", __func__, gid));
#endif
if (setegid(gid) == -1)
SETERROR((errline, sizeof(errline),
"%s: setegid(%d) failed\n", __func__, gid));
if (setgid(gid) == -1)
SETERROR((errline, sizeof(errline),
"%s: setgid(%d) failed\n", __func__, gid));
#ifdef HAVE_SETREUID
if (setreuid(uid, uid) == -1)
SETERROR((errline, sizeof(errline),
"%s: setreuid(%d) failed\n", __func__, uid));
#endif
#ifdef __OpenBSD__
if (seteuid(uid) == -1)
SETERROR((errline, sizeof(errline),
"%s: seteuid(%d) failed\n", __func__, gid));
#endif
if (setuid(uid) == -1)
SETERROR((errline, sizeof(errline),
"%s: setuid(%d) failed\n", __func__, gid));
if (getgid() != gid || getegid() != gid) {
SETERROR((errline, sizeof(errline),
"%s: could not set gid to %d", __func__, gid));
goto error;
}
if (getuid() != uid || geteuid() != uid) {
SETERROR((errline, sizeof(errline),
"%s: could not set uid to %d", __func__, uid));
goto error;
}
/* Make really sure that we dropped them */
if (uid != 0 && (setuid(0) != -1 || seteuid(0) != -1)) {
SETERROR((errline, sizeof(errline),
"%s: did not successfully drop privilege",
__func__));
goto error;
}
if (gid != 0 && (setgid(0) != -1 || setegid(0) != -1)) {
SETERROR((errline, sizeof(errline),
"%s: did not successfully drop privilege",
__func__));
goto error;
}
return;
error:
syslog(LOG_WARNING, error);
errx(1, "%s: terminated", __func__);
}
int
cmd_setpriv(struct template *tmpl)
{
extern uid_t honeyd_uid;
extern gid_t honeyd_gid;
uid_t uid = honeyd_uid;
gid_t gid = honeyd_gid;
int nofiles = 30;
struct rlimit rl;
/* Set our own priority low */
setpriority(PRIO_PROCESS, 0, 10);
if (tmpl->uid)
uid = tmpl->uid;
if (tmpl->gid)
gid = tmpl->gid;
if (tmpl->max_nofiles)
nofiles = tmpl->max_nofiles;
cmd_droppriv(uid, gid);
/* Raising file descriptor limits */
rl.rlim_cur = rl.rlim_max = nofiles;
if (setrlimit(RLIMIT_NOFILE, &rl) == -1)
err(1, "setrlimit: %d", nofiles);
return (0);
}
int
cmd_fork(struct tuple *hdr, struct command *cmd, struct template *tmpl,
char *execcmd, char **argv, void *con)
{
extern int honeyd_nchildren;
int pair[2], perr[2];
struct callback *cb;
sigset_t sigmask;
if (socketpair(AF_UNIX, hdr->type, 0, pair) == -1)
return (-1);
if (socketpair(AF_UNIX, SOCK_STREAM, 0, perr) == -1) {
TRACE_RESET(pair[0], close(pair[0]));
TRACE_RESET(pair[1], close(pair[1]));
return (-1);
}
/* Block SIGCHLD */
sigemptyset(&sigmask);
sigaddset(&sigmask, SIGCHLD);
if (sigprocmask(SIG_BLOCK, &sigmask, NULL) == -1) {
warn("sigprocmask");
goto fork_err;
}
cmd->pid = fork();
if (cmd->pid == -1) {
warn("fork");
goto unmask_err;
}
if (cmd->pid == 0) {
/* Child privileges */
cmd_setpriv(tmpl);
/* Child */
TRACE_RESET(pair[0], close(pair[0]));
if (dup2(pair[1], fileno(stdout)) == -1)
err(1, "%s: dup2", __func__);
if (dup2(pair[1], fileno(stdin)) == -1)
err(1, "%s: dup2", __func__);
TRACE_RESET(pair[0], close(perr[0]));
if (dup2(perr[1], fileno(stderr)) == -1)
err(1, "%s: dup2", __func__);
TRACE_RESET(pair[1], close(pair[1]));
TRACE_RESET(perr[1], close(perr[1]));
cmd_environment(tmpl, hdr);
if (execvp(execcmd, argv) == -1)
err(1, "%s: execv(%s)", __func__, execcmd);
/* NOT REACHED */
}
TRACE_RESET(pair[1], close(pair[1]));
TRACE(pair[0], cmd->pfd = pair[0]);
if (fcntl(cmd->pfd, F_SETFD, 1) == -1)
warn("fcntl(F_SETFD)");
if (fcntl(cmd->pfd, F_SETFL, O_NONBLOCK) == -1)
warn("fcntl(F_SETFL)");
TRACE_RESET(perr[1], close(perr[1]));
cmd->perrfd = perr[0];
if (fcntl(cmd->perrfd, F_SETFD, 1) == -1)
warn("fcntl(F_SETFD)");
if (fcntl(cmd->perrfd, F_SETFL, O_NONBLOCK) == -1)
warn("fcntl(F_SETFL)");
if (hdr->type == SOCK_STREAM)
cb = &cb_tcp;
else
cb = &cb_udp;
cmd_ready_fd(cmd, cb, con);
TRACE(cmd->pread.ev_fd, event_add(&cmd->pread, NULL));
TRACE(cmd->peread.ev_fd, event_add(&cmd->peread, NULL));
honeyd_nchildren++;
/* Install old signal handler */
if (sigprocmask(SIG_UNBLOCK, &sigmask, NULL) == -1) {
warn("sigprocmask");
goto fork_err;
}
return (0);
/* Error cleanup */
unmask_err:
/* Install old signal handler */
if (sigprocmask(SIG_UNBLOCK, &sigmask, NULL) == -1)
warn("sigprocmask");
fork_err:
TRACE_RESET(perr[0], close(perr[0]));
TRACE_RESET(perr[1], close(perr[1]));
TRACE_RESET(pair[0], close(pair[0]));
TRACE_RESET(pair[1], close(pair[1]));
cmd->pfd = -1;
return (-1);
}
int
cmd_python(struct tuple *hdr, struct command *cmd, void *con)
{
int pair[2];
struct callback *cb;
if (socketpair(AF_UNIX, hdr->type, 0, pair) == -1)
return (-1);
TRACE(pair[0], cmd->pfd = pair[0]);
if (fcntl(cmd->pfd, F_SETFD, 1) == -1)
warn("fcntl(F_SETFD)");
if (fcntl(cmd->pfd, F_SETFL, O_NONBLOCK) == -1)
warn("fcntl(F_SETFL)");
/* Python descriptors should not go across exec */
if (fcntl(pair[1], F_SETFD, 1) == -1)
warn("fcntl(F_SETFD)");
if (fcntl(pair[1], F_SETFL, O_NONBLOCK) == -1)
warn("fcntl(F_SETFL)");
if (hdr->type == SOCK_STREAM)
cb = &cb_tcp;
else
cb = &cb_udp;
cmd_ready_fd(cmd, cb, con);
TRACE(cmd->pread.ev_fd, event_add(&cmd->pread, NULL));
return (pair[1]);
}
int
cmd_subsystem(struct template *tmpl, struct subsystem *sub,
char *execcmd, char **argv)
{
extern int honeyd_nchildren;
struct command *cmd = &sub->cmd;
extern struct callback subsystem_cb;
int pair[2];
sigset_t sigmask;
if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair) == -1)
return (-1);
/* Block SIGCHLD */
sigemptyset(&sigmask);
sigaddset(&sigmask, SIGCHLD);
if (sigprocmask(SIG_BLOCK, &sigmask, NULL) == -1) {
warn("sigprocmask");
goto fork_err;
}
cmd->pid = fork();
if (cmd->pid == -1) {
warn("fork");
goto unmask_err;
}
if (cmd->pid == 0) {
char magic_buf[12];
int magic_fd;
/* Set privileges */
cmd_setpriv(tmpl);
/* Child */
TRACE_RESET(pair[0], close(pair[0]));
/* Set the communication fd */
if ((magic_fd = dup(pair[1])) == -1)
err(1, "%s: dup(%d): no magic", __func__, pair[1]);
snprintf(magic_buf, sizeof(magic_buf), "%d", magic_fd);
setenv(SUBSYSTEM_MAGICFD, magic_buf, 1);
if (dup2(fileno(stderr), fileno(stdout)) == -1)
err(1, "%s: dup2", __func__);
if (dup2(fileno(stderr), fileno(stdin)) == -1)
err(1, "%s: dup2", __func__);
TRACE_RESET(pair[1], close(pair[1]));
cmd_environment(tmpl, NULL);
/* Setup the wrapper library */
if (setenv("LD_PRELOAD", PATH_HONEYDLIB"/libhoneyd.so", 1) == -1)
err(1, "%s: setenv", __func__);
if (execv(execcmd, argv) == -1)
err(1, "%s: execv(%s)", __func__, execcmd);
/* NOT REACHED */
}
TRACE_RESET(pair[1], close(pair[1]));
TRACE(pair[0], cmd->pfd = pair[0]);
if (fcntl(cmd->pfd, F_SETFD, 1) == -1)
warn("fcntl(F_SETFD)");
cmd->perrfd = -1;
cmd_ready_fd(cmd, &subsystem_cb, sub);
TRACE(cmd->pread.ev_fd, event_add(&cmd->pread, NULL));
honeyd_nchildren++;
/* Install old signal handler */
if (sigprocmask(SIG_UNBLOCK, &sigmask, NULL) == -1) {
warn("sigprocmask");
goto fork_err;
}
return (0);
/* Error cleanup */
unmask_err:
/* Install old signal handler */
if (sigprocmask(SIG_UNBLOCK, &sigmask, NULL) == -1)
warn("sigprocmask");
fork_err:
TRACE_RESET(pair[0], close(pair[0]));
TRACE_RESET(pair[1], close(pair[1]));
cmd->pfd = -1;
return (-1);
}
static void
cmd_subsystem_connect_cb(int fd, short what, void *arg)
{
struct port_encapsulate *tmp = arg;
struct port *port = tmp->port;
TAILQ_REMOVE(&port->pending, tmp, next);
if (what != EV_WRITE) {
/* We encountered some error with this */
if (tmp->hdr->type == SOCK_STREAM)
tcp_connectfail(tmp->con);
goto out;
}
cmd_subsystem_connect(tmp->hdr, tmp->cmd, port, tmp->con);
out:
port_encapsulation_free(tmp);
}
int
cmd_subsystem_schedule_connect(struct tuple *hdr, struct command *cmd,
struct port *port, void *con)
{
struct port_encapsulate *tmp = calloc(1, sizeof(*tmp));
struct subsystem *sub = port->sub;
if (tmp == NULL)
return (-1);
tmp->hdr = hdr;
tmp->cmd = cmd;
tmp->port = port;
tmp->con = con;
/* Tell the connection that it has a pending connection */
tmp->hdr->pending = tmp;
TAILQ_INSERT_TAIL(&port->pending, tmp, next);
TRACE(port->sub_fd, event_set(&tmp->ev, port->sub_fd, EV_WRITE,
cmd_subsystem_connect_cb, tmp));
TRACE(tmp->ev.ev_fd, event_add(&tmp->ev, NULL));
syslog(LOG_DEBUG,
"Scheduling connection establishment: %s -> subsystem \"%s\"",
honeyd_contoa(hdr), sub->cmdstring);
return (0);
}
int
cmd_subsystem_connect(struct tuple *hdr, struct command *cmd,
struct port *port, void *con)
{
struct callback *cb;
struct subsystem *sub = port->sub;
struct bundle bundle;
struct addr src, dst;
int pair[2];
if (hdr->type == SOCK_STREAM)
cb = &cb_tcp;
else
cb = &cb_udp;
if (socketpair(AF_LOCAL, hdr->type, 0, pair) == -1) {
warn("%s: socketpair: %s", __func__, sub->cmdstring);
return (-1);
}
if (fcntl(pair[0], F_SETFL, O_NONBLOCK) == -1)
warn("fcntl(O_NONBLOCK)");
if (fcntl(pair[0], F_SETFD, 1) == -1)
warn("fcntl(F_SETFD)");
TRACE(pair[0], cmd->pfd = pair[0]);
/* Prepare sockaddr for both src and destination */
addr_pack(&src, ADDR_TYPE_IP, IP_ADDR_BITS, &hdr->ip_src,IP_ADDR_LEN);
addr_ntos(&src, (struct sockaddr *)&bundle.src);
bundle.src.sin_port = htons(hdr->sport);
addr_pack(&dst, ADDR_TYPE_IP, IP_ADDR_BITS, &hdr->ip_dst,IP_ADDR_LEN);
addr_ntos(&dst, (struct sockaddr *)&bundle.dst);
bundle.dst.sin_port = htons(hdr->dport);
if (send_fd(port->sub_fd, pair[1], &bundle, sizeof(bundle)) == -1) {
TRACE_RESET(pair[0], close(pair[0]));
TRACE_RESET(pair[1], close(pair[1]));
cmd->pfd = -1;
return (-1);
}
/* After transfering the file descriptor, we may close it */
TRACE_RESET(pair[1], close(pair[1]));
/* We are connected now */
(*cb->cb_connect)(pair[0], EV_WRITE, con);
syslog(LOG_INFO, "Connection established: %s -> subsystem \"%s\"",
honeyd_contoa(hdr), sub->cmdstring);
return (0);
}
/*
* Called when the 3-way handshake for a connection initiated by a
* subsystem completed successfully.
*/
int
cmd_subsystem_localconnect(struct tuple *hdr, struct command *cmd,
struct port *port, void *con)
{
struct callback *cb;
struct subsystem *sub = port->sub;
struct sockaddr_in si;
struct addr src;
int fd;
if (hdr->type == SOCK_STREAM)
cb = &cb_tcp;
else
cb = &cb_udp;
/*
* If we do not have a control file descriptor for this connection,
* then get it now. The control file descriptor will give us the
* fd that is used for the real communication.
*/
if (port->sub_fd == -1) {
char res;
while ((fd = receive_fd(sub->cmd.pfd, NULL, NULL)) == -1) {
if (errno != EAGAIN) {
warnx("%s: receive_fd", __func__);
}
}
/* Confirm success of failure */
res = fd == -1 ? -1 : 0;
TRACE(sub->cmd.pfd,
atomicio(write, sub->cmd.pfd, &res, 1));
if (fd == -1)
return (-1);
TRACE(fd, port->sub_fd = fdshare_dup(fd));
}
/* Get another fd on this special thingy */
while ((fd = receive_fd(port->sub_fd, NULL, NULL)) == -1) {
if (errno != EAGAIN) {
TRACE(port->sub_fd, fdshare_close(port->sub_fd));
warnx("%s: receive_fd", __func__);
return (-1);
}
}
if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1)
warn("%s: fcntl(O_NONBLOCK)", __func__);
if (fcntl(fd, F_SETFD, 1) == -1)
warn("%s: fcntl(F_SETFD)", __func__);
TRACE(fd, cmd->pfd = fd);
/* Prepare sockaddr */
addr_pack(&src, ADDR_TYPE_IP, IP_ADDR_BITS, &hdr->ip_dst, IP_ADDR_LEN);
addr_ntos(&src, (struct sockaddr *)&si);
si.sin_port = htons(hdr->dport);
/* Write the bound socket address to the other side */
if (atomicio(write, port->sub_fd, &si, sizeof(si)) != sizeof(si)) {
TRACE(port->sub_fd, fdshare_close(port->sub_fd));
port->sub_fd = -1;
TRACE_RESET(cmd->pfd, close(cmd->pfd));
cmd->pfd = -1;
return (-1);
}
/* Now we may close the special thingy */
TRACE(port->sub_fd, fdshare_close(port->sub_fd));
port->sub_fd = -1;
/* We are connected now */
(*cb->cb_connect)(fd, EV_WRITE, con);
syslog(LOG_INFO, "Connection established: subsystem \"%s\" -> %s",
sub->cmdstring, honeyd_contoa(hdr));
return (0);
}
syntax highlighted by Code2HTML, v. 0.9.1