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