expect doesn't match pattern correctly - bash

I tried to automate the easyrsa request generation via expect. I came up with that bash script:
#!/bin/bash
firstname=$1
lastname=$2
mail=$3
department=$4
password=$5
[...]
cd /VPN-CA/
/usr/bin/expect -c "
spawn ./easyrsa gen-req $mail
expect \"Enter PEM pass phrase:\"
send \"$password\r\"
expect \"Verifying - Enter PEM pass phrase:\"
send \"$password\r\"
expect \"Country Name (2 letter code) \[DE\]:\"
send \"\r\"
expect \"State or Province Name (full name) \[MyState\]:\"
send \"\r\"
expect \"Locality Name (eg, city) \[MyCity\]:\"
send \"\r\"
expect \"Organization Name (eg, company) \[MyOrganization\]:\"
send \"\r\"
expect \"Organizational Unit Name (eg, section) \[MyDepartment\]:\"
send \"$department\r\"
expect \"Common Name (eg: your user, host, or server name) \[$mail\]:\"
send \"$firstname $lastname\r\"
expect \"Email Address \[email#address.de\]:\"
send \"$mail\r\"
expect eof
"
# do somethin else
[...]
exit 0
The script works and the request will be generated correctly, but expect is very slow. With -d it shows me for every expected pattern after the second password query something like that:
expect: does "test\r\n\r\n-----\r\nYou are about to be asked to enter information that will be incorporated\r\ninto your certificate request.\r\nWhat you are about to enter is what is called a Distinguished Name or a DN.\r\nThere are quite a few fields but you can leave some blank\r\nFor some fields there will be a default value,\r\nIf you enter '.', the field will be left blank.\r\n-----\r\n" (spawn_id exp3) match glob pattern "Country Name (2 letter code) [DE]:"? no
Country Name (2 letter code) [DE]:
expect: does "test\r\n\r\n-----\r\nYou are about to be asked to enter information that will be incorporated\r\ninto your certificate request.\r\nWhat you are about to enter is what is called a Distinguished Name or a DN.\r\nThere are quite a few fields but you can leave some blank\r\nFor some fields there will be a default value,\r\nIf you enter '.', the field will be left blank.\r\n-----\r\nCountry Name (2 letter code) [DE]:" (spawn_id exp3) match glob pattern "Country Name (2 letter code) [DE]:"? no
expect: timed out
send: sending "\r" to { exp3 }
I don't understand why the pattern doesn't match. I mean whilst the reply is send nevertheless after the timeout it works, but it is slow as hell... (and it is crap)
Has anybody an explanation or a solution for this problem?

The problem in your code is that the shell removes the backslashes while constructing the string, so expect sees un-escaped brackets -- a command substitution. In your code, you'll have to double backslash the opening brackets.
The same problem happens here:
expect -c "
...
send \"$password\r\"
Suppose $password is "1234", then expect will see:
send "1234r" ;# <= no carriage return!
Using a double quoted string to hold the expect code leads very quickly to quoting hell. Use a here-doc instead:
/usr/bin/expect <<END_EXPECT
spawn ./easyrsa gen-req $mail
expect "Enter PEM pass phrase:"
send "$password\r"
expect "Verifying - Enter PEM pass phrase:"
send "$password\r"
expect "Country Name (2 letter code) \[DE\]:"
send "\r"
expect "State or Province Name (full name) \[MyState\]:"
send "\r"
expect "Locality Name (eg, city) \[MyCity\]:"
send "\r"
expect "Organization Name (eg, company) \[MyOrganization\]:"
send "\r"
expect "Organizational Unit Name (eg, section) \[MyDepartment\]:"
send "$department\r"
expect "Common Name (eg: your user, host, or server name) \[$mail\]:"
send "$firstname $lastname\r"
expect "Email Address \[email#address.de\]:"
send "$mail\r"
expect eof
END_EXPECT
If you use braces (expect's single quoting mechanism) you don't have to escape the brackets:
expect {Email Address [email#address.de]:}

Just to troubleshoot further, could you just check does the system is slow in general or only while running the expect command.
Sometimes because of delay in DNS name resolution also the system response slowly.
Check and remove any unnecessary entries in the /etc/resolve.conf and then try.

It seems to be a problem of the escaping of \[, the closing square bracket doesn't need to be escaped.
This fails to detect the line.
expect \"Country Name (2 letter code) \[DE\]:\"
But you can replace it with a wildcard ?
expect \"Country Name (2 letter code) ?DE]:\"
Or you could escape the opening square bracket with only 6 backslashes:
expect \"Country Name (2 letter code) \\\\\\[DE]:\"

Related

Branching based off expect script timeout occurance

Is it possible to do the following in a expect script:
expect "phrase"
if timeout reached:
do this
else:
do that
See expect's man page:
expect [[-opts] pat1 body1] ... [-opts] patn [bodyn]
... ...
If the arguments to the entire expect statement require more than
one line, all the arguments may be "braced" into one so as to
avoid terminating each line with a backslash. In this one case,
the usual Tcl substitutions will occur despite the braces.
... ...
For example, the following fragment looks for a successful login.
(Note that abort is presumed to be a procedure defined elsewhere
in the script.)
expect {
busy {puts busy\n ; exp_continue}
failed abort
"invalid password" abort
timeout abort
connected
}

Expect script not matching output

At my wits end:
Need expect to match the following string, which is also colour coded. Quotes are the illustrate the blank space to the right.
"FuseMQ:karaf#root> "
Using just "root" sends before the > prompt is ready, in some cases, not all.
expect "root" {
0m:\u001b[36mkaraf\u001b[0m\u001b[1m#\u001b[0m\u001b[34mroot"
send: sending "osgi:install wrap:file:/apps/fuse/fuse-mq-7.1.0.fuse-047/
lib/ojdbc6-11.2.0.2.0.jar\r" to { exp4 }
I cannot find a regexp that works. The output from expect -d is as follows when it works
u001b[0m\u001b[1m#\u001b[0m\u001b[34mroot\u001b[0m> " (spawn_id
exp4) match glob pattern "root"? yes
But I cannot figure out how to match this.
Try this expect "root\\u001b\[0m> $"
You could just do something like this, where you have the strings you want to match, and allow for "stuff" in between.
set prompt {FuseMQ.*:.*karaf.*#.*root.*> $}
expect -re $prompt

Expect: how to grab part of output

I'm a bit new to expect programming, so I need help.
Here's a sample session:
CMD> disable inactive
Accounts to be disabled are:
albert_a - UUID abcd-11-2222
brian_b - UUID bcde-22-3333
charley_c - UUID cdef-33-4444
Starting processing
...hundreds of lines of processing...
CMD> quit
Done.
I need to grab the username and UUIDs there (the UUIDs are not available through other means), then either save them into a file. How do I do that in expect?
Edit: the - UUID (space dash space "UUID") part of the list is static, and not found anywhere in the "hundreds of lines of processing", so I think I can match against that pattern... but how?
Assuming the answer to my question in the comments is 'yes', here's my suggestion.
First, you need to spawn whatever program will connect you to the server (ssh, telnet, or whatever), and login (expect user prompt, send password, expect prompt). You'll find plenty samples of that, so I'll skip that part.
Once you have done that, and have a command prompt, here's how I would send the command and expect & match output:
set file [open /tmp/inactive-users w] ;# open for writing and set file identifier
send "disable inactive\r"
expect {
-re "(\[a-z0-9_\]+) - UUID" { ;# match username followed by " - UUID"
puts $file "$expect_out(1,string)" ;# write username matched within parenthesis to file identifier
exp_continue ;# stay in the same expect loop in case another UUID line comes
}
-re "CMD>" { ;# if we hit the prompt, command execution has finished -> close file
close $file
}
timeout { ;# reasonably elegant exit in case something goes bad
puts $file "Error: expect block timed out"
close $file
}
}
Pls note that in the regexp I'm assuming that usernames can be composed of lowercase letters, numbers and underscores only.
If you need help with the login piece let me know, but you should be ok. There are plenty of samples of that out there.
Hope that helps!

Writing to an IO stream in Ruby instead of sanitizing user input and sending to the shell?

I was just reading a blog post about sanitizing user input in Ruby before sending it to the command line. The author's conclusion was: don't send user input it to the command line in the first place.
In creating a contact form, he said he learned that
What I should do instead is open a
pipe to the mail command as an IO
stream and just write to it like any
other file handle.
This is the code he used:
open( %Q{| mail -s "#{subject}" "#{recipient}" }, 'w' ) do |msg|
msg << body
end
(I actually added the quotes around recipient - they're needed, right?)
I don't quite understand how this works. Could anybody walk me through it?
OK, I'll explain it with the caveat that I don't think it's the best way to accomplish that task (see comments to your question).
open() with a pipe/vertical bar as the first character will spawn a shell, execute the command, and pass your input into the command through a unix-style pipe. For example the unix command cat file.txt | sort will send the contents of the file to the sort command. Similarly, open("| sort", 'w') {|cmd| cmd << file} will take the content of the file variable and send it to sort. (The 'w' means it is opened for writing).
The %Q() is an alternate way to quote a Ruby string. This way it doesn't interfere with literal quote characters in the string which can result in ugly escaping. So the mail -s command is being executed with a subject and a recipient.
Quotes are needed around the subject, because the mail command will be interpreted by the shell, and arguments are separated by spaces, so if you want spaces in an argument, you surround it with quotes. Since the -s argument is for the subject, it needs to be in quotes because it will likely contain spaces. On the other hand, the recipient is an email address and email addresses don't contain spaces, so they're not necessary.
The block is providing the piped input to the command. Everything you add to the block variable (in this case msg) is sent into the pipe. Thus the email body is being appended (via the << operator) to the message, and therefore piped to the mail command. The unix equivalent is something like this: cat body.txt | mail -s "subject" recipient#a.com

reading multiple items semi-interactively from the user in bash

I'm trying to read multiple items from the user in a shell script, with no luck. The intention is to read a list of files first (which are read from the stdin pipe), and then read twice more to get two strings interactively. What I'm trying to do is read a list of files to attach in an email, then the subject and finally the email body.
So far I have this:
photos=($(< /dev/stdin))
echo "Enter message subject"
subject=$(< /dev/stdin)
echo "Enter message body"
body=$(< /dev/stdin)
(plus error checking code that I omit for succintness)
However, this gets an empty subject and body presumably because the second and third redirections get EOF.
I've been trying to close and reopen stdin with <&- and stuff but it doesn't seem to work that way.
I even tried using a separator for the list of files, using a "while; read line" loop and break out of the loop when the separator was detected. But that didn't work either (??).
Any ideas how to build something like this?
So what I ended up doing is based on ezpz's answer and this doc: http://www.faqs.org/docs/abs/HTML/io-redirection.html Basically I prompt for the fields first from /dev/tty, and then read stdin, using the dup-and-close trick:
# close stdin after dup'ing it to FD 6
exec 6<&0
# open /dev/tty as stdin
exec 0</dev/tty
# now read the fields
echo "Enter message subject"
read subject
echo "Enter message body"
read body
# done reading interactively; now read from the pipe
exec 0<&6 6<&-
photos=($(< /dev/stdin))
Thanks!
You should be able to use read to prompt for the subject and body:
photos=($(< /dev/stdin))
read -rp "Enter message subject" subject
read -rp "Enter message body" body
Since it is possible that you have a varying number of photos, why not just prompt for the known fields first and then read 'everything else'. It is much easier than trying to get the last two fields of an unknown length in an interactive manner.
# Prompt and read two things from the terminal (not from stdin), then read stdin.
# The last line uses arrays, so is BASH-specific. The read lines are portable.
# - Ian! D. Allen - idallen#idallen.ca - www.idallen.com
read -p "Enter message subject: " subject </dev/tty
read -p "Enter message body: " body </dev/tty
photos=($(</dev/stdin))

Resources