/* * Copyright (c) 2002, 2003, 2004 Niels Provos * 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 #include #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #define _GNU_SOURCE #include #include #include #include #include #include #include #undef timeout_pending #undef timeout_initialized #include #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 = ""; port = ""; } 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); }