1# This file is part of rex - a remote execution utility 2# Copyright (C) 2012, 2013, 2015, 2016 Sergey Poznyakoff 3# 4# Rex is free software; you can redistribute it and/or modify 5# it under the terms of the GNU General Public License as published by 6# the Free Software Foundation; either version 3, or (at your option) 7# any later version. 8# 9# Rex is distributed in the hope that it will be useful, 10# but WITHOUT ANY WARRANTY; without even the implied warranty of 11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12# GNU General Public License for more details. 13# 14# You should have received a copy of the GNU General Public License 15# along with Rex. If not, see <http://www.gnu.org/licenses/>. 16 17# For instructions and a list of configurable variables, see the description 18# of startvpnc procedure below. 19 20package provide Vpnc 1.0 21 22package require Tcl 8.5 23package require Expect 5.44.1.15 24package require Entrustcard 1.0 25 26namespace eval ::vpnc { 27 namespace export checkpid start 28 namespace ensemble create 29 30 # Store the credential "what" for the connection "id" in the variable 31 # "retName". Return 1 on success, 0 on failure. 32 # 33 # This function always returns failure. The user is supposed to 34 # override it if the need be. 35 # 36 proc get_credentials {id what retName} { 37# upvar $retName ret 38 return 0 39 } 40 41 variable vpnc_command "/usr/local/sbin/vpnc" 42 variable pidof_command "/sbin/pidof" 43 44 proc checkpid {pidfile} { 45 variable cleanup_command 46 variable pidof_command 47 48 if ![file exists $pidfile] { 49 return 0 50 } 51 set fd [open $pidfile "r"] 52 set pid [gets $fd] 53 close $fd 54 if {[catch {exec $pidof_command vpnc 2>@1} res] || 55 [lsearch [split $res] $pid] == -1} { 56 if {[info exists cleanup_command] && 57 [file executable $cleanup_command]} { 58 exec sudo $cleanup_command $pidfile 59 } else { 60 exec sudo /usr/bin/rm $pidfile 61 } 62 return 0 63 } 64 return 1 65 } 66 67 # start ID CFGFILE PIDFILE 68 # 69 # Start VPN identified by ID, using configuration file CFGFILE and 70 # PID file PIDFILE. 71 # 72 # If the VPN server requires Entrust card authentication, the variable 73 # vpnc::carddir must be set to the directory keeping card files. Each 74 # file must be stored in a file named by its number. See the description of 75 # the procedure readcards in file entrustcard.tcl for its format. 76 # 77 # The current user must be able to execute VPNC binary (i.e. 78 # $vpnc_command) via sudo without password. To remove stale PID files, 79 # they must be able to run /usr/bin/rm on files in VPNC 80 # state directory. This too requires a sudo privilege, which is normally 81 # set in /etc/sudoers as follows: 82 # 83 # VPNC ALL=(ALL) NOPASSWD: /usr/bin/rm /var/run/vpnc/*.pid 84 # 85 # In case it is regarded as a too risky permission, set variable 86 # vpnc::cleanup_command to the full pathname of a wrapper script 87 # to be run instead of rm. The script should accept the full 88 # pathname of a PID file as its argument and remove it if it sees fit. An 89 # example vpnc-cleanup script is supplied in the rex distribution. 90 # 91 proc start {args} { 92 variable carddir 93 variable vpnc_command 94 95 while {[llength $args] > 0} { 96 switch -regexp -- [lindex $args 0] { 97 {^-user$} { 98 set username [lindex $args 1] 99 set args [lreplace $args [set args 0] 1] 100 } 101 {^-pass$} { 102 set password [lindex $args 1] 103 set args [lreplace $args [set args 0] 1] 104 } 105 {^--$} { break } 106 {^-.*} { 107 return -code error "unknown option [lindex $args 0]" 108 } 109 default { break } 110 } 111 } 112 113 set id [lindex $args 0] 114 set cfgfile [lindex $args 1] 115 set pidfile [lindex $args 2] 116 117 if [checkpid $pidfile] { 118 return 119 } 120 121 puts "Starting vpn $id" 122 123 set password_sent 0 124 125 spawn -ignore HUP -noecho sudo $vpnc_command --local-port 0 --pid-file $pidfile $cfgfile 126 127 expect { 128 "Enter a response to the grid challenge*\n" { 129 if {![regexp {Enter a response to the grid challenge \[([[:alnum:]])([[:alnum:]])\] \[([[:alnum:]])([[:alnum:]])\] \[([[:alnum:]])([[:alnum:]])\] using a card with serial number ([[:digit:]]+)\.$} [string trimright $expect_out(0,string)] dummy x1 y1 x2 y2 x3 y3 n]} { 130 close 131 return -code error "$id: cannot parse reply string: $expect_out(0,string)" 132 } 133 134 if {![info exist carddir]} { 135 return -code error "VPN \"$id\" requires entrustcard authentication (card $n), but carddir is not set" 136 } 137 138 if {[entrustcard load "$carddir/$n" card]} { 139 exit 1 140 } 141 142 set reply [entrustcard decode $expect_out(0,string) card] 143 exp_continue 144 } 145 -re {username for.*:[[:space:]]*$} { 146 if {[info exist username] || \ 147 [get_credentials $id username username]} { 148 send "$username\r" 149 exp_continue 150 } else { 151 close 152 return -code error "$id: username required but not supplied" 153 } 154 } 155 -re {assword for.*:[[:space:]]*$} { 156 # VPNC uses getpass(3), which needs some time to open 157 # /dev/tty and alter tty settings. The code below waits 158 # for one second before sending the reply. Hopefully this 159 # is enough for the things to settle. 160 if {[info exist reply]} { 161 sleep 1 162 send "$reply\r" 163 unset reply 164 incr password_sent 165 } elseif {$password_sent == 0} { 166 if {[info exist password] || \ 167 [get_credentials $id password password]} { 168 sleep 1 169 send "$password\r" 170 incr password_sent 171 } else { 172 close 173 return -code error "$id: password required but not supplied" 174 } 175 } elseif {$password_sent == 1} { 176 if {[get_credentials $id password password]} { 177 close 178 return -code error "$id: password not accepted" 179 } 180 sleep 1 181 send "$password\r" 182 incr password_sent 183 } else { 184 close 185 return -code error "$id: password not accepted" 186 } 187 exp_continue 188 } 189 "VPNC started*" { 190 puts -nonewline $expect_out(0,string) 191 } 192 timeout { 193 close 194 return -code error "$id: timed out" 195 } 196 } 197 } 198 wait -nowait 199} 200