-
Notifications
You must be signed in to change notification settings - Fork 17
/
Copy pathobfs4proxy-openvpn
executable file
·550 lines (467 loc) · 23.3 KB
/
obfs4proxy-openvpn
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
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
#!/usr/bin/env bash
# obfs4proxy-openvpn - Obfuscating OpenVPN traffic using obfs4proxy
# Copyright (c) 2019 Hamy - https://hamy.io
VERSION="0.15.1"
CONF_FILE="/etc/obfs4proxy-openvpn.conf"
MODE=""
TRANSPORT="obfs4"
IAT_MODE="0"
OPENVPN_CONFIG_FILE=""
CLIENT_REMOTE_CERT=""
CLIENT_REMOTE_NODE_ID=""
CLIENT_REMOTE_PUBLIC_KEY=""
CLIENT_REMOTE_CREDENTIALS=""
CLIENT_UPSTREAM_PROXY=""
CLIENT_OPENVPN_SOCKS5_FILE=""
SERVER_OBFS4_BIND_ADDR="0.0.0.0"
SERVER_OBFS4_BIND_PORT="1516"
SERVER_OPENVPN_BIND_ADDR="127.0.0.1"
SERVER_OPENVPN_BIND_PORT="1515"
SERVER_EXPORT_CERT=""
OBFS4PROXY_WORKING_DIR="/var/lib/obfs4proxy-openvpn"
OBFS4PROXY_UID="obfs4-ovpn"
OBFS4PROXY_GID="obfs4-ovpn"
OBFS4PROXY_LOG_LEVEL="error"
OBFS4PROXY_LOG_IP="false"
OBFS4PROXY_ARGS=""
OBFS4PROXY_ENVS=""
# This will be called when the script is finished/interrupted/something goes wrong
# if "$1" exists, this was a fatal error. Because of the way obfs4proxy execution
# is implemented, and the way the process works, we need to extract the pid
# and send it TERM signal or it won't terminate
cleanup() {
local PT_PID=""
ERR_MSG="$1"
if [[ "$CO_PT_PID" != "" ]]; then
# If obfsproxy terminates at startup, we could reach here before the
# sudo ends and we may end up with a non-existence PT_PID. $CO_PT_PID
# is holding the CO_PT coproc pid
PT_PID=$(ps -o pid= --ppid $(ps -o pid= --ppid $CO_PT_PID) 2>/dev/null)
if [[ $? -eq 0 ]]; then
kill $PT_PID 2>/dev/null
[[ $? -eq 0 ]] || ERR_MSG="$ERR_MSG\n***ERROR: Could not end obfs4proxy process"
fi
fi
if [[ "$ERR_MSG" != "" ]];then
echo -e "***ERROR: $ERR_MSG" >&2
exit 1
fi
exit 0
}
# We register this as early as possible
trap cleanup INT TERM
# $1:uid, S2:gid, $3:envs, $4:args
sudo_obfs4proxy() {
local OBFS4PROXY_PATH=""
# This should never really fail as obfs4proxy is checked before
# in validate_env(). But it's here again for extra protection
# against sudo execution
OBFS4PROXY_PATH="$(command -v obfs4proxy)"
[[ $? -eq 0 ]] || cleanup "Somehow failed to obtain obfs4proxy path"
# Since the $PATH might be different for invoked sudo users
# (this seems to be the case in CenOS), we get the full path
# of the obfs4proxy and use that instead.
sudo -u $1 -g $2 /usr/bin/env $3 "$OBFS4PROXY_PATH" $4
}
# $1:creds, $2:iat-mode, $3:uid, $4:gid, $5:socks_file_location
# making a compatible socks5_auth file for openvpn
# do note that the very last character, is on the second line
# this is required for proper openvpn handling
# the file permission is also in a way to allow read access for gid
# so the user could add openvpn uid, to the obfs4proxy gid and it would work.
gen_socks5_auth() {
local OLD_UMASK="$(umask)"
local RES=""
# reading permission for gid is allowed
umask 0027
[[ $? -eq 0 ]] || cleanup "Could not set the umask"
# We use this method to create the file with the required uid/gid
echo -e "$1;iat-mode=\n$2" | sudo -u $3 -g $4 tee "$5" >/dev/null
RES=$?
umask "$OLD_UMASK"
[[ $RES -eq 0 ]] || cleanup "Could not write openvpn socks5 authentication file"
}
export_cert() {
local RE="cert=([a-zA-Z0-9/+]{70}),"
local CERT=""
local RES=""
local OLD_UMASK=""
if [[ "$1" =~ $RE ]]; then
CERT="${BASH_REMATCH[1]}"
else
cleanup "Parsing server response failed. Could not retrieve the cert"
fi
if [[ "$SERVER_EXPORT_CERT" == "-" ]]; then
echo "$CERT"
elif [[ -e "$SERVER_EXPORT_CERT" ]]; then
cleanup "The destination cert file already exists"
else
OLD_UMASK="$(umask)"
umask 0077
echo "$CERT" > "$SERVER_EXPORT_CERT"
RES=$?
umask="$OLD_UMASK"
if [[ $RES -eq 0 ]]; then
echo "cert successfully exported"
else
cleanup "Could not write the cert to the destination"
fi
fi
}
# Input options and parameters are being parsed here
parse_options() {
local OPTIONS_SHORT="hVc:"
local OPTIONS_LONG="help,version,config:,export-cert:"
local OPTIONS_PARSED=""
local CONF_FILE_SUPPLIED=0
OPTIONS_PARSED=$(getopt --options=$OPTIONS_SHORT --longoptions=$OPTIONS_LONG --name "$0" -- "$@")
[[ $? -eq 0 ]] || cleanup "Invalid option. Use -h or --help for help"
eval set -- "$OPTIONS_PARSED"
while true; do
case "$1" in
-c|--config)
CONF_FILE="$2"
CONF_FILE_SUPPLIED=1
shift 2
;;
-h|--help)
show_help
shift
cleanup
;;
-V|--version)
cat <<-EOF
*********************************************************************
* obfs4proxy-openvpn - Obfuscating OpenVPN traffic using obfs4proxy *
* Copyright (c) 2019 Hamy - https://hamy.io *
*********************************************************************
obfs4proxy-openvpn v$VERSION
EOF
shift
cleanup
;;
--export-cert)
SERVER_EXPORT_CERT="$2"
shift 2
;;
--)
shift
if [[ $# -ne 0 ]]; then
if [[ $# -eq 1 && $CONF_FILE_SUPPLIED -eq 0 ]]; then
CONF_FILE="$1"
shift
break
else
cleanup "Invalid use of non-optional parameter"
fi
else
break
fi
;;
*)
cleanup "Unknown error while parsing options/arguments"
;;
esac
done
}
show_help() {
cat <<-EOF
Usage: obfs4proxy-openvpn [OPTION]... [CONFIG_FILE]
obfs4proxy-openvpn
Obfuscating OpenVPN traffic using obfs4proxy
Mandatory arguments to long options are mandatory for short options too.
-c, --config CONFIG_FILE loads the obfs4proxy-openvpn config file
see the manual for more info
default is $CONF_FILE
--export-cert FILE exports the obfs4 cert in server mode
(to be imported to the clients)
use "-" for stdout
-V, --version outputs version and copyright information
-h, --help displays this help
Please fully read the manual before using this script.
https://hamy.io
EOF
}
# validate the conf file options and import them
import_conf() {
local CONF_FILE_PERM=""
local RE=""
local CONFIG_LINE_COUNTER=0
local RE_LINE_SKIP="^[[:blank:]]*(#.*)?$"
local RE_MODE="^MODE[[:blank:]]+(client|server)[[:blank:]]*$"
local RE_TRANSPORT="^TRANSPORT[[:blank:]]+(obfs4|obfs3|obfs2)[[:blank:]]*$"
local RE_CLIENT_REMOTE_CERT="^CLIENT_REMOTE_CERT[[:blank:]]+([a-zA-Z0-9/+]{70})[[:blank:]]*$"
local RE_CLIENT_REMOTE_NODE_ID="^CLIENT_REMOTE_NODE_ID[[:blank:]]+([0-9a-z]{40})[[:blank:]]*$"
local RE_CLIENT_REMOTE_PUBLIC_KEY="^CLIENT_REMOTE_PUBLIC_KEY[[:blank:]]+([0-9a-z]{64})[[:blank:]]*$"
local RE_IAT_MODE="^IAT_MODE[[:blank:]]+([0-2])[[:blank:]]*$"
local RE_CLIENT_UPSTREAM_PROXY="^CLIENT_UPSTREAM_PROXY[[:blank:]]+((http|socks4a|socks5)://[][a-zA-Z0-9:@_%/~.-]{7,64})[[:blank:]]*$"
local RE_SERVER_OBFS4_BIND_ADDR="^SERVER_OBFS4_BIND_ADDR[[:blank:]]+([a-zA-Z0-9:.]{2,64})[[:blank:]]*$"
local RE_SERVER_OBFS4_BIND_PORT="^SERVER_OBFS4_BIND_PORT[[:blank:]]+([0-9]{2,5})[[:blank:]]*$"
local RE_SERVER_OPENVPN_BIND_ADDR="^SERVER_OPENVPN_BIND_ADDR[[:blank:]]+([a-zA-Z0-9:.]{2,64})[[:blank:]]*$"
local RE_SERVER_OPENVPN_BIND_PORT="^SERVER_OPENVPN_BIND_PORT[[:blank:]]+([0-9]{2,5})[[:blank:]]*$"
local RE_OBFS4PROXY_WORKING_DIR="^OBFS4PROXY_WORKING_DIR[[:blank:]]+(/[a-zA-Z0-9/_.~-]{2,128}[a-zA-Z0-9_.~-])/?[[:blank:]]*$"
local RE_OBFS4PROXY_UID="^OBFS4PROXY_UID[[:blank:]]+([a-z_][a-z0-9_-]{1,32})[[:blank:]]*$"
local RE_OBFS4PROXY_GID="^OBFS4PROXY_GID[[:blank:]]+([a-z_][a-z0-9_-]{1,32})[[:blank:]]*$"
local RE_OBFS4PROXY_LOG_LEVEL="^OBFS4PROXY_LOG_LEVEL[[:blank:]]+(none|error|warn|info|debug)[[:blank:]]*$"
local RE_OBFS4PROXY_LOG_IP="^OBFS4PROXY_LOG_IP[[:blank:]]+(true|false)[[:blank:]]*$"
local RE_OPENVPN_CONFIG_FILE="^OPENVPN_CONFIG_FILE[[:blank:]]+(/[a-zA-Z0-9/_.~-]{3,128})[[:blank:]]*$"
if ! [[ -f "$CONF_FILE" ]]; then
cleanup "Could not access \"$CONF_FILE\". Use --help for more info"
fi
CONF_FILE_PERM=$(stat -c %a "$CONF_FILE")
[[ $? -eq 0 ]] || cleanup "Could not invoke stat"
RE="^[46][0246][04]$"
if ! [[ "$CONF_FILE_PERM" =~ $RE ]]; then
cleanup "Security issue detected in the conf file permission. $CONF_FILE_PERM is not allowed"
fi
RE="^..0$"
if ! [[ "$CONF_FILE_PERM" =~ $RE ]]; then
echo "**WARNING: conf file is world-readable. This might be a security risk" >&2
fi
while read -r line; do
CONFIG_LINE_COUNTER=$((CONFIG_LINE_COUNTER+1))
[[ "$line" =~ $RE_LINE_SKIP ]] && continue
if [[ "$line" =~ $RE_MODE ]]; then MODE="${BASH_REMATCH[1]}"
elif [[ "$line" =~ $RE_TRANSPORT ]]; then TRANSPORT="${BASH_REMATCH[1]}"
elif [[ "$line" =~ $RE_CLIENT_REMOTE_CERT ]]; then CLIENT_REMOTE_CERT="${BASH_REMATCH[1]}"
elif [[ "$line" =~ $RE_CLIENT_REMOTE_NODE_ID ]]; then CLIENT_REMOTE_NODE_ID="${BASH_REMATCH[1]}"
elif [[ "$line" =~ $RE_CLIENT_REMOTE_PUBLIC_KEY ]]; then CLIENT_REMOTE_PUBLIC_KEY="${BASH_REMATCH[1]}"
elif [[ "$line" =~ $RE_IAT_MODE ]]; then IAT_MODE="${BASH_REMATCH[1]}"
elif [[ "$line" =~ $RE_CLIENT_UPSTREAM_PROXY ]]; then CLIENT_UPSTREAM_PROXY="${BASH_REMATCH[1]}"
elif [[ "$line" =~ $RE_SERVER_OBFS4_BIND_ADDR ]]; then SERVER_OBFS4_BIND_ADDR="${BASH_REMATCH[1]}"
elif [[ "$line" =~ $RE_SERVER_OBFS4_BIND_PORT ]]; then SERVER_OBFS4_BIND_PORT="${BASH_REMATCH[1]}"
elif [[ "$line" =~ $RE_SERVER_OPENVPN_BIND_ADDR ]]; then SERVER_OPENVPN_BIND_ADDR="${BASH_REMATCH[1]}"
elif [[ "$line" =~ $RE_SERVER_OPENVPN_BIND_PORT ]]; then SERVER_OPENVPN_BIND_PORT="${BASH_REMATCH[1]}"
elif [[ "$line" =~ $RE_OBFS4PROXY_WORKING_DIR ]]; then OBFS4PROXY_WORKING_DIR="${BASH_REMATCH[1]}"
elif [[ "$line" =~ $RE_OBFS4PROXY_UID ]]; then OBFS4PROXY_UID="${BASH_REMATCH[1]}"
elif [[ "$line" =~ $RE_OBFS4PROXY_GID ]]; then OBFS4PROXY_GID="${BASH_REMATCH[1]}"
elif [[ "$line" =~ $RE_OBFS4PROXY_LOG_LEVEL ]]; then OBFS4PROXY_LOG_LEVEL="${BASH_REMATCH[1]}"
elif [[ "$line" =~ $RE_OBFS4PROXY_LOG_IP ]]; then OBFS4PROXY_LOG_IP="${BASH_REMATCH[1]}"
elif [[ "$line" =~ $RE_OPENVPN_CONFIG_FILE ]]; then OPENVPN_CONFIG_FILE="${BASH_REMATCH[1]}"
else cleanup "Invalid option/argument in $CONF_FILE line $CONFIG_LINE_COUNTER"; fi
done <$CONF_FILE
}
# more validations after the initial conf file load
validate_obfs4_conf() {
if [[ "$MODE" == "" ]]; then
cleanup "No known MODE of operation"
fi
if [[ "$SERVER_EXPORT_CERT" != "" ]]; then
if [[ "$TRANSPORT" != "obfs4" || "$MODE" != "server" ]]; then
cleanup "exporting cert is only available for obfs4 and in server mode"
fi
fi
if [[ "$TRANSPORT" == "obfs4" ]]; then
if [[ "$MODE" == "client" ]]; then
if [[ "$CLIENT_REMOTE_CERT" != "" ]]; then
if [[ "$CLIENT_REMOTE_NODE_ID" != "" || "$CLIENT_REMOTE_PUBLIC_KEY" != "" ]]; then
cleanup "You can not specify both CLIENT_REMOTE_CERT and CLIENT_REMOTE_NODE_ID/CLIENT_REMOTE_PUBLIC_KEY";
else
CLIENT_REMOTE_CREDENTIALS="cert=$CLIENT_REMOTE_CERT"
fi
else
if [[ "$CLIENT_REMOTE_NODE_ID" == "" || "$CLIENT_REMOTE_PUBLIC_KEY" == "" ]]; then
cleanup "You must specify either CLIENT_REMOTE_CERT or both CLIENT_REMOTE_NODE_ID and CLIENT_REMOTE_PUBLIC_KEY"
else
CLIENT_REMOTE_CREDENTIALS="node-id=$CLIENT_REMOTE_NODE_ID;public-key=$CLIENT_REMOTE_PUBLIC_KEY"
fi
fi
fi
fi
}
# Some basic validation of openvpn config file
validate_openvpn_conf() {
local OPENVPN_CONFIG_FILE_PERM=""
local OPENVPN_USER="root"
local OPENVPN_GROUP=""
local RE=""
local RE_OPENVPN_USER='^(--)?user[[:blank:]]+"?([^"]+)"?[[:blank:]]*$'
local RE_OPENVPN_GROUP='^(--)?group[[:blank:]]+"?([^"]+)"?[[:blank:]]*$'
local RE_OPENVPN_CONFIG='^(--)?config[[:blank:]]'
local RE_OPENVPN_DAEMON='^(--)?(inetd|daemon)([[:blank:]]|$)'
local RE_OPENVPN_PROXY='^(--)?((http|socks)-proxy)[[:blank:]]'
local RE_OPENVPN_PORT='^(--)?((http|socks)-proxy)[[:blank:]]'
[[ -f "$OPENVPN_CONFIG_FILE" ]] || cleanup "Could not access \"$OPENVPN_CONFIG_FILE\""
OPENVPN_CONFIG_FILE_PERM=$(stat -c %a "$OPENVPN_CONFIG_FILE")
[[ $? -eq 0 ]] || cleanup "Could not invoke stat"
RE="^..[2367]$"
if [[ "$OPENVPN_CONFIG_FILE_PERM" =~ $RE ]]; then
cleanup "OpenVPN conf file is world-writable. Aborting..."
fi
while read -r line; do
if [[ "$line" =~ $RE_OPENVPN_USER ]]; then OPENVPN_USER="${BASH_REMATCH[2]}"
elif [[ "$line" =~ $RE_OPENVPN_GROUP ]]; then OPENVPN_GROUP="${BASH_REMATCH[2]}"
elif [[ "$line" =~ $RE_OPENVPN_CONFIG ]]; then echo "**WARNING: Nested openvpn \"--config\" detected in \"$OPENVPN_CONFIG_FILE\". Not going to validate those." >&2
elif [[ "$line" =~ $RE_OPENVPN_DAEMON ]]; then cleanup "Unsupported option: \"--${BASH_REMATCH[2]}\" in \"$OPENVPN_CONFIG_FILE\""
elif [[ "$line" =~ $RE_OPENVPN_PROXY ]]; then cleanup "Unsupported option: \"--${BASH_REMATCH[2]}\" in \"$OPENVPN_CONFIG_FILE\""; fi
done <"$OPENVPN_CONFIG_FILE"
if [[ "$MODE" == "client" && "$TRANSPORT" == "obfs4" ]]; then
if [[ "$OPENVPN_USER" != "root" ]]; then
if [[ "$OPENVPN_GROUP" != "$OBFS4PROXY_GID" ]]; then
echo "**WARNING: OpenVPN group differ from obfs4proxy group. This could result in OpenVPN permission issues when re-reading auth_socks file (upon SIGHUP/SIGUSR1)" >&2
fi
fi
fi
}
# Validating the environment the script is running on
validate_env() {
local COMMANDS_LIST="sudo ps kill grep getent openvpn obfs4proxy"
local UID_OK=0
local GID_OK=0
local OLD_IFS="$IFS"
local RES=""
[[ "$EUID" != "0" ]] && cleanup "This script must be run as root"
IFS=" "
for i in $COMMANDS_LIST; do
command -v $i >/dev/null
[[ $? -eq 0 ]] || cleanup "\"$i\" could not be found in PATH. Maybe you need to install it?"
done
IFS="$OLD_IFS"
getent passwd "$OBFS4PROXY_UID" >/dev/null
[[ $? -eq 0 ]] && UID_OK=1
getent group "$OBFS4PROXY_GID" >/dev/null
[[ $? -eq 0 ]] && GID_OK=1
# We try to create the required user/group if we detect they are both the same and are currently
# not created (the simplest setup). More advanced setups must be done manually to avoid any potential issues.
#
# Debian strongly advises to use "adduser" instead of "useradd" for creating user/group. At the very least,
# it uses different algorithm for allocating ids. Since adduser could also be presented and symlinked to
# useradd in some distros (e.g, CentOS), we have to properly check to ensure if the system is debian-based
# and only fallback to useradd if it wasn't.
if [[ $UID_OK -eq 0 && $GID_OK -eq 0 ]]; then
echo "**WARNING: Could not find neither \"$OBFS4PROXY_UID\" user nor \"$OBFS4PROXY_GID\" group on this system" >&2
if [[ "$OBFS4PROXY_UID" == "$OBFS4PROXY_GID" ]]; then
# This should safely check whether the distro is debian or debian based
grep --extended-regexp --quiet '^ID(_LIKE)?=("|.* )?debian( .*|")?$' /etc/os-release
if [[ $? -eq 0 ]]; then
read -p "Debian based system detected. user/group can be created using \"adduser\" continue (y/N)? "
[[ "${REPLY,}" == "y" ]] || cleanup "Creating user/group is aborted"
adduser --system --group --home "$OBFS4PROXY_WORKING_DIR" --no-create-home --gecos "used by obfs4proxy-openvpn" "$OBFS4PROXY_UID"
[[ $? -eq 0 ]] || cleanup "adduser invocation failed"
else
read -p "Non-debian based system detected. user/group can be created using \"useradd\" continue (y/N)? "
[[ "${REPLY,}" == "y" ]] || cleanup "Creating user/group is aborted"
useradd --system --user-group --home-dir "$OBFS4PROXY_WORKING_DIR" --no-create-home --shell /sbin/nologin --comment "used by obfs4proxy-openvpn" "$OBFS4PROXY_UID"
[[ $? -eq 0 ]] || cleanup "useradd invocation failed"
fi
else
cleanup "Advanced setup for user and group detected. Please manually add them yourself first."
fi
elif [[ $UID_OK -eq 0 || $GID_OK -eq 0 ]]; then
cleanup "Either \"$OBFS4PROXY_UID\" user or \"$OBFS4PROXY_GID\" group does not exist. Please manually add it first."
fi
# We test and try to create the containing WORKING_DIR and allow read access to gid.
# This is so, because obfs4proxy creats its own dir with 0777 umask which makes
# openvpn process to not be able to access socks_auth file unless its sharing the same uid
# so we create a rather more relaxed containing dir to host the socks file in a way
# that could be accessed by the OBFS4PROXY_GID.
if ! [[ -d "$OBFS4PROXY_WORKING_DIR" ]]; then
read -p "Could not find \"$OBFS4PROXY_WORKING_DIR/\" path on this system. Do you want to create it (y/N)? "
[[ "${REPLY,}" == "y" ]] || cleanup "Creating working directory path is aborted"
install "--owner=$OBFS4PROXY_UID" "--group=$OBFS4PROXY_GID" --mode=0750 --directory "$OBFS4PROXY_WORKING_DIR"
[[ $? -eq 0 ]] || cleanup "Could not create \"$OBFS4PROXY_WORKING_DIR/\" using \"$OBFS4PROXY_UID:$OBFS4PROXY_GID\""
fi
}
initiate_obfs4() {
# Care must be taken (in conf validation phase mostly) that none of the OBFS4PROXY_ENVS set of arguments
# contain white space or any other funny characters. As right now we rely on bash expansion to separate them
# later on.
OBFS4PROXY_ENVS="TOR_PT_MANAGED_TRANSPORT_VER=1"
OBFS4PROXY_ENVS="$OBFS4PROXY_ENVS TOR_PT_STATE_LOCATION=$OBFS4PROXY_WORKING_DIR/$TRANSPORT/"
if [[ "$MODE" == "server" ]]; then
OBFS4PROXY_ENVS="$OBFS4PROXY_ENVS TOR_PT_SERVER_TRANSPORTS=$TRANSPORT"
OBFS4PROXY_ENVS="$OBFS4PROXY_ENVS TOR_PT_SERVER_BINDADDR=$TRANSPORT-$SERVER_OBFS4_BIND_ADDR:$SERVER_OBFS4_BIND_PORT"
OBFS4PROXY_ENVS="$OBFS4PROXY_ENVS TOR_PT_ORPORT=$SERVER_OPENVPN_BIND_ADDR:$SERVER_OPENVPN_BIND_PORT"
if [[ "$TRANSPORT" == "obfs4" ]]; then
[[ "$IAT_MODE" != "0" ]] && OBFS4PROXY_ENVS="$OBFS4PROXY_ENVS TOR_PT_SERVER_TRANSPORT_OPTIONS=$TRANSPORT:iat-mode=$IAT_MODE"
fi
elif [[ "$MODE" == "client" ]]; then
OBFS4PROXY_ENVS="$OBFS4PROXY_ENVS TOR_PT_CLIENT_TRANSPORTS=$TRANSPORT"
[[ "$CLIENT_UPSTREAM_PROXY" != "" ]] && OBFS4PROXY_ENVS="$OBFS4PROXY_ENVS TOR_PT_PROXY=$CLIENT_UPSTREAM_PROXY"
fi
# unfortunately, unsafeLogging option doesn't really seem to work. but the
# functionality is provided anyway
if [[ "$OBFS4PROXY_LOG_LEVEL" != "none" ]]; then
OBFS4PROXY_ARGS="-enableLogging -logLevel ${OBFS4PROXY_LOG_LEVEL^^}"
[[ "$OBFS4PROXY_LOG_IP" == "true" ]] && OBFS4PROXY_ARGS="$OBFS4PROXY_ARGS -unsafeLogging"
fi
CLIENT_OPENVPN_SOCKS5_FILE="$OBFS4PROXY_WORKING_DIR/socks5_auth"
}
# This is where the magic happens. obs4proxy is started on another thread
# with its stdout being read line by line until either failed or successfully
# initializes, which would cause openvpn take control of the process until ended.
# we should not get any new messages from obfs4proxy after initialization completed
# but even if we do, and even though openvpn blocks the while loop, it still would not
# block obfs4proxy process and would add it to the pipe to be read later (and in the unlikely
# event of the pipe memory getting full, it will be overwritten)
# if obfs4proxy ends for some reason before initialization,
# the pipe will become invalid and the loop will end
call_obfs4_openvpn() {
local RE_OBFS4_CLIENT="^CMETHOD $TRANSPORT socks5 127.0.0.1:([0-9]+)"
local RE_OBFS4_SERVER="^SMETHOD $TRANSPORT ([^ ]+):([0-9]+) ARGS:([^ ]*)"
local RE_OBFS4_ERROR=".*ERROR.*"
local CLIENT_OBFS4_SOCKS5_PORT=""
local SERVER_OBFS4_BIND_ADDR_REAL=""
# You can not check the return code of coproc as will point out to the previous command's return code instead
coproc CO_PT { sudo_obfs4proxy "$OBFS4PROXY_UID" "$OBFS4PROXY_GID" "$OBFS4PROXY_ENVS" "$OBFS4PROXY_ARGS"; }
while read -r line; do
if [[ "$line" =~ $RE_OBFS4_ERROR ]]; then
cleanup "obfs4proxy: $line"
elif [[ "$MODE" == "client" ]]; then
if [[ "$line" =~ $RE_OBFS4_CLIENT ]]; then
CLIENT_OBFS4_SOCKS5_PORT=${BASH_REMATCH[1]}
elif [[ "$line" == "CMETHODS DONE" ]];then
if [[ "$CLIENT_OBFS4_SOCKS5_PORT" != "" ]]; then
if [[ "$TRANSPORT" == "obfs4" ]]; then
gen_socks5_auth "$CLIENT_REMOTE_CREDENTIALS" "$IAT_MODE" "$OBFS4PROXY_UID" "$OBFS4PROXY_GID" "$CLIENT_OPENVPN_SOCKS5_FILE"
fi
cat <<-EOF
*Client $TRANSPORT initialization successful
*OpenVPN will be using the SOCKS5 proxy running on 127.0.0.1:$CLIENT_OBFS4_SOCKS5_PORT
*Launching OpenVPN instance in 3 seconds...
EOF
sleep 3
if [[ "$TRANSPORT" == "obfs4" ]]; then
openvpn --config "$OPENVPN_CONFIG_FILE" --proto tcp-client --socks-proxy 127.0.0.1 $CLIENT_OBFS4_SOCKS5_PORT "$CLIENT_OPENVPN_SOCKS5_FILE"
else
openvpn --config "$OPENVPN_CONFIG_FILE" --proto tcp-client --socks-proxy 127.0.0.1 $CLIENT_OBFS4_SOCKS5_PORT
fi
[[ $? -eq 0 ]] || cleanup "OpenVPN launch failed"
fi
break
fi
elif [[ "$MODE" == "server" ]]; then
if [[ "$line" =~ $RE_OBFS4_SERVER ]]; then
if [[ "$SERVER_EXPORT_CERT" != "" ]]; then
export_cert "${BASH_REMATCH[3]}"
cleanup
fi
SERVER_OBFS4_BIND_ADDR_REAL="${BASH_REMATCH[1]}:${BASH_REMATCH[2]}"
elif [[ "$line" == "SMETHODS DONE" ]];then
if [[ "$SERVER_OBFS4_BIND_ADDR_REAL" != "" ]]; then
cat <<-EOF
*Server $TRANSPORT initialization successful
*Reverse proxy is listening on $SERVER_OBFS4_BIND_ADDR_REAL
*OpenVPN will be listening on $SERVER_OPENVPN_BIND_ADDR:$SERVER_OPENVPN_BIND_PORT
EOF
if [[ "$TRANSPORT" == "obfs4" ]]; then
echo "*You may use --export-cert to get the required cert for the clients"
fi
echo "*Launching OpenVPN instance in 3 seconds..."
sleep 3
openvpn --config "$OPENVPN_CONFIG_FILE" --proto tcp-server --local $SERVER_OPENVPN_BIND_ADDR --lport $SERVER_OPENVPN_BIND_PORT
[[ $? -eq 0 ]] || cleanup "OpenVPN launch failed"
fi
break
fi
fi
done <&${CO_PT[0]}
}
parse_options "$@"
import_conf
validate_env
validate_obfs4_conf
validate_openvpn_conf
initiate_obfs4
call_obfs4_openvpn
cleanup