1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
|
#!/usr/bin/env bash
# -*- tab-width: 4; sh-basic-offset: 4 -*-
# distcc-tool
# Copyright 2013 Luke Shumaker
#
# 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 <http://www.gnu.org/licenses/>.
# 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
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: $0 COMMAND [COMMAND-ARGS]"
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 used `client`.
newhosts+=("$HOSTSPEC")
;;
# GLOBAL_OPTION
--*)
# pass these through
newhosts+=("$HOSTSPEC")
;;
# ZEROCONF
+zeroconf)
error "%s does not support the +zeroconf option" "$0"
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:"$0 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
while read -r host port; do
socat STDIO TCP:"$host:$port"
break
done
}
# 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:"$0 server" &
trap "kill -- $!; rm -f '$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 "$@"
|