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