Remote Scripts

From DD-WRT Wiki

Revision as of 15:34, 30 April 2010 by Glenn (Talk | contribs)
(diff) ←Older revision | Current revision (diff) | Newer revision→ (diff)
Jump to: navigation, search

This script runs on a LAN-side host. It interacts with a dd-wrt router for tasks like launching a VPN tunnel on a one-time basis, configuring the router to persistently launch a VPN on boot, configuring the SES button to send a wake-on-lan signal, turning the radio on/off, etc.

This is a Tcl/Tk/Expect script.

Usage:

Syntax

	ddwrt.exp [ help | [-host <host>] [-pw <password>] <command> ]

Commands

	reboot

	login

	show [logs [-noexit] | vpncfg <vpn config file>]

		logs:   shows the most recent log file in /var/log/
		vpncfg: shows the VPN config file that would be sent by the start or boot vpn commands, without actually taking any action.

	start <vpn config file> [-noexit]

		Starts an openvpn session on the router using the configuration file provided.
		Paths in the configuration file are modified to suit the dd-wrt configuration,
		and all keys and other files needed are collected and pushed to the router in one aggregated command.
		The VPN session does not become a persistent configuration upon boot (see the boot command).

	stop [-noexit]

		Stops the VPN server.

	boot { wos <IP> <MAC> | clear | vpncfg <vpn config file> } [-noexit]

		wos:    Configures the SES button to send a wake-on-lan signal.  Setting is persistent.
			      IP:  ip address of machine to wake.
			      MAC: mac address of machine to wake.
		clear:  Removes the boot script.
		vpncfg: Configures the router to start a tunnel on boot.  Setting is persistent.

	radio [on | off] (default: on)

Environment variables

	DDWRT_HOST : specifies the dd-wrt host in the absense of the -host option
	DDWRT_PW   : specifies the dd-wrt login password in the absense of the -pw option.  If -pw is not used, and this variable is not set, then there will be a prompt for entry.

The script:

#!/usr/bin/expect --
#
# (copyleft) Justin Gombos
#
# Script to interact with a dd-wrt router.
#

proc put_help {} {
    puts "Syntax\n"
    puts "\tddwrt.exp \[ help | \[-host <host>] \[-pw <password>] <command> ]\n"
    puts "Commands\n"
    puts "\treboot\n"
    puts "\tlogin\n"
    puts "\tshow \[logs \[-noexit] | vpncfg <vpn config file>]\n"
    puts "\t\tlogs:   shows the most recent log file in /var/log/"
    puts "\t\tvpncfg: shows the VPN config file that would be sent by the start or boot vpn commands, without actually taking any action.\n"
    puts "\tstart <vpn config file> \[-noexit]\n"
    puts "\t\tStarts an openvpn session on the router using the configuration file provided.\n\t\tPaths in the configuration file are modified to suit the dd-wrt configuration,\n\t\tand all keys and other files needed are collected and pushed to the router in one aggregated command.\n\t\tThe VPN session does not become a persistent configuration upon boot (see the boot command).\n"
    puts "\tstop \[-noexit]\n"
    puts "\t\tStops the VPN server.\n"
    puts "\tboot { wos <IP> <MAC> | clear | vpncfg <vpn config file> } \[-noexit]\n"
    puts "\t\twos:    Configures the SES button to send a wake-on-lan signal.  Setting is persistent."
    puts "\t\t\t      IP:  ip address of machine to wake."
    puts "\t\t\t      MAC: mac address of machine to wake."
    puts "\t\tclear:  Removes the boot script."
    puts "\t\tvpncfg: Configures the router to start a tunnel on boot.  Setting is persistent.\n"
    puts "\tradio \[on | off] (default: on)"
    puts ""
    puts "Environment variables"
    puts ""
    puts "\tDDWRT_HOST : specifies the dd-wrt host in the absense of the -host option"
    puts "\tDDWRT_PW   : specifies the dd-wrt login password in the absense of the -pw option.  If -pw is not used, and this variable is not set, then there will be a prompt for entry."
    puts "\n\n"
}

proc initializeGlobals {envName argvName} {
    global host
    upvar 1 $envName  lenv
    upvar 1 $argvName largs

    if {[set pos [lsearch $largs -host]] != -1} {
	set host(ip) [lindex $largs [expr $pos + 1]]
	set largs    [lreplace $largs $pos [expr $pos + 1]]
    } elseif {[array name lenv DDWRT_HOST] != ""} {
	set host(ip) $lenv(DDWRT_HOST)
    } else {
	set host(ip) "UNKNOWN"
    }

    if {[set pos [lsearch $largs -pw]] != -1} {
	set host(pw) [lindex $largs [expr $pos + 1]]
	set largs    [lreplace $largs $pos [expr $pos + 1]]
    } elseif {[array name lenv DDWRT_PW] != ""} {
	set host(pw) $lenv(DDWRT_PW)
    } else {
	set host(pw) "UNKNOWN"
    }
}

proc getpass pwprompt {
    # taken from http://wiki.tcl.tk/3594
    set oldmode [stty -echo -raw]
    send_user "\n     $pwprompt"
    set timeout -1
    expect_user -re "(.*)\n"
    send_user "\n"
    eval stty $oldmode
    return $expect_out(1,string)
}

proc login {target_ip {target_pw "UNKNOWN"}} {
    set target(ip)   $target_ip
    set target(user) root
    set target(host) "DEADBEEF"

    if {$target_pw == "UNKNOWN"} {
	set target(pw) [getpass "Enter password for $target(user): "]
    } else {
	set target(pw) $target_pw
    }

    expect {
	"telnet>" {
	    send     "open $target(ip)\n"
	    exp_continue
	}
	-re {([[:alnum:]]*)\ (.ogin: )} {
	    set target(host) $expect_out(1,string)
	    send     "$target(user)\n"   
	    exp_continue
	}
	-glob "assword: " {
	    exp_send "$target(pw)\n"
	    exp_continue
	}
	-re {root@.*:} {
	    return $target(host)
	}
    }
}

# Returns the path to a file, first checking the existence of the
# supplied file.  If it's not found, the candidate path is checked.
#
proc pathname {target candidate_path} {

    if { [file exist $target] } {
	set return_data $target
    } else {
	set return_data [file join $candidate_path $target]
    }
    return $return_data
}

proc filetext {filename} {

    if { [file exist $filename] } {

	set fileptr     [open $filename "r"]
	set return_data "echo \'[read $fileptr]\' > [file join \$OVPN_DIR [file tail $filename]]"

	close $fileptr
    } else {
	set return_data "echo \'no $filename exists to write to\'"
    }
    return $return_data
}

proc rawtext {filename contents} {
    return "echo \'$contents\n\' > [file join \$OVPN_DIR $filename]"
}

proc aggregated_launch_string {config_file loop_timeout} {

    set dir(ovpn.target) [file join / tmp openvpn]

    set filename(conf) $config_file
    set filetail(conf) [file tail $filename(conf)]
    set fileptr(conf)  [open $filename(conf) "r"]
    set filetext(conf) [read $fileptr(conf)]

    regexp -nocase {[\n^][[:blank:]]*\mkey[[:blank:]]+([[:graph:]]+)} $filetext(conf) junk keyfile
    regexp -nocase {\mca[[:blank:]]+([[:graph:]]+)}   	 	      $filetext(conf) junk cafile
    regexp -nocase {\mcert[[:blank:]]+([[:graph:]]+)} 		      $filetext(conf) junk certfile
    regexp -nocase {\mtls-auth[[:blank:]]+([[:graph:]]+)} 	      $filetext(conf) junk takeyfile
    regexp -nocase {\mauth-user-pass[[:blank:]]+([[:graph:]]+)}       $filetext(conf) junk authfile

    set keydump     ""
    set keyfilelist ""

    if { [info exists cafile]}    {set keyfilename(ca)    [pathname $cafile    [file dirname $filename(conf)]]}
    if { [info exists certfile]}  {set keyfilename(cert)  [pathname $certfile  [file dirname $filename(conf)]]}
    if { [info exists keyfile]}   {set keyfilename(key)   [pathname $keyfile   [file dirname $filename(conf)]]}
    if { [info exists takeyfile]} {set keyfilename(takey) [pathname $takeyfile [file dirname $filename(conf)]]}
    if { [info exists authfile]}  {set keyfilename(auth)  [pathname $authfile  [file dirname $filename(conf)]]}

    foreach keyindex [array names keyfilename] {
	if {[string length $keydump] == 0} {
	    set keydump     "[filetext $keyfilename($keyindex)]"
	} else {
	    set keydump     "[filetext $keyfilename($keyindex)] && $keydump"
	}
	set keyfilelist "[file join \$OVPN_DIR [file tail $keyfilename($keyindex)]] $keyfilelist"
    }

    # Remove the pathnames from the certificates and keys in the config file
    #
    set target_config $filetext(conf)
    
    regsub {(\mca[[:blank:]]+)([[:graph:]]*/)([^/]+)}   $target_config {\1\3} target_config
    regsub {(\mcert[[:blank:]]+)([[:graph:]]*/)([^/]+)} $target_config {\1\3} target_config
    regsub {(\mkey[[:blank:]]+)([[:graph:]]*/)([^/]+)}  $target_config {\1\3} target_config
    
    set return_data "OVPN_DIR=$dir(ovpn.target) && \\
mkdir \$OVPN_DIR ; \\
cd \$OVPN_DIR ; \\
ln -s [file join / usr sbin openvpn] [file join \$OVPN_DIR openvpn] ; \\
[rawtext $filetail(conf) $target_config] && $keydump && \\
[rawtext route-up.sh   {iptables -A POSTROUTING -t nat -o tun0 -j MASQUERADE}] && \\
[rawtext route-down.sh {iptables -D POSTROUTING -t nat -o tun0 -j MASQUERADE}] && \\
chmod 600 $keyfilelist [file join \$OVPN_DIR $filetail(conf)] && \\
chmod 700 [file join \$OVPN_DIR route-up.sh] [file join \$OVPN_DIR route-down.sh] ; \\
killall openvpn ; starttime=\$(date +%s) && \\
while \[\[ \\( \$(nvram get wanup) -eq 0 \\
               -o ! -f [file join \$OVPN_DIR [file tail $keyfilename(cert)]] \\) \\
             -a \$(date +%s) -lt \$((\$starttime+$loop_timeout)) ]] ; do sleep 1; done && \\
sleep 2 && openvpn --config   [file join \$OVPN_DIR $filetail(conf)] \\
                   --route-up [file join \$OVPN_DIR route-up.sh] \\
                   --down     [file join \$OVPN_DIR route-down.sh] --daemon &
"

return $return_data
}

rename puts tcl::puts 
proc puts string {if [catch {tcl::puts $string}] exit}

initializeGlobals env argv

if {$argc == 0 || [lsearch $argv help] != -1 || $host(ip) == "UNKNOWN"} {

    put_help

} else {

    set command [lindex $argv 0]

    spawn telnet

    set target(sid) $spawn_id

    switch $command {
	reboot   {
	    set target_hostname [login $host(ip) $host(pw)]

	    send "reboot\n"

	    expect {
		-re {root@.*:} {exp_send "exit\n"}
	    }
	}
	login    {
	    set target_hostname [login $host(ip) $host(pw)]

	    interact
	}
	show     {
	    set subcommand [lindex $argv 1]

	    if {$subcommand == "vpncfg"} {

		set ovpn_config_filename [lindex $argv 2]

		puts "[aggregated_launch_string $ovpn_config_filename 90]"
	    } else {
		set target_hostname [login $host(ip) $host(pw)]

		send "ls -1rt /var/log | while read i; do tail -n 5 /var/log/\$i; done\n"

		if {[lindex $argv 2] == "-noexit"} {
		    interact
		} else {
		    expect {
			-re {root@.*:} {exp_send "exit\n"}
		    }
		}
	    }
	}
	radio {
	    set target_hostname [login $host(ip) $host(pw)]
	    set subcommand      [lindex $argv 1]

	    if {$subcommand == "off"} {
		send "wl radio off\n"
	    } else {
		send "wl radio on\n"
	    }
	    if {[lindex $argv 2] == "-noexit"} {
		interact
	    } else {
		expect {
		    -re {root@.*:} {exp_send "exit\n"}
		}
	    }
	}
	start    {
	    set target_hostname      [login $host(ip) $host(pw)]
	    set ovpn_config_filename [lindex $argv 1]
	    
	    send "[aggregated_launch_string $ovpn_config_filename 90]"
	    
	    if {[lindex $argv 2] == "-noexit"} {
		interact
	    } else {
		expect {
		    -re {root@.*:} {exp_send "exit\n"}
		}
	    }
	}  
	stop     {
	    set target_hostname [login $host(ip) $host(pw)]
	    
	    send "killall openvpn\n"

	    if {[lindex $argv 1] == "-noexit"} {
		interact
	    } else {
		expect {
		    -re {root@.*:} {exp_send "exit\n"}
		}
	    }
	}
	boot {
	    set target_hostname      [login $host(ip) $host(pw)]
	    set subcommand           [lindex $argv 1]

	    switch $subcommand {
		vpncfg {

		    send "nvram set rc_startup=\"[aggregated_launch_string [lindex $argv 2] 90]\"\n"

		    expect {
			-re {root@.*:} {exp_send "nvram commit\n"}
		    }

		    if {[lindex $argv 3] == "-noexit"} {
			interact
		    } else {
			expect {
			    -re {root@.*:} {exp_send "exit\n"}
			}
		    }
		}
		wos {
		    send "nvram set rc_startup=\"\
                            SCRIPT_FILE=/tmp/etc/config/wake_on_lan.sesbutton && mkdir -p \$(dirname \$SCRIPT_FILE) ;\
                            echo '#!/bin/sh ,,/usr/sbin/wol -i [lindex $argv 2] [lindex $argv 3] ,' \
                            | tr ',' '\n' > \$SCRIPT_FILE && chmod +x \$SCRIPT_FILE\"\n"

		    expect {
			-re {root@.*:} {exp_send "nvram commit\n"}
		    }

		    if {[lindex $argv 3] == "-noexit"} {
			interact
		    } else {
			expect {
			    -re {root@.*:} {exp_send "exit\n"}
			}
		    }
		}
		clear {

		    send "nvram unset rc_startup\n"
		    
		    expect {
			-re {root@.*:} {exp_send "nvram commit\n"}
		    }

		    if {[lindex $argv 3] == "-noexit"} {
			interact
		    } else {
			expect {
			    -re {root@.*:} {exp_send "exit\n"}
			}
		    }
		}
	    }
	}
	default  {
	    put_help
	}
    }
}