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