#!/usr/bin/env bash # -*- tab-width: 4; sh-basic-offset: 4 -*- # distcc-tool # Copyright (C) 2013-2014 Luke Shumaker # # License: GNU GPLv3+ # # This file is part of Parabola. # # Parabola 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 3 of the License, or # (at your option) any later version. # # Parabola 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 Parabola. If not, see . # This program has very few dependencies: # - bash: I don't know what version, I use fairly modern features # - socat # - cat: any version # - rm: any version # - sed: any version # On Parabola, this means the packages: # bash, coreutils, sed, socat if ! type gettext &>/dev/null; then gettext() { echo "$@"; } fi q0="$(printf '%q' "$0")" # quoted $0 panic() { echo "$(gettext 'panic: malformed call to internal function')" >&2 exit 1 } error() { mesg="$(gettext "$1")"; shift printf "$(gettext 'ERROR:') $mesg\n" "$@" >&2 exit 1 } print() { local mesg=$1 shift printf -- "$(gettext "$mesg")\n" "$@" } usage() { print "Usage: %s COMMAND [COMMAND-ARGS]" "$q0" print "Tool for using distcc within a networkless chroot" echo print "Commands:" print ' help print this message' print ' odaemon CHROOTPATH daemon to run outside of the chroot' print ' idaemon DISTCC_HOSTS daemon to run inside of the chroot' print ' rewrite DISTCC_HOSTS prints a rewritten version of DISTCC_HOSTS' print ' client HOST PORT connects stdio to TCP:$HOST:$PORT' print 'Commands: for internal use' print ' server counterpart to client; spawned by odaemon' } errusage() { if [[ $# -gt 0 ]]; then fmt="$(gettext "$1")"; shift printf "$(gettext 'ERROR:') $fmt\n" "$@" >&2 fi usage >&2 exit 1 } main() { local cmd=$1 shift case "$cmd" in help) [[ $# -eq 0 ]] || errusage '%s: invalid number of arguments' "$cmd" usage;; odaemon|idaemon|rewrite) [[ $# -eq 1 ]] || errusage '%s: invalid number of arguments' "$cmd" $cmd "$@";; client) [[ $# -eq 2 ]] || errusage '%s: invalid number of arguments' "$cmd" $cmd "$@";; server) [[ $# -eq 0 ]] || errusage '%s: invalid number of arguments' "$cmd" $cmd "$@";; *) errusage 'unknown subcommand: %s' "$cmd";; esac } ################################################################################ # DISTCC_HOSTS parser # ################################################################################ # usage: parse_DISTCC_HOSTS true|false DISTCC_HOSTS # parses DISTCC_HOSTS and: # $1==true : It sets up port forwarding for inside the choot, sleep forever # $1==false: Prints a modified version of DISTCC_HOSTS that uses the forwarded # ports that were set up when $1==true. parse_DISTCC_HOSTS() { { [[ $# -eq 2 ]] && { [[ $1 == true ]] || [[ $1 == false ]]; }; } || panic local forward_ports=$1 local DISTCC_HOSTS=$2 local pids=() # child pids local newhosts=() local newport=8000 # next port to be used for port forwarding # This is based on the grammar specified in distcc(1) local HOSTSPEC for HOSTSPEC in $(sed 's/#.*//' <<<"$DISTCC_HOSTS"); do case "$HOSTSPEC" in # LOCAL_HOST localhost|localhost/*|--localslots=*|--localslots_cpp=*) # "localhost" runs commands directly, not talking to distccd at # localhost, use an IP or real hostname for that. # So, just pass these through. newhosts+=("$HOSTSPEC") ;; # SSH_HOST *@*) # SSH_HOST doesn't allow custom port numbers, and even if it # did, ssh would complain about MITM. Instead, we'll count on # ssh ProxyCommand being configured to use `client`. newhosts+=("$HOSTSPEC") ;; # GLOBAL_OPTION --*) # pass these through newhosts+=("$HOSTSPEC") ;; # ZEROCONF +zeroconf) error "%s does not support the +zeroconf option" "$q0" exit 1 ;; # TCP_HOST or OLDSTYLE_TCP_HOST *) declare HOSTID= PORT= LIMIT= OPTIONS= if [[ $HOSTSPEC =~ ^([^:/]+)(:([0-9]+))?(/([0-9]+))?(,.*)?$ ]]; then # TCP_HOST HOSTID=${BASH_REMATCH[1]} PORT=${BASH_REMATCH[3]} LIMIT=${BASH_REMATCH[5]} OPTIONS=${BASH_REMATCH[6]} elif [[ $HOSTSPEC =~ ^([^:/]+)(/([0-9]+))?(:([0-9]+))?(,.*)?$ ]]; then # OLDSTYLE_TCP_HOST HOSTID=${BASH_REMATCH[1]} LIMIT=${BASH_REMATCH[3]} PORT=${BASH_REMATCH[5]} OPTIONS=${BASH_REMATCH[6]} else error "Could not parse HOSTSPEC: %s" "$HOSTSPEC" fi # set up port forwaring if $forward_ports; then socat TCP-LISTEN:${newport},fork SYSTEM:"$q0 client $HOSTID ${PORT:-3632}" & pids+=($!) fi # add the forwarded port local newhost="127.0.0.1:$newport" [[ -z $LIMIT ]] || newhost+="/$LIMIT" [[ -z $OPTIONS ]] || newhost+="$OPTIONS" newhosts+=("$newhost") : $((newport++)) ;; esac done if $forward_ports; then if [[ -z "${pids[*]}" ]]; then # listen on port 8000, but immediatly close, just so that we are # listening on something socat TCP-LISTEN:${newport},fork SYSTEM:true & pids+=($!) fi trap "kill -- ${pids[*]}" EXIT wait "${pids[@]}" else printf '%s\n' "${newhosts[*]}" fi } ################################################################################ # Port forwarding primitives # ################################################################################ # Usage: server # Reads "host port" from the first line of stdin, then connects stdio to the # specified TCP socket. server() { [[ $# -eq 0 ]] || panic local host port read -r host port socat STDIO TCP:"$host:$port" } # Usage: client HOST PORT # For usage inside of a chroot. It talks through the UNIX-domain socket to an # instance of `server` outside, in order to connect stdio to the specified TCP # socket. client() { [[ $# -eq 2 ]] || panic local file=/socket { printf '%s\n' "$*"; cat; } | socat UNIX-CONNECT:"$file" STDIO } ################################################################################ # High-level routines # ################################################################################ # Usage: odaemon CHROOTPATH # Listens on "$CHROOTPATH/socket" and spawns a `server` for each new connection. odaemon() { [[ $# -eq 1 ]] || panic local chrootpath=$1 umask 111 socat UNIX-LISTEN:"$chrootpath/socket",fork SYSTEM:"$q0 server" & trap "kill -- $!; rm -f -- $(printf '%q' "$chrootpath/socket")" EXIT wait } # Usage: idaemon DISTCC_HOSTS # Sets things up inside of the chroot to forward distcc hosts out. idaemon() { [[ $# -eq 1 ]] || panic parse_DISTCC_HOSTS true "$1" } # Usage: rewrite DISTCC_HOSTS # Prints a modified version of DISTCC_HOSTS for inside the chroot. rewrite() { [[ $# -eq 1 ]] || panic parse_DISTCC_HOSTS false "$1" } main "$@"