1if [ ! "$_MEDIA_FTP_SUBR" ]; then _MEDIA_FTP_SUBR=1
2#
3# Copyright (c) 2012-2013 Devin Teske
4# All rights reserved.
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions
8# are met:
9# 1. Redistributions of source code must retain the above copyright
10#    notice, this list of conditions and the following disclaimer.
11# 2. Redistributions in binary form must reproduce the above copyright
12#    notice, this list of conditions and the following disclaimer in the
13#    documentation and/or other materials provided with the distribution.
14#
15# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25# SUCH DAMAGE.
26#
27#
28############################################################ INCLUDES
29
30BSDCFG_SHARE="/usr/share/bsdconfig"
31. $BSDCFG_SHARE/common.subr || exit 1
32f_dprintf "%s: loading includes..." media/ftp.subr
33f_include $BSDCFG_SHARE/device.subr
34f_include $BSDCFG_SHARE/dialog.subr
35f_include $BSDCFG_SHARE/media/common.subr
36f_include $BSDCFG_SHARE/media/tcpip.subr
37f_include $BSDCFG_SHARE/strings.subr
38f_include $BSDCFG_SHARE/struct.subr
39f_include $BSDCFG_SHARE/variable.subr
40
41BSDCFG_LIBE="/usr/libexec/bsdconfig"
42f_include_lang $BSDCFG_LIBE/include/messages.subr
43
44############################################################ GLOBALS
45
46FTP_SKIP_RESOLV=
47
48URL_MAX=261261 # according to actual fetch(1) test-results
49
50FTP_DIRS="
51	.
52	releases/$UNAME_P
53	snapshots/$UNAME_P
54	pub/FreeBSD
55	pub/FreeBSD/releases/$UNAME_P
56	pub/FreeBSD/snapshots/$UNAME_P
57	pub/FreeBSD-Archive/old-releases/$UNAME_P
58" # END-QUOTE
59
60############################################################ FUNCTIONS
61
62# f_dialog_menu_media_ftp
63#
64# Prompt the user to select from a range of ``built-in'' FTP servers or specify
65# their own. If the user makes a choice and doesn't cancel or press Esc, stores
66# the user's choice in VAR_FTP_PATH (see variables.subr) and returns success.
67#
68f_dialog_menu_media_ftp()
69{
70	f_dialog_title "$msg_please_select_a_freebsd_ftp_distribution_site"
71	local title="$DIALOG_TITLE" btitle="$DIALOG_BACKTITLE"
72	f_dialog_title_restore
73	local prompt="$msg_please_select_the_site_closest_to_you_or_other"
74	local menu_list="
75		'$msg_main_site'          'download.freebsd.org'
76		'$msg_australia $msg_v6'  'ftp.au.freebsd.org'
77		' $msg_australia #3'      'ftp3.au.freebsd.org'
78		'$msg_austria $msg_v6'    'ftp.at.freebsd.org'
79		'$msg_brazil #2'          'ftp2.br.freebsd.org'
80		' $msg_brazil #3'         'ftp3.br.freebsd.org'
81		'$msg_bulgaria $msg_v6'   'ftp.bg.freebsd.org'
82		'$msg_czech_republic $msg_v6' 'ftp.cz.freebsd.org'
83		'$msg_denmark $msg_v6'    'ftp.dk.freebsd.org'
84		'$msg_finland'            'ftp.fi.freebsd.org'
85		'$msg_france $msg_v6'     'ftp.fr.freebsd.org'
86		' $msg_france #3'         'ftp3.fr.freebsd.org'
87		' $msg_france #6'         'ftp6.fr.freebsd.org'
88		'$msg_germany $msg_v6'     'ftp.de.freebsd.org'
89		' $msg_germany #1 $msg_v6' 'ftp1.de.freebsd.org'
90		' $msg_germany #2 $msg_v6' 'ftp2.de.freebsd.org'
91		' $msg_germany #5 $msg_v6' 'ftp5.de.freebsd.org'
92		' $msg_germany #7 $msg_v6' 'ftp7.de.freebsd.org'
93		'$msg_greece $msg_v6'     'ftp.gr.freebsd.org'
94		' $msg_greece #2 $msg_v6' 'ftp2.gr.freebsd.org'
95		'$msg_japan $msg_v6'      'ftp.jp.freebsd.org'
96		' $msg_japan #2'          'ftp2.jp.freebsd.org'
97		' $msg_japan #3'          'ftp3.jp.freebsd.org'
98		' $msg_japan #4'          'ftp4.jp.freebsd.org'
99		' $msg_japan #6 $msg_v6'  'ftp6.jp.freebsd.org'
100		'$msg_korea'              'ftp.kr.freebsd.org'
101		' $msg_korea #2'          'ftp2.kr.freebsd.org'
102		'$msg_latvia'             'ftp.lv.freebsd.org'
103		'$msg_netherlands $msg_v6' 'ftp.nl.freebsd.org'
104		' $msg_netherlands #2'    'ftp2.nl.freebsd.org'
105		'$msg_new_zealand'        'ftp.nz.freebsd.org'
106		'$msg_norway $msg_v6'     'ftp.no.freebsd.org'
107		'$msg_poland $msg_v6'     'ftp.pl.freebsd.org'
108		'$msg_russia $msg_v6'     'ftp.ru.freebsd.org'
109		' $msg_russia #2'         'ftp2.ru.freebsd.org'
110		'$msg_slovenia $msg_v6'   'ftp.si.freebsd.org'
111		'$msg_south_africa $msg_v6'     'ftp.za.freebsd.org'
112		' $msg_south_africa #2 $msg_v6' 'ftp2.za.freebsd.org'
113		' $msg_south_africa #4'         'ftp4.za.freebsd.org'
114		'$msg_sweden $msg_v6'           'ftp.se.freebsd.org'
115		'$msg_taiwan #4'          'ftp4.tw.freebsd.org'
116		' $msg_taiwan #5'         'ftp5.tw.freebsd.org'
117		'$msg_uk'                 'ftp.uk.freebsd.org'
118		' $msg_uk #2'             'ftp2.uk.freebsd.org'
119		'$msg_ukraine $msg_v6'    'ftp.ua.freebsd.org'
120		'$msg_usa #5 $msg_v6'     'ftp5.us.freebsd.org'
121		' $msg_usa #11 $msg_v6'   'ftp11.us.freebsd.org'
122		' $msg_usa #14'           'ftp14.us.freebsd.org'
123	" # END-QUOTE
124	local hline="$msg_select_a_site_thats_close"
125
126	local height width rows
127	eval f_dialog_menu_size height width rows \
128	                        \"\$title\"  \
129	                        \"\$btitle\" \
130	                        \"\$prompt\" \
131	                        \"\$hline\"  \
132	                        $menu_list
133
134	local mtag
135	mtag=$( eval $DIALOG \
136		--title \"\$title\"             \
137		--backtitle \"\$btitle\"        \
138		--hline \"\$hline\"             \
139		--ok-label \"\$msg_ok\"         \
140		--cancel-label \"\$msg_cancel\" \
141		--menu \"\$prompt\"             \
142		$height $width $rows            \
143		$menu_list                      \
144		2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
145	) || return $DIALOG_CANCEL
146	f_dialog_data_sanitize mtag
147
148	case "$mtag" in
149	URL) setvar $VAR_FTP_PATH "other" ;;
150	*)
151		local value
152		value=$( eval f_dialog_menutag2item \"\$mtag\" $menu_list )
153		setvar $VAR_FTP_PATH "ftp://$value"
154	esac
155
156	return $DIALOG_OK
157}
158
159# f_media_set_ftp
160#
161# Return success if we both found and set the media type to be an FTP server.
162# Variables from variable.subr that can be used to script user input:
163#
164# 	VAR_FTP_PATH
165# 		Can be a URL (including "ftp://" protocol-prefix) or "other"
166# 		(user is prompted to enter FTP URL). If a URL, can optionally
167# 		contain directory prefix after hostname/port. Valid examples
168# 		include:
169# 			ftp://myhost
170# 			ftp://somename:21/pub/
171# 			ftp://192.168.2.3/pub/
172# 			ftp://[::1]:21/
173# 		The default port if not specified is 21.
174# 	VAR_NAMESERVER [Optional]
175# 		If set, overrides resolv.conf(5) and sets the nameserver that
176# 		is used to convert names into addresses (when a name converts
177# 		into multiple addresses, the first address to successfully
178# 		connect is used).
179#
180# Meanwhile, the following variables from variable.subr are set after
181# successful execution:
182#
183# 	VAR_FTP_HOST
184# 		The FTP host to connect to, parsed from VAR_FTP_PATH. In the
185# 		example case of IPv6 where VAR_FTP_PATH is "ftp://[::1]", this
186# 		variable will be set to "::1" (the outer brackets are removed).
187# 	VAR_FTP_PORT
188# 		The TCP port to connect to, parsed from VAR_FTP_PATH. Usually
189# 		21 unless VAR_FTP_PATH was of one of the following forms:
190# 			ftp://hostname:OTHER_PORT
191# 			ftp://hostname:OTHER_PORT/*
192# 			ftp://ip:OTHER_PORT
193# 			ftp://ip:OTHER_PORT/*
194# 			ftp://[ip6]:OTHER_PORT
195# 			ftp://[ip6]:OTHER_PORT/*
196# 	VAR_FTP_DIR
197# 		If VAR_FTP_PATH contained a directory element (e.g.,
198# 		"ftp://localhost/pub") this variable contains only the
199# 		directory element (e.g., "/pub").
200#
201f_media_set_ftp()
202{
203	f_media_close
204
205	local url
206	f_getvar $VAR_FTP_PATH url
207
208	# If we've been through here before ...
209	if f_struct device_network && [ "${url#$msg_other}" ]; then
210		f_dialog_yesno "$msg_reuse_old_ftp_site_selection_values" ||
211			url=
212	fi
213
214	if [ ! "$url" ]; then
215		f_dialog_menu_media_ftp || return $FAILURE
216		f_getvar $VAR_FTP_PATH url
217	fi
218	[ "$url" ] || return $FAILURE
219
220	case "$url" in
221	other)
222		setvar $VAR_FTP_PATH "ftp://"
223		f_variable_get_value $VAR_FTP_PATH \
224			"$msg_please_specify_url_of_a_freebsd_distribution"
225		f_getvar $VAR_FTP_PATH url
226		if [ ! "${url#ftp://}" ]; then
227			unset $VAR_FTP_PATH
228			return $FAILURE
229		fi
230		if [ ${#url} -gt ${URL_MAX:-261261} ]; then
231			f_show_msg "$msg_length_of_specified_url_is_too_long" \
232			           ${#url} ${URL_MAX:-261261}
233			unset $VAR_FTP_PATH
234			return $FAILURE
235		fi
236		case "$url" in
237		ftp://*) : valid URL ;;
238		*)
239			f_show_msg "$msg_sorry_invalid_url" "$url"
240			unset $VAR_FTP_PATH
241			return $FAILURE
242		esac
243	esac
244	case "$url" in
245	ftp://*) : valid URL ;;
246	*)
247		f_show_msg "$msg_sorry_invalid_url" "$url"
248		unset $VAR_FTP_PATH
249		return $FAILURE
250	esac
251
252	# Set the name of the FTP device to the URL
253	f_struct_new DEVICE device_ftp
254	device_ftp set name "$url"
255
256	if ! f_struct device_network ||
257	   ! f_dialog_yesno "$msg_youve_already_done_the_network_configuration"
258	then
259		f_struct device_network &&
260			f_device_shutdown device_network
261		if ! f_device_select_tcp; then
262			unset $VAR_FTP_PATH
263			return $FAILURE
264		fi
265		local dev if
266		f_getvar $VAR_NETWORK_DEVICE if
267		f_device_find -1 "$if" $DEVICE_TYPE_NETWORK dev
268		f_struct_copy "$dev" device_network
269	fi
270	if ! f_device_init device_network; then
271		f_dprintf "f_media_set_ftp: %s" "$msg_net_device_init_failed"
272		unset $VAR_FTP_PATH
273		return $FAILURE
274	fi
275
276	local hostname="${url#*://}" port=21 dir=/
277	case "$hostname" in
278	#
279	# The order in-which the below individual cases appear is important!
280	#
281	"["*"]":*/*) # IPv6 address with port and directory
282		f_dprintf "Looks like an IPv6 addr with port/dir: %s" \
283		          "$hostname"
284		hostname="${hostname#\[}"
285		port="${hostname#*\]:}"
286		port="${port%%[!0-9]*}"
287		dir="/${hostname#*/}"
288		hostname="${hostname%%\]:*}"
289		;;
290	"["*"]":*) # IPv6 address with port
291		f_dprintf "Looks like an IPv6 addr with port: %s" "$hostname"
292		hostname="${hostname#\[}"
293		port="${hostname#*\]:}"
294		port="${port%%[!0-9]*}"
295		hostname="${hostname%%\]:*}"
296		;;
297	"["*"]"/*) # IPv6 address with directory
298		f_dprintf "Looks like an IPv6 addr with dir: %s" "$hostname"
299		hostname="${hostname#\[}"
300		dir="/${hostname#*/}"
301		hostname="${hostname%%\]*}"
302		;;
303	"["*"]") # IPv6 address
304		f_dprintf "Looks like an IPv6 addr: %s" "$hostname"
305		hostname="${hostname#\[}"
306		hostname="${hostname%\]}"
307		;;
308	#
309	# ^^^ IPv6 above / DNS Name or IPv4 below vvv
310	#
311	*:*/*) # DNS name or IPv4 address with port and directory
312		f_dprintf "Looks like a %s with port/dir: %s" \
313		          "DNS name or IPv4 addr" "$hostname"
314		port="${hostname#*:}"
315		port="${port%%[!0-9]*}"
316		dir="/${hostname#*/}"
317		hostname="${hostname%%:*}"
318		;;
319	*:*) # DNS name or IPv4 address with port
320		f_dprintf "Looks like a DNS name or IPv4 addr with port: %s" \
321		          "$hostname"
322		port="${hostname#*:}"
323		hostname="${hostname%%:*}"
324		;;
325	*/*) # DNS name or IPv4 address with directory
326		f_dprintf "Looks like a DNS name or IPv4 addr with dir: %s" \
327		          "$hostname"
328		dir="/${hostname#*/}"
329		hostname="${hostname%%/*}"
330		;;
331	*) # DNS name or IPv4 address
332		f_dprintf "Looks like a DNS name or IPv4 addr: %s" "$hostname"
333		: leave hostname as-is
334	esac
335
336	f_dprintf "hostname = \`%s'" "$hostname"
337	f_dprintf "dir = \`%s'" "$dir"
338	f_dprintf "port \# = \`%d'" "$port"
339
340	local ns
341	f_getvar $VAR_NAMESERVER ns
342	[ "$ns" ] || f_resolv_conf_nameservers ns
343	if [ "$ns" -a ! "$FTP_SKIP_RESOLV" ] && ! {
344		f_validate_ipaddr "$hostname" ||
345		f_validate_ipaddr6 "$hostname"
346	}; then
347		f_show_info "$msg_looking_up_host" "$hostname"
348		f_dprintf "%s: Looking up hostname, %s, using host(1)" \
349		          "f_media_set_ftp" "$hostname"
350		if ! f_quietly f_host_lookup "$hostname"; then
351			f_show_msg "$msg_cannot_resolve_hostname" "$hostname"
352			f_struct device_network &&
353				f_device_shutdown device_network
354			f_struct_free device_network
355			unset $VAR_FTP_PATH
356			return $FAILURE
357		fi
358		f_dprintf "Found DNS entry for %s successfully." "$hostname"
359	fi
360
361	setvar $VAR_FTP_HOST "$hostname"
362	setvar $VAR_FTP_PORT "$port"
363	setvar $VAR_FTP_DIR  "$dir"
364
365	device_ftp set type     $DEVICE_TYPE_FTP
366	device_ftp set init     f_media_init_ftp
367	device_ftp set get      f_media_get_ftp
368	device_ftp set shutdown f_media_shutdown_ftp
369	device_ftp set private  device_network
370	f_struct_copy device_ftp device_media
371	f_struct_free device_ftp
372
373	return $SUCCESS
374}
375
376# f_media_set_ftp_active
377#
378# Wrapper to f_media_set_ftp to access FTP servers actively.
379#
380f_media_set_ftp_active()
381{
382	setvar $VAR_FTP_STATE "active"
383	f_media_set_ftp
384}
385
386# f_media_set_ftp_passive
387#
388# Wrapper to f_media_set_ftp to access FTP servers passively.
389#
390f_media_set_ftp_passive()
391{
392	setvar $VAR_FTP_STATE "passive"
393	f_media_set_ftp
394}
395
396# f_media_set_ftp_userpass
397#
398# Prompt the user to enter/confirm the username/password variables that will
399# be used to communicate with the FTP servers. Returns success if the user does
400# not cancel or press Esc to either username or password.
401#
402# Variables from variable.subr that can be used to script user input:
403#
404# 	VAR_FTP_USER
405# 		The username to send via ftp(1) when connecting to an FTP
406# 		server.
407# 	VAR_FTP_PASS
408# 		The password to send with the above username.
409#
410# Does not prompt for confirmation of values if VAR_NONINTERACTIVE is set (see
411# variable.subr for more information).
412#
413f_media_set_ftp_userpass()
414{
415	local user pass
416	f_variable_get_value $VAR_FTP_USER \
417		"$msg_please_enter_the_username_you_wish_to_login_as"
418	f_getvar $VAR_FTP_USER user
419	if [ "$user" ]; then
420		f_variable_get_value $VAR_FTP_PASS \
421			"$msg_please_enter_the_password_for_this_user"
422		f_getvar $VAR_FTP_PASS pass
423	else
424		pass=
425	fi
426	[ "$pass" ] # Return status
427}
428
429# f_device_network_up $device
430#
431# Brings up attached network device, if any - takes FTP device as arg.
432#
433f_device_network_up()
434{
435	local dev="$1" netDev
436	f_struct "$dev" || return $FAILURE
437	$dev get private netDev || return $SUCCESS # No net == happy net
438debug=1 f_dprintf "netDev=[$netDev]"
439	f_device_init $netDev
440}
441
442# f_device_network_down $device
443#
444# Brings down attached network device, if any - takes FTP device as arg.
445#
446f_device_network_down()
447{
448	local dev="$1" netDev
449	f_struct "$dev" || return $FAILURE
450	$dev get private netDev || return $SUCCESS
451	f_device_shutdown $netDev
452}
453
454# f_media_init_ftp $device
455#
456# Initializes the FTP media device. Returns success if both able to log into
457# the FTP server and confirm the existence of at least one known release path
458# using ftp(1).
459#
460# Variables from variable.subr used to initialize the connection are as follows
461# (all of which are configured by f_media_set_ftp above):
462#
463# 	VAR_FTP_PATH
464# 		The unparsed FTP URL representing the server to contact.
465# 		Usually "ftp://server" for example. Can contain TCP port number
466# 		and/or directory path (but should not contain username/password
467# 		info).
468# 	VAR_FTP_HOST
469# 		The FTP host to connect to. Can be an IPv4 address (e.g.,
470# 		127.0.0.1), IPv6 address (e.g., ::1), or DNS hostname. Usually
471# 		set automatically in f_media_set_ftp() by parsing VAR_FTP_PATH.
472# 	VAR_FTP_PORT
473# 		The TCP port to connect to. Usually set automatically in
474# 		f_media_set_ftp() by parsing VAR_FTP_PATH.
475# 	VAR_FTP_DIR
476# 		The base FTP directory to use when downloading files from the
477# 		FTP server. Usually set automatically in f_media_set_ftp() by
478# 		parsing VAR_FTP_PATH.
479# 	VAR_FTP_USER [Optional]
480# 		If unset, defaults to using anonymous access.
481# 	VAR_FTP_PASS [Optional]
482# 		If unset, defaults to a sensible value.
483#
484# In addition, the following (managed either manually or by f_media_set_ftp_*):
485#
486# 	VAR_FTP_STATE
487# 		Sets FTPMODE for ftp(1) and can be one of:
488# 			active    active mode FTP only
489# 			auto      automatic determination of passive or active
490# 			          (this is the default)
491# 			gate      gate-ftp mode
492# 			passive   passive mode FTP only
493# 		See ftp(1) for additional information.
494#
495# And last, but not least (managed automatically or manually):
496#
497# 	VAR_RELNAME
498# 		Defaults to being set to $(uname -r) but can be overridden.
499# 		This sets the name of a release to look for as part of a well
500# 		known set of paths to search for release data once connected
501# 		via FTP. If set to "__RELEASE" or "any" then the VAR_FTP_DIR is
502# 		taken as the absolute path to the release and no further
503# 		searching is done (see FTP_DIRS above in the GLOBALS section
504# 		for a list of well known paths that are used when searching for
505# 		a VAR_RELNAME sub-directory).
506#
507f_media_init_ftp()
508{
509	local dev="$1"
510	local url
511
512	$dev get name url
513	f_dprintf "Init routine called for FTP device. url=[%s]" "$url"
514
515	if [ "$FTP_INITIALIZED" ]; then
516		f_dprintf "FTP device already initialized."
517		return $SUCCESS
518	fi
519
520	# If we can't initialize the network, bag it!
521	f_device_network_up $dev || return $FAILURE
522
523	local cp
524	while :; do
525		f_getvar $VAR_FTP_PATH cp
526		if [ ! "$cp" ]; then
527			if ! f_media_set_ftp ||
528			   ! f_getvar $VAR_FTP_PATH cp ||
529			   [ ! "$cp" ]
530			then
531				f_show_msg "$msg_unable_to_get_proper_ftp_path"
532				f_device_network_down $dev
533				return $FAILURE
534			fi
535		fi
536
537		local ftp_host ftp_dir
538		if ! {
539			f_getvar $VAR_FTP_HOST ftp_host &&
540			f_getvar $VAR_FTP_DIR ftp_dir
541		}; then
542			f_show_msg "$msg_missing_ftp_host_or_directory"
543			f_device_network_down $dev
544			return $FAILURE
545		fi
546
547		local ftp_port
548		f_getvar $VAR_FTP_PORT ftp_port
549		local host="$ftp_host" port="${ftp_port:+:$ftp_port}"
550		case "$host" in *:*) host="[$host]"; esac
551
552		local user pass use_anon=
553		f_getvar $VAR_FTP_USER user
554		if [ ! "$user" ]; then
555			user="anonymous"
556			use_anon=1
557		fi
558		if ! f_getvar $VAR_FTP_PASS pass; then
559			f_getvar $VAR_HOSTNAME cp
560			if f_running_as_init; then
561				pass="installer@$cp"
562			else
563				local name="$( id -un 2> /dev/null )"
564				pass="${name:-ftp}@$cp"
565			fi
566		fi
567
568		f_show_info "$msg_logging_in_to_user_at_host" \
569		            "$user" "$ftp_host"
570
571		local userpass=""
572		if [ ! "$use_anon" ] && [ "$user" -o "$pass" ]; then
573			userpass="$user${pass:+:$( f_uriencode "$pass" )}"
574			userpass="$userpass${userpass:+@}"
575		fi
576
577		local mode rx
578		f_getvar $VAR_FTP_STATE mode
579
580		if [ "$ftp_dir" ]; then
581			if ! rx=$(
582				printf 'cd "%s"\npwd\n' "$ftp_dir" | eval \
583					FTPMODE=\"\$mode\" \
584					${use_anon:+FTPANONPASS=\"\$pass\"} \
585					ftp -V ${use_anon:+-a} \
586					    \"ftp://\$userpass\$host\$port\" \
587				2>&1
588			); then
589				f_show_msg "$msg_couldnt_open_ftp_connection" \
590				           "$ftp_host" "$rx"
591				break # to failure
592			fi
593			if echo "$rx" | awk -v dir="/${ftp_dir#/}" '
594				BEGIN {
595					found = 0
596					if ( dir != "/" ) sub("/$", "", dir)
597				}
598				/^Remote directory: / {
599					sub(/^[^:]*:[[:space:]]*/, "")
600					if ($0 != dir) next
601					found = 1; exit
602				}
603				END { exit ! found }
604			'; then
605				setvar $VAR_FTP_DIR "$ftp_dir"
606				setvar $VAR_FTP_PATH \
607					"ftp://$ftp_host/${ftp_dir#/}"
608			else
609				f_show_msg \
610				    "$msg_please_check_the_url_and_try_again" \
611				    "ftp://$ftp_host/${ftp_dir#/}"
612				break # to failure
613			fi
614		fi
615
616		#
617		# Now that we've verified that the path we're given is ok,
618		# let's try to be a bit intelligent in locating the release we
619		# are looking for.  First off, if the release is specified as
620		# "__RELEASE" or "any", then just assume that the current
621		# directory is the one we want and give up.
622		#
623		local rel
624		f_getvar $VAR_RELNAME rel
625		f_dprintf "f_media_init_ftp: rel=[%s]" "$rel"
626
627		case "$rel" in
628		__RELEASE|any)
629			FTP_INITIALIZED=YES
630			return $SUCCESS
631			;;
632		*)
633			#
634			# Ok, since we have a release variable, let's walk
635			# through the list of directories looking for a release
636			# directory. First successful CWD wins.
637			#
638			if ! rx=$(
639				for dir in $FTP_DIRS; do
640					# Avoid confusing some servers
641					[ "$dir" = "." ] && continue
642					printf 'cd "/%s/%s"\npwd\n' \
643					       "$dir" "$rel"
644				done | eval \
645					FTPMODE=\"\$mode\" \
646					${use_anon:+FTPANONPASS=\"\$pass\"} \
647					ftp -V ${use_anon:+-a} \
648					    \"ftp://\$userpass\$host\$port\" \
649				2>&1
650			); then
651				f_show_msg "$msg_couldnt_open_ftp_connection" \
652				           "$ftp_host" "$rx"
653				break # to failure
654			fi
655
656			local fdir
657			if fdir=$( echo "$rx" | awk '
658				/^Remote directory: / {
659					sub(/^[^:]*:[[:space:]]*/, "")
660					if ($0 == "/") next
661					# Exit after the first dir
662					found++; print; exit
663				}
664				END { exit ! found }
665			' ); then
666				setvar $VAR_FTP_DIR "$fdir"
667				setvar $VAR_FTP_PATH "ftp://$ftp_host$fdir"
668				FTP_INITIALIZED=YES
669				return $SUCCESS
670			else
671				f_yesno "$msg_cant_find_distribution" \
672				        "$rel" "$ftp_host"
673				if [ $? -eq $DIALOG_OK ]; then
674					unset $VAR_FTP_PATH
675					f_media_set_ftp && continue
676				fi
677			fi
678		esac
679		break # to failure
680	done
681
682	unset FTP_INITIALIZED $VAR_FTP_PATH
683	f_device_network_down $dev
684	return $FAILURE
685}
686
687# f_media_get_ftp $device $file [$probe_type]
688#
689# Returns data from $file on an FTP server using ftp(1). Please note that
690# $device is unused but must be present (even if null). Information is instead
691# gathered from the environment. If $probe_type is present and non-NULL,
692# returns success if $file exists. If $probe_type is equal to $PROBE_SIZE,
693# prints the size of $file in bytes to standard-out.
694#
695# Variables from variable.subr used to configure the connection are as follows
696# (all of which are configured by f_media_set_ftp above):
697#
698# 	VAR_FTP_HOST
699# 		FTP host to connect to. Can be an IPv4 address, IPv6 address,
700# 		or DNS hostname of your choice.
701# 	VAR_FTP_PORT
702# 		TCP port to connect on; see f_media_set_ftp() above.
703# 	VAR_FTP_USER [Optional]
704# 		If unset, defaults to using anonymous access.
705# 	VAR_FTP_PASS [Optional]
706# 		If unset, defaults to a sensible value.
707#
708# In addition, the following (managed either manually or by f_media_set_ftp_*):
709#
710# 	VAR_FTP_STATE
711# 		Sets FTPMODE for ftp(1) and can be one of:
712# 			active    active mode FTP only
713# 			auto      automatic determination of passive or active
714# 			          (this is the default)
715# 			gate      gate-ftp mode
716# 			passive   passive mode FTP only
717# 		See ftp(1) for additional information.
718#
719# See variable.subr for additional information.
720#
721# Example usage:
722# 	f_media_set_ftp
723# 	f_media_get_ftp media $file
724#
725f_media_get_ftp()
726{
727	local funcname=f_media_get_ftp
728	local dev="$1" file="$2" probe_type="$3" hosts=
729
730	f_dprintf "f_media_get_ftp: dev=[%s] file=[%s] probe_type=%s" \
731	          "$dev" "$file" "$probe_type"
732
733	local ftp_host ftp_port
734	f_getvar $VAR_FTP_HOST ftp_host
735	f_getvar $VAR_FTP_PORT ftp_port
736
737	if [ ! "$FTP_INITIALIZED" ]; then
738		f_dprintf "No FTP connection open, can't get file %s" "$file"
739		return $FAILURE
740	fi
741
742	if ! {
743		f_validate_ipaddr "$ftp_host" ||
744		f_validate_ipaddr6 "$ftp_host" ||
745		{
746		  f_dprintf "%s: Looking up hostname, %s, using host(1)" \
747		            "f_media_get_ftp" "$ftp_host"
748		  f_host_lookup "$ftp_host" hosts
749		}
750	}; then
751		# All the above validations failed
752		[ "$hosts" ] && f_dialog_msgbox "$hosts"
753		return $FAILURE
754	elif [ ! "$hosts" ]; then
755		# One of the first two validations passed
756		hosts="$ftp_host"
757	fi
758
759	local host connected=
760	for host in $hosts; do
761		f_quietly nc -nz "$host" "$ftp_port" || continue
762		connected=1; break
763	done
764	if [ ! "$connected" ]; then
765		f_show_msg "$msg_couldnt_connect_to_ftp_server %s:%s" \
766		           "$ftp_host" "$ftp_port"
767		return $FAILURE
768	fi
769
770	local user pass use_anon=
771	f_getvar $VAR_FTP_USER user
772	if [ ! "$user" ]; then
773		user="anonymous"
774		use_anon=1
775	fi
776	if ! f_getvar $VAR_FTP_PASS pass; then
777		f_getvar $VAR_HOSTNAME cp
778		if f_running_as_init; then
779			pass="installer@$cp"
780		else
781			local name="$( id -un 2> /dev/null )"
782			pass="${name:-ftp}@$cp"
783		fi
784	fi
785
786	local userpass=""
787	if [ ! "$use_anon" ] && [ "$user" -o "$pass" ]; then
788		userpass="$user${pass:+:$( f_uriencode "$pass" )}"
789		userpass="$userpass${userpass:+@}"
790	fi
791
792	local dir mode rx
793	f_getvar $VAR_FTP_DIR\#/ dir
794	f_getvar $VAR_FTP_STATE mode
795
796	local port="${ftp_port:+:$ftp_port}"
797	case "$host" in *:*) host="[$host]"; esac
798
799	f_dprintf "sending ftp request for: %s" "ftp://$host$port/$dir/$file"
800
801	if [ "$probe_type" ]; then
802		local url="ftp://$userpass$host$port/$dir/$file" size
803		[ "$use_anon" ] && url="ftp://$host$port/$dir/$file"
804		if ! f_eval_catch -dk size $funcname fetch \
805			'fetch -s "%s"' "$url" || ! f_isinteger "$size"
806		then
807			f_dprintf "size request failed!"
808			[ "$probe_type" = "$PROBE_SIZE" ] && echo "-1"
809			return $FAILURE
810		fi
811		[ "$probe_type" = "$PROBE_SIZE" ] && echo "$size"
812		return $SUCCESS
813	fi
814
815	eval FTPMODE=\"\$mode\" ${use_anon:+FTPANONPASS=\"\$pass\"} \
816	     ftp -V ${use_anon:+-a} -o - \
817	     	\"ftp://\$userpass\$host\$port/\$dir/\$file\" 2> /dev/null
818	local retval=$?
819
820	[ $retval -eq $SUCCESS ] || f_dprintf "request failed!"
821	return $retval
822}
823
824# f_media_shutdown_ftp $device
825#
826# Shuts down the FTP device. Return status should be ignored. Note that since
827# we don't maintain an open connection to the FTP server there's nothing to do.
828#
829f_media_shutdown_ftp()
830{
831	[ "$FTP_INITIALIZED" ] || return $SUCCESS
832
833	unset FTP_INITIALIZED
834}
835
836############################################################ MAIN
837
838f_dprintf "%s: Successfully loaded." media/ftp.subr
839
840fi # ! $_MEDIA_FTP_SUBR
841