summaryrefslogtreecommitdiff
path: root/src/common/util.c
diff options
context:
space:
mode:
authorNick Mathewson <nickm@torproject.org>2010-09-30 16:22:39 -0400
committerNick Mathewson <nickm@torproject.org>2010-09-30 16:22:39 -0400
commit495e630a498fc4121a9852cad100af41e708cd4c (patch)
tree7c5209ab9c5a6544b47ee081190262a86891c793 /src/common/util.c
parentd39e46c26d6992dcd019fa159981c057c3d24716 (diff)
parent0e9d969bb2bb4bb9a5f7db1db824c729d7142cbc (diff)
downloadtor-495e630a498fc4121a9852cad100af41e708cd4c.tar.gz
tor-495e630a498fc4121a9852cad100af41e708cd4c.zip
Merge branch 'tor-fw-squashed2'
Conflicts: src/common/util.c
Diffstat (limited to 'src/common/util.c')
-rw-r--r--src/common/util.c409
1 files changed, 409 insertions, 0 deletions
diff --git a/src/common/util.c b/src/common/util.c
index dafe12071f..ef9c5bb624 100644
--- a/src/common/util.c
+++ b/src/common/util.c
@@ -14,6 +14,7 @@
#define _GNU_SOURCE
#include "orconfig.h"
+#define UTIL_PRIVATE
#include "util.h"
#include "torlog.h"
#undef log
@@ -2878,3 +2879,411 @@ load_windows_system_library(const TCHAR *library_name)
}
#endif
+/** Format child_state and saved_errno as a hex string placed in hex_errno.
+ * Called between fork and _exit, so must be signal-handler safe */
+void
+format_helper_exit_status(unsigned char child_state, int saved_errno,
+ char *hex_errno)
+{
+ /* Convert errno to be unsigned for hex conversion */
+ unsigned int unsigned_errno;
+ char *cur;
+
+ /* If errno is negative, negate it */
+ if (saved_errno < 0) {
+ unsigned_errno = (unsigned int) -saved_errno;
+ } else {
+ unsigned_errno = (unsigned int) saved_errno;
+ }
+
+ /* Convert errno to hex (start before \n) */
+ cur = hex_errno + HEX_ERRNO_SIZE - 2;
+
+ do {
+ *cur-- = "0123456789ABCDEF"[unsigned_errno % 16];
+ unsigned_errno /= 16;
+ } while (unsigned_errno != 0 && cur >= hex_errno);
+
+ /* Add on the minus side if errno was negative */
+ if (saved_errno < 0)
+ *cur-- = '-';
+
+ /* Leave a gap */
+ *cur-- = '/';
+
+ /* Convert child_state to hex */
+ do {
+ *cur-- = "0123456789ABCDEF"[child_state % 16];
+ child_state /= 16;
+ } while (child_state != 0 && cur >= hex_errno);
+}
+
+/* Maximum number of file descriptors, if we cannot get it via sysconf() */
+#define DEFAULT_MAX_FD 256
+
+#define CHILD_STATE_INIT 0
+#define CHILD_STATE_PIPE 1
+#define CHILD_STATE_MAXFD 2
+#define CHILD_STATE_FORK 3
+#define CHILD_STATE_DUPOUT 4
+#define CHILD_STATE_DUPERR 5
+#define CHILD_STATE_REDIRECT 6
+#define CHILD_STATE_CLOSEFD 7
+#define CHILD_STATE_EXEC 8
+#define CHILD_STATE_FAILEXEC 9
+
+#define SPAWN_ERROR_MESSAGE "ERR: Failed to spawn background process - code "
+
+/** Start a program in the background. If <b>filename</b> contains a '/', then
+ * it will be treated as an absolute or relative path. Otherwise the system
+ * path will be searched for <b>filename</b>. Returns pid on success, otherwise
+ * returns -1.
+ * Some parts of this code are based on the POSIX subprocess module from Python
+ */
+static int
+tor_spawn_background(const char *const filename, int *stdout_read,
+ int *stderr_read, const char **argv)
+{
+#ifdef MS_WINDOWS
+ (void) filename; (void) stdout_read; (void) stderr_read; (void) argv;
+ log_warn(LD_BUG, "not yet implemented on Windows.");
+ return -1;
+#else
+ pid_t pid;
+ int stdout_pipe[2];
+ int stderr_pipe[2];
+ int fd, retval;
+ ssize_t nbytes;
+
+ const char *error_message = SPAWN_ERROR_MESSAGE;
+ size_t error_message_length;
+
+ /* Represents where in the process of spawning the program is;
+ this is used for printing out the error message */
+ unsigned char child_state = CHILD_STATE_INIT;
+
+ char hex_errno[HEX_ERRNO_SIZE];
+
+ static int max_fd = -1;
+
+ /* We do the strlen here because strlen() is not signal handler safe,
+ and we are not allowed to use unsafe functions between fork and exec */
+ error_message_length = strlen(error_message);
+
+ /* Fill hex_errno with spaces, and a trailing newline */
+ memset(hex_errno, ' ', sizeof(hex_errno) - 1);
+ hex_errno[sizeof(hex_errno) - 1] = '\n';
+
+ child_state = CHILD_STATE_PIPE;
+
+ /* Set up pipe for redirecting stdout and stderr of child */
+ retval = pipe(stdout_pipe);
+ if (-1 == retval) {
+ log_err(LD_GENERAL,
+ "Failed to set up pipe for stdout communication with child process: %s",
+ strerror(errno));
+ return -1;
+ }
+
+ retval = pipe(stderr_pipe);
+ if (-1 == retval) {
+ log_err(LD_GENERAL,
+ "Failed to set up pipe for stderr communication with child process: %s",
+ strerror(errno));
+ return -1;
+ }
+
+ child_state = CHILD_STATE_MAXFD;
+
+#ifdef _SC_OPEN_MAX
+ if (-1 != max_fd) {
+ max_fd = (int) sysconf(_SC_OPEN_MAX);
+ if (max_fd == -1)
+ max_fd = DEFAULT_MAX_FD;
+ log_warn(LD_GENERAL,
+ "Cannot find maximum file descriptor, assuming %d", max_fd);
+ }
+#else
+ max_fd = DEFAULT_MAX_FD;
+#endif
+
+ child_state = CHILD_STATE_FORK;
+
+ pid = fork();
+ if (0 == pid) {
+ /* In child */
+
+ child_state = CHILD_STATE_DUPOUT;
+
+ /* Link child stdout to the write end of the pipe */
+ retval = dup2(stdout_pipe[1], STDOUT_FILENO);
+ if (-1 == retval)
+ goto error;
+
+ child_state = CHILD_STATE_DUPERR;
+
+ /* Link child stderr to the write end of the pipe */
+ retval = dup2(stderr_pipe[1], STDERR_FILENO);
+ if (-1 == retval)
+ goto error;
+
+ child_state = CHILD_STATE_REDIRECT;
+
+ /* Link stdin to /dev/null */
+ fd = open("/dev/null", O_RDONLY);
+ if (fd != -1)
+ dup2(STDIN_FILENO, fd);
+ else
+ goto error;
+
+ child_state = CHILD_STATE_CLOSEFD;
+
+ /* Close all other fds, including the read end of the pipe */
+ /* TODO: use closefrom if available */
+ for (fd = STDERR_FILENO + 1; fd < max_fd; fd++)
+ close(fd);
+
+ child_state = CHILD_STATE_EXEC;
+
+ /* Call the requested program. We need the cast because
+ execvp doesn't define argv as const, even though it
+ does not modify the arguments */
+ execvp(filename, (char *const *) argv);
+
+ /* If we got here, the exec or open(/dev/null) failed */
+
+ child_state = CHILD_STATE_FAILEXEC;
+
+ error:
+ /* TODO: are we leaking fds from the pipe? */
+
+ format_helper_exit_status(child_state, errno, hex_errno);
+
+ /* Write the error message. GCC requires that we check the return
+ value, but there is nothing we can do if it fails */
+ nbytes = write(STDOUT_FILENO, error_message, error_message_length);
+ nbytes = write(STDOUT_FILENO, hex_errno, sizeof(hex_errno));
+
+ _exit(255);
+ return -1; /* Never reached, but avoids compiler warning */
+ }
+
+ /* In parent */
+
+ if (-1 == pid) {
+ log_err(LD_GENERAL, "Failed to fork child process: %s", strerror(errno));
+ close(stdout_pipe[0]);
+ close(stdout_pipe[1]);
+ close(stderr_pipe[0]);
+ close(stderr_pipe[1]);
+ return -1;
+ }
+
+ /* Return read end of the pipes to caller, and close write end */
+ *stdout_read = stdout_pipe[0];
+ retval = close(stdout_pipe[1]);
+
+ if (-1 == retval) {
+ log_err(LD_GENERAL,
+ "Failed to close write end of stdout pipe in parent process: %s",
+ strerror(errno));
+ /* Do not return -1, because the child is running, so the parent
+ needs to know about the pid in order to reap it later */
+ }
+
+ *stderr_read = stderr_pipe[0];
+ retval = close(stderr_pipe[1]);
+
+ if (-1 == retval) {
+ log_err(LD_GENERAL,
+ "Failed to close write end of stderr pipe in parent process: %s",
+ strerror(errno));
+ /* Do not return -1, because the child is running, so the parent
+ needs to know about the pid in order to reap it later */
+ }
+
+ return pid;
+#endif
+}
+
+/** Read from stream, and send lines to log at the specified log level.
+ * Returns 1 if stream is closed normally, -1 if there is a error reading, and
+ * 0 otherwise. Handles lines from tor-fw-helper and
+ * tor_spawn_background() specially.
+ */
+static int
+log_from_pipe(FILE *stream, int severity, const char *executable,
+ int *child_status)
+{
+ char buf[256];
+
+ for (;;) {
+ char *retval;
+ retval = fgets(buf, sizeof(buf), stream);
+
+ if (NULL == retval) {
+ if (feof(stream)) {
+ /* Program has closed stream (probably it exited) */
+ /* TODO: check error */
+ fclose(stream);
+ return 1;
+ } else {
+ if (EAGAIN == errno) {
+ /* Nothing more to read, try again next time */
+ return 0;
+ } else {
+ /* There was a problem, abandon this child process */
+ fclose(stream);
+ return -1;
+ }
+ }
+ } else {
+ /* We have some data, log it and keep asking for more */
+ size_t len;
+
+ len = strlen(buf);
+ if (buf[len - 1] == '\n') {
+ /* Remove the trailing newline */
+ buf[len - 1] = '\0';
+ } else {
+ /* No newline; check whether we overflowed the buffer */
+ if (!feof(stream))
+ log_err(LD_GENERAL,
+ "Line from port forwarding helper was truncated: %s", buf);
+ /* TODO: What to do with this error? */
+ }
+
+ /* Check if buf starts with SPAWN_ERROR_MESSAGE */
+ if (strstr(buf, SPAWN_ERROR_MESSAGE) == buf) {
+ /* Parse error message */
+ int retval, child_state, saved_errno;
+ retval = sscanf(buf, SPAWN_ERROR_MESSAGE "%d/%d",
+ &child_state, &saved_errno);
+ if (retval == 2) {
+ log_err(LD_GENERAL,
+ "Failed to start child process \"%s\" in state %d: %s",
+ executable, child_state, strerror(saved_errno));
+ if (child_status)
+ *child_status = 1;
+ } else {
+ /* Failed to parse message from child process, log it as error */
+ log_err(LD_GENERAL,
+ "Unexpected message from port forwarding helper \"%s\": %s",
+ executable, buf);
+ }
+ } else {
+ log_fn(severity, LD_GENERAL, "Port forwarding helper says: %s", buf);
+ }
+ }
+ }
+
+ /* We should never get here */
+ return -1;
+}
+
+void
+tor_check_port_forwarding(const char *filename, int dir_port, int or_port,
+ time_t now)
+{
+#ifdef MS_WINDOWS
+ (void) filename; (void) dir_port; (void) or_port; (void) now;
+ (void) tor_spawn_background;
+ (void) log_from_pipe;
+ log_warn(LD_GENERAL, "Sorry, port forwarding is not yet supported "
+ "on windows.");
+#else
+/* When fw-helper succeeds, how long do we wait until running it again */
+#define TIME_TO_EXEC_FWHELPER_SUCCESS 300
+/* When fw-helper fails, how long do we wait until running it again */
+#define TIME_TO_EXEC_FWHELPER_FAIL 60
+
+ static int child_pid = -1;
+ static FILE *stdout_read = NULL;
+ static FILE *stderr_read = NULL;
+ static time_t time_to_run_helper = 0;
+ int stdout_status, stderr_status, retval;
+ const char *argv[10];
+ char s_dirport[6], s_orport[6];
+
+ tor_assert(filename);
+
+ /* Set up command line for tor-fw-helper */
+ snprintf(s_dirport, sizeof s_dirport, "%d", dir_port);
+ snprintf(s_orport, sizeof s_orport, "%d", or_port);
+
+ /* TODO: Allow different internal and external ports */
+ argv[0] = filename;
+ argv[1] = "--internal-or-port";
+ argv[2] = s_orport;
+ argv[3] = "--external-or-port";
+ argv[4] = s_orport;
+ argv[5] = "--internal-dir-port";
+ argv[6] = s_dirport;
+ argv[7] = "--external-dir-port";
+ argv[8] = s_dirport;
+ argv[9] = NULL;
+
+ /* Start the child, if it is not already running */
+ if (-1 == child_pid &&
+ time_to_run_helper < now) {
+ int fd_out, fd_err;
+
+ /* Assume tor-fw-helper will succeed, start it later*/
+ time_to_run_helper = now + TIME_TO_EXEC_FWHELPER_SUCCESS;
+
+ child_pid = tor_spawn_background(filename, &fd_out, &fd_err, argv);
+ if (child_pid < 0) {
+ log_err(LD_GENERAL, "Failed to start port forwarding helper %s",
+ filename);
+ child_pid = -1;
+ return;
+ }
+ /* Set stdout/stderr pipes to be non-blocking */
+ fcntl(fd_out, F_SETFL, O_NONBLOCK);
+ fcntl(fd_err, F_SETFL, O_NONBLOCK);
+ /* Open the buffered IO streams */
+ stdout_read = fdopen(fd_out, "r");
+ stderr_read = fdopen(fd_err, "r");
+
+ log_info(LD_GENERAL,
+ "Started port forwarding helper (%s) with pid %d", filename, child_pid);
+ }
+
+ /* If child is running, read from its stdout and stderr) */
+ if (child_pid > 0) {
+ /* Read from stdout/stderr and log result */
+ retval = 0;
+ stdout_status = log_from_pipe(stdout_read, LOG_INFO, filename, &retval);
+ stderr_status = log_from_pipe(stderr_read, LOG_ERR, filename, &retval);
+ if (retval) {
+ /* There was a problem in the child process */
+ time_to_run_helper = now + TIME_TO_EXEC_FWHELPER_FAIL;
+ }
+
+ /* Combine the two statuses in order of severity */
+ if (-1 == stdout_status || -1 == stderr_status)
+ /* There was a failure */
+ retval = -1;
+ else if (1 == stdout_status || 1 == stderr_status)
+ /* stdout or stderr was closed */
+ retval = 1;
+ else
+ /* Both are fine */
+ retval = 0;
+
+ /* If either pipe indicates a failure, act on it */
+ if (0 != retval) {
+ if (1 == retval) {
+ log_info(LD_GENERAL, "Port forwarding helper terminated");
+ } else {
+ log_err(LD_GENERAL, "Failed to read from port forwarding helper");
+ }
+
+ /* TODO: The child might not actually be finished (maybe it failed or
+ closed stdout/stderr), so maybe we shouldn't start another? */
+ child_pid = -1;
+ }
+ }
+#endif
+}
+