Link to home
Start Free TrialLog in
Avatar of stanleyhuen
stanleyhuen

asked on

expect script

Hi,

I am using an expect script for users to change their password,
it is working fine in a server, but when I copy it to another server, it is not working.

I use Redhat 8.0 now, it seems the reponse messages of /usr/bin/passwd is different.

The error message:

Passwd Change Acknowledgment
Error: mypassord Changing password for user stanley. Changing password for stanley (current) UNIX password:





The following is my code:

#!/usr/bin/expect --


puts "Content-type: text/html\n"        ;# note extra newline

puts "
<head>
<title>Passwd Change Acknowledgment</title>
</head>

<h2>Passwd Change Acknowledgment</h2>
"

proc cgi2ascii {buf} {
    regsub -all {\+} $buf { } buf
    regsub -all {([\\["$])} $buf {\\\1} buf
    regsub -all -nocase "%0d%0a" $buf "\n" buf
    regsub -all -nocase {%([a-f0-9][a-f0-9])} $buf {[format %c 0x\1]} buf
    eval return \"$buf\"
}

foreach pair [split [read stdin $env(CONTENT_LENGTH)] &] {
        regexp (.*)=(.*) $pair dummy varname val
        set val [cgi2ascii $val]
        set var($varname) $val
}

log_user 0

proc errormsg {s} {puts "<h3>Error: $s</h3>"}
proc successmsg {s} {puts "<h3>$s</h3>"}

# Change as appropriate to reflect where your passwd executable is
#
#spawn /bin/su $var(name) -c "/bin/passwd -r files $var(name)"
#spawn /bin/su $var(name) -c "/usr/bin/passwd"


set f [open "/etc/passwd" r]



while {[gets $f line] >= 0} {
  if [regexp "^$var(name).*/bin/nobash$" $line] {
     set login_user  0
        break
  } else {
     set login_user 1
  }
}

close $f



while {[gets $f line] >= 0} {
  if [regexp "^$var(name).*/bin/nobash$" $line] {
     set login_user  0
        break
  } else {
     set login_user 1
  }
}

close $f

if {$login_user == 0} {
        spawn /bin/su - $var(name)
} else {
        spawn /bin/su - $var(name) -c "/usr/bin/passwd"
}


sleep 1
expect {
    "Unknown (login|id):" {
        errormsg "unknown user: $var(name)"
        exit
    } -re "(.*) does not exist" {
        errormsg "unknown user: $var(name)"
        exit
    } default {
        errormsg "$expect_out(buffer)"
        exit
    } "Password:"
}
send "$var(old)\r"
sleep 1
expect {
    "Sorry" {
        errormsg "Old password incorrect"
        exit
    } "incorrect passwd" {
        errormsg "Old password incorrect"
        exit
    } default {
        errormsg "$expect_out(buffer)"
        exit
    } -re "(.*)(login|UNIX) password:"
}
send "$var(old)\r"
sleep 1

expect {
    "Sorry" {
        errormsg "Old password incorrect"
        exit
    } default {
        errormsg "$expect_out(buffer)"
        exit
    } -re "New (.*)password:"
}
send "$var(new1)\r"
sleep 1
expect {
    -re "passwd.SYSTEM.(.*)" {
        errormsg "$expect_out(buffer)"
        exit
    } -re "BAD(.*)" {
        errormsg "$expect_out(buffer)"
        exit
    } default {
        errormsg "Unknown error from passwd"
        exit
    } -re "Re(.*) password:"
}
send "$var(new2)\r"
sleep 1
expect {
    -re "passwd(.*) try again" {
        errormsg "$expect_out(buffer)"
        exit
    } -re "Sorry,(.*)" {
        errormsg "$expect_out(buffer)"
        exit
    } default {
        errormsg "Unknown error from passwd"
        exit
    } -re "(.*) successfully changed (.*)" {
        successmsg "Password successfully changed"
        exit
    } -re "(.*) updated successfully" {
        successmsg "Successfully updated password"
        exit
    }
}
close
wait



Avatar of danny_ebbers
danny_ebbers

Hmm it looks like the output of the passwd cmd is indeed different.

What to do..
run passwd on the new system manualy
print out or write down the response codes you need
and change the script

for example to old response of succesfull change was
"Password successfully changed for user billy"

and now it is

"i changed the password for user billy was successfully"


you should edit it like this

Before: -re "(.*) successfully changed (.*)" {
After : -re "(.*) changed (.*) successfully (.*)" {


the (.*) means for zero or more of any character      
.. or write RE's to work in both/most situations.

-re "((s|S)uccessful|(c|C)hanged).*((S|s)uccessful|(c|C)hanged)" {

..would match both before and after in danny's example.

/b
Avatar of stanleyhuen

ASKER

When I run /usr/bin/passwd:
[stanley@diamond stanley]$ /usr/bin/passwd
Changing password for user stanley.
Changing password for stanley
(current) UNIX password:


When I run :

[stanley@diamond stanley]$ /bin/su - stanley -c "/usr/bin/passwd"
Password:

[stanley@diamond stanley]$ /bin/su stanley -c "/usr/bin/passwd"
Password:


It seems the program use the first one...
If it use the first one, how can i change my program so that it works? ( i am not familiar with Expect)

Thank you.





I don't know why you try to match so large part of the string... could you try to
Change

  } -re "(.*)(login|UNIX) password:"

to

  } -re "ssword:"

Change
    } -re "New (.*)password:"

to

    } -re "ssword:"

Change
    } -re "Re(.*) password:"

to

    } -re "ssword:"

You don't have to match more chars than necessary to decide what to do, right?

You need to look into all the other RE's (for determining what went wrong in each step) I don't know all possible output from the passwd command by heart :-)

/b
As i am not familiar with Expect really, can you guys explain this to me?

expect {
    "Unknown (login|id):" {
        errormsg "unknown user: $var(name)"
        exit
    } -re "(.*) does not exist" {
        errormsg "unknown user: $var(name)"
        exit
    } default {
        errormsg "error 1: $expect_out(buffer)"
        exit
    } "Password:"
}
send "$var(old)\r"
sleep 1
expect {
    "Sorry" {
        errormsg "Old password incorrect"
        exit
    } "incorrect passwd" {
        errormsg "Old password incorrect"
        exit
    } default {
        errormsg "error 2 : $expect_out(buffer)"
        exit
    } -re "(.*)(login|UNIX) password:"
}
send "$var(old)\r"
sleep 1


What is "Sorry"? Why is it there?
What is -re "(.*)(login|UNIX) password:"?


I hope I learn it and solve the problem.
But I cannot find any tutorial has mentioned the above syntax.

Thank you.

As i am not familiar with Expect really, can you guys explain this to me?

expect {
    "Unknown (login|id):" {
        errormsg "unknown user: $var(name)"
        exit
    } -re "(.*) does not exist" {
        errormsg "unknown user: $var(name)"
        exit
    } default {
        errormsg "error 1: $expect_out(buffer)"
        exit
    } "Password:"
}
send "$var(old)\r"
sleep 1
expect {
    "Sorry" {
        errormsg "Old password incorrect"
        exit
    } "incorrect passwd" {
        errormsg "Old password incorrect"
        exit
    } default {
        errormsg "error 2 : $expect_out(buffer)"
        exit
    } -re "(.*)(login|UNIX) password:"
}
send "$var(old)\r"
sleep 1


What is "Sorry"? Why is it there?
What is -re "(.*)(login|UNIX) password:"?


I hope I learn it and solve the problem.
But I cannot find any tutorial has mentioned the above syntax.

Thank you.

As i am not familiar with Expect really, can you guys explain this to me?

expect {
    "Unknown (login|id):" {
        errormsg "unknown user: $var(name)"
        exit
    } -re "(.*) does not exist" {
        errormsg "unknown user: $var(name)"
        exit
    } default {
        errormsg "error 1: $expect_out(buffer)"
        exit
    } "Password:"
}
send "$var(old)\r"
sleep 1
expect {
    "Sorry" {
        errormsg "Old password incorrect"
        exit
    } "incorrect passwd" {
        errormsg "Old password incorrect"
        exit
    } default {
        errormsg "error 2 : $expect_out(buffer)"
        exit
    } -re "(.*)(login|UNIX) password:"
}
send "$var(old)\r"
sleep 1


What is "Sorry"? Why is it there?
What is -re "(.*)(login|UNIX) password:"?


I hope I learn it and solve the problem.
But I cannot find any tutorial has mentioned the above syntax.

Thank you.

the code above expects 3 strings:
  Unknown ...
  ... does not exist
  Password:
and catches any other string in the default case
the 2'nd expect expects also 3 strings
So "Sorry" is simply a string being expected.
-re is tcl's option to exepct a string written with regular expresions (hence: -re)

In fact, expect's man page isn't that complete 'cause it all is tcl, so read tcl's (tclsh) man pages.
Yep, and there are man pages (usually in section 'n') for most tcl commands as separate man pages as well.
Expect is a TCL interpreter with the commands "spawn" and "expect" added to simplify interaction with existing commandline utils (such as the passwd command)
You could really achive the same using "open" and "switch" commands in TCL.

In your code each of the "expect" blocks define possible actions/reactions to the output from passwd in each step involved when changing the password using the passwd command.
To know how/if to change the RE's you need to run "passwd" simulating all the possible cases.
E.g.
- try changing password for a user that does not exist, and record the output. Change the RE's accordingly.
- try entering passwords that don't match when asked to enter the password the second time. Record the output... change RE
's
-  etc etc
You get the idea..
The RE should/need only match the least substring that makes it a uniq match for the situation. This approach will probably cover all current, and possibly future changes to the output of the passwd command.
(the substring "assword" is bound to appear even in future versions (in English versions at least) ;-))

/b
Below you'll find passwd.html and passwd.cgi that will work with RedHat 8.0. The expect script doesn't work on an
RH 9 system and I don't yet know why. It may or may not be significant, but I've only tested this with an 8.0 system that has all applicable RedHat errata applied.

---passwd.html---
<HTML>
<head>
<title>Change your login password</title>
</head>
<body>

This HTML creates a form for letting users change login passwords with
a browser.  To actually use this form, install the corresponding
accompanying cgi script and then modify the action value to identify
where you put the cgi script.  (Also read the comments at the
beginning of the CGI script.) - Don Libes/Jim Levie
<hr>

<form method=post
action="http://www.domain.tld/cgi-bin/passwd.cgi">
<h2>Change your login password</h2>
<br>Username: <input name="name">
<br>Old password: <input type=password name="old">
<br>New password: <input type=password name="new1">
<br>New password: <input type=password name="new2">
<br>New password must be entered twice to avoid typos.
<br><input type=submit value="Change password">
</form>
</body>
</html>

---passwd.cgi---
#!/usr/local/bin/expect --

# This is a CGI script to process requests created by the accompanying
# passwd.html form.  This script is pretty basic, although it is
# reasonably robust.  (Purposely intent users can make the script bomb
# by mocking up their own HTML form, however they can't expose or steal
# passwords or otherwise open any security holes.)  This script doesn't
# need any special permissions.  The usual (ownership nobody) is fine.
#
# Don Libes, NIST

# Modified virtually beyond all recognition by
# Jim Levie (jim@entrophy-free.net) to work properly under Solaris or Linux.

puts "Content-type: text/html\n"      ;# note extra newline

puts "
<head>
<title>Passwd Change Acknowledgment</title>
</head>

<h2>Passwd Change Acknowledgment</h2>
"

proc cgi2ascii {buf} {
    regsub -all {\+} $buf { } buf
    regsub -all {([\\["$])} $buf {\\\1} buf
    regsub -all -nocase "%0d%0a" $buf "\n" buf
    regsub -all -nocase {%([a-f0-9][a-f0-9])} $buf {[format %c 0x\1]} buf
    eval return \"$buf\"
}

foreach pair [split [read stdin $env(CONTENT_LENGTH)] &] {
      regexp (.*)=(.*) $pair dummy varname val
      set val [cgi2ascii $val]
      set var($varname) $val
}

log_user 0

proc errormsg {s} {puts "<h3>Error: $s</h3>"}
proc successmsg {s} {puts "<h3>$s</h3>"}

# Need to su first to get around passwd's requirement that passwd cannot
# be run by a totally unrelated user.  Seems rather pointless since it's
# so easy to satisfy, eh?
#
# Solaris 2.6 & later needs the -r option to specify which
# password service (files, nis, nisplus) see man passwd.  Linux
# has passwd in a different location and doesn't need the
# service specification. (Note that I no longer have anything
# earlier than 2.6 to test with, you've been warned... there be
# dragons here).
#
# BIG NOTE!!! Linux has to have the "sleep 1" between each of
# the "expect/send" pairs. It puts out the prompt before it's actually
# ready to take input. You can comment them out for Solaris, but
# it doesn't hurt for them to be there and might be a plus
# busy server. (there be really big dragons here...)
#
# Change as appropriate to reflect where your passwd executable is
#
# The next line (commented out) is for Solaris, the one following is
# for Linux
#
#spawn /bin/su $var(name) -c "/bin/passwd -r files $var(name)"
spawn /bin/su $var(name) -c "/usr/bin/passwd"

sleep 1
expect {
    "Unknown (login|id):" {
      errormsg "unknown user: $var(name)"
      exit
    } -re "(.*) does not exist" {
      errormsg "unknown user: $var(name)"
      exit
    } default {
      errormsg "$expect_out(buffer)"
      exit
    } "Password:"
}
send "$var(old)\r"
sleep 1
expect {
    "Sorry" {
      errormsg "Old password incorrect"
      exit
    } "incorrect passwd" {
      errormsg "Old password incorrect"
      exit
    } default {
      errormsg "$expect_out(buffer)"
      exit
    } -re "(.*)(login|UNIX) password:"
}
send "$var(old)\r"
sleep 1
expect {
    "Sorry" {
      errormsg "Old password incorrect"
      exit
    } default {
      errormsg "$expect_out(buffer)"
      exit
    } -re "New (.*)password:"
}
send "$var(new1)\r"
sleep 1
expect {
    -re "passwd.SYSTEM.(.*)" {
      errormsg "$expect_out(buffer)"
      exit
    } -re "BAD(.*)" {
      errormsg "$expect_out(buffer)"
      exit
    } "passwd: Authentication token manipulation error" {
       errormsg "Old Password incorrect"
       exit
    } default {
      errormsg "Unknown error from passwd"
      exit
    } -re "Re(.*) password:"
}
send "$var(new2)\r"
sleep 1
expect {
    -re "passwd(.*) try again" {
      errormsg "$expect_out(buffer)"
      exit
    } -re "Sorry,(.*)" {
      errormsg "$expect_out(buffer)"
      exit
    } default {
      errormsg "Unknown error from passwd"
      exit
    } -re "(.*) successfully changed (.*)" {
      successmsg "Password successfully changed"
      exit
    } -re "(.*) updated successfully" {
      successmsg "Successfully updated password"
      exit
    }
}
close
wait
stanleyhuen:
This old question needs to be finalized -- accept an answer, split points, or get a refund.  For information on your options, please click here-> http:/help/closing.jsp#1 
EXPERTS:
Post your closing recommendations!  No comment means you don't care.
No comment has been added lately, so it's time to clean up this TA.
I will leave a recommendation in the Cleanup topic area that this question is to:

Be PAQ'd/Points No Refunded

Please leave any comments here within the next seven days.

PLEASE DO NOT ACCEPT THIS COMMENT AS AN ANSWER!

Paul
EE Cleanup Volunteer
ASKER CERTIFIED SOLUTION
Avatar of YensidMod
YensidMod

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial