Quoting issue with expect script

iconoclast

I have a script to automatically connect to a VPN, because I often have to connect and disconnect several times a day. (Certain things like ScreenHero and GoToMeeting fail on the VPN, and Mail servers and other things are blocked while on the VPN, but I can't connect to the Git server unless I'm on the VPN, and the app I'm working on can't reach certain back-end services unless I'm on the VPN, so development is limited when disconnected.)

The script lessens the hassle, but I have a $ in my password (which is available in the environment through the environment variable VPN_PASSWORD1), and the password value gets interpolated before it is passed to the VPN program by expect.

#!/bin/bash

{
  /usr/bin/expect << END_OF_LOGIN_SESSION
  set timeout 30
  spawn /opt/cisco/anyconnect/bin/vpn -s connect blah.blahblah.com
  expect "Username:*"
  send "\r"
  expect "Password:*"
  send "$VPN_PASSWORD\r"
  expect "accept? \[y/n\]:*"
  send "y\r"
  expect eof
END_OF_LOGIN_SESSION
}

How do I pass a password literally, without letting it be subject to string interpolation?

Changing my password is a cop-out. Saving it with a \ is also a cop-out (and horrible UX, since I have to enter it every time I source my .project file for this project2).

It seems like expect must have a solution for this.


  1. Yes I know someone who steals my laptop could try to log in, but (a) they'd have to get into my account first, and (b) they'd fail without my PIN, entered by phone, anyway.

  2. No, the password is not saved to disk. I enter it once per shell session related to this project.

glenn jackman

You have the shell variable $VPN_PASSWORD in an unquoted heredoc, so the shell will substitute the value. Suppose VPN_PASSWORD='foo$bar' => Then expect will see: send "foo$bar\r" and you'll get can't read "bar": no such variable. The solution is to use {braces} instead of double quotes, so that expect will not attempt to expand the "inner" variable.

send {$VPN_PASSWORD}; send "\r"

You need a separate send "\r" because putting \r inside the braces will remove its special meaning, and Tcl won't let you do send {$VPN_PASSWORD}"\r"

Here's a demo:

$ VPN_PASSWORD='foo$bar'
$ expect <<END
send_user "$VPN_PASSWORD\n"
END
can't read "bar": no such variable
    while executing
"send_user "foo$bar\n""
$ expect <<END
send_user {$VPN_PASSWORD}; send_user "\n"
END
foo$bar

In Tcl, braces act like single quotes in the shell: everything inside them are literal characters.


It might be cleaner to use the environment to pass the values. Here it is implemented as a shell function

vpnconnect() {
    expect <<'END'
        set timeout 30
        spawn /opt/cisco/anyconnect/bin/vpn -s connect $env(VPN_HOST)
        expect "Username:*"
        send "\r"
        expect "Password:*"
        send "$env(VPN_PASSWORD)\r"
        expect {accept? [y/n]:*}
        send "y\r"
        expect eof
END
}

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related