/* * Perform any requested security. Message replay and out of sequence * detection are given in addition to mutual authentication. * * @param to_net_sd. Socket to write to. * * @param from_net_sd. Socket to read from. * * Returns 0 on success, otherwise error. */ int dcc_gssapi_perform_requested_security(int to_net_sd, int from_net_sd) { int ret; OM_uint32 req_flags, ret_flags; req_flags = GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG; if ((ret = dcc_gssapi_establish_secure_context(to_net_sd, from_net_sd, req_flags, &ret_flags)) != 0) { return ret; } if ((ret = dcc_gssapi_compare_flags(req_flags, ret_flags)) != 0) { dcc_gssapi_delete_ctx(&distcc_ctx_handle); return ret; } if ((ret = dcc_gssapi_recv_notification(from_net_sd)) != 0) { dcc_gssapi_delete_ctx(&distcc_ctx_handle); return ret; } rs_log_info("Authentication complete - happy compiling!"); return 0; }
/* * Edit the ELF file residing at @p path, changing all occurrences of * the path @p server_path to @p client_path in the debugging info. * * We're a bit sloppy about that; rather than properly parsing * the DWARF debug info, finding the DW_AT_comp_dir (compilation working * directory) field and the DW_AT_name (source file name) field, * we just do a search-and-replace in the ".debug_info" and ".debug_str" * sections. But this is good enough. * * Returns 0 on success (whether or not the ".debug_info" and ".debug_str" * sections were found or updated). * Returns 1 on serious error that should cause distcc to fail. */ int dcc_fix_debug_info(const char *path, const char *client_path, const char *server_path) { #ifndef HAVE_ELF_H rs_trace("no <elf.h>, so can't change %s to %s in debug info for %s", server_path, client_path, path); return 0; #else /* * We can only safely replace a string with another of exactly * the same length. (Replacing a string with a shorter string * results in errors from gdb.) * So we append trailing slashes on the client side path. */ size_t client_path_len = strlen(client_path); size_t server_path_len = strlen(server_path); assert(client_path_len <= server_path_len); char *client_path_plus_slashes = malloc(server_path_len + 1); if (!client_path_plus_slashes) { rs_log_crit("failed to allocate memory"); return 1; } strcpy(client_path_plus_slashes, client_path); while (client_path_len < server_path_len) { client_path_plus_slashes[client_path_len++] = '/'; } client_path_plus_slashes[client_path_len] = '\0'; rs_log_info("client_path_plus_slashes = %s", client_path_plus_slashes); return update_debug_info(path, server_path, client_path_plus_slashes); #endif }
/* * Update the ELF file residing at @p path, replacing all occurrences * of @p search with @p replace in the section named @p desired_section_name. * The replacement string must be the same length or shorter than * the search string. */ static void update_section(const char *path, const void *base, off_t size, const char *desired_section_name, const char *search, const char *replace) { const void *desired_section = NULL; int desired_section_size = 0; if (FindElfSection(base, size, desired_section_name, &desired_section, &desired_section_size) && desired_section_size > 0) { /* The local variable below works around a bug in some versions * of gcc (4.2.1?), which issues an erroneous warning if * 'desired_section_rw' is replaced with '(void *) desired_section' * in the call below, causing compile errors with -Werror. */ void *desired_section_rw = (void *) desired_section; int count = replace_string(desired_section_rw, desired_section_size, search, replace); if (count == 0) { rs_trace("\"%s\" section of file %s has no occurrences of \"%s\"", desired_section_name, path, search); } else { rs_log_info("updated \"%s\" section of file \"%s\": " "replaced %d occurrences of \"%s\" with \"%s\"", desired_section_name, path, count, search, replace); if (count > 1) { rs_log_warning("only expected to replace one occurrence!"); } } } else { rs_trace("file %s has no \"%s\" section", path, desired_section_name); } }
/** * Fork a child to repeatedly accept and handle incoming connections. * * To protect against leaks, we quit after 50 requests and let the parent * recreate us. **/ static int dcc_preforked_child(int listen_fd) { int ireq; const int child_lifetime = 50; for (ireq = 0; ireq < child_lifetime; ireq++) { int acc_fd; struct dcc_sockaddr_storage cli_addr; socklen_t cli_len; cli_len = sizeof cli_addr; do { acc_fd = accept(listen_fd, (struct sockaddr *) &cli_addr, &cli_len); } while (acc_fd == -1 && errno == EINTR); if (acc_fd == -1) { rs_log_error("accept failed: %s", strerror(errno)); dcc_exit(EXIT_CONNECT_FAILED); } dcc_service_job(acc_fd, acc_fd, (struct sockaddr *) &cli_addr, cli_len); dcc_close(acc_fd); } rs_log_info("worn out"); return 0; }
/** * Main loop for no-fork mode. * * Much slower and may leak. Should only be used when you want to run gdb on * distccd. **/ static void dcc_nofork_parent(int listen_fd) { while (1) { int acc_fd; struct dcc_sockaddr_storage cli_addr; socklen_t cli_len; rs_log_info("waiting to accept connection"); cli_len = sizeof cli_addr; acc_fd = accept(listen_fd, (struct sockaddr *) &cli_addr, &cli_len); if (acc_fd == -1 && errno == EINTR) { ; } else if (acc_fd == -1) { rs_log_error("accept failed: %s", strerror(errno)); #ifdef HAVE_GSSAPI if (dcc_auth_enabled) { dcc_gssapi_release_credentials(); if (opt_blacklist_enabled || opt_whitelist_enabled) { dcc_gssapi_free_list(); } } #endif dcc_exit(EXIT_CONNECT_FAILED); } else { dcc_service_job(acc_fd, acc_fd, (struct sockaddr *) &cli_addr, cli_len); dcc_close(acc_fd); } } }
/* * Attempt handshake exchange with the server to indicate client's * desire to authentciate. * * @param to_net_sd. Socket to write to. * * @param from_net_sd. Socket to read from. * * Returns 0 on success, otherwise error. */ static int dcc_gssapi_send_handshake(int to_net_sd, int from_net_sd) { char auth = HANDSHAKE; fd_set sockets; int ret; struct timeval timeout; rs_log_info("Sending handshake."); if ((ret = dcc_writex(to_net_sd, &auth, sizeof(auth))) != 0) { return ret; } rs_log_info("Sent %c.", auth); FD_ZERO(&sockets); FD_SET(from_net_sd, &sockets); timeout.tv_sec = 1; timeout.tv_usec = 0; ret = select(from_net_sd + 1, &sockets, NULL, NULL, &timeout); if (ret < 0) { rs_log_error("select failed: %s.", strerror(errno)); return EXIT_IO_ERROR; } if (ret == 0) { rs_log_error("Timeout - does this server require authentication?"); return EXIT_TIMEOUT; } rs_log_info("Receiving handshake."); if ((ret = dcc_readx(from_net_sd, &auth, sizeof(auth))) != 0) { return ret; } rs_log_info("Received %c.", auth); if (auth != HANDSHAKE) { rs_log_crit("No server handshake."); return EXIT_GSSAPI_FAILED; } return 0; }
static int dcc_should_be_inetd(void) { /* Work out if we ought to serve stdin or be a standalone daemon */ if (opt_inetd_mode) return 1; else if (opt_daemon_mode) return 0; else if (is_a_socket(STDIN_FILENO)) { rs_log_info("stdin is socket; assuming --inetd mode"); return 1; } else if (isatty(STDIN_FILENO)) { rs_log_info("stdin is a tty; assuming --daemon mode"); return 0; } else { rs_log_info("stdin is neither a tty nor a socket; assuming --daemon mode"); return 0; } }
int dcc_log_daemon_started(const char *role) { rs_log_info("%s started (%s %s, built %s %s)", role, PACKAGE_VERSION, GNU_HOST, __DATE__, __TIME__); return 0; }
/* * Transmit the body of a file using sendfile(). * * Linux at the moment requires the input be page-based -- ie a disk file, and * only on particular filesystems. If the sendfile() call fails in a way that * makes us think that regular IO might work, then we try that instead. For * example, the /tmp filesystem may not support sendfile(). */ int dcc_pump_sendfile(int ofd, int ifd, size_t size) { ssize_t sent; off_t offset = 0; int ret; while (size) { /* Handle possibility of partial transmission, e.g. if * sendfile() is interrupted by a signal. size is decremented * as we go. */ sent = sys_sendfile(ofd, ifd, &offset, size); if (sent == -1) { if ((errno == ENOSYS || errno == EINVAL) && offset == 0) { /* The offset==0 tests is because we may be part way through * the file. We can't just naively go back to read/write * because sendfile() does not update the file pointer: we * would need to lseek() first. That case is not handled at * the moment because it's unlikely that sendfile() would * suddenly be unsupported while we're using it. A failure * halfway through probably indicates a genuine error.*/ rs_log_info("decided to use read/write rather than sendfile"); return dcc_pump_readwrite(ofd, ifd, size); } else if (errno == EAGAIN) { /* Sleep until we're able to write out more data. */ if ((ret = dcc_select_for_write(ofd, dcc_io_timeout)) != 0) return ret; rs_trace("select() returned, continuing to write"); } else if (errno == EINTR) { rs_trace("sendfile() interrupted, continuing"); } else { rs_log_error("sendfile failed: %s", strerror(errno)); return EXIT_IO_ERROR; } } else if (sent == 0) { rs_log_error("sendfile returned 0? can't cope"); return EXIT_IO_ERROR; } else if (sent != (ssize_t) size) { /* offset is automatically updated by sendfile. */ size -= sent; rs_log_notice("sendfile: partial transmission of %ld bytes; retrying %ld @%ld", (long) sent, (long) size, (long) offset); } else { /* normal case, everything was sent. */ break; } } return 0; }
static void rs_free_accept_thread(void *data) { rs_master_info_t *mi; mi = (rs_master_info_t *) data; /* NOTICE : if reload signal, must skip send SIGQUIT */ if(mi != NULL) { rs_log_info("free accept thread"); // kill(rs_pid, SIGQUIT); rs_close(mi->svr_fd); mi->svr_fd = -1; } }
static void dcc_log_child_exited(pid_t kid, int status) { if (WIFSIGNALED(status)) { int sig = WTERMSIG(status); int severity = sig == SIGTERM ? RS_LOG_INFO : RS_LOG_ERR; rs_log(severity, "child %d: signal %d (%s)", (int) kid, sig, WCOREDUMP(status) ? "core dumped" : "no core"); } else if (WIFEXITED(status)) { rs_log_info("child %d exited: exit status %d", (int) kid, WEXITSTATUS(status)); } }
/* * Receive notification from an authenticating server as to * whether the client has successfully gained access or not. * * @param sd. Socket to read from. * * Returns 0 on success, otherwise error. */ static int dcc_gssapi_recv_notification(int sd) { char notification; fd_set sockets; int ret; struct timeval timeout; FD_ZERO(&sockets); FD_SET(sd, &sockets); timeout.tv_sec = 1; timeout.tv_usec = 0; ret = select(sd + 1, &sockets, NULL, NULL, &timeout); if (ret < 0) { rs_log_error("select failed: %s.", strerror(errno)); return EXIT_IO_ERROR; } if (ret == 0) { rs_log_error("Timeout - error receiving notification."); return EXIT_TIMEOUT; } if ((ret = dcc_readx(sd, ¬ification, sizeof(notification))) != 0) { rs_log_crit("Failed to receive notification."); return ret; } if (notification != ACCESS) { rs_log_crit("Access denied by server."); rs_log_info("Your principal may be blacklisted or may not be whitelisted."); return EXIT_ACCESS_DENIED; } rs_log_info("Access granted by server."); return 0; }
/* Write host data to host file */ static int write_hosts(struct daemon_data *d) { struct host *h; int r = 0; assert(d); rs_log_info("writing zeroconf data.\n"); if (generic_lock(d->fd, 1, 1, 1) < 0) { rs_log_crit("lock failed: %s\n", strerror(errno)); return -1; } if (lseek(d->fd, 0, SEEK_SET) < 0) { rs_log_crit("lseek() failed: %s\n", strerror(errno)); return -1; } if (ftruncate(d->fd, 0) < 0) { rs_log_crit("ftruncate() failed: %s\n", strerror(errno)); return -1; } remove_duplicate_services(d); for (h = d->hosts; h; h = h->next) { char t[256], a[AVAHI_ADDRESS_STR_MAX]; if (h->resolver) /* Not yet fully resolved */ continue; if (h->address.proto == AVAHI_PROTO_INET6) snprintf(t, sizeof(t), "[%s]:%u/%i\n", avahi_address_snprint(a, sizeof(a), &h->address), h->port, d->n_slots * h->n_cpus); else snprintf(t, sizeof(t), "%s:%u/%i\n", avahi_address_snprint(a, sizeof(a), &h->address), h->port, d->n_slots * h->n_cpus); if (dcc_writex(d->fd, t, strlen(t)) != 0) { rs_log_crit("write() failed: %s\n", strerror(errno)); goto finish; } } r = 0; finish: generic_lock(d->fd, 1, 0, 1); return r; };
static int dcc_setup_daemon_path(void) { int ret; const char *path; if ((path = getenv("DISTCCD_PATH")) != NULL) { if ((ret = dcc_set_path(path))) return ret; return 0; } else { path = getenv("PATH"); rs_log_info("daemon's PATH is %s", path ? path : "(NULL)"); return 0; } }
static void dcc_xci_zeroconf_reply(const DNSServiceRef zeroconf, const DNSServiceFlags flags, const DNSServiceErrorType error, const char *name, const char *reg_type, const char *domain, void *user_data) { context *ctx = user_data; /* Avoid unused parameter warnings. */ (void)zeroconf; (void)flags; if (error != kDNSServiceErr_NoError) { rs_log_error("received error %d", error); ctx->thread_live = 0; pthread_exit(NULL); } else { rs_log_info("registered as \"%s.%s%s\"", name, reg_type, domain); } }
/* Called when publishing of service data completes */ static void publish_reply(AvahiEntryGroup *UNUSED(g), AvahiEntryGroupState state, void *userdata) { struct context *ctx = userdata; switch (state) { case AVAHI_ENTRY_GROUP_COLLISION: { char *n; /* Pick a new name for our service */ n = avahi_alternative_service_name(ctx->name); assert(n); avahi_free(ctx->name); ctx->name = n; register_stuff(ctx); break; } case AVAHI_ENTRY_GROUP_FAILURE: rs_log_crit("Failed to register service: %s", avahi_strerror(avahi_client_errno(ctx->client))); avahi_threaded_poll_quit(ctx->threaded_poll); break; case AVAHI_ENTRY_GROUP_ESTABLISHED: rs_log_info("registered as \"%s.%s.%s\"", avahi_client_get_host_name(ctx->client), ctx->service_type, avahi_client_get_domain_name(ctx->client)); break; case AVAHI_ENTRY_GROUP_UNCOMMITED: case AVAHI_ENTRY_GROUP_REGISTERING: ; } }
/* Given a string @p s, this function returns a new string with all * occurrences of @p find replaced with the contents of @p replace. * If any arguments are missing, or any error occurs, returns NULL. */ char *dcc_replace_substring(const char *s, const char *find, const char *replace) { int s_left; int buf_pos = 0, buf_size = 0; char *buf = NULL, *new_buf; const char *next; int replace_len, find_len; int len_change; if (!s || !find || !replace) { rs_log_error("got NULL arguments"); goto out_error; } find_len = strlen(find); replace_len = strlen(replace); if (!find_len) { rs_log_error("Asked to replace an empty string"); goto out_error; } /* This is the number of chars we'll need to add each time we do * a replacement. If replace is shorter then find, we'll catch it * with a final realloc when done. */ len_change = replace_len - find_len; if (len_change < 0) len_change = 0; s_left = strlen(s); buf_size = s_left + 1; buf = malloc(buf_size * sizeof(char)); if (!buf) { rs_log_error("malloc(%ld) failed: %s", (long)buf_size * sizeof(char), strerror(errno)); goto out_error; } /* Loop on matches */ while ((next = strstr(s, find))) { if (len_change) { buf_size += len_change; new_buf = realloc(buf, buf_size * sizeof(char)); if (!new_buf) { rs_log_error("realloc(%ld) failed: %s", (long)buf_size * sizeof(char), strerror(errno)); goto out_error; } buf = new_buf; } strncpy(buf + buf_pos, s, next - s); buf_pos += (next - s); s_left -= (next - s); strcpy(buf + buf_pos, replace); buf_pos += replace_len; s_left -= find_len; s = next + find_len; } /* Copy over what was left after the last replacement. */ if (s_left) { strcpy(buf + buf_pos, s); buf_pos += s_left; } /* Terminate it. */ buf[buf_pos++] = '\0'; /* If we shrunk the string, do a final realloc to downsize it * to be the right length. But, we don't fail if this doesn't * work because the string will still be ok, just using more * memory then needed. */ if (buf_pos < buf_size) { buf_size = buf_pos; new_buf = realloc(buf, buf_size * sizeof(char)); if (new_buf) { buf = new_buf; } else { rs_log_info("realloc(%ld) failed: %s", (long)buf_size * sizeof(char), strerror(errno)); } } return buf; out_error: if (buf) free(buf); return NULL; }
/** * Blocking wait for a child to exit. This is used when waiting for * cpp, gcc, etc. * * This is not used by the daemon-parent; it has its own * implementation in dcc_reap_kids(). They could be unified, but the * parent only waits when it thinks a child has exited; the child * waits all the time. **/ int dcc_collect_child(const char *what, pid_t pid, int *wait_status, int in_fd) { struct rusage ru; pid_t ret_pid; int ret; int wait_timeout_sec; fd_set fds,readfds; wait_timeout_sec = dcc_job_lifetime; FD_ZERO(&readfds); if (in_fd != timeout_null_fd){ FD_SET(in_fd,&readfds); } while (!dcc_job_lifetime || wait_timeout_sec-- >= 0) { /* If we're called with a socket, break out of the loop if the socket * disconnects. To do that, we need to block in select, not in * sys_wait4. (Only waitpid uses WNOHANG to mean don't block ever, * so I've modified sys_wait4 above to preferentially call waitpid.) */ int flags = (in_fd == timeout_null_fd) ? 0 : WNOHANG; ret_pid = sys_wait4(pid, wait_status, flags, &ru); if (ret_pid == -1) { if (errno == EINTR) { rs_trace("wait4 was interrupted; retrying"); } else { rs_log_error("sys_wait4(pid=%d) borked: %s", (int) pid, strerror(errno)); return EXIT_DISTCC_FAILED; } } else if (ret_pid != 0) { /* This is not the main user-visible message; that comes from * critique_status(). */ rs_trace("%s child %ld terminated with status %#x", what, (long) ret_pid, *wait_status); rs_log_info("%s times: user %lld.%06lds, system %lld.%06lds, " "%ld minflt, %ld majflt", what, ru.ru_utime.tv_sec, (long) ru.ru_utime.tv_usec, ru.ru_stime.tv_sec, (long) ru.ru_stime.tv_usec, ru.ru_minflt, ru.ru_majflt); return 0; } /* check timeout */ if (in_fd != timeout_null_fd) { struct timeval timeout; /* If client disconnects, the socket will become readable, * and a read should return -1 and set errno to EPIPE. */ fds = readfds; timeout.tv_sec = 1; timeout.tv_usec = 0; ret = select(in_fd+1,&fds,NULL,NULL,&timeout); if (ret == 1) { char buf; int nread = read(in_fd, &buf, 1); if ((nread == -1) && (errno == EWOULDBLOCK)) { /* spurious wakeup, ignore */ ; } else if (nread == 0) { rs_log_error("Client fd disconnected, killing job"); /* If killpg fails, it might means the child process is not * in a new group, so, just kill the child process */ if (killpg(pid,SIGTERM)!=0) kill(pid, SIGTERM); return EXIT_IO_ERROR; } else if (nread == 1) { rs_log_error("Bug! Read from fd succeeded when checking " "whether client disconnected!"); } else { rs_log_error("Bug! nread %d, errno %d checking whether " "client disconnected!", nread, errno); } } } else { poll(NULL, 0, 1000); } } /* If timeout, also kill the child process */ if (killpg(pid, SIGTERM) != 0) kill(pid, SIGTERM); rs_log_error("Compilation takes too long, timeout."); return EXIT_TIMEOUT; }
/** * Be a standalone server, with responsibility for sockets and forking * children. Puts the daemon in the background and detaches from the * controlling tty. **/ int dcc_standalone_server(void) { int listen_fd; int n_cpus; int ret; #ifdef HAVE_AVAHI void *avahi = NULL; #endif if ((ret = dcc_socket_listen(arg_port, &listen_fd, opt_listen_addr)) != 0) return ret; dcc_defer_accept(listen_fd); set_cloexec_flag(listen_fd, 1); if (dcc_ncpus(&n_cpus) == 0) rs_log_info("%d CPU%s online on this server", n_cpus, n_cpus == 1 ? "" : "s"); /* By default, allow one job per CPU, plus two for the pot. The extra * ones are started to allow for a bit of extra concurrency so that the * machine is not idle waiting for disk or network IO. */ if (arg_max_jobs) dcc_max_kids = arg_max_jobs; else dcc_max_kids = 2 + n_cpus; rs_log_info("allowing up to %d active jobs", dcc_max_kids); if (!opt_no_detach) { /* Don't go into the background until we're listening and * ready. This is useful for testing -- when the daemon * detaches, we know we can go ahead and try to connect. */ dcc_detach(); } else { /* Still create a new process group, even if not detached */ rs_trace("not detaching"); if ((ret = dcc_new_pgrp()) != 0) return ret; dcc_save_pid(getpid()); } /* Don't catch signals until we've detached or created a process group. */ dcc_daemon_catch_signals(); #ifdef HAVE_AVAHI /* Zeroconf registration */ if (opt_zeroconf) { if (!(avahi = dcc_zeroconf_register((uint16_t) arg_port, n_cpus, dcc_max_kids))) return EXIT_CONNECT_FAILED; } #endif /* This is called in the master daemon, whether that is detached or * not. */ dcc_master_pid = getpid(); if (opt_no_fork) { dcc_log_daemon_started("non-forking daemon"); dcc_nofork_parent(listen_fd); ret = 0; } else { dcc_log_daemon_started("preforking daemon"); ret = dcc_preforking_parent(listen_fd); } #ifdef HAVE_AVAHI /* Remove zeroconf registration */ if (opt_zeroconf) { if (dcc_zeroconf_unregister(avahi) != 0) return EXIT_CONNECT_FAILED; } #endif return ret; }
/** * Parse arguments, extract ones we care about, and also work out * whether it will be possible to distribute this invocation remotely. * * This is a little hard because the cc argument rules are pretty complex, but * the function still ought to be simpler than it already is. * * This code is called on both the client and the server, though they use the * results differently. * * This function makes a copy of the arguments, modified to ensure that * the arguments include '-o <filename>'. This is returned in *ret_newargv. * The copy is dynamically allocated and the caller is responsible for * deallocating it. * * If @p forced_cpp_ext is non NULL, it is filled it with the extension that * is forced by a -x language directive. The caller should not free this * value. * * @returns 0 if it's ok to distribute this compilation, or an error code. **/ int dcc_scan_args(char *argv[], char **input_file, char **output_file, char ***ret_newargv, const char **forced_cpp_ext) { int seen_opt_c = 0, seen_opt_s = 0; int i; char *a, *optx_lang; const char *optx_ext = NULL; int ret; /* allow for -o foo.o */ if ((ret = dcc_copy_argv(argv, ret_newargv, 2)) != 0) return ret; argv = *ret_newargv; /* FIXME: new copy of argv is leaked */ dcc_trace_argv("scanning arguments", argv); #ifdef XCODE_INTEGRATION /* Xcode invokes the distcc client as "distcc --host-info HOST" to gather * info about HOST. When the request is transmitted to the distccd server, * it will see only "--host-info" and no other arguments in argv. */ if (argv[0] && !strcmp(argv[0], "--host-info")) { return 0; } #endif /* XCODE_INTEGRATION */ /* Things like "distcc -c hello.c" with an implied compiler are * handled earlier on by inserting a compiler name. At this * point, argv[0] should always be a compiler name. */ if (argv[0] && argv[0][0] == '-') { rs_log_error("unrecognized distcc option: %s", argv[0]); exit(EXIT_BAD_ARGUMENTS); } *input_file = *output_file = NULL; for (i = 0; (a = argv[i]); i++) { if (a[0] == '-') { if (!strcmp(a, "-E")) { rs_trace("-E call for cpp must be local"); return EXIT_DISTCC_FAILED; } else if (!strcmp(a, "-MD") || !strcmp(a, "-MMD")) { /* These two generate dependencies as a side effect. They * should work with the way we call cpp. */ } else if (!strcmp(a, "-MG") || !strcmp(a, "-MP")) { /* These just modify the behaviour of other -M* options and do * nothing by themselves. */ } else if (!strcmp(a, "-MF") || !strcmp(a, "-MT") || !strcmp(a, "-MQ")) { /* As above but with extra argument. */ i++; } else if (!strncmp(a, "-MF", 3) || !strncmp(a, "-MT", 3) || !strncmp(a, "-MQ", 3)) { /* As above, without extra argument. */ } else if (a[1] == 'M') { /* -M(anything else) causes the preprocessor to produce a list of make-style dependencies on header files, either to stdout or to a local file. It implies -E, so only the preprocessor is run, not the compiler. There would be no point trying to distribute it even if we could. */ rs_trace("%s implies -E (maybe) and must be local", a); return EXIT_DISTCC_FAILED; } else if (!strcmp(a, "-march=native")) { rs_trace("-march=native generates code for local machine; " "must be local"); return EXIT_DISTCC_FAILED; } else if (!strcmp(a, "-mtune=native")) { rs_trace("-mtune=native optimizes for local machine; " "must be local"); return EXIT_DISTCC_FAILED; } else if (str_startswith("-Wa,", a)) { /* Look for assembler options that would produce output * files and must be local. * * Writing listings to stdout could be supported but it might * be hard to parse reliably. */ if (strstr(a, ",-a") || strstr(a, "--MD")) { rs_trace("%s must be local", a); return EXIT_DISTCC_FAILED; } } else if (str_startswith("-specs=", a)) { rs_trace("%s must be local", a); return EXIT_DISTCC_FAILED; } else if (!strcmp(a, "-S")) { seen_opt_s = 1; } else if (!strcmp(a, "-fprofile-arcs") || !strcmp(a, "-ftest-coverage")) { rs_log_info("compiler will emit profile info; must be local"); return EXIT_DISTCC_FAILED; } else if (!strcmp(a, "-frepo")) { rs_log_info("compiler will emit .rpo files; must be local"); return EXIT_DISTCC_FAILED; } else if (!strcmp("-x", a)) { optx_lang = argv[++i]; if (!optx_lang || !strlen(optx_lang)) { rs_log_info("-x requires an argument; running locally"); return EXIT_DISTCC_FAILED; } if (*input_file) { rs_log_info("-x must precede source file; running locally"); return EXIT_DISTCC_FAILED; } if (optx_ext) { rs_log_info("at most one -x supported; running locally"); return EXIT_DISTCC_FAILED; } optx_ext = dcc_optx_ext_lookup(optx_lang); if (!optx_ext) { rs_log_info("unsupported -x language; running locally"); return EXIT_DISTCC_FAILED; } } else if (str_startswith("-x", a)) { /* Handling -xlanguage is possible, but it makes some of the * command rewriting (over in remote.c) much harder, so it * isn't supported at this time. */ rs_log_info("-xlanguage unsupported, use -x language instead; " "running locally"); return EXIT_DISTCC_FAILED; } else if (str_startswith("-dr", a)) { rs_log_info("gcc's debug option %s may write extra files; " "running locally", a); return EXIT_DISTCC_FAILED; } else if (!strcmp(a, "-c")) { seen_opt_c = 1; } else if (!strcmp(a, "-o")) { /* Whatever follows must be the output */ a = argv[++i]; goto GOT_OUTPUT; } else if (str_startswith("-o", a)) { a += 2; /* skip "-o" */ goto GOT_OUTPUT; } } else { if (dcc_is_source(a)) { rs_trace("found input file \"%s\"", a); if (*input_file) { rs_log_info("do we have two inputs? i give up"); return EXIT_DISTCC_FAILED; } *input_file = a; } else if (str_endswith(".o", a)) { GOT_OUTPUT: rs_trace("found object/output file \"%s\"", a); if (*output_file) { rs_log_info("called for link? i give up"); return EXIT_DISTCC_FAILED; } *output_file = a; } } } /* TODO: ccache has the heuristic of ignoring arguments that are not * extant files when looking for the input file; that's possibly * worthwile. Of course we can't do that on the server. */ if (!seen_opt_c && !seen_opt_s) { rs_log_info("compiler apparently called not for compile"); return EXIT_DISTCC_FAILED; } if (!*input_file) { rs_log_info("no visible input file"); return EXIT_DISTCC_FAILED; } if (dcc_source_needs_local(*input_file)) return EXIT_DISTCC_FAILED; if (!*output_file) { /* This is a commandline like "gcc -c hello.c". They want * hello.o, but they don't say so. For example, the Ethereal * makefile does this. * * Note: this doesn't handle a.out, the other implied * filename, but that doesn't matter because it would already * be excluded by not having -c or -S. */ char *ofile; /* -S takes precedence over -c, because it means "stop after * preprocessing" rather than "stop after compilation." */ if (seen_opt_s) { if (dcc_output_from_source(*input_file, ".s", &ofile)) return EXIT_DISTCC_FAILED; } else if (seen_opt_c) { if (dcc_output_from_source(*input_file, ".o", &ofile)) return EXIT_DISTCC_FAILED; } else { rs_log_crit("this can't be happening(%d)!", __LINE__); return EXIT_DISTCC_FAILED; } rs_log_info("no visible output file, going to add \"-o %s\" at end", ofile); dcc_argv_append(argv, strdup("-o")); dcc_argv_append(argv, ofile); *output_file = ofile; } dcc_note_compiled(*input_file, *output_file); if (strcmp(*output_file, "-") == 0) { /* Different compilers may treat "-o -" as either "write to * stdout", or "write to a file called '-'". We can't know, * so we just always run it locally. Hopefully this is a * pretty rare case. */ rs_log_info("output to stdout? running locally"); return EXIT_DISTCC_FAILED; } if (forced_cpp_ext) *forced_cpp_ext = optx_ext; return 0; }
/* Get the host list from zeroconf */ int dcc_zeroconf_add_hosts(struct dcc_hostdef **ret_list, int *ret_nhosts, int n_slots, struct dcc_hostdef **ret_prev) { char host_file[PATH_MAX], lock_file[PATH_MAX], *s = NULL; int lock_fd = -1, host_fd = -1; int fork_daemon = 0; int r = -1; char *dir; struct stat st; if (get_zeroconf_dir(&dir) != 0) { rs_log_crit("failed to get zeroconf dir.\n"); goto finish; } snprintf(lock_file, sizeof(lock_file), "%s/lock", dir); snprintf(host_file, sizeof(host_file), "%s/hosts", dir); /* Open lock file */ if ((lock_fd = open(lock_file, O_RDWR|O_CREAT, 0666)) < 0) { rs_log_crit("open('%s') failed: %s\n", lock_file, strerror(errno)); goto finish; } /* Try to lock the lock file */ if (generic_lock(lock_fd, 1, 1, 0) >= 0) { /* The lock succeeded => there's no daemon running yet! */ fork_daemon = 1; generic_lock(lock_fd, 1, 0, 0); } close(lock_fd); /* Shall we fork a new daemon? */ if (fork_daemon) { pid_t pid; rs_log_info("Spawning zeroconf daemon.\n"); if ((pid = fork()) == -1) { rs_log_crit("fork() failed: %s\n", strerror(errno)); goto finish; } else if (pid == 0) { int fd; /* Child */ /* Close file descriptors and replace them by /dev/null */ close(0); close(1); close(2); fd = open("/dev/null", O_RDWR); assert(fd == 0); fd = dup(0); assert(fd == 1); fd = dup(0); assert(fd == 2); #ifdef HAVE_SETSID setsid(); #endif chdir("/"); rs_add_logger(rs_logger_syslog, RS_LOG_DEBUG, NULL, 0); _exit(daemon_proc(host_file, lock_file, n_slots)); } /* Parent */ /* Wait some time for initial host gathering */ usleep(1000000); /* 1000 ms */ } /* Open host list read-only */ if ((host_fd = open(host_file, O_RDONLY)) < 0) { rs_log_crit("open('%s') failed: %s\n", host_file, strerror(errno)); goto finish; } /* A read lock */ if (generic_lock(host_fd, 0, 1, 1) < 0) { rs_log_crit("lock failed: %s\n", strerror(errno)); goto finish; } /* Get file size */ if (fstat(host_fd, &st) < 0) { rs_log_crit("stat() failed: %s\n", strerror(errno)); goto finish; } if (st.st_size >= MAX_FILE_SIZE) { rs_log_crit("file too large.\n"); goto finish; } /* read file data */ s = malloc((size_t) st.st_size+1); assert(s); if (dcc_readx(host_fd, s, (size_t) st.st_size) != 0) { rs_log_crit("failed to read from file.\n"); goto finish; } s[st.st_size] = 0; /* Parse host data */ if (dcc_parse_hosts(s, host_file, ret_list, ret_nhosts, ret_prev) != 0) { rs_log_crit("failed to parse host file.\n"); goto finish; } r = 0; finish: if (host_fd >= 0) { generic_lock(host_fd, 0, 0, 1); close(host_fd); } free(s); return r; }
/* The main function of the background daemon */ static int daemon_proc(const char *host_file, const char *lock_file, int n_slots) { int ret = 1; int lock_fd = -1; struct daemon_data d; time_t clip_time; int error; char machine[64], version[64], stype[128]; rs_add_logger(rs_logger_syslog, RS_LOG_DEBUG, NULL, 0); /* Prepare daemon data structure */ d.fd = -1; d.hosts = NULL; d.n_slots = n_slots; d.simple_poll = NULL; d.browser = NULL; d.client = NULL; clip_time = time(NULL); rs_log_info("Zeroconf daemon running.\n"); /* Open daemon lock file and lock it */ if ((lock_fd = open(lock_file, O_RDWR|O_CREAT, 0666)) < 0) { rs_log_crit("open('%s') failed: %s\n", lock_file, strerror(errno)); goto finish; } if (generic_lock(lock_fd, 1, 1, 0) < 0) { /* lock failed, there's probably already another daemon running */ goto finish; } /* Open host file */ if ((d.fd = open(host_file, O_RDWR|O_CREAT, 0666)) < 0) { rs_log_crit("open('%s') failed: %s\n", host_file, strerror(errno)); goto finish; } /* Clear host file */ write_hosts(&d); if (!(d.simple_poll = avahi_simple_poll_new())) { rs_log_crit("Failed to create simple poll object.\n"); goto finish; } if (!(d.client = avahi_client_new( avahi_simple_poll_get(d.simple_poll), 0, client_callback, &d, &error))) { rs_log_crit("Failed to create Avahi client object: %s\n", avahi_strerror(error)); goto finish; } if (dcc_get_gcc_version(version, sizeof(version)) && dcc_get_gcc_machine(machine, sizeof(machine))) { dcc_make_dnssd_subtype(stype, sizeof(stype), version, machine); } else { rs_log_warning("Warning, failed to get CC version and machine type.\n"); strncpy(stype, DCC_DNS_SERVICE_TYPE, sizeof(stype)); stype[sizeof(stype)-1] = 0; } rs_log_info("Browsing for '%s'.\n", stype); if (!(d.browser = avahi_service_browser_new( d.client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, stype, NULL, 0, browse_reply, &d))) { rs_log_crit("Failed to create service browser object: %s\n", avahi_strerror(avahi_client_errno(d.client))); goto finish; } /* Check whether the host file has been used recently */ while (fd_last_used(d.fd, clip_time) <= MAX_IDLE_TIME) { /* Iterate the main loop for 5s */ if (avahi_simple_poll_iterate(d.simple_poll, 5000) != 0) { rs_log_crit("Event loop exited abnormaly.\n"); goto finish; } } /* Wer are idle */ rs_log_info("Zeroconf daemon unused.\n"); ret = 0; finish: /* Cleanup */ if (lock_fd >= 0) { generic_lock(lock_fd, 1, 0, 0); close(lock_fd); } if (d.fd >= 0) close(d.fd); while (d.hosts) { struct host *h = d.hosts; d.hosts = d.hosts->next; free_host(h); } if (d.client) avahi_client_free(d.client); if (d.simple_poll) avahi_simple_poll_free(d.simple_poll); rs_log_info("zeroconf daemon ended.\n"); return ret; }
/* Called whenever a new service is found or removed */ static void browse_reply( AvahiServiceBrowser *UNUSED(b), AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event, const char *name, const char *type, const char *domain, AvahiLookupResultFlags UNUSED(flags), void *userdata) { struct daemon_data *d = userdata; assert(d); switch (event) { case AVAHI_BROWSER_NEW: { struct host *h; h = malloc(sizeof(struct host)); assert(h); rs_log_info("new service: %s\n", name); if (!(h->resolver = avahi_service_resolver_new(d->client, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, resolve_reply, h))) { rs_log_warning("Failed to create service resolver for '%s': %s\n", name, avahi_strerror(avahi_client_errno(d->client))); free(h); } else { /* Fill in missing data */ h->service = strdup(name); assert(h->service); h->domain = strdup(domain); assert(h->domain); h->daemon_data = d; h->interface = interface; h->protocol = protocol; h->next = d->hosts; h->n_cpus = 1; d->hosts = h; } break; } case AVAHI_BROWSER_REMOVE: rs_log_info("Removed service: %s\n", name); remove_service(d, interface, protocol, name, domain); write_hosts(d); break; case AVAHI_BROWSER_FAILURE: rs_log_crit("Service Browser failure '%s': %s\n", name, avahi_strerror(avahi_client_errno(d->client))); avahi_simple_poll_quit(d->simple_poll); break; case AVAHI_BROWSER_CACHE_EXHAUSTED: case AVAHI_BROWSER_ALL_FOR_NOW: ; } }
/* * Establish a secure context using the GSS-API. A handshake is attempted in * order to detect a non-authenticating server. The server IP address is obtained * from the socket and is used to perform an fqdn lookup in case a DNS alias is * used as a host spec, this ensures we authenticate against the correct server. * We attempt to extract the server principal name, a service, from the * environment, if it is not specified we use "host/" as a default. * * @pram to_net_sd. Socket to write to. * * @pram from_net_sd. Socket to read from. * * @param req_flags. A representation of the security services to * be requested. * * @param ret_flags. A representation of the security services * provided/supported by the underlying mechanism * to be returned to the invoking function. * * Returns 0 on success, otherwise error. */ static int dcc_gssapi_establish_secure_context(int to_net_sd, int from_net_sd, OM_uint32 req_flags, OM_uint32 *ret_flags) { char *ext_princ_name = NULL; char *full_name = NULL; char *princ_env_val = NULL; gss_buffer_desc input_tok = GSS_C_EMPTY_BUFFER; gss_buffer_desc name_buffer = GSS_C_EMPTY_BUFFER; gss_buffer_desc output_tok = GSS_C_EMPTY_BUFFER; gss_name_t int_serv_name; gss_OID name_type; int ret; OM_uint32 major_status, minor_status, return_status; socklen_t addr_len; struct hostent *hp; struct sockaddr_in addr; addr_len = sizeof(addr); if ((ret = getpeername(to_net_sd, &addr, &addr_len)) != 0) { rs_log_error("Failed to look up peer address using socket \"%d\": %s.", to_net_sd, hstrerror(h_errno)); return EXIT_CONNECT_FAILED; } rs_log_info("Successfully looked up IP address %s using socket %d.", inet_ntoa(addr.sin_addr), to_net_sd); if ((hp = gethostbyaddr((char *) &addr.sin_addr, sizeof(addr.sin_addr), AF_INET)) == NULL) { rs_log_error("Failed to look up host by address \"%s\": %s.", inet_ntoa(addr.sin_addr), hstrerror(h_errno)); return EXIT_CONNECT_FAILED; } rs_log_info("Successfully looked up host %s using IP address %s.", hp->h_name, inet_ntoa(addr.sin_addr)); if ((full_name = malloc(strlen(hp->h_name) + 1)) == NULL) { rs_log_error("malloc failed : %ld bytes: out of memory.", (long) (strlen(hp->h_name) + 1)); return EXIT_OUT_OF_MEMORY; } strcpy(full_name, hp->h_name); if ((princ_env_val = getenv("DISTCC_PRINCIPAL"))) { if (asprintf(&ext_princ_name, "%s@%s", princ_env_val, full_name) < 0) { rs_log_error("Failed to allocate memory for asprintf."); return EXIT_OUT_OF_MEMORY; } name_type = GSS_C_NT_HOSTBASED_SERVICE; } else { if (asprintf(&ext_princ_name, "host/%s", full_name) < 0) { rs_log_error("Failed to allocate memory for asprintf."); return EXIT_OUT_OF_MEMORY; } name_type = GSS_C_NT_USER_NAME; } free(full_name); name_buffer.value = ext_princ_name; name_buffer.length = strlen(ext_princ_name); if ((major_status = gss_import_name(&minor_status, &name_buffer, name_type, &int_serv_name)) != GSS_S_COMPLETE) { rs_log_error("Failed to import service name to internal GSS-API format."); return EXIT_GSSAPI_FAILED; } input_tok.value = NULL; input_tok.length = 0; output_tok.value = NULL; output_tok.length = 0; if ((ret = dcc_gssapi_send_handshake(to_net_sd, from_net_sd)) != 0) { return ret; } do { major_status = gss_init_sec_context(&minor_status, GSS_C_NO_CREDENTIAL, &distcc_ctx_handle, int_serv_name, GSS_C_NO_OID, req_flags, 0, GSS_C_NO_CHANNEL_BINDINGS, &input_tok, NULL, &output_tok, ret_flags, NULL); if (GSS_ERROR(major_status)) { rs_log_crit("Failed to initiate a secure context."); dcc_gssapi_status_to_log(major_status, GSS_C_GSS_CODE); dcc_gssapi_status_to_log(minor_status, GSS_C_MECH_CODE); return EXIT_GSSAPI_FAILED; } if (output_tok.length > 0) { if ((ret = send_token(to_net_sd, &output_tok)) != 0) { dcc_gssapi_cleanup(&input_tok, &output_tok, &int_serv_name); return ret; } else { if ((return_status = gss_release_buffer(&minor_status, &output_tok)) != GSS_S_COMPLETE) { rs_log_error("Failed to release buffer."); } } } if (input_tok.length > 0) { if ((return_status = gss_release_buffer(&minor_status, &input_tok)) != GSS_S_COMPLETE) { rs_log_error("Failed to release buffer."); } } if (major_status == GSS_S_CONTINUE_NEEDED) { if ((ret = recv_token(from_net_sd, &input_tok)) != 0) { dcc_gssapi_cleanup(&input_tok, &output_tok, &int_serv_name); return ret; } } } while (major_status != GSS_S_COMPLETE); rs_log_info("Successfully authenticated %s.", ext_princ_name); dcc_gssapi_cleanup(&input_tok, &output_tok, &int_serv_name); if ((major_status = gss_release_buffer(&minor_status, &name_buffer)) != GSS_S_COMPLETE) { rs_log_error("Failed to release buffer."); } return 0; }
/* * Description * Create listen fd for registering slaves. * * * Return Value * On success, RS_OK is returned. On error, RS_ERR is returned * */ int rs_dump_listen(rs_master_info_t *mi) { int err, tries, reuseaddr; struct sockaddr_in svr_addr; /* init var */ reuseaddr = 1; rs_memzero(&svr_addr, sizeof(svr_addr)); svr_addr.sin_family = AF_INET; if(mi->listen_port <= 0) { rs_log_err(0, "listen_port is invalid"); goto free; } svr_addr.sin_port = htons(mi->listen_port); if(mi->listen_addr == NULL) { rs_log_err(0, "listen_addr must not be null"); goto free; } if (inet_pton(AF_INET, mi->listen_addr, &(svr_addr.sin_addr)) != 1) { rs_log_err(rs_errno, "inet_pton() failed, %s", mi->listen_addr); goto free; } for(tries = RS_RETRY_BIND_TIMES; tries; tries--) { mi->svr_fd = socket(AF_INET, SOCK_STREAM, 0); if(mi->svr_fd == -1) { rs_log_err(rs_errno, "socket() failed"); goto free; } if(setsockopt(mi->svr_fd, SOL_SOCKET, SO_REUSEADDR, (const void *) &reuseaddr, sizeof(int)) == -1) { rs_log_err(rs_errno, "setsockopt(SO_REUSEADDR) failed, %s", mi->listen_addr); goto free; } if(bind(mi->svr_fd, (const struct sockaddr *) &svr_addr, sizeof(svr_addr)) == -1) { err = errno; rs_close(mi->svr_fd); mi->svr_fd = -1; if(err != EADDRINUSE) { rs_log_err(err, "bind() failed, %s", mi->listen_addr); goto free; } rs_log_info(0, "try again to bind() after %ums" , RS_RETRY_BIND_SLEEP_MSC); usleep(RS_RETRY_BIND_SLEEP_MSC * 1000); continue; } if(listen(mi->svr_fd, RS_BACKLOG) == -1) { rs_log_err(rs_errno, "listen() failed, %s", mi->listen_addr); goto free; } break; } if(!tries) { goto free; } return RS_OK; free : return RS_ERR; }