|
@@ -14,6 +14,7 @@
|
|
|
#define _GNU_SOURCE
|
|
|
|
|
|
#include "orconfig.h"
|
|
|
+#define UTIL_PRIVATE
|
|
|
#include "util.h"
|
|
|
#include "torlog.h"
|
|
|
#undef log
|
|
@@ -2877,3 +2878,400 @@ load_windows_system_library(const TCHAR *library_name)
|
|
|
return LoadLibrary(path);
|
|
|
}
|
|
|
#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)
|
|
|
+{
|
|
|
+ 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;
|
|
|
+}
|
|
|
+
|
|
|
+/** 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)
|
|
|
+{
|
|
|
+
|
|
|
+/* 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;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|