1#! /bin/bash
2#
3# original from:
4#
5# @(#) frcp.ksh 2.2 93/11/14
6# 92/06/29 john h. dubois iii (john@armory.com)
7# 92/10/14 Cleaned up, improved, added -d and -r options
8# 92/11/11 Made work with a dest of '.'
9# 93/07/09 Added -l and -n options, & login as anonymous if no .netrc entry
10# 93/11/14 Use either passwd or password in .netrc, since ftp does.
11#
12# conversion to bash v2 syntax by Chet Ramey
13#
14# frcp: ftp front end with rcp-like syntax.
15# Note: requires any machine names given to be listed with
16#       user and password in .netrc.  If not, anonymous FTP is
17#	done.
18#
19# full path to ftp binary
20if [ -x /usr/bin/ftp ]; then
21	FTP=/usr/bin/ftp;
22elif [ -x /usr/ucb/ftp ]; then
23	FTP=/usr/ucb/ftp
24else
25	FTP=ftp
26fi
27
28istrue()
29{
30	test 0 -ne "$1"
31}
32isfalse()
33{
34	test 0 -eq "$1"
35}
36
37# For each filename given, put the filename in filename[n]
38# and the machine it is on in machine[n].
39function SplitNames {
40    typeset file
41    typeset -i i=1
42
43    unset filename[*] machine[*]
44    for file; do
45	case "$file" in
46	*:*) machine[i]=${file%%:*} ;;
47	*)   machine[i]=$LocalMach ;;
48	esac
49	filename[i]=${file#*:}
50	let i+=1
51    done
52}
53
54function verboseprint {
55    echo "$@"
56    echo "$@" 1>&2
57}
58
59function MakeDir {
60    OFS=$IFS
61    local IFS=/ dir component
62
63    case "$1" in
64    /*)	;;
65     *) dir=.
66    esac
67    set -- $1
68    IFS=$OFS
69    for component; do
70	dir=$dir/$component
71	if [ ! -d "$dir" ]; then
72	    if mkdir "$dir"; then :; else
73		echo "Could not make directory $dir." >&2
74		return 1
75	    fi
76	fi
77    done
78    return 0
79}
80
81lastisdot ()
82{
83	case "$1" in
84	*/.|*/..)	return 0;;
85	*)	return 1;;
86	esac
87}
88
89# CopyFiles: issue ftp(TC) commands to copy files.
90# Usage: CopyFiles [sourcemachine:]sourcepath ... [destmachine:]destpath
91# Global vars:
92# Uses LocalMach (should be name of local machine)
93# Sets global arrs machine[]/filename[]
94function CopyFiles {
95    unset machine[*] filename[*]
96
97    SplitNames "$@"	# split names into filename[1..n] and machine[1..n]
98
99    local DestMach=${machine[$#]}	# Machine to copy files to
100    local DestPath=${filename[$#]} 	# Destination file/dir
101
102    unset machine[$#] filename[$#]
103
104    [ -z "$DestPath" ] && DestPath=.	# dest was given as machine:
105
106    # Try to determine if destination should be a directory
107    # so that it can be forced to be a directory.
108
109    case "$DestPath" in
110    */)	;;	# don't add / if trailing / already present
111    *)  if [ $# -gt 2 ] || # if more than two args given, last must be a dir
112	    # If dest in on local machine, check whether it is a directory
113    	   [ $DestMach = $LocalMach ] && [ -d "$DestPath" ] || 
114	    # If dest ends with . or .., it is a directory
115	   lastisdot "$DestPath"
116	then
117		DestPath=$DestPath/
118	fi ;;
119    esac
120
121    # If one of the above tests made us think dest is a directory,
122    # but it isn't, complain
123    case "$DestPath" in
124    */)	if [ "$DestMach" = "$LocalMach" ] && [ ! -d "$DestPath" ]; then
125		echo "Destination is not a directory." 1>&2
126		exit 1
127        fi ;;
128    esac
129
130    DoCopy "$DestMach" "$DestPath"
131}
132
133# Usage: OpenMachine machine-name
134# Emits login sequence or doesn't, depending on .netrc file and global
135# variables anon and noanon
136OpenMachine ()
137{
138    local machine=$1 netrc=$HOME/.netrc user= password=
139
140    if isfalse $anon && [ -r $netrc ]; then
141	set -- $(gawk '
142	/machine (.* )?'"$machine"'($| )/,/^ *$/ {
143	    Fields[$1] = $2
144	    if ("passwd" in Fields)
145		Fields["password"] = Fields["passwd"]
146	    if ("login" in Fields && "password" in Fields) {
147		print Fields["login"] " " Fields["password"]
148		exit
149	    }
150	}
151	' $netrc )
152	user=$1
153	password=$2
154    fi
155    if [ -z "$password" ]; then
156	if istrue $noanon; then
157	    echo "No .netrc entry for machine $machine" 1>&2
158	    exit 1
159	fi
160	user=anonymous
161	password=$USER@$LocalMach
162    fi
163    verboseprint open $machine
164    echo user $user "*******" 1>&2
165    echo user $user $password
166}
167
168# Usage: DoCopy destination-machine destination-path
169# Copies the files in global arrs machine[]/filename[] to the given dest
170# Global vars:
171# Uses machine[], filename[], LocalMach, check
172DoCopy ()
173{
174    local DestMach=$1
175    local DestPath=$2
176    local OpenMach	# Machine that connection is currently open to
177    local OWD=$PWD SourceMach SourceFile
178    local FileName
179    typeset -i i=1
180
181    while [ $i -le ${#machine[*]} ]; do
182	istrue $check && verboseprint "runique"
183
184	SourceMach=${machine[i]}
185	SourceFile=${filename[i]}
186
187	DestFile=$DestPath
188	# if DestPath is a dir,
189	# add source filename to it without source path
190	case "$DestFile" in
191	*/)	DestFile=$DestFile${SourceFile##*/} ;;
192	esac
193
194	if [ $SourceMach = $LocalMach ]; then
195	    if [ $DestMach != "$OpenMach" ]; then
196		OpenMachine $DestMach
197		OpenMach=$DestMach
198	    fi
199	    verboseprint put $SourceFile $DestFile
200	elif [ $DestMach = $LocalMach ]; then
201	    if istrue $check && [ -f "$DestFile" ]; then
202		echo "$DestFile already exists." 1>&2
203		continue
204	    fi
205	    # If destination is on local machine,
206	    # the dest will be a full dir/filename
207	    if istrue $createdirs; then
208		MakeDir "${DestFile%/*}" || continue
209	    fi
210	    if [ $SourceMach != "$OpenMach" ]; then
211		OpenMachine $SourceMach
212		OpenMach=$SourceMach
213	    fi
214	    # If source filename has wildcards ([, ], *, ?) do an mget
215	    case "$SourceFile" in
216	    \[*\]|*\**|*\?*)
217		verboseprint lcd "$DestFile"
218		verboseprint mget "$SourceFile"
219		verboseprint lcd $OWD ;;
220	    *)  verboseprint get "$SourceFile" "$DestFile" ;;
221	    esac
222	else
223	    echo "Neither source machine \"$SourceMach\" "\
224"nor destination machine \"$DestMach\" is local." 1>&2
225	fi
226	let i+=1
227    done
228}
229
230# Start of main program
231name=${0##*/}
232
233if [ "$1" = -h ]; then
234    echo \
235"$name: do ftp transfers using rcp-style parameters.
236Usage: $name <source> <destpath>   or   $name <source> [<source> ...] <destdir>
237At least one of <source> and <destpath> must be the local system.
238A remote filename is given as machinename:filename
239If remote filenames contain wildcards, they will be globbed on the remote
240machine.  Make sure they are quoted when $name is invoked.
241If the invoking user's .netrc file (see ftp(TC)) contains an entry for the
242remote system with a login and password supplied, $name will log in using
243the given login and password.  If not, $name will login in as user
244anonymous and with the user@localsystem as the password.
245Options:
246-c: check: do not overwrite files.
247-d: create directories as needed.
248-f: force: overwrite files (default).
249-h: print this help.
250-l: fail if there is no entry with login and password for the remote system,
251    instead of logging in as anonymous.
252-n: log in as anonymous even if there is an entry for the remote system in
253    the user's .netrc file.
254-r: read source/dest filename pairs from the standard input,
255    one pair per line, and copy files accordingly."
256    exit 0
257fi
258
259typeset -i check=0 createdirs=0 readinput=0 anon=0 noanon=0
260
261while getopts :cdflnr Option
262do
263    case "$Option" in
264    c) check=1;;
265    d) createdirs=1;;
266    f) check=0;;
267    l) noanon=1;;
268    n) anon=1;;
269    r) readinput=1;;
270    \?) echo "$OPTARG: invalid option."; exit 1;;
271    esac
272done
273
274shift $((OPTIND-1))
275
276LocalMach=`hostname` 
277
278if istrue $readinput; then
279    while read line; do
280	CopyFiles $line
281    done | $FTP -nv
282else
283    if [ $# -lt 2 ]; then
284	echo "$name: Not enough arguments.  Use -h for help." 1>&2
285	exit
286    fi
287    CopyFiles "$@" | $FTP -nv
288fi
289