mirror of
https://git.savannah.gnu.org/git/inetutils.git
synced 2026-01-12 00:19:39 +08:00
2057 lines
44 KiB
C
2057 lines
44 KiB
C
/*
|
|
Copyright (C) 1995-2023 Free Software Foundation, Inc.
|
|
|
|
This file is part of GNU Inetutils.
|
|
|
|
GNU Inetutils is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or (at
|
|
your option) any later version.
|
|
|
|
GNU Inetutils is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see `http://www.gnu.org/licenses/'. */
|
|
|
|
/*
|
|
* Copyright (c) 1985, 1989, 1993, 1994
|
|
* The Regents of the University of California. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. Neither the name of the University nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS 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.
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/time.h>
|
|
#include <time.h>
|
|
#include <sys/file.h>
|
|
|
|
#include <netinet/in.h>
|
|
#ifdef HAVE_NETINET_IN_SYSTM_H
|
|
# include <netinet/in_systm.h>
|
|
#endif
|
|
#ifdef HAVE_NETINET_IP_H
|
|
# include <netinet/ip.h>
|
|
#endif
|
|
#include <arpa/inet.h>
|
|
#include <arpa/ftp.h>
|
|
#include <arpa/telnet.h>
|
|
|
|
#include <ctype.h>
|
|
#include <error.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <netdb.h>
|
|
#include <pwd.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdint.h> /* intmax_t */
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <stdarg.h>
|
|
#include <sys/select.h>
|
|
|
|
#if defined HAVE_IDN2_H && defined HAVE_IDN2
|
|
# include <idn2.h>
|
|
#elif defined HAVE_IDNA_H
|
|
# include <idna.h>
|
|
#endif
|
|
|
|
#include "ftp_var.h"
|
|
#include "attribute.h"
|
|
|
|
#if !HAVE_DECL_FCLOSE
|
|
/* Some systems don't declare fclose in <stdio.h>, so do it ourselves. */
|
|
extern int fclose (FILE *);
|
|
#endif
|
|
|
|
#if !HAVE_DECL_PCLOSE
|
|
/* Some systems don't declare pclose in <stdio.h>, so do it ourselves. */
|
|
extern int pclose (FILE *);
|
|
#endif
|
|
|
|
int data = -1;
|
|
int abrtflag = 0;
|
|
jmp_buf ptabort;
|
|
int ptabflg;
|
|
int ptflag = 0;
|
|
off_t restart_point = 0;
|
|
|
|
struct sockaddr_storage myctladdr;
|
|
struct sockaddr_storage hisctladdr;
|
|
struct sockaddr_storage data_addr;
|
|
socklen_t ctladdrlen; /* Applies to all addresses. */
|
|
|
|
/* For temporary resolving: hookup() and initconn()/noport. */
|
|
static char ia[INET6_ADDRSTRLEN];
|
|
static char portstr[10];
|
|
|
|
FILE *cin, *cout;
|
|
|
|
#if ! defined FTP_CONNECT_TIMEOUT || FTP_CONNECT_TIMEOUT < 1
|
|
# define FTP_CONNECT_TIMEOUT 5
|
|
#endif
|
|
|
|
char *
|
|
hookup (char *host, int port)
|
|
{
|
|
struct addrinfo hints, *ai = NULL, *res = NULL;
|
|
struct timeval timeout;
|
|
int status, again = 0;
|
|
int s, tos;
|
|
socklen_t len;
|
|
static char hostnamebuf[80];
|
|
char *rhost, *p;
|
|
|
|
p = strrchr (host, '@');
|
|
if (p && p != host && isprint (p[1]))
|
|
host = ++p;
|
|
|
|
#if defined HAVE_IDN || defined HAVE_IDN2
|
|
status = idna_to_ascii_lz (host, &rhost, 0);
|
|
if (status)
|
|
{
|
|
error (0, 0, "%s: %s", host, idna_strerror (status));
|
|
code = -1;
|
|
return ((char *) 0);
|
|
}
|
|
#else /* !HAVE_IDN && !HAVE_IDN2 */
|
|
rhost = strdup (host);
|
|
#endif
|
|
|
|
snprintf (portstr, sizeof (portstr) - 1, "%u", port);
|
|
memset (&hisctladdr, 0, sizeof (hisctladdr));
|
|
memset (&hints, 0, sizeof (hints));
|
|
|
|
hints.ai_family = usefamily;
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
hints.ai_flags = AI_CANONNAME;
|
|
#ifdef AI_IDN
|
|
hints.ai_flags |= AI_IDN;
|
|
#endif
|
|
#ifdef AI_CANONIDN
|
|
hints.ai_flags |= AI_CANONIDN;
|
|
#endif
|
|
|
|
status = getaddrinfo (rhost, portstr, &hints, &res);
|
|
if (status)
|
|
{
|
|
error (0, 0, "%s: %s", rhost, gai_strerror (status));
|
|
code = -1;
|
|
free (rhost);
|
|
return ((char *) 0);
|
|
}
|
|
|
|
if (res->ai_canonname)
|
|
strncpy (hostnamebuf, res->ai_canonname, sizeof (hostnamebuf) - 1);
|
|
else
|
|
strncpy (hostnamebuf, rhost, sizeof (hostnamebuf) - 1);
|
|
hostnamebuf[sizeof (hostnamebuf) - 1] = 0;
|
|
|
|
hostname = hostnamebuf;
|
|
free (rhost);
|
|
|
|
for (ai = res; ai != NULL; ai = ai->ai_next, ++again)
|
|
{
|
|
if (again)
|
|
{
|
|
getnameinfo (ai->ai_addr, ai->ai_addrlen, ia, sizeof (ia),
|
|
NULL, 0, NI_NUMERICHOST);
|
|
error (0, 0, "Trying %s ...", ia);
|
|
}
|
|
|
|
s = socket (ai->ai_family, ai->ai_socktype, ai->ai_protocol);
|
|
if (s < 0)
|
|
continue;
|
|
|
|
timeout.tv_sec = FTP_CONNECT_TIMEOUT;
|
|
timeout.tv_usec = 0;
|
|
if (setsockopt (s, SOL_SOCKET, SO_SNDTIMEO, &timeout,
|
|
sizeof (timeout)) < 0 && debug)
|
|
error (0, errno, "setsockopt (SO_SNDTIMEO)");
|
|
|
|
if (connect (s, ai->ai_addr, ai->ai_addrlen) < 0)
|
|
{
|
|
int oerrno = (errno != EINPROGRESS) ? errno : ETIMEDOUT;
|
|
|
|
getnameinfo (ai->ai_addr, ai->ai_addrlen, ia, sizeof (ia),
|
|
NULL, 0, NI_NUMERICHOST);
|
|
error (0, oerrno, "connect to address %s", ia);
|
|
close (s);
|
|
s = -1;
|
|
continue;
|
|
}
|
|
|
|
/* A standing connection is found: register the address. */
|
|
timeout.tv_sec = 0;
|
|
timeout.tv_usec = 0;
|
|
(void) setsockopt (s, SOL_SOCKET, SO_SNDTIMEO, &timeout,
|
|
sizeof (timeout));
|
|
|
|
ctladdrlen = ai->ai_addrlen;
|
|
memmove ((caddr_t) & hisctladdr, ai->ai_addr, ai->ai_addrlen);
|
|
break;
|
|
} /* for (ai = ai->ai_next) */
|
|
|
|
if (res)
|
|
freeaddrinfo (res);
|
|
|
|
if (ai == NULL)
|
|
{
|
|
error (0, 0, "no response from host");
|
|
code = -1;
|
|
goto bad;
|
|
}
|
|
|
|
len = sizeof (myctladdr);
|
|
if (getsockname (s, (struct sockaddr *) &myctladdr, &len) < 0)
|
|
{
|
|
error (0, errno, "getsockname");
|
|
code = -1;
|
|
goto bad;
|
|
}
|
|
|
|
#if defined IP_TOS && defined IPPROTO_IP && defined IPTOS_LOWDELAY
|
|
tos = IPTOS_LOWDELAY;
|
|
if (myctladdr.ss_family == AF_INET &&
|
|
setsockopt (s, IPPROTO_IP, IP_TOS, (char *) &tos, sizeof (int)) < 0)
|
|
error (0, errno, "setsockopt TOS (ignored)");
|
|
#endif
|
|
|
|
cin = fdopen (s, "r");
|
|
/* dup(s) is for sake of stdio implementations who do not
|
|
allow two fdopen's on the same file-descriptor */
|
|
cout = fdopen (dup (s), "w");
|
|
if (cin == NULL || cout == NULL)
|
|
{
|
|
error (0, 0, "fdopen failed.");
|
|
if (cin)
|
|
fclose (cin);
|
|
if (cout)
|
|
fclose (cout);
|
|
code = -1;
|
|
goto bad;
|
|
}
|
|
if (verbose)
|
|
printf ("Connected to %s.\n", hostname);
|
|
if (getreply (0) > 2)
|
|
{ /* read startup message from server */
|
|
if (cin)
|
|
fclose (cin);
|
|
if (cout)
|
|
fclose (cout);
|
|
code = -1;
|
|
goto bad;
|
|
}
|
|
|
|
#ifdef SO_OOBINLINE
|
|
{
|
|
int on = 1;
|
|
|
|
if (setsockopt (s, SOL_SOCKET, SO_OOBINLINE, (char *) &on, sizeof (on))
|
|
< 0 && debug)
|
|
{
|
|
error (0, errno, "setsockopt");
|
|
}
|
|
}
|
|
#endif /* SO_OOBINLINE */
|
|
|
|
return (hostname);
|
|
bad:
|
|
close (s);
|
|
return ((char *) 0);
|
|
}
|
|
|
|
int
|
|
login (char *host)
|
|
{
|
|
#if !HAVE_DECL_GETPASS
|
|
extern char *getpass ();
|
|
#endif
|
|
char tmp[80];
|
|
char *user, *pass, *acct, *p;
|
|
int n, aflag = 0;
|
|
|
|
user = pass = acct = 0;
|
|
if (remote_userpass (host, &user, &pass, &acct) < 0)
|
|
{
|
|
code = -1;
|
|
return (0);
|
|
}
|
|
|
|
p = strrchr (host, '@');
|
|
if (user == NULL && p && p != host && isprint (p[1]))
|
|
{
|
|
*p = 0;
|
|
user = host;
|
|
host = ++p;
|
|
}
|
|
|
|
while (user == NULL)
|
|
{
|
|
char *myname = getlogin ();
|
|
|
|
if (myname == NULL)
|
|
{
|
|
struct passwd *pp = getpwuid (getuid ());
|
|
|
|
if (pp != NULL)
|
|
myname = pp->pw_name;
|
|
}
|
|
if (myname)
|
|
printf ("Name (%s:%s): ", host, myname);
|
|
else
|
|
printf ("Name (%s): ", host);
|
|
if (fgets (tmp, sizeof (tmp) - 1, stdin))
|
|
{
|
|
/* If the user presses return immediately, we get "\n".
|
|
* In all other cases, the assignment is a no-op,
|
|
* and is always well defined thanks to fgets().
|
|
*/
|
|
tmp[strlen (tmp) - 1] = '\0';
|
|
}
|
|
else
|
|
*tmp = '\0'; /* Ctrl-D received. */
|
|
if (*tmp == '\0')
|
|
user = myname;
|
|
else
|
|
user = tmp;
|
|
}
|
|
n = command ("USER %s", user);
|
|
if (n == CONTINUE)
|
|
{
|
|
/* Is this a case of challenge-response?
|
|
* RFC 2228 stipulates code 336 for this.
|
|
* Suppress the message in verbose mode,
|
|
* since it has already been displayed.
|
|
*/
|
|
if (code == 336 && !verbose)
|
|
printf ("%s\n", reply_string + strlen ("336 "));
|
|
/* In addition, any password given on the
|
|
* command line is irrelevant, so ignore it.
|
|
*/
|
|
if (pass == NULL || code == 336)
|
|
pass = getpass ("Password: ");
|
|
n = command ("PASS %s", pass);
|
|
if (pass)
|
|
memset (pass, 0, strlen (pass));
|
|
}
|
|
if (n == CONTINUE)
|
|
{
|
|
aflag++;
|
|
acct = getpass ("Account: ");
|
|
n = command ("ACCT %s", acct);
|
|
if (acct)
|
|
memset (acct, 0, strlen (acct));
|
|
}
|
|
if (n != COMPLETE)
|
|
{
|
|
error (0, 0, "Login failed.");
|
|
return (0);
|
|
}
|
|
if (!aflag && acct != NULL)
|
|
{
|
|
command ("ACCT %s", acct);
|
|
memset (acct, 0, strlen (acct));
|
|
}
|
|
if (proxy)
|
|
return (1);
|
|
for (n = 0; n < macnum; ++n)
|
|
{
|
|
if (!strcmp ("init", macros[n].mac_name))
|
|
{
|
|
free (line);
|
|
line = calloc (MAXLINE, sizeof (*line));
|
|
linelen = MAXLINE;
|
|
if (!line)
|
|
quit (0, 0);
|
|
|
|
/* Simulate input of the macro 'init'. */
|
|
strcpy (line, "$init");
|
|
makeargv ();
|
|
domacro (margc, margv);
|
|
break;
|
|
}
|
|
}
|
|
return (1);
|
|
}
|
|
|
|
void
|
|
cmdabort (int sig MAYBE_UNUSED)
|
|
{
|
|
|
|
printf ("\n");
|
|
fflush (stdout);
|
|
abrtflag++;
|
|
if (ptflag)
|
|
longjmp (ptabort, 1);
|
|
}
|
|
|
|
int
|
|
command (const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
int r;
|
|
sighandler_t oldintr;
|
|
|
|
abrtflag = 0;
|
|
if (debug)
|
|
{
|
|
printf ("---> ");
|
|
va_start (ap, fmt);
|
|
if (strncmp ("PASS ", fmt, 5) == 0)
|
|
printf ("PASS XXXX");
|
|
else if (strncmp ("ACCT ", fmt, 5) == 0)
|
|
printf ("ACCT XXXX");
|
|
else
|
|
vfprintf (stdout, fmt, ap);
|
|
va_end (ap);
|
|
printf ("\n");
|
|
fflush (stdout);
|
|
}
|
|
if (cout == NULL)
|
|
{
|
|
error (0, 0, "No control connection for command");
|
|
code = -1;
|
|
return (0);
|
|
}
|
|
oldintr = signal (SIGINT, cmdabort);
|
|
/* Under weird circumstances, we get a SIGPIPE from fflush(). */
|
|
signal (SIGPIPE, SIG_IGN);
|
|
va_start (ap, fmt);
|
|
vfprintf (cout, fmt, ap);
|
|
va_end (ap);
|
|
fprintf (cout, "\r\n");
|
|
fflush (cout);
|
|
cpend = 1;
|
|
r = getreply (!strcmp (fmt, "QUIT"));
|
|
if (abrtflag && oldintr != SIG_IGN)
|
|
(*oldintr) (SIGINT);
|
|
signal (SIGINT, oldintr);
|
|
signal (SIGPIPE, SIG_DFL);
|
|
return (r);
|
|
}
|
|
|
|
char reply_string[BUFSIZ]; /* last line of previous reply */
|
|
|
|
int
|
|
getreply (int expecteof)
|
|
{
|
|
int c, n;
|
|
int dig;
|
|
int originalcode = 0, continuation = 0;
|
|
sighandler_t oldintr;
|
|
int pflag = 0;
|
|
char *cp, *pt = pasv;
|
|
|
|
oldintr = signal (SIGINT, cmdabort);
|
|
for (;;)
|
|
{
|
|
dig = n = code = 0;
|
|
cp = reply_string;
|
|
while ((c = getc (cin)) != '\n')
|
|
{
|
|
if (c == IAC)
|
|
{ /* handle telnet commands */
|
|
switch (c = getc (cin))
|
|
{
|
|
case WILL:
|
|
case WONT:
|
|
c = getc (cin);
|
|
fprintf (cout, "%c%c%c", IAC, DONT, c);
|
|
fflush (cout);
|
|
break;
|
|
case DO:
|
|
case DONT:
|
|
c = getc (cin);
|
|
fprintf (cout, "%c%c%c", IAC, WONT, c);
|
|
fflush (cout);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
dig++;
|
|
if (c == EOF)
|
|
{
|
|
if (expecteof)
|
|
{
|
|
signal (SIGINT, oldintr);
|
|
code = 221;
|
|
return (0);
|
|
}
|
|
lostpeer (0);
|
|
if (verbose)
|
|
{
|
|
printf
|
|
("421 Service not available, remote server has closed connection\n");
|
|
fflush (stdout);
|
|
}
|
|
code = 421;
|
|
return (TRANSIENT);
|
|
}
|
|
if (c != '\r' && (verbose > 0 ||
|
|
(verbose > -1 && n == ERROR && dig > 4)))
|
|
{
|
|
if (proxflag && (dig == 1 || (dig == 5 && verbose == 0)))
|
|
printf ("%s:", hostname);
|
|
putchar (c);
|
|
}
|
|
if (dig < 4 && isdigit (c))
|
|
code = code * 10 + (c - '0');
|
|
if (!pflag && (code == 227 || code == 228 || code == 229)) /* PASV || LPSV || EPSV */
|
|
pflag = 1;
|
|
if (dig > 4 && pflag == 1 && isdigit (c))
|
|
pflag = 2;
|
|
if (pflag == 2)
|
|
{
|
|
if (c != '\r' && c != ')')
|
|
{
|
|
if (pt < &pasv[sizeof (pasv) - 1])
|
|
*pt++ = c;
|
|
}
|
|
else
|
|
{
|
|
*pt = '\0';
|
|
pflag = 3;
|
|
}
|
|
}
|
|
if (dig == 4 && c == '-')
|
|
{
|
|
if (continuation)
|
|
code = 0;
|
|
continuation++;
|
|
}
|
|
if (n == 0)
|
|
n = c - '0'; /* Extract ARPA's reply code. */
|
|
if (cp < &reply_string[sizeof (reply_string) - 1])
|
|
*cp++ = c;
|
|
}
|
|
if (verbose > 0 || (verbose > -1 && n == ERROR))
|
|
{
|
|
putchar (c);
|
|
fflush (stdout);
|
|
}
|
|
if (continuation && code != originalcode)
|
|
{
|
|
if (originalcode == 0)
|
|
originalcode = code;
|
|
continue;
|
|
}
|
|
*cp = '\0';
|
|
if (n != PRELIM)
|
|
cpend = 0;
|
|
signal (SIGINT, oldintr);
|
|
if (code == 421 || originalcode == 421)
|
|
lostpeer (0);
|
|
if (abrtflag && oldintr != cmdabort && oldintr != SIG_IGN)
|
|
(*oldintr) (SIGINT);
|
|
return n;
|
|
}
|
|
}
|
|
|
|
int
|
|
empty (fd_set * mask, int sec)
|
|
{
|
|
struct timeval t;
|
|
|
|
t.tv_sec = (long) sec;
|
|
t.tv_usec = 0;
|
|
return (select (32, mask, (fd_set *) 0, (fd_set *) 0, &t));
|
|
}
|
|
|
|
jmp_buf sendabort;
|
|
|
|
void
|
|
abortsend (int sig MAYBE_UNUSED)
|
|
{
|
|
|
|
mflag = 0;
|
|
abrtflag = 0;
|
|
printf ("\nsend aborted\nwaiting for remote to finish abort\n");
|
|
fflush (stdout);
|
|
longjmp (sendabort, 1);
|
|
}
|
|
|
|
void
|
|
sendrequest (char *cmd, char *local, char *remote, int printnames)
|
|
{
|
|
struct stat st;
|
|
struct timeval start, stop;
|
|
int c, d;
|
|
FILE *fin, *dout = 0, *popen (const char *, const char *);
|
|
int (*closefunc) (FILE *);
|
|
sighandler_t oldintr, oldintp;
|
|
long long bytes = 0, local_hashbytes = hashbytes;
|
|
char *lmode, *bufp;
|
|
int blksize = BUFSIZ;
|
|
static int bufsize = 0;
|
|
static char *buf;
|
|
|
|
if (verbose && printnames)
|
|
{
|
|
if (local && *local != '-')
|
|
printf ("local: %s ", local);
|
|
if (remote)
|
|
printf ("remote: %s\n", remote);
|
|
}
|
|
if (proxy)
|
|
{
|
|
proxtrans (cmd, local, remote);
|
|
return;
|
|
}
|
|
if (curtype != type)
|
|
changetype (type, 0);
|
|
closefunc = NULL;
|
|
oldintr = NULL;
|
|
oldintp = NULL;
|
|
lmode = "w";
|
|
if (setjmp (sendabort))
|
|
{
|
|
while (cpend)
|
|
{
|
|
getreply (0);
|
|
}
|
|
if (data >= 0)
|
|
{
|
|
close (data);
|
|
data = -1;
|
|
}
|
|
if (oldintr)
|
|
signal (SIGINT, oldintr);
|
|
if (oldintp)
|
|
signal (SIGPIPE, oldintp);
|
|
code = -1;
|
|
return;
|
|
}
|
|
oldintr = signal (SIGINT, abortsend);
|
|
if (strcmp (local, "-") == 0)
|
|
fin = stdin;
|
|
else if (*local == '|')
|
|
{
|
|
oldintp = signal (SIGPIPE, SIG_IGN);
|
|
fin = popen (local + 1, "r");
|
|
if (fin == NULL)
|
|
{
|
|
error (0, errno, "%s", local + 1);
|
|
signal (SIGINT, oldintr);
|
|
signal (SIGPIPE, oldintp);
|
|
code = -1;
|
|
return;
|
|
}
|
|
closefunc = pclose;
|
|
}
|
|
else
|
|
{
|
|
fin = fopen (local, "r");
|
|
if (fin == NULL)
|
|
{
|
|
error (0, errno, "local: %s", local);
|
|
signal (SIGINT, oldintr);
|
|
code = -1;
|
|
return;
|
|
}
|
|
closefunc = fclose;
|
|
if (fstat (fileno (fin), &st) < 0 || (st.st_mode & S_IFMT) != S_IFREG)
|
|
{
|
|
fprintf (stdout, "%s: not a plain file.\n", local);
|
|
signal (SIGINT, oldintr);
|
|
fclose (fin);
|
|
code = -1;
|
|
return;
|
|
}
|
|
blksize = st.st_blksize;
|
|
}
|
|
if (initconn ())
|
|
{
|
|
signal (SIGINT, oldintr);
|
|
if (oldintp)
|
|
signal (SIGPIPE, oldintp);
|
|
code = -1;
|
|
if (closefunc != NULL)
|
|
(*closefunc) (fin);
|
|
return;
|
|
}
|
|
if (setjmp (sendabort))
|
|
goto abort;
|
|
|
|
if (restart_point &&
|
|
(strcmp (cmd, "STOR") == 0 || strcmp (cmd, "APPE") == 0))
|
|
{
|
|
off_t rc;
|
|
|
|
switch (curtype)
|
|
{
|
|
case TYPE_A:
|
|
rc = fseeko (fin, restart_point, SEEK_SET);
|
|
break;
|
|
case TYPE_I:
|
|
case TYPE_L:
|
|
rc = lseek (fileno (fin), restart_point, SEEK_SET);
|
|
break;
|
|
}
|
|
if (rc < 0)
|
|
{
|
|
(void) command ("ABOR");
|
|
getreply (0);
|
|
error (0, errno, "local: %s", local);
|
|
restart_point = 0;
|
|
if (closefunc != NULL)
|
|
(*closefunc) (fin);
|
|
return;
|
|
}
|
|
if (command ("REST %jd", (intmax_t) restart_point) != CONTINUE)
|
|
{
|
|
restart_point = 0;
|
|
if (closefunc != NULL)
|
|
(*closefunc) (fin);
|
|
return;
|
|
}
|
|
restart_point = 0;
|
|
lmode = "r+w";
|
|
}
|
|
if (remote)
|
|
{
|
|
if (command ("%s %s", cmd, remote) != PRELIM)
|
|
{
|
|
signal (SIGINT, oldintr);
|
|
if (oldintp)
|
|
signal (SIGPIPE, oldintp);
|
|
if (closefunc != NULL)
|
|
(*closefunc) (fin);
|
|
return;
|
|
}
|
|
}
|
|
else if (command ("%s", cmd) != PRELIM)
|
|
{
|
|
signal (SIGINT, oldintr);
|
|
if (oldintp)
|
|
signal (SIGPIPE, oldintp);
|
|
if (closefunc != NULL)
|
|
(*closefunc) (fin);
|
|
return;
|
|
}
|
|
dout = dataconn (lmode);
|
|
if (dout == NULL)
|
|
goto abort;
|
|
|
|
if (blksize > bufsize)
|
|
{
|
|
free (buf);
|
|
buf = malloc ((unsigned) blksize);
|
|
if (buf == NULL)
|
|
{
|
|
error (0, errno, "malloc");
|
|
bufsize = 0;
|
|
goto abort;
|
|
}
|
|
bufsize = blksize;
|
|
}
|
|
|
|
gettimeofday (&start, (struct timezone *) 0);
|
|
oldintp = signal (SIGPIPE, SIG_IGN);
|
|
switch (curtype)
|
|
{
|
|
|
|
case TYPE_I:
|
|
case TYPE_L:
|
|
errno = d = 0;
|
|
while ((c = read (fileno (fin), buf, bufsize)) > 0)
|
|
{
|
|
bytes += c;
|
|
for (bufp = buf; c > 0; c -= d, bufp += d)
|
|
if ((d = write (fileno (dout), bufp, c)) <= 0)
|
|
break;
|
|
if (hash)
|
|
{
|
|
while (bytes >= local_hashbytes)
|
|
{
|
|
putchar ('#');
|
|
local_hashbytes += hashbytes;
|
|
}
|
|
fflush (stdout);
|
|
}
|
|
}
|
|
if (hash && bytes > 0)
|
|
{
|
|
if (bytes < local_hashbytes)
|
|
putchar ('#');
|
|
putchar ('\n');
|
|
fflush (stdout);
|
|
}
|
|
if (c < 0)
|
|
error (0, errno, "local: %s", local);
|
|
if (d < 0)
|
|
{
|
|
if (errno != EPIPE)
|
|
error (0, errno, "netout");
|
|
bytes = -1;
|
|
}
|
|
break;
|
|
|
|
case TYPE_A:
|
|
while ((c = getc (fin)) != EOF)
|
|
{
|
|
if (c == '\n')
|
|
{
|
|
while (hash && (bytes >= local_hashbytes))
|
|
{
|
|
putchar ('#');
|
|
fflush (stdout);
|
|
local_hashbytes += hashbytes;
|
|
}
|
|
if (ferror (dout))
|
|
break;
|
|
putc ('\r', dout);
|
|
bytes++;
|
|
}
|
|
putc (c, dout);
|
|
bytes++;
|
|
/* if (c == '\r') { */
|
|
/* putc('\0', dout); // this violates rfc */
|
|
/* bytes++; */
|
|
/* } */
|
|
}
|
|
if (hash)
|
|
{
|
|
if (bytes < local_hashbytes)
|
|
putchar ('#');
|
|
putchar ('\n');
|
|
fflush (stdout);
|
|
}
|
|
if (ferror (fin))
|
|
error (0, errno, "local: %s", local);
|
|
if (ferror (dout))
|
|
{
|
|
if (errno != EPIPE)
|
|
error (0, errno, "netout");
|
|
bytes = -1;
|
|
}
|
|
break;
|
|
}
|
|
if (closefunc != NULL)
|
|
(*closefunc) (fin);
|
|
fclose (dout);
|
|
gettimeofday (&stop, (struct timezone *) 0);
|
|
getreply (0);
|
|
signal (SIGINT, oldintr);
|
|
if (oldintp)
|
|
signal (SIGPIPE, oldintp);
|
|
if (bytes > 0)
|
|
ptransfer ("sent", bytes, &start, &stop);
|
|
return;
|
|
abort:
|
|
signal (SIGINT, oldintr);
|
|
if (oldintp)
|
|
signal (SIGPIPE, oldintp);
|
|
if (!cpend)
|
|
{
|
|
code = -1;
|
|
return;
|
|
}
|
|
if (data >= 0)
|
|
{
|
|
close (data);
|
|
data = -1;
|
|
}
|
|
if (dout)
|
|
fclose (dout);
|
|
getreply (0);
|
|
code = -1;
|
|
if (closefunc != NULL && fin != NULL)
|
|
(*closefunc) (fin);
|
|
gettimeofday (&stop, (struct timezone *) 0);
|
|
if (bytes > 0)
|
|
ptransfer ("sent", bytes, &start, &stop);
|
|
}
|
|
|
|
jmp_buf recvabort;
|
|
|
|
void
|
|
abortrecv (int sig MAYBE_UNUSED)
|
|
{
|
|
|
|
mflag = 0;
|
|
abrtflag = 0;
|
|
printf ("\nreceive aborted\nwaiting for remote to finish abort\n");
|
|
fflush (stdout);
|
|
longjmp (recvabort, 1);
|
|
}
|
|
|
|
void
|
|
recvrequest (char *cmd, char *local, char *remote, char *lmode,
|
|
int printnames)
|
|
{
|
|
FILE *fout, *din = 0;
|
|
int (*closefunc) (FILE *);
|
|
sighandler_t oldintr, oldintp;
|
|
int c, d, is_retr, tcrflag, bare_lfs = 0;
|
|
int blksize = BUFSIZ;
|
|
static int bufsize = 0;
|
|
static char *buf;
|
|
long long bytes = 0, local_hashbytes = hashbytes;
|
|
struct timeval start, stop;
|
|
|
|
is_retr = strcmp (cmd, "RETR") == 0;
|
|
if (is_retr && verbose && printnames)
|
|
{
|
|
if (local && *local != '-')
|
|
printf ("local: %s ", local);
|
|
if (remote)
|
|
printf ("remote: %s\n", remote);
|
|
}
|
|
if (proxy && is_retr)
|
|
{
|
|
proxtrans (cmd, local, remote);
|
|
return;
|
|
}
|
|
closefunc = NULL;
|
|
oldintr = NULL;
|
|
oldintp = NULL;
|
|
tcrflag = !crflag && is_retr;
|
|
if (setjmp (recvabort))
|
|
{
|
|
while (cpend)
|
|
{
|
|
getreply (0);
|
|
}
|
|
if (data >= 0)
|
|
{
|
|
close (data);
|
|
data = -1;
|
|
}
|
|
if (oldintr)
|
|
signal (SIGINT, oldintr);
|
|
code = -1;
|
|
return;
|
|
}
|
|
oldintr = signal (SIGINT, abortrecv);
|
|
if (strcmp (local, "-") && *local != '|')
|
|
{
|
|
if (runique && (local = gunique (local)) == NULL)
|
|
{
|
|
signal (SIGINT, oldintr);
|
|
code = -1;
|
|
return;
|
|
}
|
|
}
|
|
if (!is_retr)
|
|
{
|
|
if (curtype != TYPE_A)
|
|
changetype (TYPE_A, 0);
|
|
}
|
|
else if (curtype != type)
|
|
changetype (type, 0);
|
|
if (initconn ())
|
|
{
|
|
signal (SIGINT, oldintr);
|
|
code = -1;
|
|
return;
|
|
}
|
|
if (setjmp (recvabort))
|
|
goto abort;
|
|
if (is_retr && restart_point &&
|
|
command ("REST %jd", (intmax_t) restart_point) != CONTINUE)
|
|
return;
|
|
if (remote)
|
|
{
|
|
if (command ("%s %s", cmd, remote) != PRELIM)
|
|
{
|
|
signal (SIGINT, oldintr);
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (command ("%s", cmd) != PRELIM)
|
|
{
|
|
signal (SIGINT, oldintr);
|
|
return;
|
|
}
|
|
}
|
|
din = dataconn ("r");
|
|
if (din == NULL)
|
|
goto abort;
|
|
|
|
if (strcmp (local, "-") == 0)
|
|
fout = stdout;
|
|
else if (*local == '|')
|
|
{
|
|
oldintp = signal (SIGPIPE, SIG_IGN);
|
|
fout = popen (local + 1, "w");
|
|
if (fout == NULL)
|
|
{
|
|
error (0, errno, "%s", local + 1);
|
|
goto abort;
|
|
}
|
|
closefunc = pclose;
|
|
}
|
|
else
|
|
{
|
|
struct stat st;
|
|
|
|
fout = fopen (local, lmode);
|
|
if (fout == NULL || fstat (fileno (fout), &st) < 0)
|
|
{
|
|
error (0, errno, "local: %s", local);
|
|
goto abort;
|
|
}
|
|
closefunc = fclose;
|
|
blksize = st.st_blksize;
|
|
}
|
|
|
|
if (blksize > bufsize)
|
|
{
|
|
free (buf);
|
|
buf = malloc ((unsigned) blksize);
|
|
if (buf == NULL)
|
|
{
|
|
error (0, errno, "malloc");
|
|
bufsize = 0;
|
|
goto abort;
|
|
}
|
|
bufsize = blksize;
|
|
}
|
|
|
|
gettimeofday (&start, (struct timezone *) 0);
|
|
switch (curtype)
|
|
{
|
|
|
|
case TYPE_I:
|
|
case TYPE_L:
|
|
if (restart_point && lseek (fileno (fout), restart_point, SEEK_SET) < 0)
|
|
{
|
|
error (0, errno, "local: %s", local);
|
|
if (closefunc != NULL)
|
|
(*closefunc) (fout);
|
|
return;
|
|
}
|
|
errno = d = 0;
|
|
while ((c = read (fileno (din), buf, bufsize)) > 0)
|
|
{
|
|
if ((d = write (fileno (fout), buf, c)) != c)
|
|
break;
|
|
bytes += c;
|
|
if (hash)
|
|
{
|
|
while (bytes >= local_hashbytes)
|
|
{
|
|
putchar ('#');
|
|
local_hashbytes += hashbytes;
|
|
}
|
|
fflush (stdout);
|
|
}
|
|
}
|
|
|
|
if (hash && bytes > 0)
|
|
{
|
|
if (bytes < local_hashbytes)
|
|
putchar ('#');
|
|
putchar ('\n');
|
|
fflush (stdout);
|
|
}
|
|
if (c < 0)
|
|
{
|
|
if (errno != EPIPE)
|
|
error (0, errno, "netin");
|
|
bytes = -1;
|
|
}
|
|
if (d < c)
|
|
{
|
|
if (d < 0)
|
|
error (0, errno, "local: %s", local);
|
|
else
|
|
error (0, 0, "%s: short write", local);
|
|
}
|
|
break;
|
|
|
|
case TYPE_A:
|
|
if (restart_point)
|
|
{
|
|
off_t i, n;
|
|
int ch;
|
|
|
|
errno = 0;
|
|
|
|
if (fseeko (fout, 0L, SEEK_SET) < 0)
|
|
goto done;
|
|
n = restart_point;
|
|
for (i = 0; i++ < n;)
|
|
{
|
|
if ((ch = getc (fout)) == EOF)
|
|
goto done;
|
|
if (ch == '\n')
|
|
i++;
|
|
}
|
|
if (fseeko (fout, 0L, SEEK_CUR) < 0)
|
|
{
|
|
done:
|
|
/* Cancel server's action quickly. */
|
|
(void) command ("ABOR");
|
|
getreply (0);
|
|
|
|
/* Explain our failure. */
|
|
if (ch == EOF)
|
|
printf ("Action not taken: offset %jd is outside of %s.\n",
|
|
restart_point, local);
|
|
else
|
|
error (0, errno, "local: %s", local);
|
|
|
|
if (closefunc != NULL)
|
|
(*closefunc) (fout);
|
|
return;
|
|
}
|
|
}
|
|
while ((c = getc (din)) != EOF)
|
|
{
|
|
if (c == '\n')
|
|
bare_lfs++;
|
|
while (c == '\r')
|
|
{
|
|
while (hash && (bytes >= local_hashbytes))
|
|
{
|
|
putchar ('#');
|
|
fflush (stdout);
|
|
local_hashbytes += hashbytes;
|
|
}
|
|
bytes++;
|
|
if ((c = getc (din)) != '\n' || tcrflag)
|
|
{
|
|
if (ferror (fout))
|
|
goto break2;
|
|
putc ('\r', fout);
|
|
if (c == '\0')
|
|
{
|
|
bytes++;
|
|
goto contin2;
|
|
}
|
|
if (c == EOF)
|
|
goto contin2;
|
|
}
|
|
}
|
|
putc (c, fout);
|
|
bytes++;
|
|
contin2:;
|
|
}
|
|
break2:
|
|
if (bare_lfs)
|
|
{
|
|
printf ("WARNING! %d bare linefeeds received in ASCII mode\n",
|
|
bare_lfs);
|
|
printf ("File may not have transferred correctly.\n");
|
|
}
|
|
if (hash)
|
|
{
|
|
if (bytes < local_hashbytes)
|
|
putchar ('#');
|
|
putchar ('\n');
|
|
fflush (stdout);
|
|
}
|
|
if (ferror (din))
|
|
{
|
|
if (errno != EPIPE)
|
|
error (0, errno, "netin");
|
|
bytes = -1;
|
|
}
|
|
if (ferror (fout))
|
|
error (0, errno, "local: %s", local);
|
|
break;
|
|
}
|
|
if (closefunc != NULL)
|
|
(*closefunc) (fout);
|
|
signal (SIGINT, oldintr);
|
|
if (oldintp)
|
|
signal (SIGPIPE, oldintp);
|
|
fclose (din);
|
|
gettimeofday (&stop, (struct timezone *) 0);
|
|
getreply (0);
|
|
if (bytes > 0 && is_retr)
|
|
ptransfer ("received", bytes, &start, &stop);
|
|
return;
|
|
abort:
|
|
|
|
/* abort using RFC959 recommended IP,SYNC sequence */
|
|
|
|
if (oldintp)
|
|
signal (SIGPIPE, oldintr);
|
|
signal (SIGINT, SIG_IGN);
|
|
if (!cpend)
|
|
{
|
|
code = -1;
|
|
signal (SIGINT, oldintr);
|
|
return;
|
|
}
|
|
|
|
abort_remote (din);
|
|
code = -1;
|
|
if (data >= 0)
|
|
{
|
|
close (data);
|
|
data = -1;
|
|
}
|
|
if (closefunc != NULL && fout != NULL)
|
|
(*closefunc) (fout);
|
|
if (din)
|
|
fclose (din);
|
|
gettimeofday (&stop, (struct timezone *) 0);
|
|
if (bytes > 0)
|
|
ptransfer ("received", bytes, &start, &stop);
|
|
signal (SIGINT, oldintr);
|
|
}
|
|
|
|
/*
|
|
* Need to start a listen on the data channel before we send the command,
|
|
* otherwise the server's connect may fail.
|
|
*/
|
|
int
|
|
initconn (void)
|
|
{
|
|
char *p = NULL, *a = NULL;
|
|
int result, tmpno = 0;
|
|
int good_epsv = 0, good_lpsv = 0, j;
|
|
socklen_t len;
|
|
int on = 1;
|
|
uint32_t a0, a1, a2, a3, p0, p1, port;
|
|
uint32_t af, hal, h[16], pal; /* RFC 1639: LPSV resonse. */
|
|
struct sockaddr_in *data_addr_sa4 = (struct sockaddr_in *) &data_addr;
|
|
struct sockaddr_in6 *data_addr_sa6 = (struct sockaddr_in6 *) &data_addr;
|
|
|
|
if (passivemode)
|
|
{
|
|
data = socket (myctladdr.ss_family, SOCK_STREAM, 0);
|
|
if (data < 0)
|
|
{
|
|
perror ("ftp: socket");
|
|
return (1);
|
|
}
|
|
if ((options & SO_DEBUG) &&
|
|
setsockopt (data, SOL_SOCKET, SO_DEBUG, (char *) &on,
|
|
sizeof (on)) < 0)
|
|
if (errno != EACCES) /* Ignore insufficient permission. */
|
|
error (0, errno, "setsockopt DEBUG (ignored)");
|
|
|
|
/* Be contemporary:
|
|
* first try EPSV,
|
|
* then fall back to PASV/LPSV.
|
|
*/
|
|
switch (myctladdr.ss_family)
|
|
{
|
|
case AF_INET:
|
|
if (doepsv4 && command ("EPSV") == COMPLETE)
|
|
{
|
|
good_epsv = 1;
|
|
break;
|
|
}
|
|
if (doepsv4)
|
|
{
|
|
/* When arriving here, EPSV failed. Prevent new attempts. */
|
|
doepsv4 = 0;
|
|
}
|
|
if (command ("PASV") == COMPLETE)
|
|
break;
|
|
if (command ("LPSV") == COMPLETE)
|
|
{
|
|
good_lpsv = 1;
|
|
break;
|
|
}
|
|
printf ("Passive mode refused.\n");
|
|
goto bad;
|
|
break;
|
|
case AF_INET6:
|
|
if (command ("EPSV") == COMPLETE)
|
|
{
|
|
good_epsv = 1;
|
|
break;
|
|
}
|
|
if (command ("LPSV") == COMPLETE)
|
|
{
|
|
good_lpsv = 1;
|
|
break;
|
|
}
|
|
printf ("Passive mode refused.\n");
|
|
goto bad;
|
|
break;
|
|
}
|
|
|
|
if (good_epsv)
|
|
{
|
|
/* EPSV: IPv4 or IPv6
|
|
*
|
|
* Expected response (perl): pasv =~ '%u|'
|
|
* This communicates a port number.
|
|
*/
|
|
if (sscanf (pasv, "%u|", &port) != 1)
|
|
{
|
|
printf ("Extended passive mode scan failure. "
|
|
"Should not happen!\n");
|
|
(void) command ("ABOR"); /* Cancel any open connection. */
|
|
goto bad;
|
|
}
|
|
data_addr = hisctladdr;
|
|
switch (data_addr.ss_family)
|
|
{
|
|
case AF_INET:
|
|
data_addr_sa4->sin_port = htons (port);
|
|
break;
|
|
case AF_INET6:
|
|
data_addr_sa6->sin6_port = htons (port);
|
|
break;
|
|
}
|
|
} /* EPSV */
|
|
else if (good_lpsv)
|
|
{
|
|
/* LPSV: IPv4 or IPv6
|
|
*
|
|
* At this point we have got a string of comma
|
|
* separated, one-byte unsigned integer values.
|
|
* Length and interpretation depends on address
|
|
* family.
|
|
*/
|
|
|
|
if (myctladdr.ss_family == AF_INET)
|
|
{
|
|
if ((sscanf (pasv, "%u," /* af */
|
|
"%u,%u,%u,%u,%u," /* hal, h[4] */
|
|
"%u,%u,%u", /* pal, p0, p1 */
|
|
&af, &hal, &h[0], &h[1], &h[2], &h[3], &pal, &p0,
|
|
&p1) != 9) || ( /* Strong checking */ af != 4
|
|
|| hal != 4 || pal != 2))
|
|
{
|
|
printf ("Passive mode address scan failure. "
|
|
"Shouldn't happen!\n");
|
|
(void) command ("ABOR"); /* Cancel any open connection. */
|
|
goto bad;
|
|
}
|
|
for (j = 0; j < 4; ++j)
|
|
h[j] &= 0xff; /* Mask only the significant bits. */
|
|
|
|
data_addr.ss_family = AF_INET;
|
|
data_addr_sa4->sin_port =
|
|
htons (((p0 & 0xff) << 8) | (p1 & 0xff));
|
|
|
|
{
|
|
uint32_t *pu32 =
|
|
(uint32_t *) & data_addr_sa4->sin_addr.s_addr;
|
|
pu32[0] =
|
|
htonl ((h[0] << 24) | (h[1] << 16) | (h[2] << 8) | h[3]);
|
|
}
|
|
if (data_addr_sa4->sin_addr.s_addr
|
|
!= ((struct sockaddr_in *) &hisctladdr)->sin_addr.s_addr)
|
|
{
|
|
printf ("Passive mode address mismatch.\n");
|
|
(void) command ("ABOR"); /* Cancel any open connection. */
|
|
goto bad;
|
|
}
|
|
} /* LPSV IPv4 */
|
|
else /* IPv6 */
|
|
{
|
|
if ((sscanf (pasv, "%u," /* af */
|
|
"%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u," /* hal, h[16] */
|
|
"%u,%u,%u", /* pal, p0, p1 */
|
|
&af, &hal, &h[0], &h[1], &h[2], &h[3], &h[4],
|
|
&h[5], &h[6], &h[7], &h[8], &h[9], &h[10], &h[11],
|
|
&h[12], &h[13], &h[14], &h[15], &pal, &p0,
|
|
&p1) != 21) || ( /* Strong checking */ af != 6
|
|
|| hal != 16 || pal != 2))
|
|
{
|
|
printf ("Passive mode address scan failure. "
|
|
"Shouldn't happen!\n");
|
|
(void) command ("ABOR"); /* Cancel any open connection. */
|
|
goto bad;
|
|
}
|
|
for (j = 0; j < 16; ++j)
|
|
h[j] &= 0xff; /* Mask only the significant bits. */
|
|
|
|
data_addr.ss_family = AF_INET6;
|
|
data_addr_sa6->sin6_port =
|
|
htons (((p0 & 0xff) << 8) | (p1 & 0xff));
|
|
|
|
{
|
|
uint32_t *pu32 =
|
|
(uint32_t *) & data_addr_sa6->sin6_addr.s6_addr;
|
|
pu32[0] =
|
|
htonl ((h[0] << 24) | (h[1] << 16) | (h[2] << 8) | h[3]);
|
|
pu32[1] =
|
|
htonl ((h[4] << 24) | (h[5] << 16) | (h[6] << 8) | h[7]);
|
|
pu32[2] =
|
|
htonl ((h[8] << 24) | (h[9] << 16) | (h[10] << 8) | h[11]);
|
|
pu32[3] =
|
|
htonl ((h[12] << 24) | (h[13] << 16) | (h[14] << 8) |
|
|
h[15]);
|
|
}
|
|
if (data_addr_sa6->sin6_addr.s6_addr
|
|
!= ((struct sockaddr_in6 *) &hisctladdr)->sin6_addr.s6_addr)
|
|
{
|
|
printf ("Passive mode address mismatch.\n");
|
|
(void) command ("ABOR"); /* Cancel any open connection. */
|
|
goto bad;
|
|
}
|
|
} /* LPSV IPv6 */
|
|
}
|
|
else /* !EPSV && !LPSV */
|
|
{ /* PASV */
|
|
if (myctladdr.ss_family == AF_INET)
|
|
{ /* PASV */
|
|
if (sscanf (pasv, "%u,%u,%u,%u,%u,%u",
|
|
&a0, &a1, &a2, &a3, &p0, &p1) != 6)
|
|
{
|
|
printf ("Passive mode address scan failure. "
|
|
"Shouldn't happen!\n");
|
|
(void) command ("ABOR"); /* Cancel any open connection. */
|
|
goto bad;
|
|
}
|
|
data_addr.ss_family = AF_INET;
|
|
data_addr_sa4->sin_addr.s_addr =
|
|
htonl ((a0 << 24) | ((a1 & 0xff) << 16)
|
|
| ((a2 & 0xff) << 8) | (a3 & 0xff));
|
|
data_addr_sa4->sin_port =
|
|
htons (((p0 & 0xff) << 8) | (p1 & 0xff));
|
|
if (data_addr_sa4->sin_addr.s_addr
|
|
!= ((struct sockaddr_in *) &hisctladdr)->sin_addr.s_addr)
|
|
{
|
|
printf ("Passive mode address mismatch.\n");
|
|
(void) command ("ABOR"); /* Cancel any open connection. */
|
|
goto bad;
|
|
}
|
|
} /* PASV */
|
|
else
|
|
{
|
|
/* Catch all impossible cases. */
|
|
printf
|
|
("Passive mode address scan failure. Shouldn't happen!\n");
|
|
goto bad;
|
|
}
|
|
} /* PASV */
|
|
|
|
if (connect (data, (struct sockaddr *) &data_addr, ctladdrlen) < 0)
|
|
{
|
|
perror ("ftp: connect");
|
|
goto bad;
|
|
}
|
|
#if defined IP_TOS && defined IPPROTO_IP && defined IPTOS_THROUGHPUT
|
|
on = IPTOS_THROUGHPUT;
|
|
if (data_addr.ss_family == AF_INET &&
|
|
setsockopt (data, IPPROTO_IP, IP_TOS, (char *) &on,
|
|
sizeof (int)) < 0)
|
|
perror ("ftp: setsockopt TOS (ignored)");
|
|
#endif
|
|
return (0);
|
|
}
|
|
|
|
noport:
|
|
data_addr = myctladdr;
|
|
if (sendport)
|
|
/* Let the system pick a port. */
|
|
switch (myctladdr.ss_family)
|
|
{
|
|
case AF_INET:
|
|
data_addr_sa4->sin_port = 0;
|
|
break;
|
|
case AF_INET6:
|
|
data_addr_sa6->sin6_port = 0;
|
|
break;
|
|
}
|
|
|
|
if (data != -1)
|
|
close (data);
|
|
data = socket (myctladdr.ss_family, SOCK_STREAM, 0);
|
|
if (data < 0)
|
|
{
|
|
error (0, errno, "socket");
|
|
if (tmpno)
|
|
sendport = 1;
|
|
return (1);
|
|
}
|
|
if (!sendport)
|
|
if (setsockopt (data, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof (on))
|
|
< 0)
|
|
{
|
|
error (0, errno, "setsockopt (reuse address)");
|
|
goto bad;
|
|
}
|
|
if (bind (data, (struct sockaddr *) &data_addr, ctladdrlen) < 0)
|
|
{
|
|
error (0, errno, "bind");
|
|
goto bad;
|
|
}
|
|
if (options & SO_DEBUG
|
|
&& setsockopt (data, SOL_SOCKET, SO_DEBUG,
|
|
(char *) &on, sizeof (on)) < 0)
|
|
if (errno != EACCES) /* Ignore insufficient permission. */
|
|
error (0, errno, "setsockopt DEBUG (ignored)");
|
|
len = sizeof (data_addr);
|
|
if (getsockname (data, (struct sockaddr *) &data_addr, &len) < 0)
|
|
{
|
|
error (0, errno, "getsockname");
|
|
goto bad;
|
|
}
|
|
if (listen (data, 1) < 0)
|
|
error (0, errno, "listen");
|
|
if (sendport)
|
|
{
|
|
#define UC(b) (((int)b)&0xff)
|
|
/* Preferences:
|
|
* IPv4: EPRT, PORT, LPRT
|
|
* IPv6: EPRT, LPRT
|
|
*/
|
|
result = ERROR; /* For success detection. */
|
|
if (data_addr.ss_family != AF_INET || doepsv4)
|
|
{
|
|
/* Use EPRT mode. */
|
|
getnameinfo ((struct sockaddr *) &data_addr, ctladdrlen,
|
|
ia, sizeof (ia), portstr, sizeof (portstr),
|
|
NI_NUMERICHOST | NI_NUMERICSERV);
|
|
result = command ("EPRT |%d|%s|%s|",
|
|
(data_addr.ss_family == AF_INET) ? 1 : 2,
|
|
ia, portstr);
|
|
}
|
|
|
|
if (data_addr.ss_family == AF_INET && doepsv4 && result != COMPLETE)
|
|
/* Do not try EPRT with IPv4 again. It fails for this host. */
|
|
doepsv4 = 0;
|
|
|
|
if (data_addr.ss_family == AF_INET && result != COMPLETE)
|
|
{
|
|
/* PORT for IPv4; possibly EPRT has failed. */
|
|
a = (char *) &data_addr_sa4->sin_addr;
|
|
p = (char *) &data_addr_sa4->sin_port;
|
|
result = command ("PORT %d,%d,%d,%d,%d,%d",
|
|
UC (a[0]), UC (a[1]), UC (a[2]), UC (a[3]),
|
|
UC (p[0]), UC (p[1]));
|
|
}
|
|
|
|
if (result != COMPLETE)
|
|
{
|
|
/* Fall back to LPRT. */
|
|
uint8_t *h, *p;
|
|
|
|
switch (data_addr.ss_family)
|
|
{
|
|
case AF_INET:
|
|
h = (uint8_t *) & data_addr_sa4->sin_addr;
|
|
p = (uint8_t *) & data_addr_sa4->sin_port;
|
|
result = command ("LPRT 4,4,%u,%u,%u,%u,2,%u,%u",
|
|
h[0], h[1], h[2], h[3], p[0], p[1]);
|
|
break;
|
|
case AF_INET6:
|
|
h = (uint8_t *) & data_addr_sa6->sin6_addr;
|
|
p = (uint8_t *) & data_addr_sa6->sin6_port;
|
|
result = command ("LPRT 6,16," /* af, hal */
|
|
"%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u," /* h[16] */
|
|
"2,%u,%u", /* pal, p[2] */
|
|
h[0], h[1], h[2], h[3], h[4], h[5], h[6],
|
|
h[7], h[8], h[9], h[10], h[11], h[12], h[13],
|
|
h[14], h[15], p[0], p[1]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (result == ERROR && sendport == -1)
|
|
{
|
|
sendport = 0;
|
|
tmpno = 1;
|
|
goto noport;
|
|
}
|
|
return (result != COMPLETE);
|
|
}
|
|
if (tmpno)
|
|
sendport = 1;
|
|
#if defined IP_TOS && defined IPPROTO_IP && defined IPTOS_THROUGHPUT
|
|
on = IPTOS_THROUGHPUT;
|
|
if (data_addr.ss_family == AF_INET &&
|
|
setsockopt (data, IPPROTO_IP, IP_TOS, (char *) &on, sizeof (int)) < 0)
|
|
error (0, errno, "setsockopt TOS (ignored)");
|
|
#endif
|
|
return (0);
|
|
bad:
|
|
close (data), data = -1;
|
|
if (tmpno)
|
|
sendport = 1;
|
|
return (1);
|
|
}
|
|
|
|
FILE *
|
|
dataconn (char *lmode)
|
|
{
|
|
struct sockaddr_storage from;
|
|
int s, tos;
|
|
socklen_t fromlen = sizeof (from);
|
|
|
|
if (passivemode)
|
|
return (fdopen (data, lmode));
|
|
|
|
s = accept (data, (struct sockaddr *) &from, &fromlen);
|
|
if (s < 0)
|
|
{
|
|
error (0, errno, "accept");
|
|
close (data), data = -1;
|
|
return (NULL);
|
|
}
|
|
close (data);
|
|
data = s;
|
|
#if defined IP_TOS && defined IPPROTO_IP && defined IPTOS_THROUGHPUT
|
|
tos = IPTOS_THROUGHPUT;
|
|
if (from.ss_family == AF_INET &&
|
|
setsockopt (s, IPPROTO_IP, IP_TOS, (char *) &tos, sizeof (int)) < 0)
|
|
error (0, errno, "setsockopt TOS (ignored)");
|
|
#endif
|
|
return (fdopen (data, lmode));
|
|
}
|
|
|
|
void
|
|
ptransfer (char *direction, long long int bytes,
|
|
struct timeval *t0, struct timeval *t1)
|
|
{
|
|
struct timeval td;
|
|
float s, bs;
|
|
|
|
if (verbose)
|
|
{
|
|
tvsub (&td, t1, t0);
|
|
s = td.tv_sec + (td.tv_usec / 1000000.);
|
|
#define nz(x) ((x) == 0 ? 1 : (x))
|
|
bs = bytes / nz (s);
|
|
|
|
printf ("%lld bytes %s in %.3g seconds", bytes, direction, s);
|
|
|
|
if (bs > 1048576.0)
|
|
printf (" (%.3g Mbytes/s)\n", bs / 1048576.0);
|
|
else if (bs > 1024.0)
|
|
printf (" (%.3g kbytes/s)\n", bs / 1024.0);
|
|
else
|
|
printf (" (%.3g bytes/s)\n", bs);
|
|
}
|
|
}
|
|
|
|
/*
|
|
void
|
|
tvadd(tsum, t0)
|
|
struct timeval *tsum, *t0;
|
|
{
|
|
|
|
tsum->tv_sec += t0->tv_sec;
|
|
tsum->tv_usec += t0->tv_usec;
|
|
if (tsum->tv_usec > 1000000)
|
|
tsum->tv_sec++, tsum->tv_usec -= 1000000;
|
|
}
|
|
*/
|
|
|
|
void
|
|
tvsub (struct timeval *tdiff, struct timeval *t1, struct timeval *t0)
|
|
{
|
|
|
|
tdiff->tv_sec = t1->tv_sec - t0->tv_sec;
|
|
tdiff->tv_usec = t1->tv_usec - t0->tv_usec;
|
|
if (tdiff->tv_usec < 0)
|
|
tdiff->tv_sec--, tdiff->tv_usec += 1000000;
|
|
}
|
|
|
|
void
|
|
psabort (int sig MAYBE_UNUSED)
|
|
{
|
|
|
|
abrtflag++;
|
|
}
|
|
|
|
void
|
|
pswitch (int flag)
|
|
{
|
|
sighandler_t oldintr;
|
|
static struct comvars
|
|
{
|
|
int connect;
|
|
char *name;
|
|
struct sockaddr_storage mctl;
|
|
struct sockaddr_storage hctl;
|
|
FILE *in;
|
|
FILE *out;
|
|
int tpe;
|
|
int curtpe;
|
|
int cpnd;
|
|
int sunqe;
|
|
int runqe;
|
|
int mcse;
|
|
int ntflg;
|
|
char nti[sizeof (ntin)];
|
|
char nto[sizeof (ntout)];
|
|
int mapflg;
|
|
char *mi;
|
|
char *mo;
|
|
} proxstruct = {
|
|
0
|
|
}, tmpstruct = {
|
|
0
|
|
};
|
|
struct comvars *ip, *op;
|
|
|
|
abrtflag = 0;
|
|
oldintr = signal (SIGINT, psabort);
|
|
if (flag)
|
|
{
|
|
if (proxy)
|
|
return;
|
|
ip = &tmpstruct;
|
|
op = &proxstruct;
|
|
proxy++;
|
|
}
|
|
else
|
|
{
|
|
if (!proxy)
|
|
return;
|
|
ip = &proxstruct;
|
|
op = &tmpstruct;
|
|
proxy = 0;
|
|
}
|
|
ip->connect = connected;
|
|
connected = op->connect;
|
|
|
|
free (ip->name);
|
|
ip->name = hostname;
|
|
hostname = op->name;
|
|
op->name = 0;
|
|
|
|
ip->hctl = hisctladdr;
|
|
hisctladdr = op->hctl;
|
|
ip->mctl = myctladdr;
|
|
myctladdr = op->mctl;
|
|
ip->in = cin;
|
|
cin = op->in;
|
|
ip->out = cout;
|
|
cout = op->out;
|
|
ip->tpe = type;
|
|
type = op->tpe;
|
|
ip->curtpe = curtype;
|
|
curtype = op->curtpe;
|
|
ip->cpnd = cpend;
|
|
cpend = op->cpnd;
|
|
ip->sunqe = sunique;
|
|
sunique = op->sunqe;
|
|
ip->runqe = runique;
|
|
runique = op->runqe;
|
|
ip->mcse = mcase;
|
|
mcase = op->mcse;
|
|
ip->ntflg = ntflag;
|
|
ntflag = op->ntflg;
|
|
strncpy (ip->nti, ntin, sizeof (ntin) - 1);
|
|
(ip->nti)[sizeof (ntin) - 1] = '\0';
|
|
strcpy (ntin, op->nti);
|
|
strncpy (ip->nto, ntout, sizeof (ntout) - 1);
|
|
(ip->nto)[sizeof (ntout) - 1] = '\0';
|
|
strcpy (ntout, op->nto);
|
|
ip->mapflg = mapflag;
|
|
mapflag = op->mapflg;
|
|
|
|
free (ip->mi);
|
|
ip->mi = mapin;
|
|
mapin = op->mi;
|
|
op->mi = 0;
|
|
|
|
free (ip->mo);
|
|
ip->mo = mapout;
|
|
mapout = op->mo;
|
|
op->mo = 0;
|
|
|
|
signal (SIGINT, oldintr);
|
|
if (abrtflag)
|
|
{
|
|
abrtflag = 0;
|
|
(*oldintr) (SIGINT);
|
|
}
|
|
}
|
|
|
|
void
|
|
abortpt (int sig MAYBE_UNUSED)
|
|
{
|
|
|
|
printf ("\n");
|
|
fflush (stdout);
|
|
ptabflg++;
|
|
mflag = 0;
|
|
abrtflag = 0;
|
|
longjmp (ptabort, 1);
|
|
}
|
|
|
|
void
|
|
proxtrans (char *cmd, char *local, char *remote)
|
|
{
|
|
sighandler_t oldintr;
|
|
int secndflag = 0, prox_type, nfnd;
|
|
char *cmd2;
|
|
fd_set mask;
|
|
|
|
if (strcmp (cmd, "RETR"))
|
|
cmd2 = "RETR";
|
|
else
|
|
cmd2 = runique ? "STOU" : "STOR";
|
|
if ((prox_type = type) == 0)
|
|
{
|
|
if (unix_server && unix_proxy)
|
|
prox_type = TYPE_I;
|
|
else
|
|
prox_type = TYPE_A;
|
|
}
|
|
if (curtype != prox_type)
|
|
changetype (prox_type, 1);
|
|
if (command ("PASV") != COMPLETE)
|
|
{
|
|
printf ("proxy server does not support third party transfers.\n");
|
|
return;
|
|
}
|
|
pswitch (0);
|
|
if (!connected)
|
|
{
|
|
printf ("No primary connection\n");
|
|
pswitch (1);
|
|
code = -1;
|
|
return;
|
|
}
|
|
if (curtype != prox_type)
|
|
changetype (prox_type, 1);
|
|
if (command ("PORT %s", pasv) != COMPLETE)
|
|
{
|
|
pswitch (1);
|
|
return;
|
|
}
|
|
if (setjmp (ptabort))
|
|
goto abort;
|
|
oldintr = signal (SIGINT, abortpt);
|
|
if (command ("%s %s", cmd, remote) != PRELIM)
|
|
{
|
|
signal (SIGINT, oldintr);
|
|
pswitch (1);
|
|
return;
|
|
}
|
|
sleep (2);
|
|
pswitch (1);
|
|
secndflag++;
|
|
if (command ("%s %s", cmd2, local) != PRELIM)
|
|
goto abort;
|
|
ptflag++;
|
|
getreply (0);
|
|
pswitch (0);
|
|
getreply (0);
|
|
signal (SIGINT, oldintr);
|
|
pswitch (1);
|
|
ptflag = 0;
|
|
printf ("local: %s remote: %s\n", local, remote);
|
|
return;
|
|
abort:
|
|
signal (SIGINT, SIG_IGN);
|
|
ptflag = 0;
|
|
if (strcmp (cmd, "RETR") && !proxy)
|
|
pswitch (1);
|
|
else if (!strcmp (cmd, "RETR") && proxy)
|
|
pswitch (0);
|
|
if (!cpend && !secndflag)
|
|
{ /* only here if cmd = "STOR" (proxy=1) */
|
|
if (command ("%s %s", cmd2, local) != PRELIM)
|
|
{
|
|
pswitch (0);
|
|
if (cpend)
|
|
abort_remote ((FILE *) NULL);
|
|
}
|
|
pswitch (1);
|
|
if (ptabflg)
|
|
code = -1;
|
|
signal (SIGINT, oldintr);
|
|
return;
|
|
}
|
|
if (cpend)
|
|
abort_remote ((FILE *) NULL);
|
|
pswitch (!proxy);
|
|
if (!cpend && !secndflag)
|
|
{ /* only if cmd = "RETR" (proxy=1) */
|
|
if (command ("%s %s", cmd2, local) != PRELIM)
|
|
{
|
|
pswitch (0);
|
|
if (cpend)
|
|
abort_remote ((FILE *) NULL);
|
|
pswitch (1);
|
|
if (ptabflg)
|
|
code = -1;
|
|
signal (SIGINT, oldintr);
|
|
return;
|
|
}
|
|
}
|
|
if (cpend)
|
|
abort_remote ((FILE *) NULL);
|
|
pswitch (!proxy);
|
|
if (cpend)
|
|
{
|
|
FD_ZERO (&mask);
|
|
FD_SET (fileno (cin), &mask);
|
|
if ((nfnd = empty (&mask, 10)) <= 0)
|
|
{
|
|
if (nfnd < 0)
|
|
{
|
|
error (0, errno, "abort");
|
|
}
|
|
if (ptabflg)
|
|
code = -1;
|
|
lostpeer (0);
|
|
}
|
|
getreply (0);
|
|
getreply (0);
|
|
}
|
|
if (proxy)
|
|
pswitch (0);
|
|
pswitch (1);
|
|
if (ptabflg)
|
|
code = -1;
|
|
signal (SIGINT, oldintr);
|
|
}
|
|
|
|
void
|
|
reset (int argc MAYBE_UNUSED, char **argv MAYBE_UNUSED)
|
|
{
|
|
fd_set mask;
|
|
int nfnd = 1;
|
|
|
|
FD_ZERO (&mask);
|
|
while (nfnd > 0)
|
|
{
|
|
FD_SET (fileno (cin), &mask);
|
|
if ((nfnd = empty (&mask, 0)) < 0)
|
|
{
|
|
error (0, errno, "reset");
|
|
code = -1;
|
|
lostpeer (0);
|
|
}
|
|
else if (nfnd)
|
|
{
|
|
getreply (0);
|
|
}
|
|
}
|
|
}
|
|
|
|
char *
|
|
gunique (char *local)
|
|
{
|
|
static char *new = 0;
|
|
char *cp;
|
|
int count = 0;
|
|
char ext = '1';
|
|
|
|
free (new);
|
|
new = malloc (strlen (local) + 1 + 3 + 1); /* '.' + 100 + '\0' */
|
|
if (!new)
|
|
{
|
|
printf ("gunique: malloc failed.\n");
|
|
return 0;
|
|
}
|
|
strcpy (new, local);
|
|
|
|
cp = new + strlen (new);
|
|
*cp++ = '.';
|
|
for (;;)
|
|
{
|
|
struct stat st;
|
|
|
|
if (++count == 100)
|
|
{
|
|
printf ("runique: can't find unique file name.\n");
|
|
return ((char *) 0);
|
|
}
|
|
*cp++ = ext;
|
|
*cp = '\0';
|
|
if (ext == '9')
|
|
ext = '0';
|
|
else
|
|
ext++;
|
|
|
|
if (stat (new, &st) != 0)
|
|
{
|
|
if (errno == ENOENT)
|
|
return new;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
if (ext != '0')
|
|
cp--;
|
|
else if (*(cp - 2) == '.')
|
|
*(cp - 1) = '1';
|
|
else
|
|
{
|
|
*(cp - 2) = *(cp - 2) + 1;
|
|
cp--;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
abort_remote (FILE * din)
|
|
{
|
|
char buf[BUFSIZ];
|
|
int nfnd;
|
|
fd_set mask;
|
|
|
|
/*
|
|
* send IAC in urgent mode instead of DM because 4.3BSD places oob mark
|
|
* after urgent byte rather than before as is protocol now
|
|
*/
|
|
sprintf (buf, "%c%c%c", IAC, IP, IAC);
|
|
if (send (fileno (cout), buf, 3, MSG_OOB) != 3)
|
|
error (0, errno, "abort");
|
|
fprintf (cout, "%cABOR\r\n", DM);
|
|
fflush (cout);
|
|
FD_ZERO (&mask);
|
|
FD_SET (fileno (cin), &mask);
|
|
if (din)
|
|
{
|
|
FD_SET (fileno (din), &mask);
|
|
}
|
|
if ((nfnd = empty (&mask, 10)) <= 0)
|
|
{
|
|
if (nfnd < 0)
|
|
{
|
|
error (0, errno, "abort");
|
|
}
|
|
if (ptabflg)
|
|
code = -1;
|
|
lostpeer (0);
|
|
}
|
|
if (din && FD_ISSET (fileno (din), &mask))
|
|
{
|
|
while (read (fileno (din), buf, sizeof (buf)) > 0)
|
|
/* LOOP */ ;
|
|
}
|
|
if (getreply (0) == ERROR && code == 552)
|
|
{
|
|
/* 552 needed for nic style abort */
|
|
getreply (0);
|
|
}
|
|
getreply (0);
|
|
}
|