1 /* -*- c-file-style: "java"; indent-tabs-mode: nil; tab-width: 4; fill-column: 78 -*-
2  *
3  * distcc -- A simple distributed compiler system
4  *
5  * Copyright (C) 2002, 2003, 2004 by Martin Pool
6  * Copyright 2007 Google Inc.
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License
10  * as published by the Free Software Foundation; either version 2
11  * of the License, or (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
21  * USA.
22  */
23 
24                 /* He who waits until circumstances completely favour *
25                  * his undertaking will never accomplish anything.    *
26                  *              -- Martin Luther                      */
27 
28 
29 /**
30  * @file
31  *
32  * Actually serve remote requests.  Called from daemon.c.
33  *
34  * @todo Make sure wait statuses are packed in a consistent format
35  * (exit<<8 | signal).  Is there any platform that doesn't do this?
36  *
37  * @todo The server should catch signals, and terminate the compiler process
38  * group before handling them.
39  *
40  * @todo It might be nice to detect that the client has dropped the
41  * connection, and then kill the compiler immediately.  However, we probably
42  * won't notice that until we try to do IO.  SIGPIPE won't help because it's
43  * not triggered until we try to do IO.  I don't think it matters a lot,
44  * though, because the client's not very likely to do that.  The main case is
45  * probably somebody getting bored and interrupting compilation.
46  *
47  * What might help is to select() on the network socket while we're waiting
48  * for the child to complete, allowing SIGCHLD to interrupt the select() when
49  * the child completes.  However I'm not sure if it's really worth the trouble
50  * of doing that just to handle a fairly marginal case.
51  **/
52 
53 
54 
55 #include <config.h>
56 
57 #include <stdio.h>
58 #include <stdlib.h>
59 #include <setjmp.h>
60 #include <unistd.h>
61 #include <string.h>
62 #include <fcntl.h>
63 #include <errno.h>
64 #include <time.h>
65 
66 #include <sys/stat.h>
67 #include <sys/types.h>
68 #include <sys/wait.h>
69 #include <sys/param.h>
70 #include <sys/socket.h>
71 #include <sys/time.h>
72 
73 #include "distcc.h"
74 #include "trace.h"
75 #include "util.h"
76 #include "stats.h"
77 #include "rpc.h"
78 #include "exitcode.h"
79 #include "snprintf.h"
80 #include "dopt.h"
81 #include "bulk.h"
82 #include "exec.h"
83 #include "srvnet.h"
84 #include "hosts.h"
85 #include "daemon.h"
86 #include "stringmap.h"
87 #include "dotd.h"
88 #include "fix_debug_info.h"
89 #ifdef HAVE_GSSAPI
90 #include "auth.h"
91 
92 /* Global security context in case confidentiality/integrity */
93 /* type services are needed in the future. */
94 extern gss_ctx_id_t distccd_ctx_handle;
95 
96 /* Simple boolean, with a non-zero value indicating that the */
97 /* --auth option was specified. */
98 int dcc_auth_enabled = 0;
99 #endif
100 
101 /**
102  * We copy all serious distccd messages to this file, as well as sending the
103  * compiler errors there, so they're visible to the client.
104  **/
105 static int dcc_compile_log_fd = -1;
106 
107 static int dcc_run_job(int in_fd, int out_fd);
108 
109 
110 /**
111  * Copy all server messages to the error file, so that they can be
112  * echoed back to the client if necessary.
113  **/
dcc_add_log_to_file(const char * err_fname)114 static int dcc_add_log_to_file(const char *err_fname)
115 {
116     if (dcc_compile_log_fd != -1) {
117         rs_log_crit("compile log already open?");
118         return 0;               /* continue? */
119     }
120 
121     dcc_compile_log_fd = open(err_fname, O_WRONLY|O_CREAT|O_TRUNC, 0600);
122     if (dcc_compile_log_fd == -1) {
123         rs_log_error("failed to open %s: %s", err_fname, strerror(errno));
124         return EXIT_IO_ERROR;
125     }
126 
127     /* Only send fairly serious errors back */
128     rs_add_logger(rs_logger_file, RS_LOG_WARNING, NULL, dcc_compile_log_fd);
129 
130     return 0;
131 }
132 
133 
134 
dcc_remove_log_to_file(void)135 static int dcc_remove_log_to_file(void)
136 {
137     if (dcc_compile_log_fd == -1) {
138         rs_log_warning("compile log not open?");
139         return 0;               /* continue? */
140     }
141 
142     /* must exactly match call in dcc_add_log_to_file */
143     rs_remove_logger(rs_logger_file, RS_LOG_WARNING, NULL,
144                      dcc_compile_log_fd);
145 
146     dcc_close(dcc_compile_log_fd);
147 
148     dcc_compile_log_fd = -1;
149 
150     return 0;
151 }
152 
153 
154 
155 /* Read and execute a job to/from socket.  This is the common entry point no
156  * matter what mode the daemon is running in: preforked, nonforked, or
157  * ssh/inetd.
158  */
dcc_service_job(int in_fd,int out_fd,struct sockaddr * cli_addr,int cli_len)159 int dcc_service_job(int in_fd,
160                     int out_fd,
161                     struct sockaddr *cli_addr,
162                     int cli_len)
163 {
164     int ret;
165 
166     dcc_job_summary_clear();
167 
168     /* Log client name and check access if appropriate.  For ssh connections
169      * the client comes from a unix-domain socket and that's always
170      * allowed. */
171     if ((ret = dcc_check_client(cli_addr, cli_len, opt_allowed)) != 0)
172         goto out;
173 
174 #ifdef HAVE_GSSAPI
175     /* If requested perform authentication. */
176     if (dcc_auth_enabled) {
177 	    rs_log_info("Performing authentication.");
178 
179         if ((ret = dcc_gssapi_check_client(in_fd, out_fd)) != 0) {
180             goto out;
181         }
182     } else {
183 	    rs_log_info("No authentication requested.");
184     }
185 
186     /* Context deleted here as we no longer need it.  However, we have it available */
187     /* in case we want to use confidentiality/integrity type services in the future. */
188     if (dcc_auth_enabled) {
189         dcc_gssapi_delete_ctx(&distccd_ctx_handle);
190     }
191 #endif
192 
193     ret = dcc_run_job(in_fd, out_fd);
194 
195     dcc_job_summary();
196 
197 out:
198     return ret;
199 }
200 
201 
dcc_input_tmpnam(char * orig_input,char ** tmpnam_ret)202 static int dcc_input_tmpnam(char * orig_input,
203                             char **tmpnam_ret)
204 {
205     const char *input_exten;
206 
207     rs_trace("input file %s", orig_input);
208     input_exten = dcc_find_extension(orig_input);
209     if (input_exten)
210         input_exten = dcc_preproc_exten(input_exten);
211     if (!input_exten)           /* previous line might return NULL */
212         input_exten = ".tmp";
213     return dcc_make_tmpnam("distccd", input_exten, tmpnam_ret);
214 }
215 
216 
217 
218 /**
219  * Check argv0 against a list of allowed commands, and possibly map it to a new value.
220  * If *compiler_name is changed, the original value is free'd, and a new value is malloc'd.
221  *
222  * If the environment variable DISTCC_CMDLIST is set,
223  * load a list of supported commands from the file named by DISTCC_CMDLIST, and
224  * refuse to serve any command whose last DISTCC_CMDLIST_NUMWORDS last words
225  * don't match those of a command in that list.
226  * Each line of the file is simply a filename.
227  * This is chiefly useful for those few installations which have so many
228  * compilers available such that the compiler must be specified with an absolute pathname.
229  *
230  * Example: if the compilers are installed in a different location on
231  * this server, e.g. if they've been copied from a shared NFS directory onto a
232  * local hard drive, you might have lines like
233  *   /local/tools/blort/sh4-linux/gcc-3.3.3-glibc-2.2.5/bin/sh4-linux-gcc
234  *   /local/tools/blort/sh4-linux/gcc-2.95.3-glibc-2.2.5/bin/sh4-linux-gcc
235  * and set DISTCC_CMDLIST_NUMWORDS=3; that way e.g. any of the commands
236  *   /local/tools/gcc-3.3.3-glibc-2.2.5/bin/sh4-linux-gcc
237  *   /shared/tools/gcc-3.3.3-glibc-2.2.5/bin/sh4-linux-gcc
238  *   /zounds/gcc-3.3.3-glibc-2.2.5/bin/sh4-linux-gcc
239  * will invoke
240  *   /local/tools/blort/sh4-linux/gcc-3.3.3-glibc-2.2.5/bin/sh4-linux-gcc
241  *
242  * Returns 0 (which will abort the compile) if compiler not in list.
243  * (This is because the list is intended to be complete,
244  * and any attempt to use a command not in the list indicates a confused user.
245  * FIXME: should probably give user the option of changing this
246  * behavior at runtime, so normal command lookup can continue even if command
247  * not found in table.)
248  **/
dcc_remap_compiler(char ** compiler_name)249 static int dcc_remap_compiler(char **compiler_name)
250 {
251     static int cmdlist_checked=0;
252     static stringmap_t *map=0;
253     const char *newname;
254 
255     /* load file if not already */
256     if (!cmdlist_checked) {
257         char *filename;
258         cmdlist_checked = 1;
259         filename = getenv("DISTCC_CMDLIST");
260         if (filename) {
261             const char *nw = getenv("DISTCC_CMDLIST_NUMWORDS");
262             int numFinalWordsToMatch=1;
263             if (nw)
264                 numFinalWordsToMatch = atoi(nw);
265             map = stringmap_load(filename, numFinalWordsToMatch);
266             if (map) {
267                 rs_trace("stringmap_load(%s, %d) found %d commands", filename, numFinalWordsToMatch, map->n);
268             } else {
269                 rs_log_error("stringmap_load(%s, %d) failed: %s", filename, numFinalWordsToMatch, strerror(errno));
270                 return EXIT_IO_ERROR;
271             }
272         }
273     }
274 
275     if (!map)
276         return 1;    /* no list of allowed names, so ok */
277 
278     /* Find what this compiler maps to */
279     newname = stringmap_lookup(map, *compiler_name);
280     if (!newname) {
281         rs_log_warning("lookup of %s in DISTCC_CMDLIST failed", *compiler_name);
282         return 0;    /* not in list, so forbidden.  FIXME: make failure an option */
283     }
284 
285     /* If mapping is not the identity mapping, replace the original name */
286     if (strcmp(newname, *compiler_name)) {
287         rs_trace("changed compiler from %s to %s", *compiler_name, newname);
288         free(*compiler_name);
289         *compiler_name = strdup(newname);
290     }
291     return 1;
292 }
293 
294 
295 /**
296  * Find the absolute path for the first occurrence of @p compiler_name on the
297  * PATH.  Print a warning if it looks like a symlink to distcc.
298  *
299  * We want to guard against somebody accidentally running the server with a
300  * masqueraded compiler on its $PATH.  The worst that's likely to happen here
301  * is wasting some time running a distcc or ccache client that does nothing,
302  * so it's not a big deal.  (This could be easy to do if it's on the default
303  * PATH and they start the daemon from the command line.)
304  *
305  * At the moment we don't look for the compiler too.
306  **/
dcc_check_compiler_masq(char * compiler_name)307 static int dcc_check_compiler_masq(char *compiler_name)
308 {
309     const char *envpath, *p, *n;
310     char *buf = NULL;
311     struct stat sb;
312     int len;
313     char linkbuf[MAXPATHLEN];
314 
315     if (compiler_name[0] == '/')
316         return 0;
317 
318     if (!(envpath = getenv("PATH"))) {
319         rs_trace("PATH seems not to be defined");
320         return 0;
321     }
322 
323     for (n = p = envpath; *n; p = n) {
324         n = strchr(p, ':');
325         if (n)
326             len = n++ - p;
327         else {
328             len = strlen(p);
329             n = p + len;
330         }
331         if (asprintf(&buf, "%.*s/%s", len, p, compiler_name) == -1) {
332             rs_log_crit("asprintf failed");
333             return EXIT_DISTCC_FAILED;
334         }
335 
336         if (lstat(buf, &sb) == -1)
337             continue;           /* ENOENT, EACCESS, etc */
338         if (!S_ISLNK(sb.st_mode)) {
339             rs_trace("%s is not a symlink", buf);
340             break;              /* found it */
341         }
342         if ((len = readlink(buf, linkbuf, sizeof linkbuf)) <= 0)
343             continue;
344         linkbuf[len] = '\0';
345 
346         if (strstr(linkbuf, "distcc")) {
347             rs_log_warning("%s on distccd's path is %s and really a link to %s",
348                            compiler_name, buf, linkbuf);
349             break;              /* but use it anyhow */
350         } else {
351             rs_trace("%s is a safe symlink to %s", buf, linkbuf);
352             break;              /* found it */
353         }
354     }
355 
356     free(buf);
357     return 0;
358 }
359 
360 /**
361  * Make sure there is a masquerade to distcc in LIBDIR/distcc in order to
362  * execute a binary of the same name.
363  *
364  * Before this it was possible to execute arbitrary command after connecting
365  * to distcc, which is quite a security risk when combined with any local root
366  * privilege escalation exploit. See CVE 2004-2687
367  *
368  * https://nvd.nist.gov/vuln/detail/CVE-2004-2687
369  * https://github.com/distcc/distcc/issues/155
370  **/
dcc_check_compiler_whitelist(char * _compiler_name)371 static int dcc_check_compiler_whitelist(char *_compiler_name)
372 {
373     char *compiler_name = _compiler_name;
374 
375     /* Support QtCreator by treating /usr/bin and /bin absolute paths as non-absolute
376      * see https://github.com/distcc/distcc/issues/279
377      */
378     const char *creator_paths[] = { "/bin/", "/usr/bin/", NULL };
379     int i;
380     for (i = 0 ; creator_paths[i] ; ++i) {
381         size_t len = strlen(creator_paths[i]);
382         // /bin and /usr/bin are absolute paths (= compare from the string start)
383         // use strncasecmp() to support case-insensitive / (= on Mac).
384         if (strncasecmp(_compiler_name, creator_paths[i], len) == 0) {
385             compiler_name = _compiler_name + len;
386             // stop at the first hit
387             break;
388         }
389     }
390 
391     if (strchr(compiler_name, '/')) {
392         rs_log_crit("compiler name <%s> cannot be an absolute path (or must set DISTCC_CMDLIST or pass --enable-tcp-insecure)", _compiler_name);
393         return EXIT_BAD_ARGUMENTS;
394     }
395 
396 #ifdef HAVE_FSTATAT
397     int dirfd = open(LIBDIR "/distcc", O_RDONLY);
398     if (dirfd < 0) {
399         if (errno == ENOENT)
400             rs_log_crit("no %s", LIBDIR "/distcc");
401         return EXIT_DISTCC_FAILED;
402     }
403 
404     if (faccessat(dirfd, compiler_name, X_OK, 0) < 0) {
405         char *compiler_path = NULL;
406         if (asprintf(&compiler_path, "/usr/lib/distcc/%s", compiler_name) && compiler_path) {
407             if (access(compiler_path, X_OK) < 0) {
408                 rs_log_crit("%s not in %s or %s whitelist.", compiler_name, LIBDIR "/distcc", "/usr/lib/distcc");
409                 return EXIT_BAD_ARGUMENTS;           /* ENOENT, EACCESS, etc */
410             }
411             free(compiler_path);
412         }
413     }
414 
415     rs_trace("%s in" LIBDIR "/distcc whitelist", compiler_name);
416     return 0;
417 #else
418     // make do with access():
419     char *compiler_path = NULL;
420     int ret = 0;
421     if (asprintf(&compiler_path, "%s/distcc/%s", LIBDIR, compiler_name) && compiler_path) {
422         if (access(compiler_path, X_OK) < 0) {
423             free(compiler_path);
424             /* check /usr/lib/distcc too */
425             if (asprintf(&compiler_path, "/usr/lib/distcc/%s", compiler_name) && compiler_path) {
426                 if (access(compiler_path, X_OK) < 0) {
427                     rs_log_crit("%s not in %s or %s whitelist.", compiler_name, LIBDIR "/distcc", "/usr/lib/distcc");
428                     ret = EXIT_BAD_ARGUMENTS;           /* ENOENT, EACCESS, etc */
429                 }
430             }
431         }
432         rs_trace("%s in" LIBDIR "/distcc whitelist", compiler_name);
433     } else {
434         rs_log_crit("Couldn't check if %s is in %s whitelist.", compiler_name, LIBDIR "/distcc");
435         ret = EXIT_DISTCC_FAILED;
436     }
437     if (compiler_path) {
438         free(compiler_path);
439     }
440     return ret;
441 #endif
442 }
443 
444 static const char *include_options[] = {
445     "-I",
446     "-include",
447     "-imacros",
448     "-idirafter",
449     "-iprefix",
450     "-iwithprefix",
451     "-iwithprefixbefore",
452     "-isystem",
453     "-iquote",
454     NULL
455 };
456 
457 
458 /**
459  * Prepend @p root_dir string to source file if absolute.
460  **/
tweak_input_argument_for_server(char ** argv,const char * root_dir)461 static int tweak_input_argument_for_server(char **argv,
462                                            const char *root_dir)
463 {
464     unsigned i;
465     /* Look for the source file and act if absolute. Note: dcc_scan_args
466      * rejects compilations with more than one source file. */
467     for (i=0; argv[i]; i++)
468         if (dcc_is_source(argv[i]) && argv[i][0]=='/') {
469             unsigned j = 0;
470             char *prefixed_name;
471             while (argv[i][j] == '/') j++;
472             if (asprintf(&prefixed_name, "%s/%s",
473                          root_dir,
474                          argv[i] + j) == -1) {
475                 rs_log_crit("asprintf failed");
476                 return EXIT_OUT_OF_MEMORY;
477             }
478             rs_trace("changed input from \"%s\" to \"%s\"", argv[i],
479                      prefixed_name);
480             free(argv[i]);
481             argv[i] = prefixed_name;
482             dcc_trace_argv("command after", argv);
483             return 0;
484         }
485     return 0;
486 }
487 
488 
489 /**
490  * Prepend @p root_dir to arguments of include options that are absolute.
491  **/
tweak_include_arguments_for_server(char ** argv,const char * root_dir)492 static int tweak_include_arguments_for_server(char **argv,
493                                               const char *root_dir)
494 {
495     int index_of_first_filename_char = 0;
496     const char *include_option;
497     unsigned int i, j;
498     for (i = 0; argv[i]; ++i) {
499         for (j = 0; include_options[j]; ++j) {
500             if (str_startswith(include_options[j], argv[i])) {
501                 if (strcmp(argv[i], include_options[j]) == 0) {
502                     /* "-I foo" , change the next argument */
503                     ++i;
504                     include_option = "";
505                     index_of_first_filename_char = 0;
506                 } else {
507                     /* "-Ifoo", change this argument */
508                     include_option = include_options[j];
509                     index_of_first_filename_char = strlen(include_option);
510                 }
511                 if (argv[i] != NULL) {  /* in case of a dangling -I */
512                     if (argv[i][index_of_first_filename_char] == '/') {
513                         char *buf;
514                         checked_asprintf(&buf, "%s%s%s",
515                                  include_option,
516                                  root_dir,
517                                  argv[i] + index_of_first_filename_char);
518                         if (buf == NULL) {
519                             return EXIT_OUT_OF_MEMORY;
520                         }
521                         free(argv[i]);
522                         argv[i] = buf;
523                     }
524                 }
525                 break;  /* from the inner loop; go look at the next argument */
526             }
527         }
528     }
529     return 0;
530 }
531 
532 /* The -MT command line flag does not work as advertised for distcc:
533  * it augments, rather than replace, the list of targets in the dotd file.
534  * The behavior we want though, is the replacing behavior.
535  * So here we delete the "-MT target" arguments, and we return the target,
536  * for use in the .d rewriting in dotd.c.
537  */
dcc_convert_mt_to_dotd_target(char ** argv,char ** dotd_target)538 static int dcc_convert_mt_to_dotd_target(char **argv, char **dotd_target)
539 {
540     int i;
541     *dotd_target = NULL;
542 
543     for (i = 0; argv[i]; ++i) {
544         if (strcmp(argv[i], "-MT") == 0) {
545             break;
546         }
547     }
548 
549     /* if we reached the end without finding -MT, fine. */
550     if (argv[i] == NULL)
551         return 0;
552 
553     /* if we find -MT but only at the very end, that's an error. */
554     if (argv[i+1] == NULL) {
555         rs_trace("found -MT at the end of the command line");
556         return 1;
557     }
558 
559     /* the dotd_target is the argument of -MT */
560     *dotd_target = argv[i+1];
561 
562     /* copy the next-next argument on top of this. */
563     for (; argv[i+2]; ++i) {
564         argv[i] = argv[i+2];
565     }
566 
567     /* and then put the terminal null in. */
568     argv[i] = argv[i+2];
569 
570     return 0;
571 }
572 
573 
574 /**
575  * Add -MMD and -MF to get a .d file.
576  * Find what the dotd target should be (if any).
577  * Prepend @p root_dir to every command
578  * line argument that refers to a file/dir by an absolute name.
579  **/
tweak_arguments_for_server(char ** argv,const char * root_dir,const char * deps_fname,char ** dotd_target,char *** tweaked_argv)580 static int tweak_arguments_for_server(char **argv,
581                                       const char *root_dir,
582                                       const char *deps_fname,
583                                       char **dotd_target,
584                                       char ***tweaked_argv)
585 {
586     int ret;
587     *dotd_target = 0;
588     if ((ret = dcc_copy_argv(argv, tweaked_argv, 3)))
589       return 1;
590 
591     if ((ret = dcc_convert_mt_to_dotd_target(*tweaked_argv, dotd_target)))
592       return 1;
593 
594     if (!dcc_argv_search(*tweaked_argv, "-MD") && !dcc_argv_search(*tweaked_argv, "-MMD")) {
595         dcc_argv_append(*tweaked_argv, strdup("-MMD"));
596     }
597     dcc_argv_append(*tweaked_argv, strdup("-MF"));
598     dcc_argv_append(*tweaked_argv, strdup(deps_fname));
599 
600     tweak_include_arguments_for_server(*tweaked_argv, root_dir);
601     tweak_input_argument_for_server(*tweaked_argv, root_dir);
602     return 0;
603 }
604 
605 
606 /**
607  * Read the client working directory from in_fd socket,
608  * and set up the server side directory corresponding to that.
609  * Inputs:
610  *   @p in_fd: the file descriptor for the socket.
611  * Outputs:
612  *   @p temp_dir: a temporary directory on the server,
613  *                corresponding to the client's root directory (/),
614  *   @p client_side_cwd: the current directory on the client
615  *   @p server_side_cwd: the corresponding directory on the server;
616  *                server_side_cwd = temp_dir + client_side_cwd
617  **/
make_temp_dir_and_chdir_for_cpp(int in_fd,char ** temp_dir,char ** client_side_cwd,char ** server_side_cwd)618 static int make_temp_dir_and_chdir_for_cpp(int in_fd,
619         char **temp_dir, char **client_side_cwd, char **server_side_cwd)
620 {
621 
622         int ret = 0;
623 
624         if ((ret = dcc_get_new_tmpdir(temp_dir)))
625             return ret;
626         if ((ret = dcc_r_cwd(in_fd, client_side_cwd)))
627             return ret;
628 
629         checked_asprintf(server_side_cwd, "%s%s", *temp_dir, *client_side_cwd);
630         if (*server_side_cwd == NULL) {
631             ret = EXIT_OUT_OF_MEMORY;
632         } else if ((ret = dcc_mk_tmp_ancestor_dirs(*server_side_cwd))) {
633             ; /* leave ret the way it is */
634         } else if ((ret = dcc_mk_tmpdir(*server_side_cwd))) {
635             ; /* leave ret the way it is */
636         } else if (chdir(*server_side_cwd) == -1) {
637             ret = EXIT_IO_ERROR;
638         }
639         return ret;
640 }
641 
642 
643 /**
644  * Read a request, run the compiler, and send a response.
645  **/
dcc_run_job(int in_fd,int out_fd)646 static int dcc_run_job(int in_fd,
647                        int out_fd)
648 {
649     char **argv = NULL;
650     char **tweaked_argv = NULL;
651     int status = 0;
652     char *temp_i = NULL, *temp_o = NULL;
653     char *err_fname = NULL, *out_fname = NULL, *deps_fname = NULL;
654     char *temp_dir = NULL; /* for receiving multiple files */
655     int ret = 0, compile_ret = 0;
656     char *orig_input = NULL, *orig_output = NULL;
657     char *orig_input_tmp, *orig_output_tmp;
658     char *dotd_target = NULL;
659     pid_t cc_pid;
660     enum dcc_protover protover;
661     enum dcc_compress compr;
662     struct timeval start, end;
663     int time_ms;
664     char *time_str;
665     int job_result = -1;
666     enum dcc_cpp_where cpp_where;
667     char *server_cwd = NULL;
668     char *client_cwd = NULL;
669     int changed_directory = 0;
670 
671     gettimeofday(&start, NULL);
672 
673     if ((ret = dcc_make_tmpnam("distcc", ".deps", &deps_fname)))
674         goto out_cleanup;
675     if ((ret = dcc_make_tmpnam("distcc", ".stderr", &err_fname)))
676         goto out_cleanup;
677     if ((ret = dcc_make_tmpnam("distcc", ".stdout", &out_fname)))
678         goto out_cleanup;
679 
680     dcc_remove_if_exists(deps_fname);
681     dcc_remove_if_exists(err_fname);
682     dcc_remove_if_exists(out_fname);
683 
684     /* Capture any messages relating to this compilation to the same file as
685      * compiler errors so that they can all be sent back to the client. */
686     dcc_add_log_to_file(err_fname);
687 
688     /* Ignore SIGPIPE; we consistently check error codes and will see the
689      * EPIPE.  Note that it is set back to the default behaviour when spawning
690      * a child, to handle cases like the assembler dying while its being fed
691      * from the compiler */
692     dcc_ignore_sigpipe(1);
693 
694     /* Allow output to accumulate into big packets. */
695     tcp_cork_sock(out_fd, 1);
696 
697     if ((ret = dcc_r_request_header(in_fd, &protover)))
698         goto out_cleanup;
699 
700     dcc_get_features_from_protover(protover, &compr, &cpp_where);
701 
702     if (cpp_where == DCC_CPP_ON_SERVER) {
703         if ((ret = make_temp_dir_and_chdir_for_cpp(in_fd,
704                           &temp_dir, &client_cwd, &server_cwd)))
705             goto out_cleanup;
706         changed_directory = 1;
707     }
708 
709     if ((ret = dcc_r_argv(in_fd, "ARGC", "ARGV", &argv))
710         || (ret = dcc_scan_args(argv, &orig_input_tmp, &orig_output_tmp,
711                                 &tweaked_argv)))
712         goto out_cleanup;
713 
714     /* The orig_input_tmp and orig_output_tmp values returned by dcc_scan_args()
715      * are aliased with some element of tweaked_argv.  We need to copy them,
716      * because the calls to dcc_set_input() and dcc_set_output() below will
717      * free those elements. */
718     orig_input = strdup(orig_input_tmp);
719     orig_output = strdup(orig_output_tmp);
720     if (orig_input == NULL || orig_output == NULL) {
721       ret = EXIT_OUT_OF_MEMORY;
722       goto out_cleanup;
723     }
724 
725     /* Our new argv is what dcc_scan_args put into tweaked_argv */
726     /* Put tweaked_argv into argv, and free old argv */
727     dcc_free_argv(argv);
728     argv = tweaked_argv;
729     tweaked_argv = NULL;
730 
731     rs_trace("output file %s", orig_output);
732     if ((ret = dcc_make_tmpnam("distccd", ".o", &temp_o)))
733         goto out_cleanup;
734 
735     /* if the protocol is multi-file, then we need to do the following
736      * in a loop.
737      */
738     if (cpp_where == DCC_CPP_ON_SERVER) {
739         if (dcc_r_many_files(in_fd, temp_dir, compr)
740             || dcc_set_output(argv, temp_o)
741             || tweak_arguments_for_server(argv, temp_dir, deps_fname,
742                                           &dotd_target, &tweaked_argv))
743             goto out_cleanup;
744         /* Repeat the switcharoo trick a few lines above. */
745         dcc_free_argv(argv);
746         argv = tweaked_argv;
747         tweaked_argv = NULL;
748     } else {
749         if ((ret = dcc_input_tmpnam(orig_input, &temp_i)))
750             goto out_cleanup;
751         if ((ret = dcc_r_token_file(in_fd, "DOTI", temp_i, compr))
752             || (ret = dcc_set_input(argv, temp_i))
753             || (ret = dcc_set_output(argv, temp_o)))
754             goto out_cleanup;
755     }
756 
757     if (!dcc_remap_compiler(&argv[0]))
758         goto out_cleanup;
759 
760     if ((ret = dcc_check_compiler_masq(argv[0])))
761         goto out_cleanup;
762 
763     if (!opt_enable_tcp_insecure &&
764         !getenv("DISTCC_CMDLIST") &&
765         dcc_check_compiler_whitelist(argv[0]))
766         goto out_cleanup;
767 
768     /* unsafe compiler options. See  https://youtu.be/bSkpMdDe4g4?t=53m12s
769        on securing https://godbolt.org/ */
770     char *a;
771     int i;
772     for (i = 0; (a = argv[i]); i++)
773         if (strncmp(a, "-fplugin=", strlen("-fplugin=")) == 0 ||
774             strncmp(a, "-specs=", strlen("-specs=")) == 0) {
775             rs_log_warning("-fplugin= and/or -specs= passed, which are insecure and not supported.");
776             goto out_cleanup;
777     }
778 
779     if ((compile_ret = dcc_spawn_child(argv, &cc_pid,
780                                        "/dev/null", out_fname, err_fname))
781         || (compile_ret = dcc_collect_child("cc", cc_pid, &status, in_fd))) {
782         /* We didn't get around to finding a wait status from the actual
783          * compiler */
784         status = W_EXITCODE(compile_ret, 0);
785     }
786 
787     if ((ret = dcc_x_result_header(out_fd, protover))
788         || (ret = dcc_x_cc_status(out_fd, status))
789         || (ret = dcc_x_file(out_fd, err_fname, "SERR", compr, NULL))
790         || (ret = dcc_x_file(out_fd, out_fname, "SOUT", compr, NULL))) {
791           /* We get a protocol derailment if we send DOTO 0 here */
792 
793         if (job_result == -1)
794             job_result = STATS_COMPILE_ERROR;
795     } else if (WIFSIGNALED(status) || WEXITSTATUS(status)) {
796         /* Something went wrong, so send DOTO 0 */
797         dcc_x_token_int(out_fd, "DOTO", 0);
798 
799         if (job_result == -1)
800             job_result = STATS_COMPILE_ERROR;
801     } else {
802         if (cpp_where == DCC_CPP_ON_SERVER) {
803           rs_trace("fixing up debug info");
804           /*
805            * We update the debugging information, replacing all occurrences
806            * of temp_dir (the server temp directory that corresponds to the
807            * client's root directory) with "/", to convert server path
808            * names to client path names.  This is safe to do only because
809            * temp_dir is of the form "/var/tmp/distccd-XXXXXX" where XXXXXX
810            * is randomly chosen by mkdtemp(), which makes it inconceivably
811            * unlikely that this pattern could occur in the debug info by
812            * chance.
813            */
814           if ((ret = dcc_fix_debug_info(temp_o, "/", temp_dir)))
815             goto out_cleanup;
816         }
817         if ((ret = dcc_x_file(out_fd, temp_o, "DOTO", compr, NULL)))
818             goto out_cleanup;
819 
820         if (cpp_where == DCC_CPP_ON_SERVER) {
821             char *cleaned_dotd;
822             ret = dcc_cleanup_dotd(deps_fname,
823                                    &cleaned_dotd,
824                                    temp_dir,
825                                    dotd_target ? dotd_target : orig_output,
826                                    temp_o);
827             if (ret) goto out_cleanup;
828             ret = dcc_x_file(out_fd, cleaned_dotd, "DOTD", compr, NULL);
829             free(cleaned_dotd);
830         }
831 
832         job_result = STATS_COMPILE_OK;
833     }
834 
835     if (compile_ret == EXIT_IO_ERROR) {
836         job_result = STATS_CLI_DISCONN;
837     } else if (compile_ret == EXIT_TIMEOUT) {
838         job_result = STATS_COMPILE_TIMEOUT;
839     }
840 
841     dcc_critique_status(status, argv[0], orig_input, dcc_hostdef_local,
842                         0);
843     tcp_cork_sock(out_fd, 0);
844 
845     rs_log(RS_LOG_INFO|RS_LOG_NONAME, "job complete");
846 
847 out_cleanup:
848 
849     /* Restore the working directory, if needed. */
850     if (changed_directory) {
851       if (chdir(dcc_daemon_wd) != 0) {
852         rs_log_warning("chdir(%s) failed: %s", dcc_daemon_wd, strerror(errno));
853       }
854     }
855 
856     switch (ret) {
857     case EXIT_BUSY: /* overloaded */
858         job_result = STATS_REJ_OVERLOAD;
859         break;
860     case EXIT_IO_ERROR: /* probably client disconnected */
861         job_result = STATS_CLI_DISCONN;
862         break;
863     case EXIT_PROTOCOL_ERROR:
864         job_result = STATS_REJ_BAD_REQ;
865         break;
866     default:
867         if (job_result != STATS_COMPILE_ERROR
868             && job_result != STATS_COMPILE_OK
869         && job_result != STATS_CLI_DISCONN
870         && job_result != STATS_COMPILE_TIMEOUT) {
871             job_result = STATS_OTHER;
872         }
873     }
874 
875     gettimeofday(&end, NULL);
876     time_ms = (end.tv_sec - start.tv_sec) * 1000 + (end.tv_usec - start.tv_usec) / 1000;
877 
878     dcc_job_summary_append(" ");
879     dcc_job_summary_append(stats_text[job_result]);
880 
881     if (job_result == STATS_COMPILE_OK) {
882         /* special case, also log compiler, file and time */
883         dcc_stats_compile_ok(argv[0], orig_input, start, end, time_ms);
884     } else {
885         dcc_stats_event(job_result);
886     }
887 
888     checked_asprintf(&time_str, " exit:%d sig:%d core:%d ret:%d time:%dms ",
889                      WEXITSTATUS(status), WTERMSIG(status), WCOREDUMP(status),
890                      ret, time_ms);
891     if (time_str != NULL) dcc_job_summary_append(time_str);
892     free(time_str);
893 
894     /* append compiler and input file info */
895     if (job_result == STATS_COMPILE_ERROR
896         || job_result == STATS_COMPILE_OK) {
897         dcc_job_summary_append(argv[0]);
898         dcc_job_summary_append(" ");
899         dcc_job_summary_append(orig_input);
900     }
901 
902     dcc_remove_log_to_file();
903     dcc_cleanup_tempfiles();
904 
905     free(orig_input);
906     free(orig_output);
907 
908     if (argv)
909         dcc_free_argv(argv);
910     if (tweaked_argv)
911         dcc_free_argv(tweaked_argv);
912 
913     free(temp_dir);
914     free(temp_i);
915     free(temp_o);
916 
917     free(deps_fname);
918     free(err_fname);
919     free(out_fname);
920 
921     free(client_cwd);
922     free(server_cwd);
923 
924     return ret;
925 }
926