/* -*- c-file-style: "java"; indent-tabs-mode: nil -*- * * distcc -- A simple distributed compiler system * * Copyright (C) 2002, 2003, 2004 by Martin Pool * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA */ /* "More computing sins are committed in the name of * efficiency (without necessarily achieving it) than * for any other single reason - including blind * stupidity." -- W.A. Wulf */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include "distcc.h" #include "trace.h" #include "util.h" #include "snprintf.h" #include "exitcode.h" /** * @file * * Routines for naming, generating and removing temporary files. * * Temporary files are stored under $TMPDIR or /tmp. * * From 2.10 on, our lock and state files are not stored in there. * * It would be nice if we could use a standard function, but I don't * think any of them are adequate: we need to control the extension * and know the filename. tmpfile() does not give back the filename. * tmpnam() is insecure. mkstemp() does not allow us to set the * extension. * * It sucks that there is no standard function. The implementation * below is inspired by the __gen_tempname() code in glibc; hopefully * it will be secure on all platforms. * * We need to touch the filename before running commands on it, * because we cannot be sure that the compiler will create it * securely. * * Even with all this, we are not necessarily secure in the presence * of a tmpreaper if the attacker can play timing tricks. However, * since we are not setuid and since there is no completely safe way * to write tmpreapers, this is left alone for now. * * If you're really paranoid, you should just use per-user TMPDIRs. * * @sa http://www.dwheeler.com/secure-programs/Secure-Programs-HOWTO/avoid-race.html#TEMPORARY-FILES **/ /* Attempts to set the owner and group of path to be the same as the directory containing it. Returns 0 on success, nonzero on error. */ int dcc_set_owner(const char *path) { int result = 1; char base[MAXPATHLEN]; int i, lastSlash; struct stat st; for (i=0; path[i]!=0; i++) { base[i] = path[i]; if (path[i] == '/') lastSlash = i; } base[lastSlash] = 0; if (stat(base, &st) == 0) { if (chown(path, st.st_uid, st.st_gid) == 0) result = 0; } return result; } int dcc_get_tmp_top(const char **p_ret) { static const char *d = NULL; if (d == NULL) { d = getenv("TMPDIR"); if (!d || d[0] == '\0') { d = "/tmp/distcc"; if (dcc_mkdir(d) == 0) { chmod(d, 0777); } else { d = "/tmp"; } } } *p_ret = d; return 0; } /** * Create the directory @p path. If it already exists as a directory * we succeed. **/ int dcc_mkdir(const char *path) { if ((mkdir(path, 0777) == -1) && (errno != EEXIST)) { rs_log_error("mkdir %s failed: %s", path, strerror(errno)); return EXIT_IO_ERROR; } dcc_set_owner(path); return 0; } /** * Return a static string holding DISTCC_DIR, or ~/.distcc. * The directory is created if it does not exist. **/ int dcc_get_top_dir(char **path_ret) { char *env; static char *cached; int ret; if (cached) { *path_ret = cached; return 0; } if ((env = getenv("DISTCC_DIR"))) { if ((cached = strdup(env)) == NULL) { return EXIT_OUT_OF_MEMORY; } else { *path_ret = cached; return 0; } } if ((env = getenv("HOME")) == NULL) { rs_log_warning("HOME is not set; can't find distcc directory"); return EXIT_BAD_ARGUMENTS; } if (asprintf(path_ret, "%s/.distcc", env) == -1) { rs_log_error("asprintf failed"); return EXIT_OUT_OF_MEMORY; } ret = dcc_mkdir(*path_ret); if (ret == 0) cached = *path_ret; return ret; } /** * Return a subdirectory of the DISTCC_DIR of the given name, making * sure that the directory exists. **/ static int dcc_get_subdir(const char *name, char **dir_ret) { int ret; char *topdir; if ((ret = dcc_get_top_dir(&topdir))) return ret; if (asprintf(dir_ret, "%s/%s", topdir, name) == -1) { rs_log_error("asprintf failed"); return EXIT_OUT_OF_MEMORY; } return dcc_mkdir(*dir_ret); } int dcc_get_lock_dir(char **dir_ret) { static char *cached; int ret; if (cached) { *dir_ret = cached; return 0; } else { ret = dcc_get_subdir("lock", dir_ret); if (ret == 0) cached = *dir_ret; return ret; } } int dcc_get_state_dir(char **dir_ret) { static char *cached; int ret; if (cached) { *dir_ret = cached; return 0; } else { ret = dcc_get_subdir("state", dir_ret); if (ret == 0) cached = *dir_ret; return ret; } } /** * Create a file inside the temporary directory and register it for * later cleanup, and return its name. * * The file will be reopened later, possibly in a child. But we know * that it exists with appropriately tight permissions. **/ int dcc_make_tmpnam(const char *prefix, const char *suffix, char **name_ret) { char *s = NULL; const char *tempdir; int ret; unsigned long random_bits; int fd; if ((ret = dcc_get_tmp_top(&tempdir))) return ret; if (access(tempdir, W_OK|X_OK) == -1) { rs_log_error("can't use TMPDIR \"%s\": %s", tempdir, strerror(errno)); return EXIT_IO_ERROR; } random_bits = (unsigned long) getpid() << 16; # if HAVE_GETTIMEOFDAY { struct timeval tv; gettimeofday(&tv, NULL); random_bits ^= tv.tv_usec << 16; random_bits ^= tv.tv_sec; } # else random_bits ^= time(NULL); # endif #if 0 random_bits = 0; /* FOR TESTING */ #endif do { free(s); if (asprintf(&s, "%s/%s_%08lx%s", tempdir, prefix, random_bits & 0xffffffffUL, suffix) == -1) return EXIT_OUT_OF_MEMORY; /* Note that if the name already exists as a symlink, this * open call will fail. * * The permissions are tight because nobody but this process * and our children should do anything with it. */ fd = open(s, O_WRONLY | O_CREAT | O_EXCL, 0600); if (fd == -1) { /* try again */ rs_trace("failed to create %s: %s", s, strerror(errno)); random_bits += 7777; /* fairly prime */ continue; } if (close(fd) == -1) { /* huh? */ rs_log_warning("failed to close %s: %s", s, strerror(errno)); return EXIT_IO_ERROR; } break; } while (1); if ((ret = dcc_add_cleanup(s))) { free(s); return ret; } *name_ret = s; return 0; }