# Needed to let distcc distinguish commands by partial paths
# instead of just by full paths or without paths
# Lets multiple independent toolchains coexist better
# Puts temporary files in /var/tmp/distcc to avoid overflowing /tmp
# Dan Kegel
# Copyright 2005, Google
# Licensed under the GPL for redistribution with distcc

diff -Naur distcc-2.18.3.orig/Makefile.in distcc-2.18.3/Makefile.in
--- distcc-2.18.3.orig/Makefile.in	2004-10-23 22:05:48.000000000 -0700
+++ distcc-2.18.3/Makefile.in	2005-09-16 18:50:48.000000000 -0700
@@ -186,6 +186,7 @@
 	src/daemon.o  src/dopt.o src/dparent.o src/dsignal.o		\
 	src/ncpus.o							\
 	src/prefork.o							\
+	src/stringmap.o							\
 	src/serve.o src/setuid.o src/srvnet.o src/srvrpc.o src/state.o	\
 	$(common_obj) @BUILD_POPT@
 
@@ -236,7 +237,8 @@
 	src/remote.c src/renderer.c src/rpc.c				\
 	src/safeguard.c src/sendfile.c src/setuid.c src/serve.c		\
 	src/snprintf.c src/state.c					\
-	src/srvnet.c src/srvrpc.c src/ssh.c src/strip.c			\
+	src/srvnet.c src/srvrpc.c src/ssh.c 				\
+	src/stringmap.c src/strip.c					\
 	src/tempfile.c src/timefile.c                     		\
 	src/timeval.c src/traceenv.c					\
 	src/trace.c src/util.c src/where.c				
@@ -252,6 +254,7 @@
 	src/netutil.h							\
 	src/renderer.h src/rpc.h					\
 	src/snprintf.h src/state.h		 			\
+	src/stringmap.h							\
 	src/timefile.h src/timeval.h src/trace.h			\
 	src/types.h							\
 	src/util.h							\
diff -Naur distcc-2.18.3.orig/src/serve.c distcc-2.18.3/src/serve.c
--- distcc-2.18.3.orig/src/serve.c	2004-10-23 22:05:49.000000000 -0700
+++ distcc-2.18.3/src/serve.c	2005-09-16 18:50:48.000000000 -0700
@@ -83,7 +83,7 @@
 #include "srvnet.h"
 #include "hosts.h"
 #include "daemon.h"
-
+#include "stringmap.h"
 
 /**
  * We copy all serious distccd messages to this file, as well as sending the
@@ -180,6 +180,83 @@
 
 
 /**
+ * Check argv0 against a list of allowed commands, and possibly map it to a new value.
+ * If *compiler_name is changed, the original value is free'd, and a new value is malloc'd.
+ *
+ * If the environment variable DISTCC_CMDLIST is set,
+ * load a list of supported commands from the file named by DISTCC_CMDLIST, and
+ * refuse to serve any command whose last DISTCC_CMDLIST_MATCHWORDS last words
+ * don't match those of a command in that list.
+ * Each line of the file is simply a filename.
+ * This is chiefly useful for those few installations which have so many 
+ * compilers available such that the compiler must be specified with an absolute pathname.
+ *
+ * Example: if the compilers are installed in a different location on 
+ * this server, e.g. if they've been copied from a shared NFS directory onto a 
+ * local hard drive, you might have lines like
+ *   /local/tools/blort/sh4-linux/gcc-3.3.3-glibc-2.2.5/bin/sh4-linux-gcc
+ *   /local/tools/blort/sh4-linux/gcc-2.95.3-glibc-2.2.5/bin/sh4-linux-gcc
+ * and set DISTCC_CMDLIST_MATCHWORDS=3; that way e.g. any of the commands
+ *   /local/tools/gcc-3.3.3-glibc-2.2.5/bin/sh4-linux-gcc
+ *   /shared/tools/gcc-3.3.3-glibc-2.2.5/bin/sh4-linux-gcc
+ *   /zounds/gcc-3.3.3-glibc-2.2.5/bin/sh4-linux-gcc
+ * will invoke 
+ *   /local/tools/blort/sh4-linux/gcc-3.3.3-glibc-2.2.5/bin/sh4-linux-gcc
+ *
+ * Returns 0 (which will abort the compile) if compiler not in list. 
+ * (This is because the list is intended to be complete,
+ * and any attempt to use a command not in the list indicates a confused user.
+ * FIXME: should probably give user the option of changing this
+ * behavior at runtime, so normal command lookup can continue even if command
+ * not found in table.)
+ **/
+static int dcc_remap_compiler(char **compiler_name)
+{
+	static int cmdlist_checked=0;
+	static stringmap_t *map=0;
+	const char *newname;
+
+	/* load file if not already */
+	if (!cmdlist_checked) {
+		char *filename;
+		cmdlist_checked = 1;
+		filename = getenv("DISTCC_CMDLIST");
+		if (filename) {
+			const char *nw = getenv("DISTCC_CMDLIST_NUMWORDS");
+			int numFinalWordsToMatch=1;
+			if (nw)
+				numFinalWordsToMatch = atoi(nw);
+			map = stringmap_load(filename, numFinalWordsToMatch);
+			if (map) {
+				rs_trace("stringmap_load(%s, %d) found %d commands", filename, numFinalWordsToMatch, map->n);
+			} else {
+				rs_log_error("stringmap_load(%s, %d) failed: %s", filename, numFinalWordsToMatch, strerror(errno));
+				return EXIT_IO_ERROR;
+			}
+		}
+	}
+
+	if (!map)
+		return 1;	/* no list of allowed names, so ok */
+
+	/* Find what this compiler maps to */
+	newname = stringmap_lookup(map, *compiler_name);
+	if (!newname) {
+		rs_log_warning("lookup of %s in DISTCC_CMDLIST failed", *compiler_name);
+		return 0;	/* not in list, so forbidden.  FIXME: make failure an option */
+	}
+
+	/* If mapping is not the identity mapping, replace the original name */
+	if (strcmp(newname, *compiler_name)) {
+		rs_trace("changed compiler from %s to %s", *compiler_name, newname);
+		free(*compiler_name);
+		*compiler_name = strdup(newname);
+	}
+	return 1;
+}
+
+
+/**
  * 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.
  *
@@ -301,6 +378,9 @@
         || (ret = dcc_set_output(argv, temp_o)))
         goto out_cleanup;
 
+    if (!dcc_remap_compiler(&argv[0]))
+	goto out_cleanup;
+
     if ((ret = dcc_check_compiler_masq(argv[0])))
         goto out_cleanup;
 
diff -Naur distcc-2.18.3.orig/src/stringmap.c distcc-2.18.3/src/stringmap.c
--- distcc-2.18.3.orig/src/stringmap.c	1969-12-31 16:00:00.000000000 -0800
+++ distcc-2.18.3/src/stringmap.c	2005-09-16 18:50:48.000000000 -0700
@@ -0,0 +1,116 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <assert.h>
+#include "stringmap.h"
+
+#ifndef NULL
+#define NULL 0
+#endif
+
+/* Load the given list of strings into the key/value map.
+ * The key for each string is the numFinalWordsToMatch of the string;
+ * the value for each string is the entire string.
+ * FIXME: doesn't work for utf-8 strings, since it scans raw chars for /
+ */
+stringmap_t *stringmap_load(const char *filename, int numFinalWordsToMatch)
+{
+	stringmap_t *result = calloc(1, sizeof(*result));
+	FILE *fp = fopen(filename, "r");
+	char buf[2*PATH_MAX];
+	int n;
+
+	result->numFinalWordsToMatch = numFinalWordsToMatch;
+	if (!fp)
+		return NULL;
+	n=0;
+	while (fgets(buf, sizeof(buf), fp))
+		n++;
+	result->n = n;
+	result->map = malloc(n * sizeof(result->map[0]));
+
+	rewind(fp);
+	n=0;
+	while (fgets(buf, sizeof(buf), fp)) {
+		int pos, w;
+
+		int len = strlen(buf);
+		/* strip trailing \n */
+		if (len > 0 && buf[len-1] == '\n') {
+			buf[len-1] = 0;
+			len--;
+		}
+		/* set pos to the start of the significant part of the string */
+		for (pos=len-1, w=0; pos>0; pos--) {
+			if (buf[pos] == '/') {
+				w++;
+				if (w >= numFinalWordsToMatch)
+					break;
+			}
+		}
+
+		result->map[n].value = strdup(buf);
+		result->map[n].key = strdup(buf+pos);
+		n++;
+	} fclose(fp);
+	return result;
+}
+
+const char *stringmap_lookup(const stringmap_t *map, const char *string)
+{
+	int i, w;
+	int len = strlen(string);
+	int pos;
+	for (pos=len-1, w=0; pos>0; pos--) {
+		if (string[pos] == '/') {
+			w++;
+			if (w >= map->numFinalWordsToMatch)
+				break;
+		}
+	}
+	for (i=0; i<map->n; i++) {
+		/*printf("Comparing %s and %s\n", map->map[i].key, string+pos);*/
+		if (!strcmp(map->map[i].key, string+pos))
+			return map->map[i].value;
+	}
+	return NULL;
+}
+
+#if 0
+
+void dumpMap(stringmap_t *sm)
+{
+	int i;
+	printf("map has %d elements, and numFinalWordsToMatch is %d\n", sm->n, sm->numFinalWordsToMatch);
+	for (i=0; i < sm->n; i++) {
+		printf("row %d: key %s, value %s\n", i, sm->map[i].key, sm->map[i].value);
+	}
+}
+
+#define verifyMap(sm, a, b) { \
+	const char *c = stringmap_lookup(sm, a); \
+	if (!b) \
+		assert(!c); \
+	else { \
+		assert(c); \
+		assert(!strcmp(b, c)); } }
+	
+main(int argc, char **argv)
+{
+	FILE *fp;
+	stringmap_t *sm;
+
+	fp = fopen("stringmap_test.dat", "w");
+	fprintf(fp, "/foo/bar/bletch\n");
+	fclose(fp);
+
+
+	sm = stringmap_load("stringmap_test.dat", 1);
+	dumpMap(sm);
+	verifyMap(sm, "/bar/bletch", "/foo/bar/bletch");
+	verifyMap(sm, "bletch", NULL);
+	verifyMap(sm, "/foo/bar/bletch", "/foo/bar/bletch");
+}
+
+#endif
diff -Naur distcc-2.18.3.orig/src/stringmap.h distcc-2.18.3/src/stringmap.h
--- distcc-2.18.3.orig/src/stringmap.h	1969-12-31 16:00:00.000000000 -0800
+++ distcc-2.18.3/src/stringmap.h	2005-09-16 18:50:48.000000000 -0700
@@ -0,0 +1,27 @@
+#ifndef STRINGMAP_H
+#define STRINGMAP_H
+
+typedef struct {
+	/* the strings, and what they map to */
+	struct {
+		char *key;
+		char *value;
+	} *map;
+
+	/* number of elements in map */
+	int n;
+
+	/* if nonzero, ignore all but this many trailing words,
+         * where words are separated by the '/' char
+	 * Example:
+	 * 	comparison	num=1	num=2	num=3	
+	 *	a/b/z =? 1/y/z	match	no	no
+	 *	a/b/z =? 1/b/z	match	match	no
+	 */
+	int numFinalWordsToMatch;
+} stringmap_t;
+
+stringmap_t *stringmap_load(const char *filename, int numFinalWordsToMatch);
+const char *stringmap_lookup(const stringmap_t *map, const char *string);
+
+#endif
diff -Naur distcc-2.18.3.orig/packaging/RedHat/init.d/distcc distcc-2.18.3/packaging/RedHat/init.d/distcc
--- distcc-2.18.3.orig/packaging/RedHat/init.d/distcc	1969-12-31 16:00:00.000000000 -0800
+++ distcc-2.18.3/packaging/RedHat/init.d/distcc	2005-09-17 22:23:19.000000000 -0700
@@ -0,0 +1,137 @@
+#!/bin/bash
+#
+#	/etc/rc.d/init.d/distcc
+#
+# Starts the distccd daemon
+#
+# WARNING: Don't enable on untrusted networks
+#
+# NOTE: Allows access from all networks listed in clients.allow
+# clients.allow is searched for in directories
+#   /etc/distcc/`dnsdomainname`/clients.allow
+#   /etc/distcc/clients.allow
+# Format of clients.allow is 
+#   addr/size
+#   addr/size
+#   addr/size
+# etc. e.g.
+#   196.168.0.0/16
+#   127.0.0.0/8
+#
+# chkconfig: - 80 20
+# description: Start the distcc daemon
+# processname: distccd
+#
+
+# Source function library.
+. /etc/init.d/functions
+
+# Source networking configuration.
+. /etc/sysconfig/network
+
+# Check that networking is up.
+[ ${NETWORKING} = "no" ] && exit 0
+
+OPTIONS="--daemon --stats --job-lifetime=1200"
+USER=distcc
+DOMAIN=`/bin/dnsdomainname`
+CONFIGDIR=/etc/distcc
+
+# Tell distccd to store temp files in /var/tmp/distcc to avoid filling up /
+# The sysadmin may want to make /var/tmp/distcc be a symlink to a
+# directory on a larger partition (hence the /. on the chown)
+TMPDIR=/var/tmp/distcc
+export TMPDIR
+mkdir -p $TMPDIR
+chown $USER $TMPDIR/.
+rm -rf $TMPDIR/* || :
+chmod 700 $TMPDIR
+
+RETVAL=0
+prog="distccd"
+
+[ -x /usr/bin/distccd ] || exit 0
+
+ALLOWFILE=""
+if test -f $CONFIGDIR/$DOMAIN/clients.allow; then
+	ALLOWFILE=$CONFIGDIR/$DOMAIN/clients.allow
+elif test -f $CONFIGDIR/clients.allow; then
+	ALLOWFILE=$CONFIGDIR/clients.allow
+fi
+
+if test "$ALLOWFILE"x != x; then
+        ALLOW_OPTIONS=`sed 's/^/--allow=/' < $ALLOWFILE`
+	OPTIONS="$OPTIONS $ALLOW_OPTIONS"
+fi
+
+start() {
+	echo -n "Starting $prog"
+
+        #--- begin crosstool-specific configuration ---
+	# List directories which might contain installations of crosstool on this node
+	# This will let users compile on this node regardless of where crosstool
+	# is installed on client or server
+	# Neccessary because crosstool compilers are identified not just by
+	# a command name, but also by the directory they're in (if you have
+	# several toolchains built for a particular target).
+	COMPILERS=
+	# modern crosstool 
+	test -d /usr/crosstool && COMPILERS="$COMPILERS /usr/crosstool/*/*/bin/*-[cg][+c][+c]"
+	# old crosstool (which stored everything in a directory named for the build system type)
+	test -d /opt/crosstool && COMPILERS="$COMPILERS /opt/crosstool/*/*/*/bin/*-[cg][+c][+c]"
+	if test ! -d $CONFIGDIR; then
+		mkdir -p $CONFIGDIR
+	fi
+	CMDLIST=$CONFIGDIR/commands.allow
+	# Expand the wildcards in $COMPILERS, put one file per line, empty file if no match
+	echo $COMPILERS | tr '\040' '\012' > $CMDLIST
+	ENV="DISTCC_CMDLIST=$CMDLIST DISTCC_CMDLIST_NUMWORDS=4 TMPDIR=$TMPDIR"
+        #--- done crosstool-specific configuration ---
+
+	daemon --user $USER $ENV $prog $OPTIONS
+	RETVAL=$?
+	echo
+	[ $RETVAL -eq 0 ] && touch /var/lock/subsys/$prog
+        return $RETVAL
+}
+
+stop() {
+	echo -n "Shutting down $prog"
+	killproc $prog
+	RETVAL=$?
+        echo
+        [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/$prog
+        return $RETVAL
+}
+
+# See how we were called.
+case "$1" in
+	start)
+		start
+		;;
+	stop)
+		stop
+		;;
+	status)
+		status $prog
+		RETVAL=$?
+		;;
+	restart|reload)
+		stop
+		start
+		RETVAL=$?
+		;;
+	condrestart)
+		if [ -f /var/lock/subsys/$prog ]; then
+			stop
+			start
+			RETVAL=$?
+		fi
+		;;
+	*)
+		echo $"Usage $0 {start|stop|restart|condrestart|status}"
+		exit 1
+esac
+
+exit $RETVAL
+
diff -Naur distcc-2.18.3.orig/packaging/RedHat/xinetd.d/distcc distcc-2.18.3/packaging/RedHat/xinetd.d/distcc
--- distcc-2.18.3.orig/packaging/RedHat/xinetd.d/distcc	1969-12-31 16:00:00.000000000 -0800
+++ distcc-2.18.3/packaging/RedHat/xinetd.d/distcc	2005-09-16 18:50:48.000000000 -0700
@@ -0,0 +1,15 @@
+# default: on
+# description:	distccd serves C/C++ compilation requests from distcc clients.
+#		WARNING: This service should ONLY be used on trusted networks.
+service distcc
+{
+	socket_type	= stream        
+	protocol	= tcp
+	port		= 3632
+	wait		= no
+	user		= nobody
+	server		= /usr/bin/distccd
+	server_args	= --inetd
+	disable		= yes
+}
+
