cb0cd746b5
from the restart point, not the end of the file (blush!). Submitted by: John-Mark Gurney <jmg@nike.efn.org>
416 lines
10 KiB
C
416 lines
10 KiB
C
/*-
|
|
* Copyright 1997 Massachusetts Institute of Technology
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software and
|
|
* its documentation for any purpose and without fee is hereby
|
|
* granted, provided that both the above copyright notice and this
|
|
* permission notice appear in all copies, that both the above
|
|
* copyright notice and this permission notice appear in all
|
|
* supporting documentation, and that the name of M.I.T. not be used
|
|
* in advertising or publicity pertaining to distribution of the
|
|
* software without specific, written prior permission. M.I.T. makes
|
|
* no representations about the suitability of this software for any
|
|
* purpose. It is provided "as is" without express or implied
|
|
* warranty.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''. M.I.T. DISCLAIMS
|
|
* ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE,
|
|
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT
|
|
* SHALL M.I.T. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
|
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
|
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*
|
|
* $Id: ftp.c,v 1.2 1997/02/05 19:59:12 wollman Exp $
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <err.h>
|
|
#include <errno.h>
|
|
#include <ftpio.h>
|
|
#include <limits.h>
|
|
#include <netdb.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sysexits.h>
|
|
#include <unistd.h>
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include "fetch.h"
|
|
|
|
struct ftp_state {
|
|
char *ftp_hostname;
|
|
char *ftp_user;
|
|
char *ftp_password;
|
|
char *ftp_remote_file;
|
|
unsigned ftp_port;
|
|
};
|
|
|
|
static int ftp_close(struct fetch_state *fs);
|
|
static int ftp_retrieve(struct fetch_state *fs);
|
|
static int ftp_parse(struct fetch_state *fs, const char *uri);
|
|
static int ftp_proxy_parse(struct fetch_state *fs, const char *uri);
|
|
|
|
struct uri_scheme ftp_scheme =
|
|
{ "ftp", ftp_parse, ftp_proxy_parse, "FTP_PROXY", "ftp,http" };
|
|
|
|
static int
|
|
ftp_parse(struct fetch_state *fs, const char *uri)
|
|
{
|
|
const char *p, *colon, *slash, *q;
|
|
char *hostname, *atsign;
|
|
unsigned port;
|
|
struct ftp_state *ftps;
|
|
|
|
p = uri + 4;
|
|
port = 0;
|
|
|
|
if (p[0] != '/' || p[1] != '/') {
|
|
warnx("`%s': invalid `ftp' URL", uri);
|
|
return EX_USAGE;
|
|
}
|
|
|
|
p += 2;
|
|
colon = strchr(p, ':');
|
|
slash = strchr(p, '/');
|
|
if (colon && slash && colon < slash)
|
|
q = colon;
|
|
else
|
|
q = slash;
|
|
if (q == 0) {
|
|
warnx("`%s': malformed `ftp' URL", uri);
|
|
return EX_USAGE;
|
|
}
|
|
hostname = alloca(q - p + 1);
|
|
hostname[0] = '\0';
|
|
strncat(hostname, p, q - p);
|
|
p = slash;
|
|
|
|
if (colon && colon + 1 != slash) {
|
|
unsigned long ul;
|
|
char *ep;
|
|
|
|
errno = 0;
|
|
ul = strtoul(colon + 1, &ep, 10);
|
|
if (ep != slash || ep == colon + 1 || errno != 0
|
|
|| ul < 1 || ul > 65534) {
|
|
warn("`%s': invalid port in URL", uri);
|
|
return EX_USAGE;
|
|
}
|
|
|
|
port = ul;
|
|
} else {
|
|
port = 21;
|
|
}
|
|
|
|
p = slash + 1;
|
|
|
|
ftps = safe_malloc(sizeof *ftps);
|
|
|
|
/*
|
|
* Now, we have a copy of the hostname in hostname, the specified port
|
|
* (or the default value) in port, and p points to the filename part
|
|
* of the URI. We just need to check for a user in the hostname,
|
|
* and then save all the bits in our state.
|
|
*/
|
|
atsign = strrchr(hostname, '@');
|
|
if (atsign) {
|
|
if (atsign[1] == '\0') {
|
|
warnx("`%s': malformed `ftp' hostname", hostname);
|
|
free(ftps);
|
|
return EX_USAGE;
|
|
}
|
|
|
|
*atsign = '\0';
|
|
ftps->ftp_user = percent_decode(hostname);
|
|
ftps->ftp_hostname = safe_strdup(atsign + 1);
|
|
} else {
|
|
ftps->ftp_user = 0;
|
|
ftps->ftp_hostname = safe_strdup(hostname);
|
|
ftps->ftp_port = port;
|
|
}
|
|
|
|
p = ftps->ftp_remote_file = percent_decode(p);
|
|
/* now p is the decoded version */
|
|
|
|
if (fs->fs_outputfile == 0) {
|
|
slash = strrchr(p, '/');
|
|
fs->fs_outputfile = slash ? slash + 1 : p;
|
|
}
|
|
|
|
ftps->ftp_password = getenv("FTP_PASSWORD");
|
|
if (ftps->ftp_password != 0) {
|
|
ftps->ftp_password = safe_strdup(ftps->ftp_password);
|
|
} else {
|
|
char *pw;
|
|
const char *logname;
|
|
char localhost[MAXHOSTNAMELEN];
|
|
|
|
logname = getlogin();
|
|
if (logname == 0)
|
|
logname = "root";
|
|
gethostname(localhost, sizeof localhost);
|
|
pw = safe_malloc(strlen(logname) + 1 + strlen(localhost) + 1);
|
|
strcpy(pw, logname);
|
|
strcat(pw, "@");
|
|
strcat(pw, localhost);
|
|
ftps->ftp_password = pw;
|
|
setenv("FTP_PASSWORD", pw, 0); /* cache the result */
|
|
}
|
|
|
|
if (ftps->ftp_user == 0) {
|
|
const char *user = getenv("FTP_LOGIN");
|
|
if (user != 0)
|
|
ftps->ftp_user = safe_strdup(user);
|
|
}
|
|
|
|
fs->fs_proto = ftps;
|
|
fs->fs_close = ftp_close;
|
|
fs->fs_retrieve = ftp_retrieve;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* The only URIs we can handle in the FTP proxy are FTP URLs.
|
|
* This makes it possible to take a few short cuts.
|
|
*/
|
|
static int
|
|
ftp_proxy_parse(struct fetch_state *fs, const char *uri)
|
|
{
|
|
int rv;
|
|
char *hostname;
|
|
char *port;
|
|
const char *user;
|
|
char *newpass;
|
|
unsigned portno;
|
|
struct ftp_state *ftps;
|
|
|
|
hostname = getenv("FTP_PROXY");
|
|
port = strchr(hostname, ':');
|
|
if (port == 0) {
|
|
portno = 21;
|
|
} else {
|
|
unsigned long ul;
|
|
char *ep;
|
|
|
|
/* All this to avoid modifying the environment. */
|
|
ep = alloca(strlen(hostname) + 1);
|
|
strcpy(ep, hostname);
|
|
port = ep + (port - hostname);
|
|
hostname = ep;
|
|
|
|
*port++ = '\0';
|
|
errno = 0;
|
|
ul = strtoul(port, &ep, 0);
|
|
if (*ep || !*port || errno != 0 || ul < 1 || ul > 65534) {
|
|
warnx("`%s': invalid port specification for FTP proxy",
|
|
port);
|
|
return EX_USAGE;
|
|
}
|
|
portno = ul;
|
|
}
|
|
|
|
/* ftp_parse() does most of the work; we can just fix things up */
|
|
rv = ftp_parse(fs, uri);
|
|
if (rv)
|
|
return rv;
|
|
/* Oops.. it got turned into a file: */
|
|
if (fs->fs_retrieve != ftp_retrieve) {
|
|
return 0;
|
|
}
|
|
|
|
ftps = fs->fs_proto;
|
|
if (ftps->ftp_port != 21) {
|
|
ftp_close(fs);
|
|
warnx("`%s': FTP proxy requires the use of the standard port",
|
|
uri);
|
|
return EX_USAGE;
|
|
}
|
|
|
|
ftps->ftp_port = portno;
|
|
user = ftps->ftp_user ? ftps->ftp_user : "anonymous";
|
|
newpass = safe_malloc(strlen(ftps->ftp_user
|
|
? ftps->ftp_user : "anonymous")
|
|
+ 1 + strlen(ftps->ftp_hostname) + 1);
|
|
|
|
strcpy(newpass, user);
|
|
strcat(newpass, "@");
|
|
strcpy(newpass, ftps->ftp_hostname);
|
|
free(ftps->ftp_hostname);
|
|
ftps->ftp_hostname = safe_strdup(hostname);
|
|
free(ftps->ftp_password);
|
|
ftps->ftp_password = newpass;
|
|
free(ftps->ftp_user);
|
|
ftps->ftp_user = getenv("FTP_PROXY_USER");
|
|
if (ftps->ftp_user)
|
|
ftps->ftp_user = safe_strdup(ftps->ftp_user);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
ftp_close(struct fetch_state *fs)
|
|
{
|
|
struct ftp_state *ftps = fs->fs_proto;
|
|
|
|
if (ftps->ftp_user)
|
|
free(ftps->ftp_user);
|
|
free(ftps->ftp_hostname);
|
|
free(ftps->ftp_password);
|
|
free(ftps->ftp_remote_file);
|
|
free(ftps);
|
|
fs->fs_proto = 0;
|
|
fs->fs_outputfile = 0;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
ftp_retrieve(struct fetch_state *fs)
|
|
{
|
|
struct ftp_state *ftps = fs->fs_proto;
|
|
FILE *ftp, *remote, *local;
|
|
int status;
|
|
off_t size;
|
|
off_t seekloc, wehave;
|
|
time_t modtime;
|
|
size_t readresult, writeresult;
|
|
|
|
ftp = ftpLogin(ftps->ftp_hostname,
|
|
(char *)(ftps->ftp_user ? ftps->ftp_user : "anonymous"),
|
|
/* XXX ^^^^ bad API */
|
|
ftps->ftp_password, 0, fs->fs_verbose > 1,
|
|
&status);
|
|
if (ftp == 0) {
|
|
warnx("%s: %s", ftps->ftp_hostname,
|
|
status ? ftpErrString(status) : hstrerror(h_errno));
|
|
return EX_IOERR;
|
|
}
|
|
ftpBinary(ftp);
|
|
ftpPassive(ftp, fs->fs_passive_mode);
|
|
size = ftpGetSize(ftp, ftps->ftp_remote_file);
|
|
modtime = ftpGetModtime(ftp, ftps->ftp_remote_file);
|
|
if (modtime <= 0) { /* xxx */
|
|
warnx("%s: cannot get remote modification time",
|
|
ftps->ftp_remote_file);
|
|
modtime = -1;
|
|
}
|
|
fs->fs_modtime = modtime;
|
|
seekloc = wehave = 0;
|
|
if (fs->fs_restart || fs->fs_mirror) {
|
|
struct stat stab;
|
|
|
|
if (fs->fs_outputfile[0] == '-'
|
|
&& fs->fs_outputfile[1] == '\0')
|
|
status = fstat(STDOUT_FILENO, &stab);
|
|
else
|
|
status = stat(fs->fs_outputfile, &stab);
|
|
if (status < 0) {
|
|
stab.st_mtime = -1;
|
|
stab.st_size = 0;
|
|
}
|
|
if (status == 0 && !S_ISREG(stab.st_mode)) {
|
|
fs->fs_restart = 0;
|
|
fs->fs_mirror = 0;
|
|
}
|
|
if (fs->fs_mirror && stab.st_size == size
|
|
&& modtime <= stab.st_mtime) {
|
|
fclose(ftp);
|
|
return 0;
|
|
}
|
|
if (fs->fs_restart) {
|
|
if (stab.st_size != 0 && stab.st_size < size)
|
|
seekloc = wehave = stab.st_size;
|
|
}
|
|
}
|
|
|
|
remote = ftpGet(ftp, ftps->ftp_remote_file, &seekloc);
|
|
if (remote == 0) {
|
|
if (ftpErrno(ftp)) {
|
|
warnx("%s: %s", ftps->ftp_hostname,
|
|
ftpErrString(ftpErrno(ftp)));
|
|
fclose(ftp);
|
|
return EX_IOERR;
|
|
} else {
|
|
warn("ftpGet");
|
|
return EX_OSERR;
|
|
}
|
|
}
|
|
|
|
if (fs->fs_outputfile[0] == '-' && fs->fs_outputfile[1] == '\0')
|
|
local = fopen("/dev/stdout", wehave ? "a" : "w");
|
|
else
|
|
local = fopen(fs->fs_outputfile, wehave ? "a" : "w");
|
|
if (local == 0) {
|
|
warn("%s", fs->fs_outputfile);
|
|
fclose(remote);
|
|
fclose(ftp);
|
|
return EX_OSERR;
|
|
}
|
|
|
|
if (fs->fs_timeout) {
|
|
char buf[sizeof("18446744073709551616")]; /* 2**64 */
|
|
snprintf(buf, sizeof buf, "%d", fs->fs_timeout);
|
|
setenv("FTP_TIMEOUT", buf, 1);
|
|
} else {
|
|
char *env = getenv("FTP_TIMEOUT");
|
|
char *ep;
|
|
unsigned long ul;
|
|
|
|
if (env) {
|
|
errno = 0;
|
|
ul = strtoul(env, &ep, 0);
|
|
if (*env && *ep && errno == 0 && ul <= INT_MAX)
|
|
fs->fs_timeout = ul;
|
|
else
|
|
warnx("`%s': invalid FTP timeout", env);
|
|
}
|
|
}
|
|
|
|
display(fs, size, wehave);
|
|
setup_sigalrm();
|
|
|
|
do {
|
|
char buf[BUFFER_SIZE];
|
|
|
|
alarm(fs->fs_timeout);
|
|
readresult = fread(buf, 1, sizeof buf, remote);
|
|
alarm(0);
|
|
if (readresult == 0)
|
|
break;
|
|
display(fs, size, readresult);
|
|
writeresult = fwrite(buf, 1, readresult, local);
|
|
} while (writeresult == readresult);
|
|
unsetup_sigalrm();
|
|
|
|
if (ferror(remote)) {
|
|
warn("reading remote file from %s", ftps->ftp_hostname);
|
|
fclose(local);
|
|
fclose(remote);
|
|
fclose(ftp);
|
|
rm(fs);
|
|
return EX_IOERR;
|
|
} else if(ferror(local)) {
|
|
warn("%s", fs->fs_outputfile);
|
|
fclose(local);
|
|
fclose(remote);
|
|
fclose(ftp);
|
|
rm(fs);
|
|
return EX_IOERR;
|
|
}
|
|
|
|
fclose(local);
|
|
fclose(remote);
|
|
fclose(ftp);
|
|
display(fs, size, -1);
|
|
adjmodtime(fs);
|
|
return 0;
|
|
}
|