[TAG] 2-cent tip: Pushing files to multiple hosts

Ben Okopnik ben at callahans.org
Mon Feb 2 18:20:58 MSK 2004


When I teach a class, I often need to push one or more files to my
students' systems. Previously, I would write a "for-do-done" loop and
use "scp" to get the files across, laboriously logging in and exiting
out of each system every time I wanted to do a transfer - painfully
clunky.

Then I did some searching on the Net and found "sshtool" by "noconflic".
Written in Expect, it allows multiple host logins and copying. However,
it did not have a "no password" mode (i.e., logging in when
".ssh/authorized_keys" contains your key) and read the list of hosts
from a list defined within the program. I've modified it to read an
external file called "pushlist" and added a "no password" mode; this
last, of course, requires that you first push a "~/.ssh/authorized_keys"
to the host list. 

===== SNIP HERE sshtool ================================================
#!/usr/bin/expect --
#
# Original idea and code by noconflic
# nocon at darkflame.net
# http://nocon.darkflame.net
# Wed Mar 12 16:01:47 CST 2003
#
# Modified Sat Dec 13 16:04:08 EST 2003 by Ben Okopnik <ben at callahans.org>
#	- Added the "no password" options for "authorized_keys" logins
#	- Set "hosts" list to read the contents of the "pushlist" file
#		(this makes it easy to use different sets of hosts)


# Set the path to your local scp/ssh commands
set sshcmd /usr/bin/ssh
set sshcpy /usr/bin/scp
set timeout "-1"

# Read in the list of hosts
if { [catch "open pushlist" FID] == 0 } {
  set hosts [read $FID]
  close $FID
}


proc usage {} {
	send_user "Usage: sshtool \[option\] <username>\n\n"
	send_user "Option:    (one of the following)\n"
	send_user "     -u    execute command on hosts as a normal user.\n"
	send_user "     -U    execute command on hosts as a normal user WITHOUT a password.\n"
	send_user "     -r    execute command on hosts as root (uses sudo).\n"
	send_user "     -c    copy file(s) or directory to hosts.\n"
	send_user "     -C    copy file(s) or directory to hosts WITHOUT a password.\n"
	send_user "     -h    this help\n\n"
        send_user "<username> username of login account.\n\n"
	exit
}

proc getpass {} {
       stty -echo
       send_user "SSH Password: "
       expect_user -re "(.*)\n"
       set password(pass) $expect_out(1,string)
       send_user "\n"
       stty echo
   return $password(pass)
}

proc getcommand {} {
       send_user "Enter Command: "
       expect_user -re "(.*)\n"
       set command(cmd) $expect_out(1,string)
   return $command(cmd)
}

proc getfiledir {} {
        send_user "Local directory or file: "
        expect_user -re "(.*)\n"
        set dirfile(dfile) $expect_out(1,string)
    return $dirfile(dfile)
}


proc doit {user pass cmmnd files su f} {
global hosts sshcmd sshcpy sucmd

  foreach host [set hosts] {
     send_user "Connecting to: $host...\n"

          if {$su == 1} {
              spawn $sshcmd $user@$host $cmmnd
              expect "password:"
              send $pass\r
              interact
          } 

          if {$su == 2} {
              spawn $sshcmd $user@$host $sucmd $cmmnd
              expect "password:"
              send $pass\r
              expect "Password:"
              send $pass\r
              interact
          }

          if {$f == 1} {
              spawn $sshcpy -r $files $user@$host:~/
              expect "password:"
              send $pass\r
              interact
          }

    sleep 1
  }
}

proc doit_nopass {user cmmnd files su f} {
global hosts sshcmd sshcpy sucmd

  foreach host [set hosts] {
     send_user "Connecting to: $host...\n"

          if {$su == 1} {
              spawn $sshcmd $user@$host $cmmnd
              interact
          } 

          if {$su == 2} {
              spawn $sshcmd $user@$host $sucmd $cmmnd
              interact
          }

          if {$f == 1} {
              spawn $sshcpy -r $files $user@$host:~/
              interact
          }

    sleep 1
  }
}


# Program body
if {[llength $argv]!=2} usage

set sucmd sudo
set user [lindex $argv 1]
set su 0
set files 0
set f 0
set cmmnd 0

while {[llength $argv]>0} {
   set flag [lindex $argv 0]
   switch -- $flag \
        "-u" {
              send_user "Running command(s) on hosts as a normal user...\n"  
              set su 1
              set pass [getpass]
              set cmmnd [getcommand]
              doit $user $pass $cmmnd $files $su $f
              set argv [lrange $argv 2 end]

      } "-U" {
              send_user "Running command(s) on hosts as a normal user WITHOUT a password...\n"  
              set su 1
              set cmmnd [getcommand]
              doit_nopass $user $cmmnd $files $su $f
              set argv [lrange $argv 2 end]

      } "-r" {
              send_user "Running command(s) on hosts as root...\n"
              set su 2
              set pass [getpass]
              set cmmnd [getcommand]
              doit $user $pass $cmmnd $files $su $f
              set argv [lrange $argv 2 end]

      } "-C" {
              send_user "Sending file(s) to hosts WITHOUT a password...\n"
              set f 1
              set files [getfiledir]
              doit_nopass $user $cmmnd $files $su $f
              set argv [lrange $argv 2 end]

      } "-c" {
              send_user "Sending file(s) to hosts...\n"
              set f 1
              set pass [getpass]
              set files [getfiledir]
              doit $user $pass $cmmnd $files $su $f
              set argv [lrange $argv 2 end]

      } "-h" {
              usage
              set argv [lrange $argv 2 end]

      } default {
	      usage

      }
}

 send_user "SSHTool Finished.\n"

exit
===== SNIP HERE ========================================================

First, create your "pushlist", possibly from an "/etc/hosts" on one of
the local machines. It should contain all your target hosts, one line
per host. Next, create your ".ssh/authorized_keys" in the directory
where you keep "sshtool" by copying your public keys into it:

ben at Fenrir:~/sshtool$ mkdir .ssh; cat ~/ssh/*pub > .ssh/authorized_keys

Then, push it out to your hosts (NOTE: this *replaces* the remote hosts'
"authorized_keys" files!):

# Log in as user "student" and send the local file
ben at Fenrir:~/sshtool$ ./sshtool -c .ssh/authorized_keys student

After this, I can push out any file or list of files simply by typing

ben at Fenrir:~/sshtool$ ./sshtool -C .ssh/authorized_keys student

I can also execute a command on all the systems via the "-U" option.

Note: I'm not an Expect programmer; otherwise, "sshtool" would accept a
"local:remote" syntax so files wouldn't need to be in identical
locations. It would also allow you to specify per-host usernames in the
push list (not an option I need, but something to make it more
flexible.) Anyone adding these features - please send me a copy. :)))


* Ben Okopnik * okopnik.freeshell.org * Technical Editor, Linux Gazette *
-*- See the Linux Gazette in its new home: <http://linuxgazette.net/> -*-




More information about the TAG mailing list