Beispiel #1
0
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;
}
Beispiel #2
0
/**
 * 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;
}
Beispiel #3
0
/**
 * 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;
}
Beispiel #4
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:
            ;
    }
}
Beispiel #5
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;

};
Beispiel #6
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
}
Beispiel #7
0
/**
 * 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;
}
Beispiel #8
0
/**
 * 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;
}
Beispiel #9
0
Datei: rpc.c Projekt: aosm/distcc
/**
 * 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);
}
Beispiel #10
0
/* 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;
}
Beispiel #11
0
/**
 * 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;
}
Beispiel #12
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;
}
Beispiel #13
0
/**
 * 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;
}
Beispiel #14
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, &notification, 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;
}
Beispiel #15
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;
}
Beispiel #16
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;
}
Beispiel #17
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;
}
Beispiel #18
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:
            ;
    }
}
Beispiel #19
0
/**
 * 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;
}
Beispiel #20
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;
}
Beispiel #21
0
/* 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:
            ;
    }
}
Beispiel #22
0
Datei: rpc.c Projekt: aosm/distcc
/**
 * 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;
}
Beispiel #23
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;
}
Beispiel #24
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;
}
Beispiel #25
0
/* 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:
            ;

    }
}
Beispiel #26
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;
}
Beispiel #27
0
/* 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;
}
Beispiel #28
0
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);
}
Beispiel #29
0
/**
 * 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;
}