jcpunk has asked for the wisdom of the Perl Monks concerning the following question:

I have currently got some code that will do su via Expect.pm, but it is a little slow and hard to get the right output out of it. By hard I mean, Net::Telnet is much easier and by slow I mean it takes 9 seconds longer then just doing it by hand.
Is there a better way of doing this? I have to run a bunch of commands and really do not need a telnet server on the box (I have it setup to run on the loopback interface instead of the network), so I figured, hey why not switch it over to su...

any ideas as to how to boost the performence, or an easy way of getting only some of the output off of expect, or a different module to use for this?

This is what I've got, in a different program I need to read out several files, but not the commands to read them - expect provides both.
my $exp = Expect->spawn("su - $username") || die "Cannot spawn su: $!\ +n"; ####################################### $exp->debug(3); ####################################### $exp->raw_pty(1); my $spawn_ok; $exp->expect($timeout, [ 'Password: \$', sub { my $fh = shift; print $fh "$password\n"; $spawn_ok = "yes"; exp_continue; } ], [ eof => sub { if ($spawn_ok) { die "ERROR: premature EOF in login.\n"; } else { die "ERROR: could not spawn su.\n"; } } ], [ timeout => sub { die "Login process timed out\n"; } ], # ); #$exp->expect($timeout, # [ '-re', qr'[#>:] \$', sub { if($spawn_ok eq "yes") { if($on_off eq "on") { foreach $FILENAME (@list_of_files) { $exp->send("/bin/touch /home/$username/$FI +LENAME"); if ( $FILENAME eq ".forward") { #.forward must look like \username, + "|/usr/bin/vacation username" $set_fwd="\\$username, \"|/usr/bin/vac +ation $username\""; $exp->send("/bin/echo $set_fwd > /home +/$username/$FILENAME"); } if ( $FILENAME eq ".vacation.msg") { $message .= '\n'; $exp->send("/bin/echo $message > /home +/$username/$FILENAME"); } $exp->send("/bin/chmod 644 /home/$username +/$FILENAME"); $exp->send("/bin/chown $username /home/$us +ername/$FILENAME"); } if($on_off eq "off") { foreach $FILENAME (@list_of_files) { $exp->send("/bin/rm -f /home/$username +/$FILENAME"); } } } } } # ], # [ # timeout => # sub { # die "Login process timed out\n"; # } # ], );

jcpunk
all code is tested, and doesn't work so there :p (varient on common PM sig for my own ammusment)

Replies are listed 'Best First'.
Re: Faster way to do su
by Fletch (Bishop) on Apr 16, 2004 at 20:48 UTC

    Erm, why not just write a script which does what you want (perl or sh) and then use sudo to let it run as root (or whomever) without prompting for a password rather than mucking with su in the first place?

      This program, when completed (the Net::Telnet version is) will run as part of a webserver. Putting user nobody (or apache depending) into the sudoers file is usually considered bad form.

      jcpunk
      all code is tested, and doesn't work so there :p (varient on common PM sig for my own ammusment)
Re: Faster way to do su
by matija (Priest) on Apr 16, 2004 at 22:48 UTC
    Faster way? Sure: create a SUID perl script that does the commands you need (touch, cteare the files, chmod, chown, etc), and which reads the username and list_of_files parameters from the command line (don't forget to use -T and propperly untaint the values).

    Then simply call that script from your CGI like you would any other command.

Re: Faster way to do su
by b10m (Vicar) on Apr 16, 2004 at 20:49 UTC

    Short answer, but hey, it may be faster: check out sudo. At least you don't need the r00t password stored somewhere :)

    --
    b10m

    All code is usually tested, but rarely trusted.
      I think we may be talking past each other. I just told Flectch why sudo is a bad idea (Re: Re: Faster way to do su). Sadly that was about 1 hour after you and he suggested it. That would be my bad in failing to provide enough information.
      I am confused as to why you think I need the root password stored somewhere. I have got su - expecting to be prompted for a password about 5 lines in..... My best guess is that you figured, why would anyone be using several su - processes for multiple users unless he/she had root on the box. In which case, since as root there is no password required, it is much simpler.
      Any thoughts as to a more 'pure perl' way of doing it from user nobody (who can really only get a shell through perl).

      jcpunk
      all code is tested, and doesn't work so there :p (varient on common PM sig for my own ammusment)

        Ahhh; the code watching for a password prompt from su was giving the impression you weren't root already.

        If you just need to switch to a different user and you're already root then just fork, set $< to the uid of the user in question (or call POSIX::setuid), and exec the script which does the real work.

        Update: OK, reading again it looks like it is nobody that'll be running this. Again, I'd see no problems with a sudo config which allows nobody to run one specific script which immediately drops to the user UID and execs another specific script to do the actual work. If you were truly paranoid you'd write a minimalist wrapper in C and make that suid root (if you're really, really paranoid you'll snip the cat5 and be done with it . . . :).

        Another possibility: use suexec to run this one CGI as another not-nobody unpriviledged account which is allowed to sudo to the desired user and run the real work.