commit ed39621a9d97dc07063b6e9052b52a91b99d82d6 Merge: 98e5c63 1174bb9 Author: Nick Mathewson nickm@torproject.org Date: Fri Oct 7 16:05:13 2011 -0400
Merge remote-tracking branch 'asn2/bug3656'
Conflicts: src/common/util.c src/common/util.h src/or/config.h src/or/main.c src/test/test_util.c
src/common/util.c | 206 +++++++--- src/common/util.h | 27 +- src/or/Makefile.am | 2 + src/or/circuitbuild.c | 186 ++++++++-- src/or/circuitbuild.h | 15 +- src/or/config.c | 501 +++++++++++++++++++++--- src/or/config.h | 5 + src/or/connection.c | 9 +- src/or/main.c | 21 +- src/or/or.h | 5 + src/or/transports.c | 1048 +++++++++++++++++++++++++++++++++++++++++++++++++ src/or/transports.h | 105 +++++ src/test/Makefile.am | 1 + src/test/test.c | 2 + src/test/test_pt.c | 147 +++++++ src/test/test_util.c | 8 +- 16 files changed, 2141 insertions(+), 147 deletions(-)
diff --cc src/common/util.c index df77c33,a0777ea..a3716e4 --- a/src/common/util.c +++ b/src/common/util.c @@@ -3140,131 -2993,28 +3192,129 @@@ tor_terminate_process(pid_t pid #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>. The strings in - * <b>argv</b> will be passed as the command line arguments of the child - * program (following convention, argv[0] should normally be the filename of - * the executable). The last element of argv must be NULL. If the child - * program is launched, the PID will be returned and <b>stdout_read</b> and - * <b>stdout_err</b> will be set to file descriptors from which the stdout - * and stderr, respectively, output of the child program can be read, and the - * stdin of the child process shall be set to /dev/null. Otherwise returns - * -1. Some parts of this code are based on the POSIX subprocess module from - * Python. +/** Start a program in the background. If <b>filename</b> contains a '/', then + * it will be treated as an absolute or relative path. Otherwise, on + * non-Windows systems, the system path will be searched for <b>filename</b>. + * On Windows, only the current directory will be searched. Here, to search the + * system path (as well as the application directory, current working + * directory, and system directories), set filename to NULL. + * + * The strings in <b>argv</b> will be passed as the command line arguments of + * the child program (following convention, argv[0] should normally be the + * filename of the executable, and this must be the case if <b>filename</b> is + * NULL). The last element of argv must be NULL. A handle to the child process + * will be returned in process_handle (which must be non-NULL). Read + * process_handle.status to find out if the process was successfully launched. + * For convenience, process_handle.status is returned by this function. + * + * Some parts of this code are based on the POSIX subprocess module from + * Python, and example code from + * http://msdn.microsoft.com/en-us/library/ms682499%28v=vs.85%29.aspx. */ - int -tor_spawn_background(const char *const filename, int *stdout_read, - int *stderr_read, const char **argv, const char **envp) +tor_spawn_background(const char *const filename, const char **argv, ++ const char **envp, + process_handle_t *process_handle) { #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 + HANDLE stdout_pipe_read = NULL; + HANDLE stdout_pipe_write = NULL; + HANDLE stderr_pipe_read = NULL; + HANDLE stderr_pipe_write = NULL; + + STARTUPINFO siStartInfo; + BOOL retval = FALSE; + + SECURITY_ATTRIBUTES saAttr; + char *joined_argv; + + /* process_handle must not be NULL */ + tor_assert(process_handle != NULL); + + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + /* TODO: should we set explicit security attributes? (#2046, comment 5) */ + saAttr.lpSecurityDescriptor = NULL; + + /* Assume failure to start process */ + memset(process_handle, 0, sizeof(process_handle_t)); + process_handle->status = PROCESS_STATUS_ERROR; + + /* Set up pipe for stdout */ + if (!CreatePipe(&stdout_pipe_read, &stdout_pipe_write, &saAttr, 0)) { + log_warn(LD_GENERAL, + "Failed to create pipe for stdout communication with child process: %s", + format_win32_error(GetLastError())); + return process_handle->status; + } + if (!SetHandleInformation(stdout_pipe_read, HANDLE_FLAG_INHERIT, 0)) { + log_warn(LD_GENERAL, + "Failed to configure pipe for stdout communication with child " + "process: %s", format_win32_error(GetLastError())); + return process_handle->status; + } + + /* Set up pipe for stderr */ + if (!CreatePipe(&stderr_pipe_read, &stderr_pipe_write, &saAttr, 0)) { + log_warn(LD_GENERAL, + "Failed to create pipe for stderr communication with child process: %s", + format_win32_error(GetLastError())); + return process_handle->status; + } + if (!SetHandleInformation(stderr_pipe_read, HANDLE_FLAG_INHERIT, 0)) { + log_warn(LD_GENERAL, + "Failed to configure pipe for stderr communication with child " + "process: %s", format_win32_error(GetLastError())); + return process_handle->status; + } + + /* Create the child process */ + + /* Windows expects argv to be a whitespace delimited string, so join argv up + */ + joined_argv = tor_join_win_cmdline(argv); + + ZeroMemory(&(process_handle->pid), sizeof(PROCESS_INFORMATION)); + ZeroMemory(&siStartInfo, sizeof(STARTUPINFO)); + siStartInfo.cb = sizeof(STARTUPINFO); + siStartInfo.hStdError = stderr_pipe_write; + siStartInfo.hStdOutput = stdout_pipe_write; + siStartInfo.hStdInput = NULL; + siStartInfo.dwFlags |= STARTF_USESTDHANDLES; + + /* Create the child process */ + + retval = CreateProcess(filename, // module name + joined_argv, // command line + /* TODO: should we set explicit security attributes? (#2046, comment 5) */ + NULL, // process security attributes + NULL, // primary thread security attributes + TRUE, // handles are inherited + /*(TODO: set CREATE_NEW CONSOLE/PROCESS_GROUP to make GetExitCodeProcess() + * work?) */ + 0, // creation flags + NULL, // use parent's environment + NULL, // use parent's current directory + &siStartInfo, // STARTUPINFO pointer + &(process_handle->pid)); // receives PROCESS_INFORMATION + + tor_free(joined_argv); + + if (!retval) { + log_warn(LD_GENERAL, + "Failed to create child process %s: %s", filename?filename:argv[0], + format_win32_error(GetLastError())); + } else { + /* TODO: Close hProcess and hThread in process_handle->pid? */ + process_handle->stdout_pipe = stdout_pipe_read; + process_handle->stderr_pipe = stderr_pipe_read; + process_handle->status = PROCESS_STATUS_RUNNING; + } + + /* TODO: Close pipes on exit */ + + return process_handle->status; +#else // MS_WINDOWS pid_t pid; int stdout_pipe[2]; int stderr_pipe[2]; @@@ -3428,341 -3173,73 +3481,342 @@@ log_warn(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 + process_handle->status = PROCESS_STATUS_RUNNING; + /* Set stdout/stderr pipes to be non-blocking */ + fcntl(process_handle->stdout_pipe, F_SETFL, O_NONBLOCK); + fcntl(process_handle->stderr_pipe, F_SETFL, O_NONBLOCK); + /* Open the buffered IO streams */ + process_handle->stdout_handle = fdopen(process_handle->stdout_pipe, "r"); + process_handle->stderr_handle = fdopen(process_handle->stderr_pipe, "r"); + + return process_handle->status; +#endif // MS_WINDOWS +} + +/** Get the exit code of a process specified by <b>process_handle</b> and store + * it in <b>exit_code</b>, if set to a non-NULL value. If <b>block</b> is set + * to true, the call will block until the process has exited. Otherwise if + * the process is still running, the function will return + * PROCESS_EXIT_RUNNING, and exit_code will be left unchanged. Returns + * PROCESS_EXIT_EXITED if the process did exit. If there is a failure, + * PROCESS_EXIT_ERROR will be returned and the contents of exit_code (if + * non-NULL) will be undefined. N.B. Under *nix operating systems, this will + * probably not work in Tor, because waitpid() is called in main.c to reap any + * terminated child processes.*/ +int +tor_get_exit_code(const process_handle_t process_handle, + int block, int *exit_code) +{ +#ifdef MS_WINDOWS + DWORD retval; + BOOL success; + + if (block) { + /* Wait for the process to exit */ + retval = WaitForSingleObject(process_handle.pid.hProcess, INFINITE); + if (retval != WAIT_OBJECT_0) { + log_warn(LD_GENERAL, "WaitForSingleObject() failed (%d): %s", + (int)retval, format_win32_error(GetLastError())); + return PROCESS_EXIT_ERROR; + } + } else { + retval = WaitForSingleObject(process_handle.pid.hProcess, 0); + if (WAIT_TIMEOUT == retval) { + /* Process has not exited */ + return PROCESS_EXIT_RUNNING; + } else if (retval != WAIT_OBJECT_0) { + log_warn(LD_GENERAL, "WaitForSingleObject() failed (%d): %s", + (int)retval, format_win32_error(GetLastError())); + return PROCESS_EXIT_ERROR; + } + } + + if (exit_code != NULL) { + success = GetExitCodeProcess(process_handle.pid.hProcess, + (PDWORD)exit_code); + if (!success) { + log_warn(LD_GENERAL, "GetExitCodeProcess() failed: %s", + format_win32_error(GetLastError())); + return PROCESS_EXIT_ERROR; + } + } +#else + int stat_loc; + int retval; + + retval = waitpid(process_handle.pid, &stat_loc, block?0:WNOHANG); + if (!block && 0 == retval) { + /* Process has not exited */ + return PROCESS_EXIT_RUNNING; + } else if (retval != process_handle.pid) { + log_warn(LD_GENERAL, "waitpid() failed for PID %d: %s", process_handle.pid, + strerror(errno)); + return PROCESS_EXIT_ERROR; + } + + if (!WIFEXITED(stat_loc)) { + log_warn(LD_GENERAL, "Process %d did not exit normally", + process_handle.pid); + return PROCESS_EXIT_ERROR; + } + + if (exit_code != NULL) + *exit_code = WEXITSTATUS(stat_loc); +#endif // MS_WINDOWS + + return PROCESS_EXIT_EXITED; }
-/** Reads from <b>stream</b> and stores input in <b>buf_out</b> making - * sure it's below <b>count</b> bytes. - * If the string has a trailing newline, we strip it off. - * - * This function is specifically created to handle input from managed - * proxies, according to the pluggable transports spec. Make sure it - * fits your needs before using it. - * - * Returns: - * IO_STREAM_CLOSED: If the stream is closed. - * IO_STREAM_EAGAIN: If there is nothing to read and we should check back - * later. - * IO_STREAM_TERM: If something is wrong with the stream. - * IO_STREAM_OKAY: If everything went okay and we got a string - * in <b>buf_out</b>. */ -enum stream_status -get_string_from_pipe(FILE *stream, char *buf_out, size_t count) +#ifdef MS_WINDOWS +/** Read from a handle <b>h</b> into <b>buf</b>, up to <b>count</b> bytes. If + * <b>hProcess</b> is NULL, the function will return immediately if there is + * nothing more to read. Otherwise <b>hProcess</b> should be set to the handle + * to the process owning the <b>h</b>. In this case, the function will exit + * only once the process has exited, or <b>count</b> bytes are read. Returns + * the number of bytes read, or -1 on error. */ +ssize_t +tor_read_all_handle(HANDLE h, char *buf, size_t count, + const process_handle_t *process) +{ + size_t numread = 0; + BOOL retval; + DWORD byte_count; + BOOL process_exited = FALSE; + + if (count > SIZE_T_CEILING || count > SSIZE_T_MAX) + return -1; + + while (numread != count) { + /* Check if there is anything to read */ + retval = PeekNamedPipe(h, NULL, 0, NULL, &byte_count, NULL); + if (!retval) { + log_warn(LD_GENERAL, + "Failed to peek from handle: %s", + format_win32_error(GetLastError())); + return -1; + } else if (0 == byte_count) { + /* Nothing available: process exited or it is busy */ + + /* Exit if we don't know whether the process is running */ + if (NULL == process) + break; + + /* The process exited and there's nothing left to read from it */ + if (process_exited) + break; + + /* If process is not running, check for output one more time in case + it wrote something after the peek was performed. Otherwise keep on + waiting for output */ + tor_assert(process != NULL); + byte_count = WaitForSingleObject(process->pid.hProcess, 0); + if (WAIT_TIMEOUT != byte_count) + process_exited = TRUE; + + continue; + } + + /* There is data to read; read it */ + retval = ReadFile(h, buf+numread, count-numread, &byte_count, NULL); + tor_assert(byte_count + numread <= count); + if (!retval) { + log_warn(LD_GENERAL, "Failed to read from handle: %s", + format_win32_error(GetLastError())); + return -1; + } else if (0 == byte_count) { + /* End of file */ + break; + } + numread += byte_count; + } + return (ssize_t)numread; +} +#else +/** Read from a handle <b>h</b> into <b>buf</b>, up to <b>count</b> bytes. If + * <b>process</b> is NULL, the function will return immediately if there is + * nothing more to read. Otherwise data will be read until end of file, or + * <b>count</b> bytes are read. Returns the number of bytes read, or -1 on + * error. Sets <b>eof</b> to true if <b>eof</b> is not NULL and the end of the + * file has been reached. */ +ssize_t +tor_read_all_handle(FILE *h, char *buf, size_t count, + const process_handle_t *process, + int *eof) { + size_t numread = 0; char *retval; - size_t len;
- retval = fgets(buf_out, count, stream); + if (eof) + *eof = 0;
- if (!retval) { - if (feof(stream)) { - /* Program has closed stream (probably it exited) */ - /* TODO: check error */ - return IO_STREAM_CLOSED; - } else { - if (EAGAIN == errno) { - /* Nothing more to read, try again next time */ - return IO_STREAM_EAGAIN; + if (count > SIZE_T_CEILING || count > SSIZE_T_MAX) + return -1; + + while (numread != count) { + /* Use fgets because that is what we use in log_from_pipe() */ + retval = fgets(buf+numread, (int)(count-numread), h); + if (NULL == retval) { + if (feof(h)) { + log_debug(LD_GENERAL, "fgets() reached end of file"); + fclose(h); + if (eof) + *eof = 1; + break; } else { - /* There was a problem, abandon this child process */ - return IO_STREAM_TERM; + if (EAGAIN == errno) { + if (process) + continue; + else + break; + } else { + log_warn(LD_GENERAL, "fgets() from handle failed: %s", + strerror(errno)); + fclose(h); + return -1; + } } } - } else { - len = strlen(buf_out); - tor_assert(len>0); + tor_assert(retval != NULL); + tor_assert(strlen(retval) + numread <= count); + numread += strlen(retval); + }
- if (buf_out[len - 1] == '\n') { - /* Remove the trailing newline */ - buf_out[len - 1] = '\0'; - } else { - /* No newline; check whether we overflowed the buffer */ - if (!feof(stream)) - log_info(LD_GENERAL, - "Line from stream was truncated: %s", buf_out); - /* TODO: What to do with this error? */ + log_debug(LD_GENERAL, "fgets() read %d bytes from handle", (int)numread); + return (ssize_t)numread; +} +#endif + +/** Read from stdout of a process until the process exits. */ +ssize_t +tor_read_all_from_process_stdout(const process_handle_t *process_handle, + char *buf, size_t count) +{ +#ifdef MS_WINDOWS + return tor_read_all_handle(process_handle->stdout_pipe, buf, count, + process_handle); +#else + return tor_read_all_handle(process_handle->stdout_handle, buf, count, + process_handle, NULL); +#endif +} + +/** Read from stdout of a process until the process exits. */ +ssize_t +tor_read_all_from_process_stderr(const process_handle_t *process_handle, + char *buf, size_t count) +{ +#ifdef MS_WINDOWS + return tor_read_all_handle(process_handle->stderr_pipe, buf, count, + process_handle); +#else + return tor_read_all_handle(process_handle->stderr_handle, buf, count, + process_handle, NULL); +#endif +} + +/** Split buf into lines, and add to smartlist. The buffer <b>buf</b> will be + * modified. The resulting smartlist will consist of pointers to buf, so there + * is no need to free the contents of sl. <b>buf</b> must be a NUL-terminated + * string. <b>len</b> should be set to the length of the buffer excluding the + * NUL. Non-printable characters (including NUL) will be replaced with "." */ +int +tor_split_lines(smartlist_t *sl, char *buf, int len) +{ + /* Index in buf of the start of the current line */ + int start = 0; + /* Index in buf of the current character being processed */ + int cur = 0; + /* Are we currently in a line */ + char in_line = 0; + + /* Loop over string */ + while (cur < len) { + /* Loop until end of line or end of string */ + for (; cur < len; cur++) { + if (in_line) { + if ('\r' == buf[cur] || '\n' == buf[cur]) { + /* End of line */ + buf[cur] = '\0'; + /* Point cur to the next line */ + cur++; + /* Line starts at start and ends with a nul */ + break; + } else { + if (!TOR_ISPRINT(buf[cur])) + buf[cur] = '.'; + } + } else { + if ('\r' == buf[cur] || '\n' == buf[cur]) { + /* Skip leading vertical space */ + ; + } else { + in_line = 1; + start = cur; + if (!TOR_ISPRINT(buf[cur])) + buf[cur] = '.'; + } + } } + /* We are at the end of the line or end of string. If in_line is true there + * is a line which starts at buf+start and ends at a NUL. cur points to + * the character after the NUL. */ + if (in_line) + smartlist_add(sl, (void *)(buf+start)); + in_line = 0; + } + return smartlist_len(sl); +}
- return IO_STREAM_OKAY; +#ifdef MS_WINDOWS +/** Read from stream, and send lines to log at the specified log level. + * Returns -1 if there is a error reading, and 0 otherwise. + * If the generated stream is flushed more often than on new lines, or + * a read exceeds 256 bytes, lines will be truncated. This should be fixed, + * along with the corresponding problem on *nix (see bug #2045). + */ +static int +log_from_handle(HANDLE *pipe, int severity) +{ + char buf[256]; + int pos; + smartlist_t *lines; + + pos = tor_read_all_handle(pipe, buf, sizeof(buf) - 1, NULL); + if (pos < 0) { + /* Error */ + log_warn(LD_GENERAL, "Failed to read data from subprocess"); + return -1; }
- /* We should never get here */ - return IO_STREAM_TERM; + if (0 == pos) { + /* There's nothing to read (process is busy or has exited) */ + log_debug(LD_GENERAL, "Subprocess had nothing to say"); + return 0; + } + + /* End with a null even if there isn't a \r\n at the end */ + /* TODO: What if this is a partial line? */ + buf[pos] = '\0'; + log_debug(LD_GENERAL, "Subprocess had %d bytes to say", pos); + + /* Split up the buffer */ + lines = smartlist_create(); + tor_split_lines(lines, buf, pos); + + /* Log each line */ + SMARTLIST_FOREACH(lines, char *, line, + { + log_fn(severity, LD_GENERAL, "Port forwarding helper says: %s", line); + }); + smartlist_free(lines); + + return 0; }
+#else ++ /** 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 @@@ -3773,72 -3250,50 +3827,110 @@@ log_from_pipe(FILE *stream, int severit int *child_status) { char buf[256]; + enum stream_status r;
for (;;) { - char *retval; - retval = fgets(buf, sizeof(buf), stream); + r = get_string_from_pipe(stream, buf, sizeof(buf) - 1);
- if (NULL == retval) { - if (feof(stream)) { - /* Program has closed stream (probably it exited) */ - /* TODO: check error */ - fclose(stream); - return 1; + if (r == IO_STREAM_CLOSED) { + fclose(stream); + return 1; + } else if (r == IO_STREAM_EAGAIN) { + return 0; + } else if (r == IO_STREAM_TERM) { + fclose(stream); + return -1; + } + + tor_assert(r == IO_STREAM_OKAY); + + /* Check if buf starts with SPAWN_ERROR_MESSAGE */ + if (strcmpstart(buf, SPAWN_ERROR_MESSAGE) == 0) { + /* Parse error message */ + int retval, child_state, saved_errno; + retval = tor_sscanf(buf, SPAWN_ERROR_MESSAGE "%x/%x", + &child_state, &saved_errno); + if (retval == 2) { + log_warn(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 { - 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; - } + /* Failed to parse message from child process, log it as a + warning */ + log_warn(LD_GENERAL, + "Unexpected message from port forwarding helper "%s": %s", + executable, buf); } } else { - /* We have some data, log it and keep asking for more */ - size_t len; + log_fn(severity, LD_GENERAL, "Port forwarding helper says: %s", buf); + } + }
- 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_warn(LD_GENERAL, - "Line from port forwarding helper was truncated: %s", buf); - /* TODO: What to do with this error? */ - } + /* We should never get here */ + return -1; + } ++#endif + - /* Check if buf starts with SPAWN_ERROR_MESSAGE */ - if (strcmpstart(buf, SPAWN_ERROR_MESSAGE) == 0) { - /* Parse error message */ - int retval, child_state, saved_errno; - retval = tor_sscanf(buf, SPAWN_ERROR_MESSAGE "%x/%x", - &child_state, &saved_errno); - if (retval == 2) { - log_warn(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 a - warning */ - log_warn(LD_GENERAL, - "Unexpected message from port forwarding helper "%s": %s", - executable, buf); - } ++/** Reads from <b>stream</b> and stores input in <b>buf_out</b> making ++ * sure it's below <b>count</b> bytes. ++ * If the string has a trailing newline, we strip it off. ++ * ++ * This function is specifically created to handle input from managed ++ * proxies, according to the pluggable transports spec. Make sure it ++ * fits your needs before using it. ++ * ++ * Returns: ++ * IO_STREAM_CLOSED: If the stream is closed. ++ * IO_STREAM_EAGAIN: If there is nothing to read and we should check back ++ * later. ++ * IO_STREAM_TERM: If something is wrong with the stream. ++ * IO_STREAM_OKAY: If everything went okay and we got a string ++ * in <b>buf_out</b>. */ ++enum stream_status ++get_string_from_pipe(FILE *stream, char *buf_out, size_t count) ++{ ++ char *retval; ++ size_t len; ++ ++ retval = fgets(buf_out, count, stream); ++ ++ if (!retval) { ++ if (feof(stream)) { ++ /* Program has closed stream (probably it exited) */ ++ /* TODO: check error */ ++ return IO_STREAM_CLOSED; ++ } else { ++ if (EAGAIN == errno) { ++ /* Nothing more to read, try again next time */ ++ return IO_STREAM_EAGAIN; + } else { - log_fn(severity, LD_GENERAL, "Port forwarding helper says: %s", buf); ++ /* There was a problem, abandon this child process */ ++ return IO_STREAM_TERM; + } + } ++ } else { ++ len = strlen(buf_out); ++ tor_assert(len>0); ++ ++ if (buf_out[len - 1] == '\n') { ++ /* Remove the trailing newline */ ++ buf_out[len - 1] = '\0'; ++ } else { ++ /* No newline; check whether we overflowed the buffer */ ++ if (!feof(stream)) ++ log_info(LD_GENERAL, ++ "Line from stream was truncated: %s", buf_out); ++ /* TODO: What to do with this error? */ ++ } ++ ++ return IO_STREAM_OKAY; + } + + /* We should never get here */ - return -1; ++ return IO_STREAM_TERM; +} - #endif
void tor_check_port_forwarding(const char *filename, int dir_port, int or_port, @@@ -3883,26 -3343,24 +3975,26 @@@ /* 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, NULL); - if (child_pid < 0) { +#ifdef MS_WINDOWS + /* Passing NULL as lpApplicationName makes Windows search for the .exe */ - tor_spawn_background(NULL, argv, &child_handle); ++ tor_spawn_background(NULL, argv, NULL &child_handle); +#else - tor_spawn_background(filename, argv, &child_handle); ++ tor_spawn_background(filename, argv, NULL, &child_handle); +#endif + if (PROCESS_STATUS_ERROR == child_handle.status) { log_warn(LD_GENERAL, "Failed to start port forwarding helper %s", filename); - child_pid = -1; + time_to_run_helper = now + TIME_TO_EXEC_FWHELPER_FAIL; 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"); - +#ifdef MS_WINDOWS log_info(LD_GENERAL, - "Started port forwarding helper (%s) with pid %d", filename, child_pid); + "Started port forwarding helper (%s)", filename); +#else + log_info(LD_GENERAL, + "Started port forwarding helper (%s) with pid %d", filename, + child_handle.pid); +#endif }
/* If child is running, read from its stdout and stderr) */ diff --cc src/common/util.h index c8cce39,04ae7cb..77ed1ca --- a/src/common/util.h +++ b/src/common/util.h @@@ -348,57 -352,18 +360,62 @@@ void write_pidfile(char *filename) void tor_check_port_forwarding(const char *filename, int dir_port, int or_port, time_t now);
+ int tor_terminate_process(pid_t pid); -int tor_spawn_background(const char *const filename, int *stdout_read, - int *stderr_read, const char **argv, - const char **envp); ++typedef struct process_handle_s process_handle_t; ++int tor_spawn_background(const char *const filename, const char **argv, ++ const char **envp, process_handle_t *process_handle); ++ + #define SPAWN_ERROR_MESSAGE "ERR: Failed to spawn background process - code " + #ifdef MS_WINDOWS HANDLE load_windows_system_library(const TCHAR *library_name); #endif
#ifdef UTIL_PRIVATE /* Prototypes for private functions only used by util.c (and unit tests) */ + +/* Values of process_handle_t.status. PROCESS_STATUS_NOTRUNNING must be + * 0 because tor_check_port_forwarding depends on this being the initial + * statue of the static instance of process_handle_t */ +#define PROCESS_STATUS_NOTRUNNING 0 +#define PROCESS_STATUS_RUNNING 1 +#define PROCESS_STATUS_ERROR -1 - typedef struct process_handle_s { ++struct process_handle_s { + int status; +#ifdef MS_WINDOWS + HANDLE stdout_pipe; + HANDLE stderr_pipe; + PROCESS_INFORMATION pid; +#else + int stdout_pipe; + int stderr_pipe; + FILE *stdout_handle; + FILE *stderr_handle; + pid_t pid; +#endif // MS_WINDOWS - } process_handle_t; - - int tor_spawn_background(const char *const filename, const char **argv, - process_handle_t *process_handle); ++}; + +/* Return values of tor_get_exit_code() */ +#define PROCESS_EXIT_RUNNING 1 +#define PROCESS_EXIT_EXITED 0 +#define PROCESS_EXIT_ERROR -1 +int tor_get_exit_code(const process_handle_t process_handle, + int block, int *exit_code); +int tor_split_lines(struct smartlist_t *sl, char *buf, int len); +#ifdef MS_WINDOWS +ssize_t tor_read_all_handle(HANDLE h, char *buf, size_t count, + const process_handle_t *process); +#else +ssize_t tor_read_all_handle(FILE *h, char *buf, size_t count, + const process_handle_t *process, + int *eof); +#endif +ssize_t tor_read_all_from_process_stdout( + const process_handle_t *process_handle, char *buf, size_t count); +ssize_t tor_read_all_from_process_stderr( + const process_handle_t *process_handle, char *buf, size_t count); +char *tor_join_win_cmdline(const char *argv[]); ++ void format_helper_exit_status(unsigned char child_state, int saved_errno, char *hex_errno);
diff --cc src/or/config.c index c44b09c,536324d..07f0082 --- a/src/or/config.c +++ b/src/or/config.c @@@ -3722,11 -3699,14 +3738,16 @@@ options_validate(or_options_t *old_opti if (validate_dir_authorities(options, old_options) < 0) REJECT("Directory authority line did not parse. See logs for details.");
+ if (options->UseBridges && !options->Bridges) + REJECT("If you set UseBridges, you must specify at least one bridge."); if (options->UseBridges && !options->TunnelDirConns) - REJECT("TunnelDirConns set to 0 only works with UseBridges set to 0"); + REJECT("If you set UseBridges, you must set TunnelDirConns.");
+ for (cl = options->Bridges; cl; cl = cl->next) { + if (parse_bridge_line(cl->value, 1)<0) + REJECT("Bridge line did not parse. See logs for details."); + } + for (cl = options->ClientTransportPlugin; cl; cl = cl->next) { if (parse_client_transport_line(cl->value, 1)<0) REJECT("Transport line did not parse. See logs for details."); diff --cc src/or/config.h index 4a5afdf,45baf45..76f6841 --- a/src/or/config.h +++ b/src/or/config.h @@@ -64,10 -63,11 +64,15 @@@ or_state_t *get_or_state(void) int did_last_state_file_write_fail(void); int or_state_save(time_t now);
+const smartlist_t *get_configured_client_ports(void); + +int options_need_geoip_info(const or_options_t *options, + const char **reason_out); ++ + void save_transport_to_state(const char *transport_name, + const tor_addr_t *addr, uint16_t port); + const char *get_bindaddr_for_transport(const char *transport); + -int options_need_geoip_info(or_options_t *options, const char **reason_out); int getinfo_helper_config(control_connection_t *conn, const char *question, char **answer, const char **errmsg); diff --cc src/or/main.c index 4948d59,6297f0f..aa167e1 --- a/src/or/main.c +++ b/src/or/main.c @@@ -1086,7 -1068,8 +1087,9 @@@ run_scheduled_events(time_t now static int should_init_bridge_stats = 1; static time_t time_to_retry_dns_init = 0; static time_t time_to_next_heartbeat = 0; + static int has_validated_pt = 0; - or_options_t *options = get_options(); + const or_options_t *options = get_options(); ++ int is_server = server_mode(options); int i; int have_dir_info; diff --cc src/or/transports.c index 0000000,c531fe7..3c533cc mode 000000,100644..100644 --- a/src/or/transports.c +++ b/src/or/transports.c @@@ -1,0 -1,1041 +1,1048 @@@ + /* Copyright (c) 2011, The Tor Project, Inc. */ + /* See LICENSE for licensing information */ + + /** + * \file transports.c + * \brief Pluggable Transports related code. + **/ + + #define PT_PRIVATE + #include "or.h" + #include "config.h" + #include "circuitbuild.h" + #include "transports.h" + #include "util.h" + + static void set_managed_proxy_environment(char ***envp, + const managed_proxy_t *mp); + static INLINE int proxy_configuration_finished(const managed_proxy_t *mp); + + static void managed_proxy_destroy(managed_proxy_t *mp); + + static void handle_finished_proxy(managed_proxy_t *mp); + static void configure_proxy(managed_proxy_t *mp); + + static void parse_method_error(const char *line, int is_server_method); + #define parse_server_method_error(l) parse_method_error(l, 1) + #define parse_client_method_error(l) parse_method_error(l, 0) + + static INLINE void free_execve_args(char **arg); + + /** Managed proxy protocol strings */ + #define PROTO_ENV_ERROR "ENV-ERROR" + #define PROTO_NEG_SUCCESS "VERSION" + #define PROTO_NEG_FAIL "VERSION-ERROR no-version" + #define PROTO_CMETHOD "CMETHOD" + #define PROTO_SMETHOD "SMETHOD" + #define PROTO_CMETHOD_ERROR "CMETHOD-ERROR" + #define PROTO_SMETHOD_ERROR "SMETHOD-ERROR" + #define PROTO_CMETHODS_DONE "CMETHODS DONE" + #define PROTO_SMETHODS_DONE "SMETHODS DONE" + + /* The smallest valid managed proxy protocol line that can + appear. It's the size of "VERSION 1" */ + #define SMALLEST_MANAGED_LINE_SIZE 9 + + /** Number of environment variables for managed proxy clients/servers. */ + #define ENVIRON_SIZE_CLIENT 5 + #define ENVIRON_SIZE_SERVER 8 + + /** The first and only supported - at the moment - configuration + protocol version. */ + #define PROTO_VERSION_ONE 1 + + /** List of unconfigured managed proxies. */ + static smartlist_t *managed_proxy_list = NULL; + /** Number of still unconfigured proxies. */ + static int unconfigured_proxies_n = 0; + + /** "The main idea is:" + + Each managed proxy is represented by a 'managed_proxy_t'. + Each managed proxy can support multiple transports. + Each managed proxy gets configured through a multistep process. + + 'managed_proxy_list' contains all the managed proxies this tor + instance is supporting. + In the 'managed_proxy_list' there are 'unconfigured_proxies_n' + managed proxies that are still unconfigured. + + In every run_scheduled_event() tick, we attempt to launch and then + configure the unconfiged managed proxies, using the configuration + protocol defined in the 180_pluggable_transport.txt proposal. A + managed proxy might need several ticks to get fully configured. + + When a managed proxy is fully configured, we register all its + transports to the circuitbuild.c subsystem. At that point the + transports are owned by the circuitbuild.c subsystem. + + When a managed proxy fails to follow the 180 configuration + protocol, it gets marked as broken and gets destroyed. + + "In a little more technical detail:" + + While we are serially parsing torrc, we store all the transports + that a proxy should spawn in its 'transports_to_launch' element. + + When we finish reading the torrc, we spawn the managed proxy and + expect {S,C}METHOD lines from its output. We add transports + described by METHOD lines to its 'transports' element, as + 'transport_t' structs. + + When the managed proxy stops spitting METHOD lines (signified by a + '{S,C}METHODS DONE' message) we register all the transports + collected to the circuitbuild.c subsystem. At this point, the + 'transport_t's can be transformed into dangling pointers at any + point by the circuitbuild.c subsystem, and so we replace all + 'transport_t's with strings describing the transport names. We + can still go from a transport name to a 'transport_t' using the + fact that transport names uniquely identify 'transport_t's. + + "In even more technical detail I shall describe what happens when + the SIGHUP bell tolls:" + + We immediately destroy all unconfigured proxies (We shouldn't have + unconfigured proxies in the first place, except when SIGHUP rings + immediately after tor is launched.). + + We mark all managed proxies and transports to signify that they + must be removed if they don't contribute by the new torrc + (marked_for_removal). + We also mark all managed proxies to signify that they might need + to be restarted so that they end up supporting all the transports + the new torrc wants them to support (got_hup). + We also clear their 'transports_to_launch' list so that we can put + there the transports we need to launch according to the new torrc. + + We then start parsing torrc again. + + Everytime we encounter a transport line using a known pre-SIGHUP + managed proxy, we cleanse that proxy from the removal mark. + + We also mark it as unconfigured so that on the next scheduled + events tick, we investigate whether we need to restart the proxy + so that it also spawns the new transports. + If the post-SIGHUP 'transports_to_launch' list is identical to the + pre-SIGHUP one, it means that no changes were introduced to this + proxy during the SIGHUP and no restart has to take place. + + During the post-SIGHUP torrc parsing, we unmark all transports + spawned by managed proxies that we find in our torrc. + We do that so that if we don't need to restart a managed proxy, we + can continue using its old transports normally. + If we end up restarting the proxy, we destroy and unregister all + old transports from the circuitbuild.c subsystem. + */ + + /** Return true if there are still unconfigured managed proxies. */ + int + pt_proxies_configuration_pending(void) + { + return !! unconfigured_proxies_n; + } + + /** Return true if <b>mp</b> has the same argv as <b>proxy_argv</b> */ + static int + managed_proxy_has_argv(const managed_proxy_t *mp, char **proxy_argv) + { + char **tmp1=proxy_argv; + char **tmp2=mp->argv; + + tor_assert(tmp1); + tor_assert(tmp2); + + while (*tmp1 && *tmp2) { + if (strcmp(*tmp1++, *tmp2++)) + return 0; + } + + if (!*tmp1 && !*tmp2) + return 1; + + return 0; + } + + /** Return a managed proxy with the same argv as <b>proxy_argv</b>. + * If no such managed proxy exists, return NULL. */ + static managed_proxy_t * + get_managed_proxy_by_argv_and_type(char **proxy_argv, int is_server) + { + if (!managed_proxy_list) + return NULL; + + SMARTLIST_FOREACH_BEGIN(managed_proxy_list, managed_proxy_t *, mp) { + if (managed_proxy_has_argv(mp, proxy_argv) && + mp->is_server == is_server) + return mp; + } SMARTLIST_FOREACH_END(mp); + + return NULL; + } + + /** Add <b>transport</b> to managed proxy <b>mp</b>. */ + static void + add_transport_to_proxy(const char *transport, managed_proxy_t *mp) + { + tor_assert(mp->transports_to_launch); + if (!smartlist_string_isin(mp->transports_to_launch, transport)) + smartlist_add(mp->transports_to_launch, tor_strdup(transport)); + } + + /** Called when a SIGHUP occurs. Returns true if managed proxy + * <b>mp</b> needs to be restarted after the SIGHUP, based on the new + * torrc. */ + static int + proxy_needs_restart(const managed_proxy_t *mp) + { + /* mp->transport_to_launch is populated with the names of the + transports that must be launched *after* the SIGHUP. + mp->transports is populated with the names of the transports that + were launched *before* the SIGHUP. + + If the two lists contain the same strings, we don't need to + restart the proxy, since it already does what we want. */ + + tor_assert(smartlist_len(mp->transports_to_launch) > 0); + tor_assert(mp->conf_state == PT_PROTO_COMPLETED); + + if (smartlist_len(mp->transports_to_launch) != smartlist_len(mp->transports)) + goto needs_restart; + + SMARTLIST_FOREACH_BEGIN(mp->transports_to_launch, char *, t_t_l) { + if (!smartlist_string_isin(mp->transports, t_t_l)) + goto needs_restart; + + } SMARTLIST_FOREACH_END(t_t_l); + + return 0; + + needs_restart: + return 1; + } + + /** Managed proxy <b>mp</b> must be restarted. Do all the necessary + * preparations and then flag its state so that it will be relaunched + * in the next tick. */ + static void + proxy_prepare_for_restart(managed_proxy_t *mp) + { + transport_t *t_tmp = NULL; + + tor_assert(mp->conf_state == PT_PROTO_COMPLETED); + tor_assert(mp->pid); + + /* kill the old obfsproxy process */ + tor_terminate_process(mp->pid); + mp->pid = 0; + fclose(mp->_stdout); + + /* destroy all its old transports. we no longer use them. */ + SMARTLIST_FOREACH_BEGIN(mp->transports, const char *, t_name) { + t_tmp = transport_get_by_name(t_name); + if (t_tmp) + t_tmp->marked_for_removal = 1; + } SMARTLIST_FOREACH_END(t_name); + sweep_transport_list(); + + /* free the transport names in mp->transports */ + SMARTLIST_FOREACH(mp->transports, char *, t_name, tor_free(t_name)); + smartlist_clear(mp->transports); + + /* flag it as an infant proxy so that it gets launched on next tick */ + mp->conf_state = PT_PROTO_INFANT; + } + + /** Launch managed proxy <b>mp</b>. */ + static int + launch_managed_proxy(managed_proxy_t *mp) + { ++ (void) mp; ++ (void) set_managed_proxy_environment; ++ return -1; ++#if 0 ++ /* XXXX023 we must reenable this code for managed proxies to work. ++ * "All it needs" is revision to work with the new tor_spawn_background ++ * API. */ + char **envp=NULL; + int pid; ++ process_handle_t proc; + FILE *stdout_read = NULL; + int stdout_pipe=-1, stderr_pipe=-1; + + /* prepare the environment variables for the managed proxy */ + set_managed_proxy_environment(&envp, mp); + - pid = tor_spawn_background(mp->argv[0], &stdout_pipe, - &stderr_pipe, (const char **)mp->argv, - (const char **)envp); ++ pid = tor_spawn_background(mp->argv[0], (const char **)mp->argv, ++ (const char **)envp, &proc); + if (pid < 0) { + log_warn(LD_GENERAL, "Managed proxy at '%s' failed at launch.", + mp->argv[0]); + return -1; + } + + /* free the memory allocated by set_managed_proxy_environment(). */ + free_execve_args(envp); + + /* Set stdout/stderr pipes to be non-blocking */ + #ifdef _WIN32 + { + u_long nonblocking = 1; + ioctlsocket(stdout_pipe, FIONBIO, &nonblocking); + } + #else + fcntl(stdout_pipe, F_SETFL, O_NONBLOCK); + #endif + + /* Open the buffered IO streams */ + stdout_read = fdopen(stdout_pipe, "r"); + + log_info(LD_CONFIG, "Managed proxy has spawned at PID %d.", pid); + + mp->conf_state = PT_PROTO_LAUNCHED; + mp->_stdout = stdout_read; + mp->pid = pid; - ++#endif + return 0; + } + + /** Check if any of the managed proxies we are currently trying to + * configure have anything new to say. This is called from + * run_scheduled_events(). */ + void + pt_configure_remaining_proxies(void) + { + log_debug(LD_CONFIG, "Configuring remaining managed proxies (%d)!", + unconfigured_proxies_n); + SMARTLIST_FOREACH_BEGIN(managed_proxy_list, managed_proxy_t *, mp) { + tor_assert(mp->conf_state != PT_PROTO_BROKEN); + + if (mp->got_hup) { + mp->got_hup = 0; + + /* This proxy is marked by a SIGHUP. Check whether we need to + restart it. */ + if (proxy_needs_restart(mp)) { + log_info(LD_GENERAL, "Preparing managed proxy for restart."); + proxy_prepare_for_restart(mp); + continue; + } else { /* it doesn't need to be restarted. */ + log_info(LD_GENERAL, "Nothing changed for managed proxy after HUP: " + "not restarting."); + unconfigured_proxies_n--; + tor_assert(unconfigured_proxies_n >= 0); + } + + continue; + } + + /* If the proxy is not fully configured, try to configure it + futher. */ + if (!proxy_configuration_finished(mp)) + configure_proxy(mp); + + } SMARTLIST_FOREACH_END(mp); + } + + /** Attempt to continue configuring managed proxy <b>mp</b>. */ + static void + configure_proxy(managed_proxy_t *mp) + { + enum stream_status r; + char stdout_buf[200]; + + /* if we haven't launched the proxy yet, do it now */ + if (mp->conf_state == PT_PROTO_INFANT) { + launch_managed_proxy(mp); + return; + } + + tor_assert(mp->conf_state != PT_PROTO_INFANT); + + while (1) { + r = get_string_from_pipe(mp->_stdout, stdout_buf, + sizeof(stdout_buf) - 1); + + if (r == IO_STREAM_OKAY) { /* got a line; handle it! */ + handle_proxy_line((const char *)stdout_buf, mp); + } else if (r == IO_STREAM_EAGAIN) { /* check back later */ + return; + } else if (r == IO_STREAM_CLOSED || r == IO_STREAM_TERM) { /* snap! */ + log_notice(LD_GENERAL, "Managed proxy stream closed. " + "Most probably application stopped running"); + mp->conf_state = PT_PROTO_BROKEN; + } else { /* unknown stream status */ + log_notice(LD_GENERAL, "Unknown stream status while configuring proxy."); + } + + /* if the proxy finished configuring, exit the loop. */ + if (proxy_configuration_finished(mp)) { + handle_finished_proxy(mp); + return; + } + } + } + + /** Register server managed proxy <b>mp</b> transports to state */ + static void + register_server_proxy(managed_proxy_t *mp) + { + /* After we register this proxy's transports, we switch its + mp->transports to a list containing strings of its transport + names. (See transports.h) */ + smartlist_t *sm_tmp = smartlist_create(); + + tor_assert(mp->conf_state != PT_PROTO_COMPLETED); + SMARTLIST_FOREACH_BEGIN(mp->transports, transport_t *, t) { + save_transport_to_state(t->name, &t->addr, t->port); + smartlist_add(sm_tmp, tor_strdup(t->name)); + } SMARTLIST_FOREACH_END(t); + + /* Since server proxies don't register their transports in the + circuitbuild.c subsystem, it's our duty to free them when we + switch mp->transports to strings. */ + SMARTLIST_FOREACH(mp->transports, transport_t *, t, transport_free(t)); + smartlist_free(mp->transports); + + mp->transports = sm_tmp; + } + + /** Register all the transports supported by client managed proxy + * <b>mp</b> to the bridge subsystem. */ + static void + register_client_proxy(managed_proxy_t *mp) + { + int r; + /* After we register this proxy's transports, we switch its + mp->transports to a list containing strings of its transport + names. (See transports.h) */ + smartlist_t *sm_tmp = smartlist_create(); + + tor_assert(mp->conf_state != PT_PROTO_COMPLETED); + SMARTLIST_FOREACH_BEGIN(mp->transports, transport_t *, t) { + r = transport_add(t); + switch (r) { + case -1: + log_notice(LD_GENERAL, "Could not add transport %s. Skipping.", t->name); + transport_free(t); + break; + case 0: + log_info(LD_GENERAL, "Succesfully registered transport %s", t->name); + smartlist_add(sm_tmp, tor_strdup(t->name)); + break; + case 1: + log_info(LD_GENERAL, "Succesfully registered transport %s", t->name); + smartlist_add(sm_tmp, tor_strdup(t->name)); + transport_free(t); + break; + } + } SMARTLIST_FOREACH_END(t); + + smartlist_free(mp->transports); + mp->transports = sm_tmp; + } + + /** Register the transports of managed proxy <b>mp</b>. */ + static INLINE void + register_proxy(managed_proxy_t *mp) + { + if (mp->is_server) + register_server_proxy(mp); + else + register_client_proxy(mp); + } + + /** Free memory allocated by managed proxy <b>mp</b>. */ + static void + managed_proxy_destroy(managed_proxy_t *mp) + { + if (mp->conf_state != PT_PROTO_COMPLETED) + SMARTLIST_FOREACH(mp->transports, transport_t *, t, transport_free(t)); + else + SMARTLIST_FOREACH(mp->transports, char *, t_name, tor_free(t_name)); + + /* free the transports smartlist */ + smartlist_free(mp->transports); + + /* free the transports_to_launch smartlist */ + SMARTLIST_FOREACH(mp->transports_to_launch, char *, t, tor_free(t)); + smartlist_free(mp->transports_to_launch); + + /* remove it from the list of managed proxies */ + smartlist_remove(managed_proxy_list, mp); + + /* close its stdout stream */ + if (mp->_stdout) + fclose(mp->_stdout); + + /* free the argv */ + free_execve_args(mp->argv); + + if (mp->pid) + tor_terminate_process(mp->pid); + + tor_free(mp); + } + + /** Handle a configured or broken managed proxy <b>mp</b>. */ + static void + handle_finished_proxy(managed_proxy_t *mp) + { + switch (mp->conf_state) { + case PT_PROTO_BROKEN: /* if broken: */ + managed_proxy_destroy(mp); /* annihilate it. */ + break; + case PT_PROTO_CONFIGURED: /* if configured correctly: */ + register_proxy(mp); /* register its transports */ + mp->conf_state = PT_PROTO_COMPLETED; /* and mark it as completed. */ + break; + case PT_PROTO_INFANT: + case PT_PROTO_LAUNCHED: + case PT_PROTO_ACCEPTING_METHODS: + case PT_PROTO_COMPLETED: + default: + log_warn(LD_CONFIG, "Unexpected managed proxy state in " + "handle_finished_proxy()."); + tor_assert(0); + } + + unconfigured_proxies_n--; + tor_assert(unconfigured_proxies_n >= 0); + } + + /** Return true if the configuration of the managed proxy <b>mp</b> is + finished. */ + static INLINE int + proxy_configuration_finished(const managed_proxy_t *mp) + { + return (mp->conf_state == PT_PROTO_CONFIGURED || + mp->conf_state == PT_PROTO_BROKEN); + } + + /** This function is called when a proxy sends an {S,C}METHODS DONE message. */ + static void + handle_methods_done(const managed_proxy_t *mp) + { + tor_assert(mp->transports); + + if (smartlist_len(mp->transports) == 0) + log_notice(LD_GENERAL, "Proxy was spawned successfully, " + "but it didn't laucn any pluggable transport listeners!"); + + log_info(LD_CONFIG, "%s managed proxy configuration completed!", + mp->is_server ? "Server" : "Client"); + } + + /** Handle a configuration protocol <b>line</b> received from a + * managed proxy <b>mp</b>. */ + void + handle_proxy_line(const char *line, managed_proxy_t *mp) + { + log_debug(LD_GENERAL, "Got a line from managed proxy: %s\n", line); + + if (strlen(line) < SMALLEST_MANAGED_LINE_SIZE) { + log_warn(LD_GENERAL, "Managed proxy configuration line is too small. " + "Discarding"); + goto err; + } + + if (!strcmpstart(line, PROTO_ENV_ERROR)) { + if (mp->conf_state != PT_PROTO_LAUNCHED) + goto err; + + parse_env_error(line); + goto err; + } else if (!strcmpstart(line, PROTO_NEG_FAIL)) { + if (mp->conf_state != PT_PROTO_LAUNCHED) + goto err; + + log_warn(LD_CONFIG, "Managed proxy could not pick a " + "configuration protocol version."); + goto err; + } else if (!strcmpstart(line, PROTO_NEG_SUCCESS)) { + if (mp->conf_state != PT_PROTO_LAUNCHED) + goto err; + + if (parse_version(line,mp) < 0) + goto err; + + tor_assert(mp->conf_protocol != 0); + mp->conf_state = PT_PROTO_ACCEPTING_METHODS; + return; + } else if (!strcmpstart(line, PROTO_CMETHODS_DONE)) { + if (mp->conf_state != PT_PROTO_ACCEPTING_METHODS) + goto err; + + handle_methods_done(mp); + + mp->conf_state = PT_PROTO_CONFIGURED; + return; + } else if (!strcmpstart(line, PROTO_SMETHODS_DONE)) { + if (mp->conf_state != PT_PROTO_ACCEPTING_METHODS) + goto err; + + handle_methods_done(mp); + + mp->conf_state = PT_PROTO_CONFIGURED; + return; + } else if (!strcmpstart(line, PROTO_CMETHOD_ERROR)) { + if (mp->conf_state != PT_PROTO_ACCEPTING_METHODS) + goto err; + + parse_client_method_error(line); + goto err; + } else if (!strcmpstart(line, PROTO_SMETHOD_ERROR)) { + if (mp->conf_state != PT_PROTO_ACCEPTING_METHODS) + goto err; + + parse_server_method_error(line); + goto err; + } else if (!strcmpstart(line, PROTO_CMETHOD)) { + if (mp->conf_state != PT_PROTO_ACCEPTING_METHODS) + goto err; + + if (parse_cmethod_line(line, mp) < 0) + goto err; + + return; + } else if (!strcmpstart(line, PROTO_SMETHOD)) { + if (mp->conf_state != PT_PROTO_ACCEPTING_METHODS) + goto err; + + if (parse_smethod_line(line, mp) < 0) + goto err; + + return; + } else if (!strcmpstart(line, SPAWN_ERROR_MESSAGE)) { + log_warn(LD_GENERAL, "Could not launch managed proxy executable!"); + goto err; + } + + log_warn(LD_CONFIG, "Unknown line received by managed proxy. (%s)", line); + + err: + mp->conf_state = PT_PROTO_BROKEN; + return; + } + + /** Parses an ENV-ERROR <b>line</b> and warns the user accordingly. */ + void + parse_env_error(const char *line) + { + /* (Length of the protocol string) plus (a space) and (the first char of + the error message) */ + if (strlen(line) < (strlen(PROTO_ENV_ERROR) + 2)) + log_notice(LD_CONFIG, "Managed proxy sent us an %s without an error " + "message.", PROTO_ENV_ERROR); + + log_warn(LD_CONFIG, "Managed proxy couldn't understand the " + "pluggable transport environment variables. (%s)", + line+strlen(PROTO_ENV_ERROR)+1); + } + + /** Handles a VERSION <b>line</b>. Updates the configuration protocol + * version in <b>mp</b>. */ + int + parse_version(const char *line, managed_proxy_t *mp) + { + if (strlen(line) < (strlen(PROTO_NEG_SUCCESS) + 2)) { + log_warn(LD_CONFIG, "Managed proxy sent us malformed %s line.", + PROTO_NEG_SUCCESS); + return -1; + } + + if (strcmp("1", line+strlen(PROTO_NEG_SUCCESS)+1)) { /* hardcoded temp */ + log_warn(LD_CONFIG, "Managed proxy tried to negotiate on version '%s'. " + "We only support version '1'", line+strlen(PROTO_NEG_SUCCESS)+1); + return -1; + } + + mp->conf_protocol = PROTO_VERSION_ONE; /* temp. till more versions appear */ + return 0; + } + + /** Parses {C,S}METHOD-ERROR <b>line</b> and warns the user + * accordingly. If <b>is_server</b> it is an SMETHOD-ERROR, + * otherwise it is a CMETHOD-ERROR. */ + static void + parse_method_error(const char *line, int is_server) + { + const char* error = is_server ? + PROTO_SMETHOD_ERROR : PROTO_CMETHOD_ERROR; + + /* (Length of the protocol string) plus (a space) and (the first char of + the error message) */ + if (strlen(line) < (strlen(error) + 2)) + log_warn(LD_CONFIG, "Managed proxy sent us an %s without an error " + "message.", error); + + log_warn(LD_CONFIG, "%s managed proxy encountered a method error. (%s)", + is_server ? "Server" : "Client", + line+strlen(error)+1); + } + + /** Parses an SMETHOD <b>line</b> and if well-formed it registers the + * new transport in <b>mp</b>. */ + int + parse_smethod_line(const char *line, managed_proxy_t *mp) + { + int r; + smartlist_t *items = NULL; + + char *method_name=NULL; + + char *addrport=NULL; + tor_addr_t addr; + uint16_t port = 0; + + transport_t *transport=NULL; + + items = smartlist_create(); + smartlist_split_string(items, line, NULL, + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1); + if (smartlist_len(items) < 3) { + log_warn(LD_CONFIG, "Server managed proxy sent us a SMETHOD line " + "with too few arguments."); + goto err; + } + + tor_assert(!strcmp(smartlist_get(items,0),PROTO_SMETHOD)); + + method_name = smartlist_get(items,1); + if (!string_is_C_identifier(method_name)) { + log_warn(LD_CONFIG, "Transport name is not a C identifier (%s).", + method_name); + goto err; + } + + addrport = smartlist_get(items, 2); + if (tor_addr_port_parse(addrport, &addr, &port)<0) { + log_warn(LD_CONFIG, "Error parsing transport " + "address '%s'", addrport); + goto err; + } + + if (!port) { + log_warn(LD_CONFIG, + "Transport address '%s' has no port.", addrport); + goto err; + } + + transport = transport_create(&addr, port, method_name, PROXY_NONE); + if (!transport) + goto err; + + smartlist_add(mp->transports, transport); + + /* For now, notify the user so that he knows where the server + transport is listening. */ + log_info(LD_CONFIG, "Server transport %s at %s:%d.", + method_name, fmt_addr(&addr), (int)port); + + r=0; + goto done; + + err: + r = -1; + + done: + SMARTLIST_FOREACH(items, char*, s, tor_free(s)); + smartlist_free(items); + return r; + } + + /** Parses a CMETHOD <b>line</b>, and if well-formed it registers + * the new transport in <b>mp</b>. */ + int + parse_cmethod_line(const char *line, managed_proxy_t *mp) + { + int r; + smartlist_t *items = NULL; + + char *method_name=NULL; + + char *socks_ver_str=NULL; + int socks_ver=PROXY_NONE; + + char *addrport=NULL; + tor_addr_t addr; + uint16_t port = 0; + + transport_t *transport=NULL; + + items = smartlist_create(); + smartlist_split_string(items, line, NULL, + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1); + if (smartlist_len(items) < 4) { + log_warn(LD_CONFIG, "Client managed proxy sent us a CMETHOD line " + "with too few arguments."); + goto err; + } + + tor_assert(!strcmp(smartlist_get(items,0),PROTO_CMETHOD)); + + method_name = smartlist_get(items,1); + if (!string_is_C_identifier(method_name)) { + log_warn(LD_CONFIG, "Transport name is not a C identifier (%s).", + method_name); + goto err; + } + + socks_ver_str = smartlist_get(items,2); + + if (!strcmp(socks_ver_str,"socks4")) { + socks_ver = PROXY_SOCKS4; + } else if (!strcmp(socks_ver_str,"socks5")) { + socks_ver = PROXY_SOCKS5; + } else { + log_warn(LD_CONFIG, "Client managed proxy sent us a proxy protocol " + "we don't recognize. (%s)", socks_ver_str); + goto err; + } + + addrport = smartlist_get(items, 3); + if (tor_addr_port_parse(addrport, &addr, &port)<0) { + log_warn(LD_CONFIG, "Error parsing transport " + "address '%s'", addrport); + goto err; + } + + if (!port) { + log_warn(LD_CONFIG, + "Transport address '%s' has no port.", addrport); + goto err; + } + + transport = transport_create(&addr, port, method_name, socks_ver); + if (!transport) + goto err; + + smartlist_add(mp->transports, transport); + + log_info(LD_CONFIG, "Transport %s at %s:%d with SOCKS %d. " + "Attached to managed proxy.", + method_name, fmt_addr(&addr), (int)port, socks_ver); + + r=0; + goto done; + + err: + r = -1; + + done: + SMARTLIST_FOREACH(items, char*, s, tor_free(s)); + smartlist_free(items); + return r; + } + + /** Return a string containing the address:port that <b>transport</b> + * should use. It's the responsibility of the caller to free() the + * received string. */ + static char * + get_bindaddr_for_proxy(const managed_proxy_t *mp) + { + char *bindaddr = NULL; + smartlist_t *string_tmp = smartlist_create(); + + tor_assert(mp->is_server); + + SMARTLIST_FOREACH_BEGIN(mp->transports_to_launch, char *, t) { + tor_asprintf(&bindaddr, "%s-%s", t, get_bindaddr_for_transport(t)); + smartlist_add(string_tmp, bindaddr); + } SMARTLIST_FOREACH_END(t); + + bindaddr = smartlist_join_strings(string_tmp, ",", 0, NULL); + + SMARTLIST_FOREACH(string_tmp, char *, t, tor_free(t)); + smartlist_free(string_tmp); + + return bindaddr; + } + + /** Prepare the <b>envp</b> of managed proxy <b>mp</b> */ + static void + set_managed_proxy_environment(char ***envp, const managed_proxy_t *mp) + { - or_options_t *options = get_options(); ++ const or_options_t *options = get_options(); + char **tmp=NULL; + char *state_loc=NULL; + char *transports_to_launch=NULL; + char *bindaddr=NULL; + + int n_envs = mp->is_server ? ENVIRON_SIZE_SERVER : ENVIRON_SIZE_CLIENT; + + /* allocate enough space for our env. vars and a NULL pointer */ + *envp = tor_malloc(sizeof(char*)*(n_envs+1)); + tmp = *envp; + + state_loc = get_datadir_fname("pt_state/"); /* XXX temp */ + transports_to_launch = + smartlist_join_strings(mp->transports_to_launch, ",", 0, NULL); + + tor_asprintf(tmp++, "HOME=%s", getenv("HOME")); + tor_asprintf(tmp++, "PATH=%s", getenv("PATH")); + tor_asprintf(tmp++, "TOR_PT_STATE_LOCATION=%s", state_loc); + tor_asprintf(tmp++, "TOR_PT_MANAGED_TRANSPORT_VER=1"); /* temp */ + if (mp->is_server) { + bindaddr = get_bindaddr_for_proxy(mp); + + /* XXX temp */ + tor_asprintf(tmp++, "TOR_PT_ORPORT=127.0.0.1:%d", options->ORPort); + tor_asprintf(tmp++, "TOR_PT_SERVER_BINDADDR=%s", bindaddr); + tor_asprintf(tmp++, "TOR_PT_SERVER_TRANSPORTS=%s", transports_to_launch); + /* XXX temp*/ + tor_asprintf(tmp++, "TOR_PT_EXTENDED_SERVER_PORT=127.0.0.1:4200"); + } else { + tor_asprintf(tmp++, "TOR_PT_CLIENT_TRANSPORTS=%s", transports_to_launch); + } + *tmp = NULL; + + tor_free(state_loc); + tor_free(transports_to_launch); + tor_free(bindaddr); + } + + /** Create and return a new managed proxy for <b>transport</b> using + * <b>proxy_argv</b>. If <b>is_server</b> is true, it's a server + * managed proxy. */ + static managed_proxy_t * + managed_proxy_create(const smartlist_t *transport_list, + char **proxy_argv, int is_server) + { + managed_proxy_t *mp = tor_malloc_zero(sizeof(managed_proxy_t)); + mp->conf_state = PT_PROTO_INFANT; + mp->is_server = is_server; + mp->argv = proxy_argv; + mp->transports = smartlist_create(); + + mp->transports_to_launch = smartlist_create(); + SMARTLIST_FOREACH(transport_list, const char *, transport, + add_transport_to_proxy(transport, mp)); + + /* register the managed proxy */ + if (!managed_proxy_list) + managed_proxy_list = smartlist_create(); + smartlist_add(managed_proxy_list, mp); + unconfigured_proxies_n++; + + return mp; + } + + /** Register <b>transport</b> using proxy with <b>proxy_argv</b> to + * the managed proxy subsystem. + * If <b>is_server</b> is true, then the proxy is a server proxy. */ + void + pt_kickstart_proxy(const smartlist_t *transport_list, + char **proxy_argv, int is_server) + { + managed_proxy_t *mp=NULL; + transport_t *old_transport = NULL; + + mp = get_managed_proxy_by_argv_and_type(proxy_argv, is_server); + + if (!mp) { /* we haven't seen this proxy before */ + managed_proxy_create(transport_list, proxy_argv, is_server); + + } else { /* known proxy. add its transport to its transport list */ + if (mp->got_hup) { + /* If the managed proxy we found is marked by a SIGHUP, it means + that it's not useless and should be kept. If it's marked for + removal, unmark it and increase the unconfigured proxies so + that we try to restart it if we need to. Afterwards, check if + a transport_t for 'transport' used to exist before the SIGHUP + and make sure it doesn't get deleted because we might reuse + it. */ + if (mp->marked_for_removal) { + mp->marked_for_removal = 0; + unconfigured_proxies_n++; + } + + SMARTLIST_FOREACH_BEGIN(transport_list, const char *, transport) { + old_transport = transport_get_by_name(transport); + if (old_transport) + old_transport->marked_for_removal = 0; + } SMARTLIST_FOREACH_END(transport); + } + + SMARTLIST_FOREACH(transport_list, const char *, transport, + add_transport_to_proxy(transport, mp)); + free_execve_args(proxy_argv); + } + } + + /** Frees the array of pointers in <b>arg</b> used as arguments to + execve(2). */ + static INLINE void + free_execve_args(char **arg) + { + char **tmp = arg; + while (*tmp) /* use the fact that the last element of the array is a + NULL pointer to know when to stop freeing */ + _tor_free(*tmp++); + + tor_free(arg); + } + + /** Tor will read its config. + * Prepare the managed proxy list so that proxies not used in the new + * config will shutdown, and proxies that need to spawn different + * transports will do so. */ + void + pt_prepare_proxy_list_for_config_read(void) + { + if (!managed_proxy_list) + return; + + SMARTLIST_FOREACH_BEGIN(managed_proxy_list, managed_proxy_t *, mp) { + /* Destroy unconfigured proxies. */ + if (mp->conf_state != PT_PROTO_COMPLETED) { + managed_proxy_destroy(mp); + unconfigured_proxies_n--; + continue; + } + + tor_assert(mp->conf_state == PT_PROTO_COMPLETED); + + mp->marked_for_removal = 1; + mp->got_hup = 1; + SMARTLIST_FOREACH(mp->transports_to_launch, char *, t, tor_free(t)); + smartlist_clear(mp->transports_to_launch); + } SMARTLIST_FOREACH_END(mp); + + tor_assert(unconfigured_proxies_n == 0); + } + + /** The tor config was read. + * Destroy all managed proxies that were marked by a previous call to + * prepare_proxy_list_for_config_read() and are not used by the new + * config. */ + void + sweep_proxy_list(void) + { + if (!managed_proxy_list) + return; + + SMARTLIST_FOREACH_BEGIN(managed_proxy_list, managed_proxy_t *, mp) { + if (mp->marked_for_removal) { + SMARTLIST_DEL_CURRENT(managed_proxy_list, mp); + managed_proxy_destroy(mp); + } + } SMARTLIST_FOREACH_END(mp); + } + + /** Release all storage held by the pluggable transports subsystem. */ + void + pt_free_all(void) + { + if (managed_proxy_list) { + /* If the proxy is in PT_PROTO_COMPLETED, it has registered its + transports and it's the duty of the circuitbuild.c subsystem to + free them. Otherwise, it hasn't registered its transports yet + and we should free them here. */ + SMARTLIST_FOREACH(managed_proxy_list, managed_proxy_t *, mp, + managed_proxy_destroy(mp)); + + smartlist_free(managed_proxy_list); + managed_proxy_list=NULL; + } + } + diff --cc src/test/test_util.c index f9672c1,c778faa..6603ab0 --- a/src/test/test_util.c +++ b/src/test/test_util.c @@@ -1388,42 -1389,34 +1388,42 @@@ run_util_spawn_background(const char *a char stdout_buf[100], stderr_buf[100];
/* Start the program */ - retval = tor_spawn_background(argv[0], &stdout_pipe, &stderr_pipe, - argv, NULL); - tt_int_op(retval, >, 0); - tt_int_op(stdout_pipe, >, 0); - tt_int_op(stderr_pipe, >, 0); - pid = retval; +#ifdef MS_WINDOWS - tor_spawn_background(NULL, argv, &process_handle); ++ tor_spawn_background(NULL, argv, NULL, &process_handle); +#else - tor_spawn_background(argv[0], argv, &process_handle); ++ tor_spawn_background(argv[0], argv, NULL, &process_handle); +#endif + + tt_int_op(process_handle.status, ==, expected_status); + + /* If the process failed to start, don't bother continuing */ + if (process_handle.status == PROCESS_STATUS_ERROR) + return; + + tt_int_op(process_handle.stdout_pipe, >, 0); + tt_int_op(process_handle.stderr_pipe, >, 0);
/* Check stdout */ - pos = read_all(stdout_pipe, stdout_buf, sizeof(stdout_buf) - 1, 0); + pos = tor_read_all_from_process_stdout(&process_handle, stdout_buf, + sizeof(stdout_buf) - 1); tt_assert(pos >= 0); stdout_buf[pos] = '\0'; - tt_int_op(pos, ==, strlen(expected_out)); tt_str_op(stdout_buf, ==, expected_out); + tt_int_op(pos, ==, strlen(expected_out));
/* Check it terminated correctly */ - retval = waitpid(pid, &stat_loc, 0); - tt_int_op(retval, ==, pid); - tt_assert(WIFEXITED(stat_loc)); - tt_int_op(WEXITSTATUS(stat_loc), ==, expected_exit); - tt_assert(!WIFSIGNALED(stat_loc)); - tt_assert(!WIFSTOPPED(stat_loc)); + retval = tor_get_exit_code(process_handle, 1, &exit_code); + tt_int_op(retval, ==, PROCESS_EXIT_EXITED); + tt_int_op(exit_code, ==, expected_exit); + // TODO: Make test-child exit with something other than 0
/* Check stderr */ - pos = read_all(stderr_pipe, stderr_buf, sizeof(stderr_buf) - 1, 0); + pos = tor_read_all_from_process_stderr(&process_handle, stderr_buf, + sizeof(stderr_buf) - 1); tt_assert(pos >= 0); stderr_buf[pos] = '\0'; - tt_int_op(pos, ==, strlen(expected_err)); tt_str_op(stderr_buf, ==, expected_err); + tt_int_op(pos, ==, strlen(expected_err));
done: ; @@@ -1471,219 -1446,9 +1471,219 @@@ test_util_spawn_background_fail(void *p
(void)ptr;
- run_util_spawn_background(argv, expected_out, expected_err, 255); + run_util_spawn_background(argv, expected_out, expected_err, 255, + expected_status); } + +/** Test that reading from a handle returns a partial read rather than + * blocking */ +static void +test_util_spawn_background_partial_read(void *ptr) +{ + const int expected_exit = 0; + const int expected_status = PROCESS_STATUS_RUNNING; + + int retval, exit_code; + ssize_t pos = -1; + process_handle_t process_handle; + char stdout_buf[100], stderr_buf[100]; +#ifdef MS_WINDOWS + const char *argv[] = {"test-child.exe", "--test", NULL}; + const char *expected_out[] = { "OUT\r\n--test\r\nSLEEPING\r\n", + "DONE\r\n", + NULL }; + const char *expected_err = "ERR\r\n"; +#else + const char *argv[] = {BUILDDIR "/src/test/test-child", "--test", NULL}; + const char *expected_out[] = { "OUT\n--test\nSLEEPING\n", + "DONE\n", + NULL }; + const char *expected_err = "ERR\n"; + int eof = 0; +#endif + int expected_out_ctr; + (void)ptr; + + /* Start the program */ +#ifdef MS_WINDOWS - tor_spawn_background(NULL, argv, &process_handle); ++ tor_spawn_background(NULL, argv, NULL, &process_handle); +#else - tor_spawn_background(argv[0], argv, &process_handle); ++ tor_spawn_background(argv[0], argv, NULL, &process_handle); #endif + tt_int_op(process_handle.status, ==, expected_status); + + /* Check stdout */ + for (expected_out_ctr =0; expected_out[expected_out_ctr] != NULL;) { +#ifdef MS_WINDOWS + pos = tor_read_all_handle(process_handle.stdout_pipe, stdout_buf, + sizeof(stdout_buf) - 1, NULL); +#else + /* Check that we didn't read the end of file last time */ + tt_assert(!eof); + pos = tor_read_all_handle(process_handle.stdout_handle, stdout_buf, + sizeof(stdout_buf) - 1, NULL, &eof); +#endif + log_info(LD_GENERAL, "tor_read_all_handle() returned %d", (int)pos); + + /* We would have blocked, keep on trying */ + if (0 == pos) + continue; + + tt_int_op(pos, >, 0); + stdout_buf[pos] = '\0'; + tt_str_op(stdout_buf, ==, expected_out[expected_out_ctr]); + tt_int_op(pos, ==, strlen(expected_out[expected_out_ctr])); + expected_out_ctr++; + } + + /* The process should have exited without writing more */ +#ifdef MS_WINDOWS + pos = tor_read_all_handle(process_handle.stdout_pipe, stdout_buf, + sizeof(stdout_buf) - 1, + &process_handle); + tt_int_op(pos, ==, 0); +#else + if (!eof) { + /* We should have got all the data, but maybe not the EOF flag */ + pos = tor_read_all_handle(process_handle.stdout_handle, stdout_buf, + sizeof(stdout_buf) - 1, + &process_handle, &eof); + tt_int_op(pos, ==, 0); + tt_assert(eof); + } + /* Otherwise, we got the EOF on the last read */ +#endif + + /* Check it terminated correctly */ + retval = tor_get_exit_code(process_handle, 1, &exit_code); + tt_int_op(retval, ==, PROCESS_EXIT_EXITED); + tt_int_op(exit_code, ==, expected_exit); + + // TODO: Make test-child exit with something other than 0 + + /* Check stderr */ + pos = tor_read_all_from_process_stderr(&process_handle, stderr_buf, + sizeof(stderr_buf) - 1); + tt_assert(pos >= 0); + stderr_buf[pos] = '\0'; + tt_str_op(stderr_buf, ==, expected_err); + tt_int_op(pos, ==, strlen(expected_err)); + + done: + ; +} + +/** + * Test that we can properly format q Windows command line + */ +static void +test_util_join_win_cmdline(void *ptr) +{ + /* Based on some test cases from "Parsing C++ Command-Line Arguments" in + * MSDN but we don't exercise all quoting rules because tor_join_win_cmdline + * will try to only generate simple cases for the child process to parse; + * i.e. we never embed quoted strings in arguments. */ + + const char *argvs[][4] = { + {"a", "bb", "CCC", NULL}, // Normal + {NULL, NULL, NULL, NULL}, // Empty argument list + {"", NULL, NULL, NULL}, // Empty argument + {""a", "b"b", "CCC"", NULL}, // Quotes + {"a\tbc", "dd dd", "E", NULL}, // Whitespace + {"a\\\b", "de fg", "H", NULL}, // Backslashes + {"a\"b", "\c", "D\", NULL}, // Backslashes before quote + {"a\\b c", "d", "E", NULL}, // Backslashes not before quote + {} // Terminator + }; + + const char *cmdlines[] = { + "a bb CCC", + "", + """", + "\"a b\"b CCC\"", + ""a\tbc" "dd dd" E", + "a\\\b "de fg" H", + "a\\\"b \c D\", + ""a\\b c" d E", + NULL // Terminator + }; + + int i; + char *joined_argv; + + (void)ptr; + + for (i=0; cmdlines[i]!=NULL; i++) { + log_info(LD_GENERAL, "Joining argvs[%d], expecting <%s>", i, cmdlines[i]); + joined_argv = tor_join_win_cmdline(argvs[i]); + tt_str_op(joined_argv, ==, cmdlines[i]); + tor_free(joined_argv); + } + + done: + ; +} + +#define MAX_SPLIT_LINE_COUNT 3 +struct split_lines_test_t { + const char *orig_line; // Line to be split (may contain \0's) + int orig_length; // Length of orig_line + const char *split_line[MAX_SPLIT_LINE_COUNT]; // Split lines +}; + +/** + * Test that we properly split a buffer into lines + */ +static void +test_util_split_lines(void *ptr) +{ + /* Test cases. orig_line of last test case must be NULL. + * The last element of split_line[i] must be NULL. */ + struct split_lines_test_t tests[] = { + {"", 0, {NULL}}, + {"foo", 3, {"foo", NULL}}, + {"\n\rfoo\n\rbar\r\n", 12, {"foo", "bar", NULL}}, + {"fo o\r\nb\tar", 10, {"fo o", "b.ar", NULL}}, + {"\x0f""f\0o\0\n\x01""b\0r\0\r", 12, {".f.o.", ".b.r.", NULL}}, + {NULL, 0, {}} + }; + + int i, j; + char *orig_line; + smartlist_t *sl; + + (void)ptr; + + for (i=0; tests[i].orig_line; i++) { + sl = smartlist_create(); + /* Allocate space for string and trailing NULL */ + orig_line = tor_memdup(tests[i].orig_line, tests[i].orig_length + 1); + tor_split_lines(sl, orig_line, tests[i].orig_length); + + j = 0; + log_info(LD_GENERAL, "Splitting test %d of length %d", + i, tests[i].orig_length); + SMARTLIST_FOREACH(sl, const char *, line, + { + /* Check we have not got too many lines */ + tt_int_op(j, <, MAX_SPLIT_LINE_COUNT); + /* Check that there actually should be a line here */ + tt_assert(tests[i].split_line[j] != NULL); + log_info(LD_GENERAL, "Line %d of test %d, should be <%s>", + j, i, tests[i].split_line[j]); + /* Check that the line is as expected */ + tt_str_op(tests[i].split_line[j], ==, line); + j++; + }); + /* Check that we didn't miss some lines */ + tt_assert(tests[i].split_line[j] == NULL); + tor_free(orig_line); + smartlist_free(sl); + } + + done: + ; +}
static void test_util_di_ops(void)
tor-commits@lists.torproject.org