void temp_fstream::open (std::ios_base::openmode mode) { close(); const char* tmpdir = getenv("TMPDIR"); size_t tmpdir_len = tmpdir ? std::strlen(tmpdir) : 0; if (tmpdir_len == 0 || tmpdir_len > 4096) { // no $TMPDIR or it's excessively long => fall back to /tmp tmpdir = "/tmp"; tmpdir_len = 4; } std::vector<char> path_buffer(tmpdir_len + 18); char* path = &path_buffer[0]; std::strcpy(path, tmpdir); std::strcpy(path + tmpdir_len, "/git-crypt.XXXXXX"); mode_t old_umask = umask(0077); int fd = mkstemp(path); if (fd == -1) { int mkstemp_errno = errno; umask(old_umask); throw System_error("mkstemp", "", mkstemp_errno); } umask(old_umask); std::fstream::open(path, mode); if (!std::fstream::is_open()) { unlink(path); ::close(fd); throw System_error("std::fstream::open", path, 0); } unlink(path); ::close(fd); }
std::vector<std::string> get_directory_contents (const char* path) { std::vector<std::string> filenames; std::string patt(path); if (!patt.empty() && patt[patt.size() - 1] != '/' && patt[patt.size() - 1] != '\\') { patt.push_back('\\'); } patt.push_back('*'); WIN32_FIND_DATAA ffd; HANDLE h = FindFirstFileA(patt.c_str(), &ffd); if (h == INVALID_HANDLE_VALUE) { throw System_error("FindFirstFileA", patt, GetLastError()); } do { if (std::strcmp(ffd.cFileName, ".") != 0 && std::strcmp(ffd.cFileName, "..") != 0) { filenames.push_back(ffd.cFileName); } } while (FindNextFileA(h, &ffd) != 0); DWORD err = GetLastError(); if (err != ERROR_NO_MORE_FILES) { throw System_error("FileNextFileA", patt, err); } FindClose(h); return filenames; }
std::vector<std::string> get_directory_contents (const char* path) { std::vector<std::string> contents; DIR* dir = opendir(path); if (!dir) { throw System_error("opendir", path, errno); } try { std::vector<unsigned char> buffer(sizeof_dirent_for(dir)); struct dirent* dirent_buffer = reinterpret_cast<struct dirent*>(&buffer[0]); struct dirent* ent = NULL; int err = 0; while ((err = readdir_r(dir, dirent_buffer, &ent)) == 0 && ent != NULL) { if (std::strcmp(ent->d_name, ".") == 0 || std::strcmp(ent->d_name, "..") == 0) { continue; } contents.push_back(ent->d_name); } if (err != 0) { throw System_error("readdir_r", path, errno); } } catch (...) { closedir(dir); throw; } closedir(dir); std::sort(contents.begin(), contents.end()); return contents; }
void temp_fstream::open (std::ios_base::openmode mode) { close(); char tmpdir[MAX_PATH + 1]; DWORD ret = GetTempPath(sizeof(tmpdir), tmpdir); if (ret == 0) { throw System_error("GetTempPath", "", GetLastError()); } else if (ret > sizeof(tmpdir) - 1) { throw System_error("GetTempPath", "", ERROR_BUFFER_OVERFLOW); } char tmpfilename[MAX_PATH + 1]; if (GetTempFileName(tmpdir, TEXT("git-crypt"), 0, tmpfilename) == 0) { throw System_error("GetTempFileName", "", GetLastError()); } filename = tmpfilename; std::fstream::open(filename.c_str(), mode); if (!std::fstream::is_open()) { DeleteFile(filename.c_str()); throw System_error("std::fstream::open", filename, 0); } }
int Coprocess::wait () { if (WaitForSingleObject(proc_handle, INFINITE) == WAIT_FAILED) { throw System_error("WaitForSingleObject", "", GetLastError()); } DWORD exit_code; if (!GetExitCodeProcess(proc_handle, &exit_code)) { throw System_error("GetExitCodeProcess", "", GetLastError()); } return exit_code; }
static int wait_for_child (HANDLE child_handle) { if (WaitForSingleObject(child_handle, INFINITE) == WAIT_FAILED) { throw System_error("WaitForSingleObject", "", GetLastError()); } DWORD exit_code; if (!GetExitCodeProcess(child_handle, &exit_code)) { throw System_error("GetExitCodeProcess", "", GetLastError()); } return exit_code; }
void Coprocess::spawn (const std::vector<std::string>& args) { pid = fork(); if (pid == -1) { throw System_error("fork", "", errno); } if (pid == 0) { if (stdin_pipe_writer != -1) { close(stdin_pipe_writer); } if (stdout_pipe_reader != -1) { close(stdout_pipe_reader); } if (stdin_pipe_reader != -1) { dup2(stdin_pipe_reader, 0); close(stdin_pipe_reader); } if (stdout_pipe_writer != -1) { dup2(stdout_pipe_writer, 1); close(stdout_pipe_writer); } execvp(args[0], args); perror(args[0].c_str()); _exit(-1); } if (stdin_pipe_reader != -1) { close(stdin_pipe_reader); stdin_pipe_reader = -1; } if (stdout_pipe_writer != -1) { close(stdout_pipe_writer); stdout_pipe_writer = -1; } }
static HANDLE spawn_command (const std::vector<std::string>& command, HANDLE stdin_handle, HANDLE stdout_handle, HANDLE stderr_handle) { PROCESS_INFORMATION proc_info; ZeroMemory(&proc_info, sizeof(proc_info)); STARTUPINFO start_info; ZeroMemory(&start_info, sizeof(start_info)); start_info.cb = sizeof(STARTUPINFO); start_info.hStdInput = stdin_handle ? stdin_handle : GetStdHandle(STD_INPUT_HANDLE); start_info.hStdOutput = stdout_handle ? stdout_handle : GetStdHandle(STD_OUTPUT_HANDLE); start_info.hStdError = stderr_handle ? stderr_handle : GetStdHandle(STD_ERROR_HANDLE); start_info.dwFlags |= STARTF_USESTDHANDLES; std::string cmdline(format_cmdline(command)); if (!CreateProcessA(NULL, // application name (NULL to use command line) const_cast<char*>(cmdline.c_str()), NULL, // process security attributes NULL, // primary thread security attributes TRUE, // handles are inherited 0, // creation flags NULL, // use parent's environment NULL, // use parent's current directory &start_info, &proc_info)) { throw System_error("CreateProcess", cmdline, GetLastError()); } CloseHandle(proc_info.hThread); return proc_info.hProcess; }
int Coprocess::wait () { int status = 0; if (waitpid(pid, &status, 0) == -1) { throw System_error("waitpid", "", errno); } return status; }
void create_protected_file (const char* path) { int fd = open(path, O_WRONLY | O_CREAT, 0600); if (fd == -1) { throw System_error("open", path, errno); } close(fd); }
size_t Coprocess::write_stdin (void* handle, const void* buf, size_t count) { DWORD bytes_written; if (!WriteFile(static_cast<Coprocess*>(handle)->stdin_pipe_writer, buf, count, &bytes_written, nullptr)) { throw System_error("WriteFile", "", GetLastError()); } return bytes_written; }
int exec_command (const std::vector<std::string>& command, std::ostream& output) { HANDLE stdout_pipe_reader = NULL; HANDLE stdout_pipe_writer = NULL; SECURITY_ATTRIBUTES sec_attr; // Set the bInheritHandle flag so pipe handles are inherited. sec_attr.nLength = sizeof(SECURITY_ATTRIBUTES); sec_attr.bInheritHandle = TRUE; sec_attr.lpSecurityDescriptor = NULL; // Create a pipe for the child process's STDOUT. if (!CreatePipe(&stdout_pipe_reader, &stdout_pipe_writer, &sec_attr, 0)) { throw System_error("CreatePipe", "", GetLastError()); } // Ensure the read handle to the pipe for STDOUT is not inherited. if (!SetHandleInformation(stdout_pipe_reader, HANDLE_FLAG_INHERIT, 0)) { throw System_error("SetHandleInformation", "", GetLastError()); } HANDLE child_handle = spawn_command(command, NULL, stdout_pipe_writer, NULL); CloseHandle(stdout_pipe_writer); // Read from stdout_pipe_reader. // Note that ReadFile on a pipe may return with bytes_read==0 if the other // end of the pipe writes zero bytes, so don't break out of the read loop // when this happens. When the other end of the pipe closes, ReadFile // fails with ERROR_BROKEN_PIPE. char buffer[1024]; DWORD bytes_read; while (ReadFile(stdout_pipe_reader, buffer, sizeof(buffer), &bytes_read, NULL)) { output.write(buffer, bytes_read); } const DWORD read_error = GetLastError(); if (read_error != ERROR_BROKEN_PIPE) { throw System_error("ReadFile", "", read_error); } CloseHandle(stdout_pipe_reader); int exit_code = wait_for_child(child_handle); CloseHandle(child_handle); return exit_code; }
void touch_file (const std::string& filename) { HANDLE fh = CreateFileA(filename.c_str(), FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if (fh == INVALID_HANDLE_VALUE) { throw System_error("CreateFileA", filename, GetLastError()); } SYSTEMTIME system_time; GetSystemTime(&system_time); FILETIME file_time; SystemTimeToFileTime(&system_time, &file_time); if (!SetFileTime(fh, NULL, NULL, &file_time)) { DWORD error = GetLastError(); CloseHandle(fh); throw System_error("SetFileTime", filename, error); } CloseHandle(fh); }
size_t Coprocess::write_stdin (void* handle, const void* buf, size_t count) { const int fd = static_cast<Coprocess*>(handle)->stdin_pipe_writer; ssize_t ret; while ((ret = write(fd, buf, count)) == -1 && errno == EINTR); // restart if interrupted if (ret < 0) { throw System_error("write", "", errno); } return ret; }
size_t Coprocess::read_stdout (void* handle, void* buf, size_t count) { const int fd = static_cast<Coprocess*>(handle)->stdout_pipe_reader; ssize_t ret; while ((ret = read(fd, buf, count)) == -1 && errno == EINTR); // restart if interrupted if (ret < 0) { throw System_error("read", "", errno); } return ret; }
void remove_file (const std::string& filename) { if (!DeleteFileA(filename.c_str())) { DWORD error = GetLastError(); if (error == ERROR_FILE_NOT_FOUND) { return; } else { throw System_error("DeleteFileA", filename, error); } } }
int exec_command_with_input (const std::vector<std::string>& command, const char* p, size_t len) { HANDLE stdin_pipe_reader = NULL; HANDLE stdin_pipe_writer = NULL; SECURITY_ATTRIBUTES sec_attr; // Set the bInheritHandle flag so pipe handles are inherited. sec_attr.nLength = sizeof(SECURITY_ATTRIBUTES); sec_attr.bInheritHandle = TRUE; sec_attr.lpSecurityDescriptor = NULL; // Create a pipe for the child process's STDIN. if (!CreatePipe(&stdin_pipe_reader, &stdin_pipe_writer, &sec_attr, 0)) { throw System_error("CreatePipe", "", GetLastError()); } // Ensure the write handle to the pipe for STDIN is not inherited. if (!SetHandleInformation(stdin_pipe_writer, HANDLE_FLAG_INHERIT, 0)) { throw System_error("SetHandleInformation", "", GetLastError()); } HANDLE child_handle = spawn_command(command, stdin_pipe_reader, NULL, NULL); CloseHandle(stdin_pipe_reader); // Write to stdin_pipe_writer. while (len > 0) { DWORD bytes_written; if (!WriteFile(stdin_pipe_writer, p, len, &bytes_written, NULL)) { throw System_error("WriteFile", "", GetLastError()); } p += bytes_written; len -= bytes_written; } CloseHandle(stdin_pipe_writer); int exit_code = wait_for_child(child_handle); CloseHandle(child_handle); return exit_code; }
std::ostream* Coprocess::stdin_pipe () { if (!stdin_pipe_ostream) { int fds[2]; if (pipe(fds) == -1) { throw System_error("pipe", "", errno); } stdin_pipe_reader = fds[0]; stdin_pipe_writer = fds[1]; stdin_pipe_ostream = new ofhstream(this, write_stdin); } return stdin_pipe_ostream; }
std::istream* Coprocess::stdout_pipe () { if (!stdout_pipe_istream) { int fds[2]; if (pipe(fds) == -1) { throw System_error("pipe", "", errno); } stdout_pipe_reader = fds[0]; stdout_pipe_writer = fds[1]; stdout_pipe_istream = new ifhstream(this, read_stdout); } return stdout_pipe_istream; }
std::string our_exe_path () { std::vector<char> buffer(128); size_t len; while ((len = GetModuleFileNameA(NULL, &buffer[0], buffer.size())) == buffer.size()) { // buffer may have been truncated - grow and try again buffer.resize(buffer.size() * 2); } if (len == 0) { throw System_error("GetModuleFileNameA", "", GetLastError()); } return std::string(buffer.begin(), buffer.begin() + len); }
std::istream* Coprocess::stdout_pipe () { if (!stdout_pipe_istream) { SECURITY_ATTRIBUTES sec_attr; // Set the bInheritHandle flag so pipe handles are inherited. sec_attr.nLength = sizeof(SECURITY_ATTRIBUTES); sec_attr.bInheritHandle = TRUE; sec_attr.lpSecurityDescriptor = nullptr; // Create a pipe for the child process's STDOUT. if (!CreatePipe(&stdout_pipe_reader, &stdout_pipe_writer, &sec_attr, 0)) { throw System_error("CreatePipe", "", GetLastError()); } // Ensure the read handle to the pipe for STDOUT is not inherited. if (!SetHandleInformation(stdout_pipe_reader, HANDLE_FLAG_INHERIT, 0)) { throw System_error("SetHandleInformation", "", GetLastError()); } stdout_pipe_istream = new ifhstream(this, read_stdout); } return stdout_pipe_istream; }
void mkdir_parent (const std::string& path) { std::string::size_type slash(path.find('/', 1)); while (slash != std::string::npos) { std::string prefix(path.substr(0, slash)); if (GetFileAttributes(prefix.c_str()) == INVALID_FILE_ATTRIBUTES) { // prefix does not exist, so try to create it if (!CreateDirectory(prefix.c_str(), NULL)) { throw System_error("CreateDirectory", prefix, GetLastError()); } } slash = path.find('/', slash + 1); } }
void mkdir_parent (const std::string& path) { std::string::size_type slash(path.find('/', 1)); while (slash != std::string::npos) { std::string prefix(path.substr(0, slash)); struct stat status; if (stat(prefix.c_str(), &status) == 0) { // already exists - make sure it's a directory if (!S_ISDIR(status.st_mode)) { throw System_error("mkdir_parent", prefix, ENOTDIR); } } else { if (errno != ENOENT) { throw System_error("mkdir_parent", prefix, errno); } // doesn't exist - mkdir it if (mkdir(prefix.c_str(), 0777) == -1) { throw System_error("mkdir", prefix, errno); } } slash = path.find('/', slash + 1); } }
size_t Coprocess::read_stdout (void* handle, void* buf, size_t count) { // Note that ReadFile on a pipe may return with bytes_read==0 if the other // end of the pipe writes zero bytes, so retry when this happens. // When the other end of the pipe actually closes, ReadFile // fails with ERROR_BROKEN_PIPE. DWORD bytes_read; do { if (!ReadFile(static_cast<Coprocess*>(handle)->stdout_pipe_reader, buf, count, &bytes_read, nullptr)) { const DWORD read_error = GetLastError(); if (read_error != ERROR_BROKEN_PIPE) { throw System_error("ReadFile", "", read_error); } return 0; } } while (bytes_read == 0); return bytes_read; }
void remove_file (const std::string& filename) { if (unlink(filename.c_str()) == -1 && errno != ENOENT) { throw System_error("unlink", filename, errno); } }
void touch_file (const std::string& filename) { if (utimes(filename.c_str(), NULL) == -1 && errno != ENOENT) { throw System_error("utimes", filename, errno); } }
int main (int argc, char** argv) try { init_signals(); // Initialize OpenSSL ERR_load_crypto_strings(); SSL_library_init(); SSL_load_error_strings(); // This cipher list is the "Intermediate compatibility" list from https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28default.29 as of 2014-12-09 vhost_defaults.ciphers = "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA"; vhost_defaults.dhgroup = make_dh(dh_group14_prime, dh_group14_generator); // 2048 bit group vhost_defaults.ecdhcurve = get_ecdhcurve("prime256v1"); // a.k.a. secp256r1 // Set default SSL options, which can be overridden by config file vhost_defaults.ssl_options[SSL_OP_NO_COMPRESSION] = true; vhost_defaults.ssl_options[SSL_OP_NO_SSLv3] = true; vhost_defaults.ssl_options[SSL_OP_NO_TLSv1] = false; vhost_defaults.ssl_options[SSL_OP_NO_TLSv1_1] = false; vhost_defaults.ssl_options[SSL_OP_NO_TLSv1_2] = false; vhost_defaults.ssl_options[SSL_OP_CIPHER_SERVER_PREFERENCE] = true; // These can't be overriden by config file: vhost_defaults.ssl_options[SSL_OP_SINGLE_DH_USE] = true; vhost_defaults.ssl_options[SSL_OP_SINGLE_ECDH_USE] = true; vhost_defaults.ssl_options[SSL_OP_NO_SSLv2] = true; // Command line arguments come in pairs of the form "--name value" and correspond // directly to the name/value option pairs in the config file (a la OpenVPN). for (int i = 1; i < argc; ) { if (std::strncmp(argv[i], "--", 2) == 0 && i + 1 < argc) { process_config_param(argv[i] + 2, argv[i+1]); i += 2; } else { std::clog << argv[0] << ": Bad arguments" << std::endl; return 2; } } if (vhost_configs.empty()) { // No vhosts specified, so add one implicitly that matches all local addresses / SNI names. // It will use the options from vhost_defaults. vhost_configs.emplace_back(); } for (size_t i = 0; i < vhost_configs.size(); ++i) { vhosts.emplace_back(); Vhost& vhost(vhosts.back()); Vhost_config& config(vhost_configs[i]); vhost.id = i; vhost.servername_set = config.servername_set; vhost.servername = config.servername; init_ssl_ctx(vhost, config); resolve_addresses(vhost, config); } // Free up some memory that's no longer needed: vhost_configs.clear(); vhost_defaults = Basic_vhost_config(); // Listen listening_sock = socket(AF_INET6, SOCK_STREAM, 0); if (listening_sock == -1) { throw System_error("socket", "", errno); } set_reuseaddr(listening_sock); set_not_v6only(listening_sock); if (transparent == TRANSPARENT_ON) { set_transparent(listening_sock); } // TODO: support binding to specific IP addresses struct sockaddr_in6 listening_address; std::memset(&listening_address, '\0', sizeof(listening_address)); listening_address.sin6_family = AF_INET6; listening_address.sin6_addr = in6addr_any; listening_address.sin6_port = htons(listening_port); if (bind(listening_sock, reinterpret_cast<const struct sockaddr*>(&listening_address), sizeof(listening_address)) == -1) { throw System_error("bind", "", errno); } if (listen(listening_sock, SOMAXCONN) == -1) { throw System_error("listen", "", errno); } // Set up UNIX domain socket for communicating with the key server. // Put it in a temporary directory with restrictive permissions so // other users can't traverse its path. We have to use a named // socket as opposed to a socketpair because we need every child process // to communicate with the key server using its own socket. (Duping one // end of a socketpair wouldn't work because then every child would // be referring to the same underlying socket, which provides // insufficient isolation.) temp_directory = make_temp_directory(); filedesc keyserver_sock(make_unix_socket(temp_directory + "/server.sock", &keyserver_sockaddr, &keyserver_sockaddr_len)); if (listen(keyserver_sock, SOMAXCONN) == -1) { throw System_error("listen", "", errno); } // Write PID file, daemonize, etc. std::ofstream pid_file_out; if (!pid_file.empty()) { // Open PID file before forking so we can report errors pid_file_out.open(pid_file.c_str(), std::ofstream::out | std::ofstream::trunc); if (!pid_file_out) { throw Configuration_error("Unable to open PID file " + pid_file + " for writing."); } pid_file_created = true; } if (run_as_daemon) { daemonize(); } if (pid_file_out) { pid_file_out << getpid() << '\n'; pid_file_out.close(); } // Spawn the master key server process keyserver_pid = spawn(keyserver_main, std::move(keyserver_sock)); // Spawn spare children to accept() and service connections if (pipe(children_pipe) == -1) { throw System_error("pipe", "", errno); } set_nonblocking(children_pipe[0], true); spawn_children(); // Wait for signals and readability on children_pipe sigset_t empty_sigset; sigemptyset(&empty_sigset); fd_set readfds; FD_ZERO(&readfds); FD_SET(children_pipe[0], &readfds); is_running = 1; struct timespec timeout = { 2, 0 }; int select_res = 0; while (is_running && ((select_res = pselect(children_pipe[0] + 1, &readfds, NULL, NULL, failed_children ? &timeout : NULL, &empty_sigset)) >= 0 || errno == EINTR)) { if (failed_children && std::time(NULL) >= last_failed_child_time + 2) { failed_children = 0; } if (pending_sigchld) { on_sigchld(); pending_sigchld = 0; } if (select_res > 0) { read_children_pipe(); } FD_SET(children_pipe[0], &readfds); } if (is_running && select_res == -1) { throw System_error("pselect", "", errno); } cleanup(); return 0; } catch (const System_error& error) { std::clog << "titus: System error: " << error.syscall; if (!error.target.empty()) { std::clog << ": " << error.target; } std::clog << ": " << std::strerror(error.number) << std::endl; cleanup(); return 3; } catch (const Openssl_error& error) { std::clog << "titus: OpenSSL error: " << error.message() << std::endl; cleanup(); return 4; } catch (const Configuration_error& error) { std::clog << "titus: Configuration error: " << error.message << std::endl; cleanup(); return 5; } catch (const Too_many_failed_children& error) { // TODO: better error reporting when this happens std::clog << "titus: Too many child processes failed." << std::endl; cleanup(); return 7; } catch (const Keyserver_died& error) { // TODO: better error reporting when this happens std::clog << "titus: Key server died." << std::endl; cleanup(); return 8; }
void remove_file (const std::string& filename) { if (!DeleteFileA(filename.c_str())) { throw System_error("DeleteFileA", filename, GetLastError()); } }