dowse

Copyright (C) 2013-2014 Dyne.org Foundation

Dowse is written by Denis Roio http://jaromil.dyne.org

This source code is free software; you can redistribute it and/or modify it under the terms of the GNU Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version.

This source code 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. Please refer to the GNU Public License for more details.

You should have received a copy of the GNU Public License along with this source code; if not, write to: Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

#!/usr/bin/env zsh


GLOBALS


VERSION=0.3
DATE="Jan/2014"

QUIET=0
DEBUG=0

SCRIPT=$0

standard output message routines

autoload colors; colors

it's always useful to wrap them, in case we change behaviour later

notice() { if [[ $QUIET == 0 ]]; then print "$fg_bold[green][*]$fg_no_bold[default] $1" >&2; fi }
error()  { if [[ $QUIET == 0 ]]; then print "$fg[red][!]$fg[default] $1" >&2; fi }
func()   { if [[ $DEBUG == 1 ]]; then print "$fg[blue][D]$fg[default] $1" >&2; fi }
act()    {
    if [[ $QUIET == 0 ]]; then
	if [ "$1" = "-n" ]; then
	    print -n "$fg_bold[white] . $fg_no_bold[default] $2" >&2;
	else
	    print "$fg_bold[white] . $fg_no_bold[default] $1" >&2;
	fi
    fi
}


honor quiet and debug flags as early as possible

if [[ ${@} == *-q* ]]; then QUIET=1; fi
if [[ ${@} == *-D* ]]; then DEBUG=1; fi

DIR=${DOWSE:-`pwd`}


pid files for our daemons

pid_tor=$DIR/run/tor.pid
pid_squid=$DIR/run/squid.pid
pid_dnsmasq=$DIR/run/dnsmasq.pid
pid_privoxy=$DIR/run/privoxy.pid

read the network configuration of known hosts

known=`cat conf/network | grep -v '^#'`



CHECKS


{ test -r conf } || {
    error "The dowse script must be run inside its source directory"
    return 1 }

{ test -r conf/settings } || {
    error "Dowse configuration is missing, create conf/settings"
    return 1 }

{ test -r conf/network } || {
    error "No network is configured, create conf/network"
    return 1 }

notice "Dowse $VERSION - local area network rabdomancy"
cat <<EOF

 Copyright (C) 2013-2014 Dyne.org Foundation, License GNU GPL v3+
 This is free software: you are free to change and redistribute it
 For more informations see http://www.dyne.org/software/dowse

EOF

func "root access"
{ test "$UID" = "0" } || {
    error "Dowse needs root privileges to operate."
    return 1 }

func "loading configuration from $DIR/conf/settings"
source conf/settings

setup dirs

mkdir -p log
chmod go-rwx log
chown -R $dowseuid:$dowsegid log

mkdir -p run
chmod go-rwx run
chown -R $dowseuid:$dowsegid run

create the cache dir in RAM

mkdir -p /dev/shm/dowse
chown -R $dowseuid:$dowsegid /dev/shm/dowse
chmod go-rwx /dev/shm/dowse



START/STOP DAEMONS


waitpid()

waitpid() {

takes a pid

	pid="$1"
	lastnewline=0
	while true; do
		ps -p "$pid" > /dev/null
		if [ $? = 0 ]; then print -n . ; lastnewline=1; sleep 1
		else break; fi

todo: timeout with kill -9

	done

just because we care to look good on the console

	{ test $lastnewline = 1 } && { print }
}


dnsmasq_stop()

dnsmasq_stop() {
    { test -r $pid_dnsmasq } && {
	pid=`cat $pid_dnsmasq`
	act "Stopping dnsmasq ($pid)"
	kill $pid
	waitpid $pid
	rm -f $pid_dnsmasq
    }
}

dnsmasq_start()

dnsmasq_start() {
    act "Preparing to launch dnsmasq..."

if running, stop to restart

    dnsmasq_stop

    func "dnsmasq --pid-file $DIR/run/dnsmasq.pid -C $DIR/dnsmasq.conf"
    dnsmasq --pid-file=$pid_dnsmasq -C $DIR/run/dnsmasq.conf
}

squid_stop()

squid_stop() {
    { test -r $pid_squid } && {
	pid=`cat $pid_squid`
	ps -p "$pid" > /dev/null
	{ test $? = 0 } || {
	    func "removing stale pid for squid"
	    rm -f $pid_squid
	    return 1 }
	act "Stopping squid ($pid)"
	setuidgid $dowseuid squid3 -f $DIR/run/squid.conf -k shutdown
	{ test $? = 0 } || {
	    error "Error running squid3, the daemon might be left running."
	    return 1 }
	waitpid $pid
	rm -f $pid_squid
    }
}

squid_start()

squid_start() {
    act "Preparing to launch Squid..."

    func "setuidgid $dowseuid squid -f $DIR/run/squid.conf"

cleanup all previous cache in ram

    rm -rf /dev/shm/dowse/*

populate the volatile cache

    setuidgid $dowseuid squid3 -z -f $DIR/run/squid.conf

launch the squid

    setuidgid $dowseuid squid3 -f $DIR/run/squid.conf
}

privoxy_stop()

privoxy_stop() {
    { test -r $pid_privoxy } && {
	pid=`cat $pid_privoxy`
	act "Stopping privoxy ($pid)"
	kill $pid
	waitpid $pid
	rm -f $pid_privoxy
    }
}

privoxy_start()

privoxy_start() {
    act "Preparing to launch privoxy..."

if running, stop to restart

    privoxy_stop

    privoxy --user $dowseuid --pidfile $pid_privoxy $DIR/run/privoxy.conf
}

tor_stop()

tor_stop() {
    command -v tor >/dev/null
    { test $? = 0 } || { func "tor not found, skipping"; return 1 }

    { test -r $pid_tor } && {
	pid=`cat $pid_tor`
	act "Stopping tor ($pid)"
	kill $pid
	waitpid $pid

pid file is deleted by tor

    }
}

tor_start()

tor_start() {
    command -v tor >/dev/null
    { test $? = 0 } || { func "tor not found, skipping"; return 1 }

    act "Preparing to launch tor..."

if running, stop to restart

    tor_stop

    tor -f $DIR/run/tor.conf
}

iptables_flush()

iptables_flush() {
    act "Flushing iptables firewall rules"
    iptables -F
    iptables -X
    iptables -t nat -F
    iptables -t nat -X
    iptables -t mangle -F
    iptables -t mangle -X
    iptables -P INPUT ACCEPT
    iptables -P FORWARD ACCEPT
    iptables -P OUTPUT ACCEPT
}

iptables_start()

iptables_start() {
    notice "Setting up iptables firewall rules"

    iptables -P OUTPUT ACCEPT
    iptables -P INPUT DROP
    iptables -P FORWARD DROP

    func "allow only local loopback"
    iptables -A INPUT -i eth0 -d 127.0.0.1 -j DROP
    iptables -A INPUT -i eth0 -s 127.0.0.1 -j DROP
    iptables -A FORWARD -i eth0 -s 127.0.0.1 -j DROP
    iptables -A FORWARD -i eth0 -d 127.0.0.1 -j DROP
    iptables -A INPUT -s 127.0.0.1 -j ACCEPT
    iptables -A INPUT -d 127.0.0.1 -j ACCEPT


Block outgoing NetBios (if you have windows machines running on the private subnet). This will not affect any NetBios traffic that flows over the VPN tunnel, but it will stop local windows machines from broadcasting themselves to the internet. iptables -A FORWARD -p tcp --sport 137:139 -j DROP iptables -A FORWARD -p udp --sport 137:139 -j DROP iptables -A OUTPUT -p tcp --sport 137:139 -j DROP iptables -A OUTPUT -p udp --sport 137:139 -j DROP


Check source address validity on packets going out to internet iptables -A FORWARD -s ! ${dowsenet} -i eth0 -j DROP


Allow packets from private subnets

    iptables -A INPUT -s ${dowsenet} -j ACCEPT
    iptables -A FORWARD -i ${interface} -s ${dowsenet} -j ACCEPT

Allow DHCP service

    iptables -A INPUT -p udp --sport 67:68 --dport 67:68 -j ACCEPT

Allow incoming pings (can be disabled)

    iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT

Allow services such as www and ssh (can be disabled)

    iptables -A INPUT -p tcp --dport http -j ACCEPT
    iptables -A INPUT -p tcp --dport ssh -j ACCEPT

Keep state of connections from local machine and private subnets

    iptables -A OUTPUT -m state --state NEW -o eth0 -j ACCEPT
    iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
    iptables -A FORWARD -m state --state NEW -o eth0 -j ACCEPT
    iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT

Allow incoming OpenVPN packets. Duplicate the line below for each OpenVPN tunnel, changing --dport n to match the OpenVPN UDP port. In OpenVPN, the port number is controlled by the --port n option. If you put this option in the config file, you can remove the leading '--' If you taking the stateful firewall approach (see the OpenVPN HOWTO), then comment out the line below. iptables -A INPUT -p udp --dport 1194 -j ACCEPT



    func "defend the network from ipv6"
    ip6tables -F
    ip6tables -P INPUT DROP
    ip6tables -P FORWARD DROP
    ip6tables -P OUTPUT DROP

}

ebtables_flush()

ebtables_flush() {
    command -v ebtables >/dev/null
    { test $? = 0 } || { func "ebtables not found, skipping"; return 1 }

    act "flushing ebtables (layer 2 firewall)"
    ebtables -P FORWARD ACCEPT
    ebtables -P OUTPUT ACCEPT
    ebtables -P INPUT ACCEPT
    ebtables -F
}

ebtables_start()

ebtables_start() {
    command -v ebtables >/dev/null
    { test $? = 0 } || { func "ebtables not found, skipping"; return 1 }

    notice "Setting up ebtables rules (layer 2 firewall)"
    ebtables -P FORWARD DROP
    ebtables -A FORWARD -p IPv4 -j ACCEPT
    ebtables -A FORWARD -p ARP -j ACCEPT
    ebtables -A FORWARD -p LENGTH -j ACCEPT
    ebtables -A FORWARD --log-level info --log-ip --log-prefix EBFW
    ebtables -P INPUT DROP
    ebtables -A INPUT -p IPv4 -j ACCEPT
    ebtables -A INPUT -p ARP -j ACCEPT
    ebtables -A INPUT -p LENGTH -j ACCEPT
    ebtables -A INPUT --log-level info --log-ip --log-prefix EBFW
    ebtables -P OUTPUT DROP
    ebtables -A OUTPUT -p IPv4 -j ACCEPT
    ebtables -A OUTPUT -p ARP -j ACCEPT
    ebtables -A OUTPUT -p LENGTH -j ACCEPT
    ebtables -A OUTPUT --log-level info --log-ip --log-arp --log-prefix EBFW -j DROP

    act "pinning down known MAC addresses to IP"
    for i in ${(f)known}; do

check if its a mac address

	echo "$i" | grep '^..:..:..:..:..:..' > /dev/null
	{ test $? = 0 } || { continue } # skip if no mac address

	mac=${i[(w)1]}
	host=${i[(w)2]}
	ip=${i[(w)3]}

	{ test "$host" = "ignore" } && { continue }

	func "$i"

	ebtables -A FORWARD -p IPv4 --ip-src ${ip} -s ! ${mac} -j DROP

    done

}

sysctl_setup()

sysctl_setup() {
cat <<EOF | sysctl -p -
net.ipv4.tcp_syncookies = 1
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.icmp_ignore_bogus_error_responses = 1
net.core.rmem_max = 33554432
net.core.wmem_max = 33554432
net.ipv4.tcp_fin_timeout = 4
vm.min_free_kbytes = 65536
net.netfilter.nf_conntrack_tcp_timeout_established = 7200
net.netfilter.nf_conntrack_checksum = 0
net.netfilter.nf_conntrack_tcp_timeout_syn_sent = 15
net.ipv4.tcp_keepalive_time = 60
net.ipv4.tcp_keepalive_intvl = 10
net.ipv4.tcp_keepalive_probes = 3
net.ipv4.ip_local_port_range = 1025 65530
net.ipv4.tcp_timestamps = 0
EOF
}





DOWSE FUNCTIONS


dowse_check()

dowse_check() {
    act "Checking requirements to run dowse..."

    for req in setuidgid dnsmasq iptables privoxy squid3; do
	command -v $req >/dev/null
	{ test $? != 0 } && {
	    error "Cannot find $req"
	    error "You need it to run dowse, please install it."
	    return 1
	}
    done

    return 0
}

dowse_setup()

dowse_setup() {
    act "Configuring our box and connecting it to the Internet..."

    func "reading configuration in conf/settings"

    func "generating dnsmasq.conf"
    cat <<EOF > $DIR/run/dnsmasq.conf
address=/$hostname/$dowse
address=/.i2p/$dowse
address=/.onion/$dowse
bogus-priv
cache-size=1500
conf-dir=/etc/dnsmasq.d
dhcp-range=$dowseguests
addn-hosts=$DIR/run/hosts
dhcp-leasefile=$DIR/run/leases
domain-needed
domain=$lan
expand-hosts
interface=$interface
listen-address=$dowse,127.0.0.1
local=/$lan/
user=$dowseuid
group=$dowsegid
EOF

    func "generating privoxy.conf"
    cat <<EOF > $DIR/run/privoxy.conf
user-manual /usr/share/doc/privoxy/user-manual
confdir /etc/privoxy
logdir $DIR/log/privoxy
listen-address  0.0.0.0:8118
toggle  1
enable-remote-toggle  0
enable-remote-http-toggle  0
enable-edit-actions 1
enforce-blocks 0
buffer-limit 64000

forwarded-connect-retries  0
accept-intercepted-requests 1
allow-cgi-request-crunching 0
split-large-forms 0
keep-alive-timeout 5
socket-timeout 300
handle-as-empty-doc-returns-ok 1


# DIVIDER
forward-socks4a .onion $dowse:9050 .

# DIVIDER

# DIVIDER
forward $hostname .

filterfile default.filter
actionsfile match-all.action # Actions that are applied to all sites and maybe overruled later on.
actionsfile default.action   # Main actions file
actionsfile user.action      # User customizations
EOF

    func "generating squid.conf"

    cat <<EOF > $DIR/run/squid.conf
pid_filename $pid_squid
cache_effective_user $dowseuid
cache_store_log none
cache_log $DIR/log/squid_cache.log
access_log /dev/null
# DIVIDER

# DIVIDER
cache_mem 16 MB
cache_dir aufs /dev/shm/dowse 256 16 256
maximum_object_size 16 MB
maximum_object_size_in_memory 1 MB
minimum_object_size 16 KB
memory_pools off


acl all src all
acl manager proto cache_object
acl localhost src 127.0.0.1/32
acl to_localhost dst 127.0.0.0/8 0.0.0.0/32

acl localnet src $dowsenet

acl SSL_ports port 443		# https
acl Safe_ports port 80		# http
acl Safe_ports port 443		# https
acl purge method PURGE
acl CONNECT method CONNECT

http_access allow manager localhost
http_access deny manager
http_access allow purge localhost
http_access deny purge
http_access deny !Safe_ports
http_access deny CONNECT !SSL_ports

http_access allow localnet
http_access allow localhost

http_access deny all

icp_access allow localnet

http_port 3128 transparent

hierarchy_stoplist cgi-bin ?

refresh_pattern ^ftp:		1440	20%	10080
refresh_pattern ^gopher:	1440	0%	1440
refresh_pattern -i (/cgi-bin/|\?) 0	0%	0
refresh_pattern -i (deb|tar|gz|tgz|bz2|zip|rar|msi|exe|rpm)$ 0 90% 1440
refresh_pattern (Release|Packages(.gz)*)$	0	20%	2880
refresh_pattern .		0	20%	4320

acl shoutcast rep_header X-HTTP09-First-Line ^ICY.[0-9]
# DIVIDER

acl apache rep_header Server ^Apache
# DIVIDER

# DIVIDER

cache_mgr Dowse

hosts_file $DIR/run/hosts

coredump_dir $DIR/log

cache_peer localhost parent 8118 0 default no-query no-digest no-netdb-exchange
never_direct allow all

# DIVIDER

# DIVIDER


EOF

    cat <<EOF > $DIR/run/tor.conf
User $dowseuid
PidFile $pid_tor
SocksPort $dowse:9050
SocksPolicy accept 10.0.0.250 # $dowsenet
SocksPolicy reject *
Log notice file $DIR/log/tor.log
RunAsDaemon 1
DataDirectory $DIR/run
ControlPort 9051
CookieAuthentication 1
ExitPolicy reject *:*
EOF

    func "Fixing entries for known peers"
    rm -f $DIR/run/dnsmasq.network

pass through tor for urls.onion

    print "dhcp-option=option:router,$dowse" > $DIR/run/dnsmasq.network
    

pass through i2p for urls.i2p forward .i2p $dowse:4444 forward .i2p 127.0.0.1:4444

    func "Generating hosts file"
    rm -f $DIR/run/hosts
    echo "127.0.0.1 localhost" > $DIR/run/hosts
    for i in ${(f)known}; do
	echo "$i" | grep '^..:..:..:..:..:..' > /dev/null
	if [ $? = 0 ]; then # mac address is first
	    host=${i[(w)2]}
	    ip=${i[(w)3]}
	else # no mac address specified
	    host=${i[(w)1]}
	    ip=${i[(w)2]}
	fi
	{ test "$host" = "ignore" } || {

direct access

	    print "$ip $host" >> $DIR/run/hosts }
    done
    
    func "generating dnsmask.network"
    for i in ${(f)known}; do
	echo "$i" | grep '^..:..:..:..:..:..' > /dev/null
	{ test $? = 0 } || { continue } # skip if no mac address
	func "$i"

access_log $DIR/log/squid_access.log squid

	mac=${i[(w)1]}
	host=${i[(w)2]}
	ip=${i[(w)3]}
	

avoid having a physical cache directory

	print "dhcp-host=$mac, $host, $ip" >> $DIR/run/dnsmasq.network
	
    done
    

upgrade_http0.9 deny shoutcast

    cat $DIR/run/dnsmasq.network >> $DIR/run/dnsmasq.conf
    

broken_vary_encoding allow apache

    chown -R $dowseuid:$dowsegid log
    chown -R $dowseuid:$dowsegid run
    
    notice "Setup completed in $DIR"
    return 0
}


extension_methods REPORT MERGE MKACTIVITY CHECKOUT

dowse_start() {

    notice "Setting up the network..."

    PGL=`pidof pgld`
    { test "$PGL" = "" } || {
	act "PeerGuardian found running, will restart it accordingly"
	pglcmd stop }

    act "Setting up $interface interface"
    ifconfig $interface $dowse netmask $netmask up
    route add default gw $wan

    func "enable masquerading"
    modprobe nf_conntrack_ipv4
    sysctl net.netfilter.nf_conntrack_acct=1

header_access From deny all

    sysctl_setup

    func "enable ip forwarding"
    print 1 > /proc/sys/net/ipv4/ip_forward

    func "bugfix for routing table weirdness in Linux >3.1"

the settings below are restrictive: they grant more privacy but break many websites! header_access Link deny all header_access Server deny all header_access Referer deny all header_access User-Agent deny all header_access WWW-Authenticate deny all

    echo 0 >>/proc/sys/net/ipv4/conf/eth0/accept_redirects

    { test "$firewall" = "no" } || {
	ebtables_flush
	iptables_flush
    }

    { test "$firewall" = "yes" } && {
	ebtables_start
	iptables_start
    }

    notice "Setting up masquerading (NAT)"
    func "setup route towards wired network"
    iptables --table nat --append POSTROUTING --out-interface $interface -j SNAT --to $dowse

    func "setup transparent proxy to squid"
    iptables -t nat -A PREROUTING -i $interface -s $dowsenet -p tcp --dport 80 -j REDIRECT --to-port 3128

this is basically a dnsmasq host configuration file

    dnsmasq_start

this is our generated hosts file

    privoxy_start

add a line to the hosts list

    squid_start

gather configuration into variables, line by line

    tor_start

add a line to the dnsmasq host list

    { test "$PGL" = "" } || { pglcmd start }

}

append network settings to dnsmasq conf

dowse_stop() {
    notice "Stopping all services."
    tor_stop
    squid_stop
    privoxy_stop
    dnsmasq_stop
}

set permissions of setup files


dowse_start()


dowse_check
{ test $? = 1 } && {
    error "Aborting operation for missing requirements."
    return 1 }

strenghten and optimize a bit the system for networking

case "$1" in
    restart|start) dowse_setup; dowse_stop; dowse_start ;;
    release) rm $DIR/run/leases; dowse_setup; dnsmasq_stop; dnsmasq_start ;;
    reload) rm -rf /dev/shm/dowse/*; dowse_setup; setuidgid $dowseuid squid3 -f $DIR/run/squid.conf -k rec ;;
    stop) dowse_stop ;;
    *) echo "dowse: command not found: $1" ;;
esac

see https://lkml.org/lkml/2011/11/18/191 and http://www.spinics.net/lists/netdev/msg179687.html

return 0

start the dnsmasq daemon



start the privoxy daemon


start the squid daemon

if found, start Tor

if PeerGuardian was running, start it again

dowse_stop()


MAIN

we use a very simple argument parser

be nice with the environment