1#!/bin/sh 2 3# Copyright (C) 2015 by Yuri Victorovich. All rights reserved. 4 5# PROVIDE: vm_to_tor 6# REQUIRE: LOGIN ipfw sysctl 7# KEYWORD: shutdown 8# BEFORE: tor 9 10# Add the following lines to /etc/rc.conf to enable vm_to_tor: 11# vm_to_tor_enable (bool): * Set to "YES" to enable vm-to-tor. 12# Default: "NO" 13# vm_to_tor_ifaces (list): * Set it to the list of interfaces. 14# At least one interface is required. 15# Default: "tap0" 16# vm_to_tor_net_pattern (str): * Set it to the network pattern. 17# Default: "172.16" 18# vm_to_tor_rule_base (uint): * Set it to the the rule number block 19# location. Default: "3000" 20# vm_to_tor_extra_ports (list): * Set it to the the list of ports to open 21# from host to VMs. Default: "" 22# vm_to_tor_control_socket (bool): * Set it to "YES" to allow host 23# administration using ControlSocket. 24# Default: "NO" 25# vm_to_tor_allow_cookie_auth (bool): * Set it to "YES" to allow cookie 26# administration on ControlSocket. 27# Default: "NO" 28# vm_to_tor_use_base (bool): * Set it to "YES" to use central tor 29# daemon on host. 30# By default individual tor instance is 31# launched per interface. 32# Choosing "YES" will share the host tor 33# instance will require slightly less 34# resources, and will share the same 35# tor entry guard, but all data will pass 36# through the same process. This may be 37# considered more secure by some, and less 38# secure by others. 39# It requires tor service to run, but tor 40# should start after vm-to-tor. 41# CAVEAT Choosing "YES" runs into tor 42# inability to change config in a graceful 43# way, so vm-to-tor will be writing 44# torrc file, which can conflict with tor. 45# Default: "NO" (WARNING BROKEN, UNTESTED) 46# vm_to_tor_hs (str): * Set it to the list of hidden services to 47# route to VMs. 48# List is 'newline' or '|' separarated. 49# Hidden services are in the format: 50# tapN /dir/to/hidden/svc oport iport 51# Default: "" 52# vm_to_tor_user (str): * Set it to the user name that vm-to-tor 53# uses. Should be the same that tor uses. 54# Default: "_tor" 55# vm_to_tor_group (str): * Set it to the group name that vm-to-tor 56# uses. Should be the same that tor uses. 57# Default: "_tor" 58# vm_to_tor_vm_type (str): * This option tells vm-to-tor what kind of 59# virtual machine software you are running. 60# Possible options are: vbox,bhyve,none. 61# Please note that per-vm option 'vm_type' 62# can specify different vm types for 63# different machines. 64# Default: "none" 65# vm_to_tor_show_colors (bool): * Choosing "NO" prevents colors in service 66# messages. 67# Default: "YES" 68# vm_to_tor_fw_rule_gap (uint): * Setting the positive value makes 69# vm-to-tor leave that many available ipfw 70# rules for every VM. 71# This is to allow an expecrt user to add 72# any extra-rules per VM. 73# Default: "0" 74# ### per-VM options 75# vm_to_tor_tapN_tor_options (str): * Set it to the newline-separated list of 76# additional options to add to every VM. 77# Default: "" 78# vm_to_tor_tapN_fw_allow_nfs (list): * Set it to space-separated list of NFS 79# servers VM is allowed to mount from. 80# Currently vm-to-tor only supports 81# NFS servers running on host. Please 82# note that adding remote NFS servers 83# would have required adding routing 84# entries or NAT rules. 85# Default: "" 86# vm_to_tor_tapN_vm_type (str): * Specifies vm type on per-vm basis. See 87# 'vm_to_tor_vm_type'. 88# Default: "" 89# 90 91# 92# This module depends on these external executables in LOCALBASE: 93# * bin/tor 94# * bin/tiny-dhcp-server 95# * bin/tiny-udp-anti-nat 96# 97 98. /etc/rc.subr 99 100name=vm_to_tor 101nameb=vm-to-tor 102rcvar=vm_to_tor_enable 103start_cmd="vm_to_tor_start" 104stop_cmd="vm_to_tor_stop" 105status_cmd="vm_to_tor_status" 106required_modules="ipfw ipdivert if_tap" 107 108: ${LOCALBASE="/usr/local"} 109 110# signature that will be placed into torrc to later be able to find our lines 111torrc_signature="VBoxToTOR" 112# where all tors data dirs are 113tor_dir=/var/tmp/vm-to-tor 114tor_conf=${LOCALBASE}/etc/tor/torrc 115# consts 116NL=$'\n' 117TAB=$'\t' 118nfs_ports="111 1110 2049 4045" # 111=portmap 1110=nfsd-status/nfsd-keepalive 2049=nfsd 4045=lockd + mountd port will be added based on the actual value 119 120load_rc_config ${name} 121 122: ${vm_to_tor_enable="NO"} 123: ${vm_to_tor_ifaces="tap0"} 124: ${vm_to_tor_net_pattern="172.16"} 125: ${vm_to_tor_rule_base="3000"} 126: ${vm_to_tor_extra_ports=""} 127: ${vm_to_tor_control_socket="NO"} 128: ${vm_to_tor_allow_cookie_auth="NO"} 129: ${vm_to_tor_use_base="NO"} 130: ${vm_to_tor_hs=""} 131: ${vm_to_tor_user="_tor"} 132: ${vm_to_tor_group="_tor"} 133: ${vm_to_tor_vm_type="none"} 134: ${vm_to_tor_show_colors="YES"} 135: ${vm_to_tor_fw_rule_gap="0"} 136 137## internals 138 139echo_out() { 140 echo "${nameb}: $1" 141} 142 143echo_err() { 144 echo "${nameb}: $1" >&2 145} 146 147tm() { 148 echo "[$(date '+%Y-%m-%d %H:%M:%S %Z')]" 149} 150 151is_user_authorized() { 152 [ $(id -u) = 0 -o $(id -un) = ${vm_to_tor_user} ] || return 1 153} 154 155is_user_authorized_with_msg() { 156 is_user_authorized || ! echo_err "Only root and ${vm_to_tor_user} can $1" || return 1 157} 158 159ck_iface_exists() { 160 ifconfig $1 >/dev/null 2>&1 161} 162 163are_any_processes_running() { 164 local do_prn=$1 165 [ -d ${tor_dir} ] || return 0 166 local msg="" 167 # dhcp daemon 168 ! check_process_by_pid_file "${tor_dir}/dhcp-daemon.pid" || msg="${msg}* dhcp daemon pid=$(cat ${tor_dir}/dhcp-daemon.pid)${NL}" 169 # interfaces, tor processes, anti-nat daemons 170 for iface in ${vm_to_tor_ifaces}; do 171 ! ck_iface_exists $iface || msg="${msg}* ${iface} network interface${NL}" 172 ! check_process_by_pid_file "${tor_dir}/${iface}/tor.pid" || msg="${msg}* tor pid=$(cat ${tor_dir}/${iface}/tor.pid)${NL}" 173 ! check_process_by_pid_file "${tor_dir}/${iface}/udp-anti-nat.pid" || msg="${msg}* udp anti-nat pid=$(cat ${tor_dir}/${iface}/udp-anti-nat.pid)${NL}" 174 done 175 # output 176 [ -n "$msg" ] && 177 ([ $do_prn -eq 0 ] || 178 echo_err "can't start because essential elements are alive:${NL}${msg}Did you forget to stop ${nameb}? Is there the configuration problem?") && 179 return 1 180 return 0 181} 182 183is_tor_installed() { 184 [ -x ${LOCALBASE}/bin/tor ] || return 1 185} 186 187get_option_by_name() { 188 eval echo "\"\$vm_to_tor_${1}\"" 189} 190 191get_per_vm_option() { 192 eval echo "\"\$vm_to_tor_${1}_${2}\"" 193} 194 195get_per_vm_option_or_default() { 196 local val=$(get_per_vm_option $1 $2) 197 [ -n "$val" ] || val=$(get_option_by_name $2) 198 echo "$val" 199} 200 201set_per_vm_option() { 202 eval "vm_to_tor_${1}_${2}=\"${3}\"" 203} 204 205unique_port_set() { 206 echo $vm_to_tor_extra_ports $vm_to_tor_hs_all_iports | tr " ${TAB}" '\n' | sort | uniq 207} 208 209unique_port_set_for_iface() { 210 # eliminate port 22 here, I would rather keep 22 as a separate line in ipfw section to keep it explicit 211 eval "echo \$vm_to_tor_extra_ports \$vm_to_tor_hs_${1}_iports | tr \" \${TAB}\" '\\n' | sort | uniq | grep -v ^22\$" 212} 213 214lst_size() { 215 local elt cnt=0 216 for elt in $1; do 217 cnt=$((cnt+1)) 218 done 219 echo $cnt 220} 221 222lst_uniquify() { 223 echo "$1" | tr " ${TAB}" '\n' | sort | uniq 224} 225 226calc_max_nfs_hosts_needed() { 227 local max=0 228 for iface in ${vm_to_tor_ifaces}; do 229 local cnt=$(lst_size "$(get_per_vm_option ${iface} fw_allow_nfs)") 230 if [ $cnt -gt $max ]; then 231 max=$cnt 232 fi 233 done 234 echo $max 235} 236 237nfs_ports_count() { 238 # +1 due to the mountd variable port 239 echo $(($(lst_size "$nfs_ports")+1)) 240} 241 242nfs_mountd_actual_port() { 243 local port 244 port=$(rpcinfo -p 2>/dev/null | grep " mountd$" | awk '{print $4}' | uniq) 245 if [ $? -ne 0 -o -z "$port" ]; then 246 echo_err "can't determine mountd port: NFS requested but mountd isn't running?" 247 return 1 248 fi 249 echo $port 250} 251 252calc_rule_step() { 253 local cnt=0 254 # add NFS ports if requested: tcp and udp per port 255 cnt=$((cnt+2*$(nfs_ports_count)*$(calc_max_nfs_hosts_needed))) 256 # 9 base rules required per interface 257 cnt=$((cnt+9)) 258 # add extra ports and HS ports requested by the user (they are added together in add_ipfw_rules too) 259 cnt=$((cnt+$(lst_size "$(unique_port_set)"))) 260 # add the gap if requested by user 261 cnt=$((cnt+${vm_to_tor_fw_rule_gap})) 262 # add deny-all rule 263 cnt=$((cnt+1)) 264 # pick the round number closest to needed 265 for step in 10 20 50 100 200 500 1000; do 266 if [ $cnt -le $step ]; then 267 echo ${step} 268 return 269 fi 270 done 271 echo_err "too many ports requested" 272 return 1 273} 274 275run_su() { 276 su -m ${vm_to_tor_user} -c "$1" 277} 278 279set_file_perms_r() { 280 chown ${vm_to_tor_user}:${vm_to_tor_group} "$1" 281 chmod 0400 "$1" 282} 283 284set_file_perms_rw() { 285 chown ${vm_to_tor_user}:${vm_to_tor_group} "$1" 286 chmod 0600 "$1" 287} 288 289set_file_perms_rwx() { 290 chown ${vm_to_tor_user}:${vm_to_tor_group} "$1" 291 chmod 0700 "$1" 292} 293 294save_daemon_cmd_file() { 295 local cmd="$1" 296 local dname="$2" 297 echo "${cmd}" > ${tor_dir}/${dname}.cmd 298 set_file_perms_r ${tor_dir}/${dname}.cmd 299} 300 301mkdir_perms() { 302 (mkdir "${1}" && set_file_perms_rwx "${1}") > /dev/null 2>&1 303} 304 305vm_type_to_group() { 306 case $1 in 307 none) 308 echo "nobody" 309 ;; 310 vbox) 311 echo "vboxusers" 312 ;; 313 bhyve) 314 echo "wheel" 315 ;; 316 esac 317} 318 319## major steps in init/fini 320 321create_dirs() { 322 rm -rf "${tor_dir}" 323 mkdir_perms "${tor_dir}" || ! echo_err "failed to create work dir ${tor_dir}" || return 1 324 for iface in ${vm_to_tor_ifaces}; do 325 mkdir_perms "${tor_dir}/${iface}" || ! echo_err "failed to create work dir ${tor_dir}/${iface}" || return 1 326 done 327} 328 329delete_dir() { 330 rm -rf "${tor_dir}" > /dev/null 2>&1 331} 332 333check_tor_config() { 334 # check we can really write /usr/local/etc/tor/torrc 335 local rnd=$(jot -r 1 1000000 10000000) 336 (test -w ${LOCALBASE}/etc/tor/torrc && 337 test -w ${LOCALBASE}/etc/tor && 338 touch ${LOCALBASE}/etc/tor/torrc.tmp.${rnd} && 339 rm -f ${LOCALBASE}/etc/tor/torrc.tmp.${rnd}) > /dev/null 2>&1 || 340 ! echo_err "failed to check_tor_config" || return 1 341} 342 343parse_hs() { 344 # parse HS definitions, if any 345 local IFS="${NL}|" 346 for hs in ${vm_to_tor_hs}; do 347 if [ -z "${hs}" ]; then 348 continue 349 fi 350 local fidx=0 351 local fld iface dir oport iport 352 IFS=" ${TAB}" 353 for fld in ${hs}; do 354 case $fidx in 355 0) 356 iface="${fld}" 357 ;; 358 1) 359 dir="${fld}" 360 ;; 361 2) 362 oport="${fld}" 363 ;; 364 3) 365 iport="${fld}" 366 ;; 367 *) 368 echo_err "too many fields in the hidden service definition: ${hs}" 369 return 1 370 ;; 371 esac 372 fidx=$((fidx+1)) 373 done 374 if [ -z "${iface}" -o -z "${dir}" -o -z "${oport}" -o -z "${iport}" ]; then 375 echo_err "too few fields in the hidden service definition: ${hs}" 376 return 1 377 fi 378 # fill hs info into global vars 379 eval "vm_to_tor_hs_${iface}=\"\$vm_to_tor_hs_${iface} ${dir} ${oport} ${iport}\"" 380 eval "vm_to_tor_hs_${iface}_iports=\"\$vm_to_tor_hs_${iface}_iports ${iport}\"" 381 eval "vm_to_tor_hs_all_iports=\"\$vm_to_tor_hs_all_iports ${iport}\"" 382 done 383 vm_to_tor_hs_all_iports=$(lst_uniquify "${vm_to_tor_hs_all_iports}") 384} 385 386check_process_by_pid_file() { 387 local pid_file="$1" 388 [ -r ${pid_file} -a -s ${pid_file} -a \ 389 "$(procstat $(cat ${pid_file} 2>/dev/null) 2>/dev/null | tail -1 | sed -E 's/^[[:space:]]*([0-9]+).*/\1/g' 2>/dev/null)" = "$(cat ${pid_file} 2>/dev/null)" ] >/dev/null 2>&1 || return 1 390} 391 392create_tap() { 393 # link0 below is to keep the network interface open when vm is closed. The side-effect is that the IP address also stays. 394 for iface in ${vm_to_tor_ifaces}; do 395 local vm_id=$(echo $iface | sed -e 's/[a-z]*//') 396 if !(ifconfig $iface create && \ 397 ifconfig $iface up && \ 398 ifconfig $iface inet "${vm_to_tor_net_pattern}.${vm_id}.1/24" link0 && \ 399 chown root:$(vm_type_to_group $(get_per_vm_option_or_default $iface "vm_type")) /dev/$iface && \ 400 chmod 0660 /dev/$iface) >/dev/null 2>/dev/null; then 401 echo_err "failed to create interface $iface" 402 destroy_tap_silently 403 return 1 404 fi 405 done 406} 407 408destroy_tap() { 409 for iface in ${vm_to_tor_ifaces}; do 410 if ! ifconfig $iface destroy >/dev/null 2>/dev/null; then 411 echo_err "failed to destroy interface $iface" 412 destroy_tap_silently 413 return 1 414 fi 415 done 416} 417 418destroy_tap_silently() { 419 for iface in ${vm_to_tor_ifaces}; do 420 ifconfig $iface destroy >/dev/null 2>&1 421 done 422 return 0 423} 424 425check_config_vm_type() { 426 case "$1" in 427 vbox|bhyve|none) 428 ;; 429 *) 430 echo_err "unknown vm_type=$1, allowed values are vbox,bhyve,none" 431 return 1 432 ;; 433 esac 434} 435 436check_config() { 437 [ $(count_tunnels) -gt 0 ] || 438 ! echo_err "no tunnels: 'vm_to_tor_ifaces' should have at least one tapN tunnel set" || return 1 439 check_config_vm_type "${vm_to_tor_vm_type}" || return 1 440 for iface in ${vm_to_tor_ifaces}; do 441 local vm_type=$(get_per_vm_option $iface "vm_type") 442 [ -z "${vm_type}" ] || check_config_vm_type "${vm_type}" || return 1 443 done 444} 445 446check_sysctl() { 447 # VM process needs to be able to open tapN, so need net.link.tap.user_open=1. 448 # Even when it is set in /etc/sysctl.conf, if_tap might have been not loaded 449 # when sysctl was initialized. We depend on if_tap, so it must be loaded by now, 450 # therefore rerun sysctl before the check to make sure it is set. 451 /etc/rc.d/sysctl start >/dev/null 2>&1 452 [ $(sysctl -n net.link.tap.user_open) -eq 1 ] || 453 ! echo_err "net.link.tap.user_open is not set" || 454 ! echo_err "please add the line 'net.link.tap.user_open=1' to /etc/sysctl.conf" || 455 return 1 456} 457 458add_ipfw_rules() { 459 local cmd=$vm_to_tor_rule_base 460 local step=$(calc_rule_step) 461 local port 462 local fwcmd="/sbin/ipfw -q" 463 local nfs_mountd_port 464 465 for iface in ${vm_to_tor_ifaces}; do 466 local vm_id=$(echo $iface | sed -e 's/[a-z]*//') 467 local vm_net="${vm_to_tor_net_pattern}.${vm_id}.0/24" 468 local vm_gw="${vm_to_tor_net_pattern}.${vm_id}.1" 469 470 # rule sub-index 471 local pidx=0 472 473 # NFS if requested (NFS should be befre the main section, because we override TCP here that normally goes to tor) 474 local nfs_host nfs_port 475 for nfs_host in $(get_per_vm_option $iface "fw_allow_nfs"); do 476 [ -n "$nfs_mountd_port" ] || nfs_mountd_port=$(nfs_mountd_actual_port) || return 1 477 for nfs_port in $nfs_ports $nfs_mountd_port; do 478 ${fwcmd} add $((cmd+pidx+0)) allow tcp from ${vm_net} to ${nfs_host} ${nfs_port} in via $iface keep-state 479 ${fwcmd} add $((cmd+pidx+1)) allow udp from ${vm_net} to ${nfs_host} ${nfs_port} in via $iface keep-state 480 pidx=$((pidx+2)) 481 done 482 done 483 484 # TCP: sink all TCP from this net to Tor's TransPort 485 ${fwcmd} add $((cmd+pidx+0)) fwd ${vm_gw},9030 tcp from $vm_net to any in via $iface keep-state 486 487 ## DNS (direct): allow DNS to go to tor DNS port (no port conversion, needs net.inet.ip.portrange.reservedhigh=52) 488 #${fwcmd} add $((cmd+pidx+1)) allow udp from ${vm_net} to ${vm_gw} 53 via $iface keep-state 489 # DNS (with port conversion): convert UDP port 53->10053 using anti-nat, no need for sysctl change 490 local divert_port=$cmd 491 ${fwcmd} add $((cmd+pidx+1)) divert $divert_port log udp from ${vm_net} to any dst-port 53 in via $iface 492 ${fwcmd} add $((cmd+pidx+2)) divert $divert_port log udp from ${vm_gw} to ${vm_net} src-port 10053 out via $iface 493 ${fwcmd} add $((cmd+pidx+3)) allow log udp from ${vm_net} to ${vm_gw} dst-port 10053 in via $iface 494 ${fwcmd} add $((cmd+pidx+4)) allow log udp from ${vm_gw} to ${vm_net} src-port 53 out via $iface 495 set_per_vm_option ${iface} "dns_anti_nat_daemon_cmd" "${LOCALBASE}/bin/tiny-udp-anti-nat -d -U ${vm_to_tor_user}:${vm_to_tor_group} -p ${tor_dir}/${iface}/udp-anti-nat.pid -l ${tor_dir}/${iface}/udp-anti-nat.log -D ${vm_gw}:$divert_port -P 53:10053" 496 497 # DHCP: sink DHCP to server on gateway (DHCP requests can have both zero and non-zero source addresses) 498 ${fwcmd} add $((cmd+pidx+5)) fwd ${vm_gw} udp from 0.0.0.0 to 255.255.255.255 67 in via $iface 499 ${fwcmd} add $((cmd+pidx+6)) fwd ${vm_gw} udp from ${vm_net} to 255.255.255.255 67 in via $iface 500 ${fwcmd} add $((cmd+pidx+7)) allow udp from ${vm_gw} to any 68 out via $iface 501 502 # SSH: allow host-to-VM SSH for local administration 503 ${fwcmd} add $((cmd+pidx+8)) allow tcp from ${vm_gw} to ${vm_net} 22 out via $iface keep-state 504 505 pidx=$((pidx+9)) # 9 main rules 506 507 # HS: you can add any additional host-to-VM TCP ports for tor hidden services, or other purposes 508 for port in $(unique_port_set_for_iface $iface); do 509 ${fwcmd} add $((cmd+pidx)) allow tcp from ${vm_gw} to ${vm_net} ${port} out via $iface keep-state 510 pidx=$((pidx+1)) 511 done 512 513 # DENY: no other communication is allowed on this interface 514 ${fwcmd} add $((cmd+step-1)) deny all from any to any via $iface 515 516 # move on 517 cmd=$((cmd+step)) 518 done 519} 520 521start_daemon_and_check() { 522 local cmd="$1" 523 local pid_file="$2" 524 ${cmd} >/dev/null 2>&1 525 check_process_by_pid_file "${pid_file}" || return 1 526} 527 528stop_daemon() { 529 local pid_file="$1" 530 check_process_by_pid_file "${pid_file}" && kill $(cat ${pid_file}) >/dev/null 2>&1 || return 1 531 # TODO need to wait here for this pid, because when we delete the enclosing folder 532 # this daemon might still not have deleted its pid file, and will fail for that reason which isn't nice 533 # However, it looks like there is currently no way for the shell program to wait for the file deletion. 534} 535 536stop_daemon_silently() { 537 [ -f "$1" ] && kill $(cat $1) >/dev/null 2>&1 538} 539 540start_daemons() { 541 local dir="${tor_dir}" 542 # dhcp daemon 543 local cmd="${LOCALBASE}/bin/tiny-dhcp-server -d -U ${vm_to_tor_user}:${vm_to_tor_group} -p ${dir}/dhcp-daemon.pid -l ${dir}/dhcp-daemon.log ${vm_to_tor_ifaces}" 544 start_daemon_and_check "${cmd}" "${dir}/dhcp-daemon.pid" || 545 ! echo_err "failed to start dhcp daemon" || return 1 546 save_daemon_cmd_file "${cmd}" dhcp-daemon 547 548 # anti-nat daemons 549 for iface in ${vm_to_tor_ifaces}; do 550 local cmd="$(get_per_vm_option ${iface} dns_anti_nat_daemon_cmd)" 551 start_daemon_and_check "${cmd}" ${tor_dir}/${iface}/udp-anti-nat.pid || 552 ! echo_err "failed to start DNS anti-nat daemon for VM ${iface}" || return 1 553 save_daemon_cmd_file "${cmd}" "${iface}/udp-anti-nat" 554 done 555} 556 557stop_daemons() { 558 stop_daemon "${tor_dir}/dhcp-daemon.pid" || 559 ! echo_err "failed to stop dhcp daemon" || return 1 560 for iface in ${vm_to_tor_ifaces}; do 561 stop_daemon "${tor_dir}/${iface}/udp-anti-nat.pid" || 562 ! echo_err "failed to stop DNS anti-nat daemon for VM ${iface}" || return 1 563 done 564} 565 566stop_daemons_silently() { 567 stop_daemon_silently "${tor_dir}/dhcp-daemon.pid" 568 for iface in ${vm_to_tor_ifaces}; do 569 stop_daemon_silently "${tor_dir}/${iface}/udp-anti-nat.pid" 570 done 571 return 0 572} 573 574start_tor_instance() { 575 local iface="$1" 576 local dir="$2" 577 local gw="$3" 578 local cl="$4" 579 # create tor data dir 580 mkdir_perms "${dir}/data" || ! echo_err "failed to create tor data dir ${dir}/data" || return 1 581 # create blank torrc, because it isn't clear how to avoid using the default 582 touch ${dir}/torrc && set_file_perms_r ${dir}/torrc 583 # prepare command line arguments 584 local cmd="${LOCALBASE}/bin/tor" 585 cmd="${cmd} -f ${dir}/torrc" 586 cmd="${cmd} --PidFile ${dir}/tor.pid" 587 cmd="${cmd} --RunAsDaemon 1" 588 cmd="${cmd} --DataDirectory ${dir}/data" 589 cmd="${cmd} --+Log \"notice file ${dir}/tor.log\"" 590 cmd="${cmd} --DNSPort ${gw}:10053" 591 cmd="${cmd} --TransProxyType ipfw" 592 cmd="${cmd} --TransPort ${gw}:9030" 593 cmd="${cmd} --SocksPort 0" 594 if [ "${vm_to_tor_control_socket}" = "YES" ]; then 595 cmd="${cmd} --ControlSocket ${dir}/ctrl" 596 cmd="${cmd} --CookieAuthentication 1" 597 fi 598 # add per-VM tor options 599 IFS_OLD="$IFS" 600 local IFS="${NL}" 601 local opts=$(get_per_vm_option ${iface} "tor_options") opt 602 for opt in $opts; do 603 opt=$(echo $opt | sed -e 's/^ *//g;s/ *$//g') 604 if [ -n "${opt}" ]; then 605 cmd="${cmd} --${opt}" 606 fi 607 done 608 IFS="${IFS_OLD}" 609 # enable hidden services resolution 610 cmd="${cmd} --VirtualAddrNetworkIPv4 10.192.0.0/10" 611 cmd="${cmd} --AutomapHostsOnResolve 1" 612 # add hidden services that were requested 613 local hidx=0 614 local hs_fld hs_dir hs_oport hs_iport 615 for hs_fld in $(eval "echo \$vm_to_tor_hs_${iface}"); do 616 case $hidx in 617 0) 618 hs_dir=${hs_fld} 619 ;; 620 1) 621 hs_oport=${hs_fld} 622 ;; 623 2) 624 hs_iport=${hs_fld} 625 cmd="${cmd} --HiddenServiceDir ${hs_dir}" 626 cmd="${cmd} --HiddenServicePort \"${hs_oport} ${cl}:${hs_iport}\"" 627 ;; 628 esac 629 [ $hidx -lt 2 ] && hidx=$((hidx+1)) || hidx=0 630 done 631 # log 632 echo "${cmd}" >> ${dir}/tor.cmd 633 set_file_perms_r ${dir}/tor.cmd 634 # run it 635 echo "$(tm) ----- BEGIN OUTPUT (tunnel $iface) -----" >> ${dir}/cmd.log 636 run_su "${cmd}" >> ${dir}/cmd.log 2>&1 637 echo "$(tm) ----- END OUTPUT (tunnel $iface) -----" >> ${dir}/cmd.log 638 set_file_perms_r ${dir}/cmd.log 639 # check if it is running 640 check_process_by_pid_file "${dir}/tor.pid" || return 1 641} 642 643start_tor_instances() { 644 for iface in ${vm_to_tor_ifaces}; do 645 local vm_id=$(echo $iface | sed -e 's/[a-z]*//') 646 local vm_gw="${vm_to_tor_net_pattern}.${vm_id}.1" 647 local vm_cl="${vm_to_tor_net_pattern}.${vm_id}.2" 648 649 start_tor_instance "${iface}" "${tor_dir}/$iface" "${vm_gw}" "${vm_cl}" || 650 ! echo_err "failed to start tor instance for VM $iface" || return 1 651 done 652} 653 654stop_tor_instances() { 655 for iface in ${vm_to_tor_ifaces}; do 656 ([ -s ${tor_dir}/${iface}/tor.pid ] && kill $(cat ${tor_dir}/${iface}/tor.pid)) || 657 ! echo_err "failed to stop tor instance for VM $iface" || ! stop_tor_instances_silently || return 1 658 done 659} 660 661stop_tor_instances_silently() { 662 for iface in ${vm_to_tor_ifaces}; do 663 [ -s ${tor_dir}/${iface}/tor.pid ] && kill $(cat ${tor_dir}/${iface}/tor.pid) >/dev/null 2>&1 664 done 665 # emergency stop, something went wrong, so keep logs 666 return 0 667} 668 669stop_all_silently() { 670 stop_tor_instances_silently 671 destroy_tap_silently 672 stop_daemons_silently 673} 674 675start_tor() { 676 ( 677 ([ ${vm_to_tor_use_base} = "YES" ] && write_tor_config_section) || 678 ([ ${vm_to_tor_use_base} = "NO" ] && start_tor_instances) 679 ) || return 1 680} 681 682stop_tor() { 683 ( 684 ([ ${vm_to_tor_use_base} = "YES" ] && delete_tor_config_section) || 685 ([ ${vm_to_tor_use_base} = "NO" ] && stop_tor_instances) 686 ) || return 1 687} 688 689warn_to_restart_tor_if_running() { 690 if [ -r /var/run/tor/tor.pid ] && procstat $(cat /var/run/tor/tor.pid) > /dev/null 2>/dev/null; then 691 echo "WARNING vm-to-tor changed tor configuration, you need to restart tor: 'service tor restart'" 692 fi 693} 694 695write_tor_config_section() { 696 # The "right" way of doing this is through Tor control port. 697 # However, this requires authentication, and we don't have 698 # the password. vm-to-tor is an automated process, and has 699 # to always succeed. So we just overwrite that file in the 700 # old fashion way. We add our signature to every line, so 701 # that we can always delete these lines when we are stopped. 702 # See torproject ticket#15649 with the request for this 703 # functionality 704 705 local conf_tmp_old="${tor_conf}.vm-to-tor.old.tmp" 706 local conf_tmp_new="${tor_conf}.vm-to-tor.new.tmp" 707 708 rm -f $conf_tmp_new $conf_tmp_old && 709 (cat $tor_conf && 710 echo "# [$torrc_signature] BEGIN vm-to-tor section" && 711 (for iface in ${vm_to_tor_ifaces}; do 712 local vm_id=$(echo $iface | sed -e 's/[a-z]*//') 713 echo "DNSListenAddress ${vm_to_tor_net_pattern}.${vm_id}.1 # $torrc_signature" 714 echo "TransListenAddress ${vm_to_tor_net_pattern}.${vm_id}.1 # $torrc_signature" 715 done) && 716 echo "# [$torrc_signature] END vm-to-tor section") > $conf_tmp_new 717 if [ $? = 0 -a -f $conf_tmp_new ]; then 718 mv $tor_conf $conf_tmp_old && 719 mv $conf_tmp_new $tor_conf && 720 rm -f $conf_tmp_old 721 warn_to_restart_tor_if_running 722 else 723 return 1 724 fi 725} 726 727delete_tor_config_section() { 728 local conf_tmp_old="${tor_conf}.vm-to-tor.old.tmp" 729 local conf_tmp_new="${tor_conf}.vm-to-tor.new.tmp" 730 731 rm -f $conf_tmp_new $conf_tmp_old && 732 (cat $tor_conf | grep -v $torrc_signature) > $conf_tmp_new 733 if [ $? = 0 -a -f $conf_tmp_new ]; then 734 mv $tor_conf $conf_tmp_old && 735 mv $conf_tmp_new $tor_conf && 736 rm -f $conf_tmp_old 737 warn_to_restart_tor_if_running 738 else 739 return 1 740 fi 741} 742 743delete_ipfw_rules() { 744 local cmd=$vm_to_tor_rule_base 745 local step=$(calc_rule_step) 746 local fwcmd="/sbin/ipfw -q" 747 for iface in ${vm_to_tor_ifaces}; do 748 ${fwcmd} delete $(jot - $cmd $((cmd+step-1))) 2>/dev/null 749 cmd=$((cmd+step)) 750 done 751} 752 753guess_vm_type() { 754 local proc_line=$(procstat -c $1 | tail -1) 755 local proc_vbox=$(echo "${proc_line}" | grep " VirtualBox ") 756 if [ -n "${proc_vbox}" ]; then 757 echo "vbox" 758 return 759 fi 760 local proc_bhyve=$(echo "${proc_line}" | grep " bhyve ") 761 if [ -n "${proc_bhyve}" ]; then 762 echo "bhyve" 763 return 764 fi 765} 766 767guess_process_name() { 768 local proc_line=$(procstat -c $1 | tail -1) 769 local proc_vbox=$(echo "${proc_line}" | grep " VirtualBox ") 770 if [ -n "${proc_vbox}" ]; then 771 echo $(echo ${proc_vbox} | sed -E "s/.*--comment (([^ ]| [^-]| -[^-])*)( --.*|[[:space:]]*)$/\1/g") 772 return 773 fi 774 local proc_bhyve=$(echo "${proc_line}" | grep " bhyve ") 775 if [ -n "${proc_bhyve}" ]; then 776 echo $(echo ${proc_bhyve} | sed -E "s/.* bhyve: (.*)$/\1/g") 777 return 778 fi 779} 780 781count_tunnels() { 782 local cnt=0 783 local iface 784 for iface in ${vm_to_tor_ifaces}; do 785 cnt=$((cnt+1)) 786 done 787 echo $cnt 788} 789 790print_hs_for_iface() { 791 local iface="$1" 792 local do_colors="$2" 793 local hidx=0 794 local hs_fld hs_dir hs_oport hs_iport 795 local cnt=0 796 for hs_fld in $(eval "echo \$vm_to_tor_hs_${iface}"); do 797 case $hidx in 798 0) 799 hs_dir=${hs_fld} 800 ;; 801 1) 802 hs_oport=${hs_fld} 803 ;; 804 2) 805 hs_iport=${hs_fld} 806 # output: no newline in the end 807 if [ $cnt -gt 0 ]; then 808 echo "" 809 fi 810 local hs_name 811 if [ -r "${hs_dir}/hostname" ]; then 812 echo -n " * hs: $(color_hs_name $(cat ${hs_dir}/hostname) ${do_colors}) (${hs_dir}) ${hs_oport} -> ${hs_iport}" 813 else 814 echo -n " * hs: $(color_hs_name ${hs_dir} ${do_colors}) ${hs_oport} -> ${hs_iport}" 815 fi 816 cnt=$((cnt+1)) 817 ;; 818 esac 819 [ $hidx -lt 2 ] && hidx=$((hidx+1)) || hidx=0 820 done 821} 822 823## internals: colors 824 825RCol='\e[0m' # Text Reset 826 827# Regular Bold Underline High Intensity BoldHigh Intens Background High Intensity Backgrounds 828Bla='\e[0;30m'; BBla='\e[1;30m'; UBla='\e[4;30m'; IBla='\e[0;90m'; BIBla='\e[1;90m'; On_Bla='\e[40m'; On_IBla='\e[0;100m'; 829Red='\e[0;31m'; BRed='\e[1;31m'; URed='\e[4;31m'; IRed='\e[0;91m'; BIRed='\e[1;91m'; On_Red='\e[41m'; On_IRed='\e[0;101m'; 830Gre='\e[0;32m'; BGre='\e[1;32m'; UGre='\e[4;32m'; IGre='\e[0;92m'; BIGre='\e[1;92m'; On_Gre='\e[42m'; On_IGre='\e[0;102m'; 831Yel='\e[0;33m'; BYel='\e[1;33m'; UYel='\e[4;33m'; IYel='\e[0;93m'; BIYel='\e[1;93m'; On_Yel='\e[43m'; On_IYel='\e[0;103m'; 832Blu='\e[0;34m'; BBlu='\e[1;34m'; UBlu='\e[4;34m'; IBlu='\e[0;94m'; BIBlu='\e[1;94m'; On_Blu='\e[44m'; On_IBlu='\e[0;104m'; 833Pur='\e[0;35m'; BPur='\e[1;35m'; UPur='\e[4;35m'; IPur='\e[0;95m'; BIPur='\e[1;95m'; On_Pur='\e[45m'; On_IPur='\e[0;105m'; 834Cya='\e[0;36m'; BCya='\e[1;36m'; UCya='\e[4;36m'; ICya='\e[0;96m'; BICya='\e[1;96m'; On_Cya='\e[46m'; On_ICya='\e[0;106m'; 835Whi='\e[0;37m'; BWhi='\e[1;37m'; UWhi='\e[4;37m'; IWhi='\e[0;97m'; BIWhi='\e[1;97m'; On_Whi='\e[47m'; On_IWhi='\e[0;107m'; 836 837color_os_name() { 838 local os_name="$1" 839 local do_colors="$2" 840 if [ ${vm_to_tor_show_colors} = "YES" -a ${do_colors} = 1 ]; then 841 if expr "${os_name}" : "FreeBSD" > /dev/null; then 842 echo -e "${Gre}${os_name}${RCol}" 843 elif expr "${os_name}" : "Arch" > /dev/null; then 844 echo -e "${Cya}${os_name}${RCol}" 845 elif expr "${os_name}" : "Windows" > /dev/null; then 846 echo -e "${Pur}${os_name}${RCol}" 847 elif expr "${os_name}" : "Mint" > /dev/null; then 848 echo -e "${Gre}${os_name}${RCol}" 849 elif expr "${os_name}" : "Ubuntu" > /dev/null; then 850 echo -e "${IRed}${os_name}${RCol}" 851 elif expr "${os_name}" : "Fedora" > /dev/null; then 852 echo -e "${IBlu}${os_name}${RCol}" 853 else 854 echo -e "${UBla}${os_name}${RCol}" 855 fi 856 else 857 echo "${os_name}" 858 fi 859} 860 861color_hs_name() { 862 local hs_name="$1" 863 local do_colors="$2" 864 if [ ${vm_to_tor_show_colors} = "YES" -a ${do_colors} = 1 ]; then 865 echo -e "${URed}${hs_name}${RCol}" 866 else 867 echo "${hs_name}" 868 fi 869} 870 871## interface: for service manager 872 873vm_to_tor_start() { 874 # checks 875 is_user_authorized_with_msg "start ${nameb}" || return 1 876 are_any_processes_running 1 || return 1 877 [ ${vm_to_tor_use_base} = "YES" ] || is_tor_installed || ! echo_err "tor is not installed, ${nameb} can't be started" || return 1 878 # start (every procedure below is responsible for printing its error messages if any) 879 ( 880 check_config && 881 check_sysctl && 882 create_dirs && 883 parse_hs && 884 ([ ${vm_to_tor_use_base} = "NO" ] || check_tor_config) && 885 create_tap && 886 add_ipfw_rules && 887 start_daemons && 888 start_tor && 889 echo_out "started with interfaces ${vm_to_tor_ifaces}" 890 ) || ! echo_err "failed to start $nameb" || ! stop_all_silently || return 1 891} 892 893vm_to_tor_status() { 894 parse_hs || return 1 895 local cntOk=0 896 local cntNo=0 897 local msgOk msgNo strOpened 898 local ntunnels=$(count_tunnels) 899 local do_colors=0 900 if [ -t 1 ]; then 901 do_colors=1 902 fi 903 if [ $ntunnels -gt 1 ]; then 904 local xnl="${NL}" 905 local xbu=" * " 906 local xlt=": " 907 local xrt="" 908 else 909 local xnl=" " 910 local xbu="" 911 local xlt="(" 912 local xrt=")" 913 fi 914 for iface in ${vm_to_tor_ifaces}; do 915 if ! (ls /dev/ | grep $iface >/dev/null 2>/dev/null); then 916 cntNo=$((cntNo+1)) 917 if [ $cntNo -gt 1 ]; then 918 msgNo="${msgNo}" 919 fi 920 msgNo="${msgNo}${iface}" 921 else 922 cntOk=$((cntOk+1)) 923 local descr="" 924 # vm pid 925 strOpenedPid=$(ifconfig ${iface} | grep -i open | sed -e 's/[a-zA-Z[:space:]]*//g') 926 if [ -n "${strOpenedPid}" ]; then 927 descr="vm-pid=${strOpenedPid}" 928 local vm_type="$(guess_vm_type ${strOpenedPid})" 929 if [ -n "${vm_type}" ]; then 930 descr="${descr} vm-type=${vm_type}" 931 fi 932 local processName=$(color_os_name "$(guess_process_name ${strOpenedPid})" ${do_colors}) 933 if [ -n "${processName}" ]; then 934 descr="${descr} vm-name=${processName}" 935 fi 936 fi 937 # tor pid 938 if [ ${vm_to_tor_use_base} = "NO" ]; then 939 if [ -n "${descr}" ]; then 940 descr="${descr} " 941 fi 942 descr="${descr}tor-pid=$(cat ${tor_dir}/${iface}/tor.pid 2>/dev/null || echo 'n/a')" 943 fi 944 # print iface status 945 if [ $cntOk -gt 1 ]; then 946 msgOk="${msgOk}${xnl}" 947 fi 948 if [ -n "${descr}" ]; then 949 msgOk="${msgOk}${xbu}${iface}${xlt}${descr}${xrt}" 950 else 951 msgOk="${msgOk}${xbu}${iface}" 952 fi 953 local hs=$(print_hs_for_iface ${iface} ${do_colors}) 954 if [ -n "${hs}" ]; then 955 msgOk="${msgOk}${NL}${hs}" 956 fi 957 fi 958 done 959 if [ $cntNo = 0 ]; then 960 echo "$nameb is running with tunnels:${xnl}${msgOk}" 961 elif [ $cntOk = 0 ]; then 962 echo "$nameb is not running (${vm_to_tor_ifaces})" 963 return 1 964 else 965 echo "$nameb isn't properly setup: $cntNo of $((cntOk+cntNo)) tunnels doesn't exist (${vm_to_tor_ifaces})" 966 return 1 967 fi 968} 969 970vm_to_tor_stop() { 971 # checks 972 is_user_authorized_with_msg "stop $nameb" || return 1 973 ! are_any_processes_running 0 || ! echo_err "not running" || return 1 974 # stop 975 local fail=0 976 parse_hs || fail=$((fail+1)) 977 delete_ipfw_rules || fail=$((fail+1)) 978 stop_daemons || fail=$((fail+1)) 979 destroy_tap || fail=$((fail+1)) 980 stop_tor || fail=$((fail+1)) 981 [ $fail -ne 0 ] || delete_dir || fail=$((fail+1)) 982 [ $fail -eq 0 ] || ! echo_err "failed to stop $nameb: $fail problems" || return 1 983 [ $fail -ne 0 ] || echo_out "stopped" 984} 985 986command="/usr/bin/true" 987 988run_rc_command "$1" 989