static char* read_string_from_popen(const char *cmdline, char *s, size_t nbytes) { FILE *p = NULL; char *ret = NULL; errno = 0; if (!(p = popen(cmdline, "r"))) { rs_log_crit("Failed to read string from C compiler: %s\n", errno ? strerror(errno) : "failure"); goto fail; } if (!fgets(s, (int) nbytes, p)) { rs_log_crit("Failed to read string from C compiler.\n"); goto fail; } s[nbytes-1] = 0; s[strcspn(s, " \t\n\r")] = 0; ret = s; fail: if (p) pclose(p); return ret; }
/** * Change object file or suffix of -o to @p ofname * Frees the old value, if it exists. * * It's crucially important that in every case where an output file is * detected by dcc_scan_args(), it's also correctly identified here. * It might be better to make the code shared. **/ int dcc_set_output(char **a, char *ofname) { int i; for (i = 0; a[i]; i++) if (0 == strcmp(a[i], "-o") && a[i+1] != NULL) { rs_trace("changed output from \"%s\" to \"%s\"", a[i+1], ofname); free(a[i+1]); a[i+1] = strdup(ofname); if (a[i+1] == NULL) { rs_log_crit("failed to allocate space for output parameter"); return EXIT_OUT_OF_MEMORY; } dcc_trace_argv("command after", a); return 0; } else if (0 == strncmp(a[i], "-o", 2)) { char *newptr; rs_trace("changed output from \"%s\" to \"%s\"", a[i]+2, ofname); free(a[i]); if (asprintf(&newptr, "-o%s", ofname) == -1) { rs_log_crit("failed to allocate space for output parameter"); return EXIT_OUT_OF_MEMORY; } a[i] = newptr; dcc_trace_argv("command after", a); return 0; } rs_log_error("failed to find \"-o\""); return EXIT_DISTCC_FAILED; }
/** * Add to the list of files to delete on exit. * If it runs out of memory, it returns non-zero. */ int dcc_add_cleanup(const char *filename) { char *new_filename; int new_n_cleanups = n_cleanups + 1; /* Increase the size of the cleanups array, if needed. * We avoid using realloc() here, to ensure that 'cleanups' remains * valid at all times - we might get a signal in the middle here * that could call dcc_cleanup_tempfiles_from_signal_handler(). */ if (new_n_cleanups > cleanups_size) { char **old_cleanups; int new_cleanups_size = (cleanups_size == 0 ? 10 : cleanups_size * 3); char **new_cleanups = malloc(new_cleanups_size * sizeof(char *)); if (new_cleanups == NULL) { rs_log_crit("malloc failed - too many cleanups"); return EXIT_OUT_OF_MEMORY; } memcpy(new_cleanups, (char **)cleanups, cleanups_size * sizeof(char *)); old_cleanups = (char **)cleanups; cleanups = new_cleanups; /* Atomic assignment. */ cleanups_size = new_cleanups_size; /* Atomic assignment. */ free(old_cleanups); } new_filename = strdup(filename); if (new_filename == NULL) { rs_log_crit("strdup failed - too many cleanups"); return EXIT_OUT_OF_MEMORY; } cleanups[new_n_cleanups - 1] = new_filename; /* Atomic assignment. */ n_cleanups = new_n_cleanups; /* Atomic assignment. */ return 0; }
static void client_callback(AvahiClient *client, AvahiClientState state, void *userdata) { struct context *ctx = userdata; ctx->client = client; switch (state) { case AVAHI_CLIENT_S_RUNNING: register_stuff(ctx); break; case AVAHI_CLIENT_S_COLLISION: case AVAHI_CLIENT_S_REGISTERING: if (ctx->group) avahi_entry_group_reset(ctx->group); break; case AVAHI_CLIENT_FAILURE: if (avahi_client_errno(client) == AVAHI_ERR_DISCONNECTED) { int error; avahi_client_free(ctx->client); ctx->client = NULL; ctx->group = NULL; /* Reconnect to the server */ if (!(ctx->client = avahi_client_new( avahi_threaded_poll_get(ctx->threaded_poll), AVAHI_CLIENT_NO_FAIL, client_callback, ctx, &error))) { rs_log_crit("Failed to contact server: %s", avahi_strerror(error)); avahi_threaded_poll_quit(ctx->threaded_poll); } } else { rs_log_crit("Client failure: %s", avahi_strerror(avahi_client_errno(client))); avahi_threaded_poll_quit(ctx->threaded_poll); } break; case AVAHI_CLIENT_CONNECTING: ; } }
/* 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; };
/* * 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 }
/** * Read state. If loaded successfully, store a pointer to the newly * allocated structure into *ppl. */ static int dcc_mon_load_state(int fd, char *fullpath, struct dcc_task_state **ppl) { int ret; struct dcc_task_state *tl; tl = calloc(1, sizeof *tl); if (!tl) { rs_log_crit("failed to allocate dcc_task_state"); return EXIT_OUT_OF_MEMORY; } ret = dcc_mon_read_state(fd, fullpath, tl); if (ret) { dcc_task_state_free(tl); *ppl = NULL; return ret; } if (tl->curr_phase != DCC_PHASE_DONE) { ret = dcc_mon_check_orphans(tl); if (ret) { dcc_task_state_free(tl); *ppl = NULL; return ret; } } *ppl = tl; return ret; }
/** * Returns a newly allocated buffer. **/ int dcc_make_lock_filename(const char *lockname, const struct dcc_hostdef *host, int iter, char **filename_ret) { char * buf; int ret; char *lockdir; if ((ret = dcc_get_lock_dir(&lockdir))) return ret; if (host->mode == DCC_MODE_LOCAL) { if (asprintf(&buf, "%s/%s_localhost_%d", lockdir, lockname, iter) == -1) return EXIT_OUT_OF_MEMORY; } else if (host->mode == DCC_MODE_TCP) { if (asprintf(&buf, "%s/%s_tcp_%s_%d_%d", lockdir, lockname, host->hostname, host->port, iter) == -1) return EXIT_OUT_OF_MEMORY; } else if (host->mode == DCC_MODE_SSH) { if (asprintf(&buf, "%s/%s_ssh_%s_%d", lockdir, lockname, host->hostname, iter) == -1) return EXIT_OUT_OF_MEMORY; } else { rs_log_crit("oops"); return EXIT_PROTOCOL_ERROR; } *filename_ret = buf; return 0; }
/** * Transmit token name (4 characters) and value (32-bit int, as 8 hex * characters). **/ int dcc_x_token_int(int ofd, const char *token, unsigned param) { char buf[13]; int shift; char *p; const char *hex = "0123456789abcdef"; if (strlen(token) != 4) { rs_log_crit("token \"%s\" seems wrong", token); return EXIT_PROTOCOL_ERROR; } memcpy(buf, token, 4); /* Quick and dirty int->hex. The only standard way is to call snprintf * (?), which is undesirably slow for such a frequently-called * function. */ for (shift=28, p = &buf[4]; shift >= 0; shift -= 4, p++) { *p = hex[(param >> shift) & 0xf]; } buf[12] = '\0'; rs_trace("send %s", buf); return dcc_writex(ofd, buf, 12); }
/* Return the supplied path with the current-working directory prefixed (if * needed) and all "dir/.." references removed. Supply path_len if you want * to use only a substring of the path string, otherwise make it 0. */ char *dcc_abspath(const char *path, int path_len) { static char buf[MAXPATHLEN]; unsigned len; char *p, *slash; if (*path == '/') len = 0; else { char *ret; #ifdef HAVE_GETCWD ret = getcwd(buf, sizeof buf); if (ret == NULL) { rs_log_crit("getcwd failed: %s", strerror(errno)); } #else ret = getwd(buf); if (ret == NULL) { rs_log_crit("getwd failed: %s", strerror(errno)); } #endif len = strlen(buf); if (len >= sizeof buf) { rs_log_crit("getwd overflowed in dcc_abspath()"); } buf[len++] = '/'; } if (path_len <= 0) path_len = strlen(path); if (path_len >= 2 && *path == '.' && path[1] == '/') { path += 2; path_len -= 2; } if (len + (unsigned)path_len >= sizeof buf) { rs_log_error("path overflowed in dcc_abspath()"); exit(EXIT_OUT_OF_MEMORY); } strncpy(buf + len, path, path_len); buf[len + path_len] = '\0'; for (p = buf+len-(len > 0); (p = strstr(p, "/../")) != NULL; p = slash) { *p = '\0'; if (!(slash = strrchr(buf, '/'))) slash = p; strcpy(slash, p+3); } return buf; }
/** * Redirect a file descriptor into (or out of) a file. * * Used, for example, to catch compiler error messages into a * temporary file. **/ int dcc_redirect_fd(int fd, const char *fname, int mode) { int newfd; /* ignore errors */ close(fd); newfd = open(fname, mode, 0666); if (newfd == -1) { rs_log_crit("failed to reopen fd%d onto %s: %s", fd, fname, strerror(errno)); return EXIT_IO_ERROR; } else if (newfd != fd) { rs_log_crit("oops, reopened fd%d onto fd%d?", fd, newfd); return EXIT_IO_ERROR; } return 0; }
/* Return the number of seconds, when the specified file was last * read. If the atime of that file is < clip_time, use clip_time * instead */ static time_t fd_last_used(int fd, time_t clip_time) { struct stat st; time_t now, ft; assert(fd >= 0); if (fstat(fd, &st) < 0) { rs_log_crit("fstat() failed: %s\n", strerror(errno)); return -1; } if ((now = time(NULL)) == (time_t) -1) { rs_log_crit("time() failed: %s\n", strerror(errno)); return -1; } ft = clip_time ? (st.st_atime < clip_time ? clip_time : st.st_atime) : st.st_atime; assert(ft <= now); return now - ft; }
/** * Find the absolute path for the first occurrence of @p compiler_name on the * PATH. Print a warning if it looks like a symlink to distcc. * * We want to guard against somebody accidentally running the server with a * masqueraded compiler on its $PATH. The worst that's likely to happen here * is wasting some time running a distcc or ccache client that does nothing, * so it's not a big deal. (This could be easy to do if it's on the default * PATH and they start the daemon from the command line.) * * At the moment we don't look for the compiler too. **/ static int dcc_check_compiler_masq(char *compiler_name) { const char *envpath, *p, *n; char *buf = NULL; struct stat sb; int len; char linkbuf[MAXPATHLEN]; if (compiler_name[0] == '/') return 0; if (!(envpath = getenv("PATH"))) { rs_trace("PATH seems not to be defined"); return 0; } for (n = p = envpath; *n; p = n) { n = strchr(p, ':'); if (n) len = n++ - p; else { len = strlen(p); n = p + len; } if (asprintf(&buf, "%.*s/%s", len, p, compiler_name) == -1) { rs_log_crit("asnprintf failed"); return EXIT_DISTCC_FAILED; } if (lstat(buf, &sb) == -1) continue; /* ENOENT, EACCESS, etc */ if (!S_ISLNK(sb.st_mode)) { rs_trace("%s is not a symlink", buf); break; /* found it */ } if ((len = readlink(buf, linkbuf, sizeof linkbuf)) <= 0) continue; linkbuf[len] = '\0'; if (strstr(linkbuf, "distcc")) { rs_log_warning("%s on distccd's path is %s and really a link to %s", compiler_name, buf, linkbuf); break; /* but use it anyhow */ } else { rs_trace("%s is a safe symlink to %s", buf, linkbuf); break; /* found it */ } } free(buf); return 0; }
/* * 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; }
/** * Add to the list of files to delete on exit. * * The string pointer must remain valid until exit. */ int dcc_add_cleanup(char *filename) { int i; for (i = 0; cleanups[i]; i++) ; if (i >= N_CLEANUPS) { rs_log_crit("too many cleanups"); return EXIT_OUT_OF_MEMORY; } cleanups[i] = filename; return 0; }
/** * Make sure that distccd never runs as root, by discarding privileges if we * have them. * * This used to also check gid!=0, but on BSD that is group wheel and is * apparently common for daemons or users. * * This is run before dissociating from the calling terminal so any errors go * to stdout. **/ int dcc_discard_root(void) { uid_t uid; gid_t gid; int ret; if (getuid() != 0 && geteuid() != 0) { /* Already not root. No worries. */ return 0; } if ((ret = dcc_preferred_user(&uid, &gid)) != 0) return ret; /* GNU C Library Manual says that when run by root, setgid() and setuid() * permanently discard privileges: both the real and effective uid are * set. */ if (setgid(gid)) { rs_log_error("setgid(%d) failed: %s", (int) gid, strerror(errno)); return EXIT_SETUID_FAILED; } #ifdef HAVE_SETGROUPS /* Get rid of any supplementary groups this process might have * inherited. */ /* XXX: OS X Jaguar broke setgroups so that setting it to 0 fails. */ if (setgroups(1, &gid)) { rs_log_error("setgroups failed: %s", strerror(errno)); return EXIT_SETUID_FAILED; } #endif if (setuid(uid)) { rs_log_error("setuid(%d) failed: %s", (int) uid, strerror(errno)); return EXIT_SETUID_FAILED; } if (getuid() == 0 || geteuid() == 0) { rs_log_crit("still have root privileges after trying to discard them!"); return EXIT_SETUID_FAILED; } rs_trace("discarded root privileges, changed to uid=%d gid=%d", (int) uid, (int) gid); return 0; }
/* * 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 void client_callback(AvahiClient *client, AvahiClientState state, void *userdata) { struct daemon_data *d = userdata; switch (state) { case AVAHI_CLIENT_FAILURE: rs_log_crit("Client failure: %s\n", avahi_strerror(avahi_client_errno(client))); avahi_simple_poll_quit(d->simple_poll); break; case AVAHI_CLIENT_S_COLLISION: case AVAHI_CLIENT_S_REGISTERING: case AVAHI_CLIENT_S_RUNNING: case AVAHI_CLIENT_CONNECTING: ; } }
/** * Copy all server messages to the error file, so that they can be * echoed back to the client if necessary. **/ static int dcc_add_log_to_file(const char *err_fname) { if (dcc_compile_log_fd != -1) { rs_log_crit("compile log already open?"); return 0; /* continue? */ } dcc_compile_log_fd = open(err_fname, O_WRONLY|O_CREAT|O_TRUNC, 0600); if (dcc_compile_log_fd == -1) { rs_log_error("failed to open %s: %s", err_fname, strerror(errno)); return EXIT_IO_ERROR; } /* Only send fairly serious errors back */ rs_add_logger(rs_logger_file, RS_LOG_WARNING, NULL, dcc_compile_log_fd); return 0; }
/** * Change input file to a copy of @p ifname; called on compiler. * Frees the old value. * * @todo Unify this with dcc_scan_args * * @todo Test this by making sure that when the modified arguments are * run through scan_args, the new ifname is identified as the input. **/ int dcc_set_input(char **a, char *ifname) { int i; for (i =0; a[i]; i++) if (dcc_is_source(a[i])) { rs_trace("changed input from \"%s\" to \"%s\"", a[i], ifname); free(a[i]); a[i] = strdup(ifname); if (a[i] == NULL) { rs_log_crit("failed to allocate space for input parameter"); return EXIT_OUT_OF_MEMORY; } dcc_trace_argv("command after", a); return 0; } rs_log_error("failed to find input file"); return EXIT_DISTCC_FAILED; }
/* 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: ; } }
/** * Read a byte string of length @p l into a newly allocated buffer, returned in @p buf. **/ int dcc_r_str_alloc(int fd, unsigned l, char **buf) { char *s; #if 0 /* never true */ if (l < 0) { rs_log_crit("oops, l < 0"); return EXIT_PROTOCOL_ERROR; } #endif /* rs_trace("read %d byte string", l); */ s = *buf = malloc((size_t) l + 1); if (!s) rs_log_error("malloc failed"); if (dcc_readx(fd, s, (size_t) l)) return EXIT_OUT_OF_MEMORY; s[l] = 0; return 0; }
/* * 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; }
/* 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: ; } }
/* 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; }
/* Register a distcc service in DNS-SD/mDNS. If advertise_capabilities is * true, the registration will contain information about the distcc server's * capabilities. If service_type is NULL, the default service type will be * used. advertise_capabilities should be true when using the default * service type. */ void* dcc_zeroconf_register_extended(int advertise_capabilities, const char *service_type, uint16_t port, int n_cpus) { struct context *ctx = NULL; char hostname[_POSIX_HOST_NAME_MAX + 1]; const AvahiPoll *threaded_poll; int len, error; ctx = calloc(1, sizeof(struct context)); if (!ctx) { rs_log_crit("calloc() failed for ctx: %s", strerror(errno)); goto fail; } ctx->advertise_capabilities = advertise_capabilities; ctx->port = port; ctx->n_cpus = n_cpus; /* Prepare service type. Use the supplied value, or the default if * NULL was supplied. */ if (service_type) ctx->service_type = strdup(service_type); else ctx->service_type = strdup(DCC_DNS_SERVICE_TYPE); if (!ctx->service_type) { rs_log_crit("strdup() failed for ctx->service_type: %s", strerror(errno)); goto fail; } /* Prepare service name. This is just the chosen service type with * '@' and the hostname appended. If this collides with anything else, * avahi_alternative_service_name will choose a replacement name. */ if (gethostname(hostname, sizeof(hostname))) { rs_log_crit("gethostname() failed: %s", strerror(errno)); goto fail; } /* Leave room for the '@' and trailing NUL. */ len = strlen(ctx->service_type) + strlen(hostname) + 2; if (!(ctx->name = avahi_malloc(len))) { rs_log_crit("avahi_malloc() failed for ctx->name"); goto fail; } snprintf(ctx->name, len, "%s@%s", ctx->service_type, hostname); /* Create the Avahi client. */ if (!(ctx->threaded_poll = avahi_threaded_poll_new())) { rs_log_crit("Failed to create event loop object."); goto fail; } threaded_poll = avahi_threaded_poll_get(ctx->threaded_poll); if (!(ctx->client = avahi_client_new(threaded_poll, AVAHI_CLIENT_NO_FAIL, client_callback, ctx, &error))) { rs_log_crit("Failed to create client object: %s", avahi_strerror(error)); goto fail; } /* Create the mDNS event handler */ if (avahi_threaded_poll_start(ctx->threaded_poll) < 0) { rs_log_crit("Failed to create thread."); goto fail; } return ctx; fail: if (ctx) dcc_zeroconf_unregister(ctx); return NULL; }
static void register_stuff(struct context *ctx) { if (!ctx->group) { if (!(ctx->group = avahi_entry_group_new(ctx->client, publish_reply, ctx))) { rs_log_crit("Failed to create entry group: %s", avahi_strerror(avahi_client_errno(ctx->client))); goto fail; } } if (avahi_entry_group_is_empty(ctx->group)) { char cpus[32] = ""; char machine[64] = "cc_machine=", version[64] = "cc_version="; char *m = NULL, *v = NULL; if (ctx->advertise_capabilities) { snprintf(cpus, sizeof(cpus), "cpus=%i", ctx->n_cpus); v = dcc_get_gcc_version(version+11, sizeof(version)-11); m = dcc_get_gcc_machine(machine+11, sizeof(machine)-11); } /* Register our service */ if (avahi_entry_group_add_service( ctx->group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, ctx->name, ctx->service_type, NULL, NULL, ctx->port, !ctx->advertise_capabilities ? NULL : "txtvers=1", cpus, "distcc="PACKAGE_VERSION, "gnuhost="GNU_HOST, v ? version : NULL, m ? machine : NULL, NULL) < 0) { rs_log_crit("Failed to add service: %s", avahi_strerror(avahi_client_errno(ctx->client))); goto fail; } if (ctx->advertise_capabilities) { if (v && m) { char stype[128]; dcc_make_dnssd_subtype(stype, sizeof(stype), v, m); if (avahi_entry_group_add_service_subtype( ctx->group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, ctx->name, ctx->service_type, NULL, stype) < 0) { rs_log_crit("Failed to add service: %s", avahi_strerror(avahi_client_errno(ctx->client))); goto fail; } } else rs_log_warning("Failed to determine CC version, not registering DNS-SD service subtype!"); } if (avahi_entry_group_commit(ctx->group) < 0) { rs_log_crit("Failed to commit entry group: %s", avahi_strerror(avahi_client_errno(ctx->client))); goto fail; } } return; fail: avahi_threaded_poll_quit(ctx->threaded_poll); }
/** * 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; }