1/*
2 * The new sysinstall program.
3 *
4 * This is probably the last attempt in the `sysinstall' line, the next
5 * generation being slated to essentially a complete rewrite.
6 *
7 * $FreeBSD$
8 *
9 * Copyright (c) 1995
10 *	Jordan Hubbard.  All rights reserved.
11 *
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 * 1. Redistributions of source code must retain the above copyright
16 *    notice, this list of conditions and the following disclaimer,
17 *    verbatim and that no modifications are made prior to this
18 *    point in the file.
19 * 2. Redistributions in binary form must reproduce the above copyright
20 *    notice, this list of conditions and the following disclaimer in the
21 *    documentation and/or other materials provided with the distribution.
22 *
23 * THIS SOFTWARE IS PROVIDED BY JORDAN HUBBARD ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED.  IN NO EVENT SHALL JORDAN HUBBARD OR HIS PETS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, LIFE OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
34 *
35 */
36
37#include "sysinstall.h"
38#include <sys/socket.h>
39#include <netinet/in.h>
40#include <arpa/inet.h>
41#include <sys/param.h>
42#include <sys/wait.h>
43#include <netdb.h>
44#include <pwd.h>
45#include <ftpio.h>
46
47Boolean ftpInitted = FALSE;
48static FILE *OpenConn;
49int FtpPort;
50
51/* List of sub directories to look for under a given FTP server. */
52const char *ftp_dirs[] = { ".", "releases/"MACHINE, "snapshots/"MACHINE,
53    "releases/"MACHINE"/"MACHINE_ARCH, "snapshots/"MACHINE"/"MACHINE_ARCH,
54    "pub/FreeBSD", "pub/FreeBSD/releases/"MACHINE,
55    "pub/FreeBSD/snapshots/"MACHINE,
56    "pub/FreeBSD/releases/"MACHINE"/"MACHINE_ARCH,
57    "pub/FreeBSD/snapshots/"MACHINE"/"MACHINE_ARCH,
58    NULL };
59
60/* Brings up attached network device, if any - takes FTP device as arg */
61static Boolean
62netUp(Device *dev)
63{
64    Device *netdev = (Device *)dev->private;
65
66    if (netdev)
67	return DEVICE_INIT(netdev);
68    else
69	return TRUE;	/* No net == happy net */
70}
71
72/* Brings down attached network device, if any - takes FTP device as arg */
73static void
74netDown(Device *dev)
75{
76    Device *netdev = (Device *)dev->private;
77
78    if (netdev)
79	DEVICE_SHUTDOWN(netdev);
80}
81
82Boolean
83mediaInitFTP(Device *dev)
84{
85    int i, code, af, fdir;
86    char *cp, *rel, *hostname, *dir;
87    char *user, *login_name, password[80];
88
89    if (ftpInitted)
90	return TRUE;
91
92    if (OpenConn) {
93	fclose(OpenConn);
94	OpenConn = NULL;
95    }
96
97    /* If we can't initialize the network, bag it! */
98    if (!netUp(dev))
99	return FALSE;
100
101try:
102    cp = variable_get(VAR_FTP_PATH);
103    if (!cp) {
104	if (DITEM_STATUS(mediaSetFTP(NULL)) == DITEM_FAILURE || (cp = variable_get(VAR_FTP_PATH)) == NULL) {
105	    msgConfirm("Unable to get proper FTP path.  FTP media not initialized.");
106	    netDown(dev);
107	    return FALSE;
108	}
109    }
110
111    hostname = variable_get(VAR_FTP_HOST);
112    dir = variable_get(VAR_FTP_DIR);
113    if (!hostname || !dir) {
114	msgConfirm("Missing FTP host or directory specification.  FTP media not initialized.");
115	netDown(dev);
116	return FALSE;
117    }
118    user = variable_get(VAR_FTP_USER);
119    login_name = (!user || !*user) ? "anonymous" : user;
120
121    if (variable_get(VAR_FTP_PASS))
122	SAFE_STRCPY(password, variable_get(VAR_FTP_PASS));
123    else if (RunningAsInit)
124	sprintf(password, "installer@%s", variable_get(VAR_HOSTNAME));
125    else {
126	struct passwd *pw;
127	char *user;
128
129	pw = getpwuid(getuid());
130	user = pw ? pw->pw_name : "ftp";
131	sprintf(password, "%s@%s", user, variable_get(VAR_HOSTNAME));
132    }
133    af = variable_cmp(VAR_IPV6_ENABLE, "YES") ? AF_INET : AF_UNSPEC;
134    msgNotify("Logging in to %s@%s..", login_name, hostname);
135    if ((OpenConn = ftpLoginAf(hostname, af, login_name, password, FtpPort, isDebug(), &code)) == NULL) {
136	msgConfirm("Couldn't open FTP connection to %s:\n  %s.", hostname, ftpErrString(code));
137	goto punt;
138    }
139
140    ftpPassive(OpenConn, !strcmp(variable_get(VAR_FTP_STATE), "passive"));
141    ftpBinary(OpenConn);
142    if (dir && *dir != '\0') {
143	if ((i = ftpChdir(OpenConn, dir)) != 0) {
144	    if (i == 550)
145		msgConfirm("No such directory ftp://%s/%s\n"
146			   "please check your URL and try again.", hostname, dir);
147	    else
148		msgConfirm("FTP chdir to ftp://%s/%s returned error status:\n  %s.", hostname, dir, ftpErrString(i));
149	    goto punt;
150	}
151    }
152
153    /*
154     * Now that we've verified that the path we're given is ok, let's try to
155     * be a bit intelligent in locating the release we are looking for.  First
156     * off, if the release is specified as "__RELEASE" or "any", then just
157     * assume that the current directory is the one we want and give up.
158     */
159    rel = variable_get(VAR_RELNAME);
160    if (strcmp(rel, "__RELEASE") && strcmp(rel, "any")) {
161	/*
162	 * Ok, since we have a release variable, let's walk through the list
163	 * of directories looking for a release directory.  The first one to
164	 * match wins.  For each case, we chdir to ftp_dirs[fdir] first.  If
165	 * that fails, we skip to the next one.  Otherwise, we try to chdir to
166	 * rel.  If it succeeds we break out.  If it fails, then we go back to
167	 * the base directory and try again.  Lots of chdirs, but oh well. :)
168	 */
169	for (fdir = 0; ftp_dirs[fdir]; fdir++) {
170	    /* Avoid sending CWD . commands which confuse some ftp servers */
171	    if (strcmp(ftp_dirs[fdir], ".") &&
172		(ftpChdir(OpenConn, (char *)ftp_dirs[fdir]) != 0))
173		continue;
174	    if (ftpChdir(OpenConn, rel) == 0) {
175		ftpInitted = TRUE;
176		return TRUE;
177	    }
178	    else	/* reset to "root" dir for a fresh try */
179		ftpChdir(OpenConn, "/");
180	}
181
182	/*
183	 * If we get here, then all of the directories we tried failed, so
184	 * print out the error message and ask the user if they want to try
185	 * again.
186	 */
187	if (!msgYesNo("Warning:  Can't find the `%s' distribution on this\n"
188		      "FTP server.  You may need to visit a different server for\n"
189		      "the release you are trying to fetch or go to the Options\n"
190		      "menu and to set the release name to explicitly match what's\n"
191		      "available on %s (or set to \"any\").\n\n"
192		      "Would you like to select another FTP server?",
193		      rel, hostname)) {
194	    variable_unset(VAR_FTP_PATH);
195	    if (DITEM_STATUS(mediaSetFTP(NULL)) != DITEM_FAILURE)
196		goto try;
197	}
198    } else {
199	ftpInitted = TRUE;
200	return TRUE;
201    }
202
203punt:
204    ftpInitted = FALSE;
205    if (OpenConn != NULL) {
206	fclose(OpenConn);
207	OpenConn = NULL;
208    }
209    netDown(dev);
210    variable_unset(VAR_FTP_PATH);
211    return FALSE;
212}
213
214FILE *
215mediaGetFTP(Device *dev, char *file, Boolean probe)
216{
217    int nretries = 1;
218    FILE *fp;
219    char *try, buf[PATH_MAX];
220
221    if (!OpenConn) {
222	msgDebug("No FTP connection open, can't get file %s\n", file);
223	return NULL;
224    }
225
226    try = file;
227    while ((fp = ftpGet(OpenConn, try, 0)) == NULL) {
228	int ftperr = ftpErrno(OpenConn);
229
230	/* If a hard fail, try to "bounce" the ftp server to clear it */
231	if (ftperr != 550) {
232	    if (ftperr != 421)	/* Timeout? */
233		variable_unset(VAR_FTP_PATH);
234	    /* If we can't re-initialize, just forget it */
235	    DEVICE_SHUTDOWN(dev);
236	    if (!DEVICE_INIT(dev)) {
237		netDown(dev);
238		if (OpenConn) {
239		    fclose(OpenConn);
240		    OpenConn = NULL;
241		}
242		variable_unset(VAR_FTP_PATH);
243		return NULL;
244	    }
245	}
246	else if (probe)
247	    return NULL;
248	else {
249	    /* Try some alternatives */
250	    switch (nretries++) {
251	    case 1:
252		sprintf(buf, "releases/%s", file);
253		try = buf;
254		break;
255
256	    case 2:
257		sprintf(buf, "%s/%s", variable_get(VAR_RELNAME), file);
258		try = buf;
259		break;
260
261	    case 3:
262		sprintf(buf, "%s/releases/%s", variable_get(VAR_RELNAME), file);
263		try = buf;
264		break;
265
266	    case 4:
267		try = file;
268		break;
269	    }
270	}
271    }
272    return fp;
273}
274
275void
276mediaShutdownFTP(Device *dev)
277{
278    if (!ftpInitted)
279	return;
280
281    if (OpenConn != NULL) {
282	fclose(OpenConn);
283	OpenConn = NULL;
284    }
285    ftpInitted = FALSE;
286}
287