Files
inetutils/ftp/main.c
2023-01-01 01:37:41 +01:00

643 lines
13 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
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.
*/
/*
* FTP User Program -- Command Interface.
*/
#include <config.h>
/*#include <sys/ioctl.h>*/
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/ftp.h>
#include <argp.h>
#include <ctype.h>
#include <error.h>
#include <netdb.h>
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#ifdef HAVE_LOCALE_H
# include <locale.h>
#endif
/* Define macro to nothing so declarations
* in "ftp_var.h" become definitions.
*/
#define FTP_EXTERN
#include "ftp_var.h"
#include "libinetutils.h"
#include "attribute.h"
#ifdef HAVE_READLINE_READLINE_H
# include <readline/readline.h>
#elif defined HAVE_EDITLINE_READLINE_H
# include <editline/readline.h>
#endif
#ifdef HAVE_READLINE_HISTORY_H
# include <readline/history.h>
#elif defined HAVE_EDITLINE_HISTORY_H
# include <editline/history.h>
#endif
static char *slurpstring (void);
static char *argbase; /* current pointer into arg buffer */
static char *argbuf; /* allocated argument storage buffer */
static char *stringbase; /* current scan point in line buffer */
#define DEFAULT_PROMPT "ftp> "
static char *prompt = NULL;
const char args_doc[] = "[HOST [PORT]]";
const char doc[] = "Remote file transfer.";
enum
{
OPT_PROMPT = CHAR_MAX + 1,
};
static struct argp_option argp_options[] = {
#define GRP 0
{"debug", 'd', NULL, 0, "enable debugging output", GRP + 1},
{"no-edit", 'e', NULL, 0,
#if HAVE_READLINE
"disable command line editing",
#else /* !HAVE_READLINE */
"(ignored)",
#endif /* !HAVE_READLINE */
GRP + 1},
{"no-glob", 'g', NULL, 0, "turn off file name globbing", GRP + 1},
{"no-prompt", 'i', NULL, 0, "do not prompt during multiple file transfers",
GRP + 1},
{"no-login", 'n', NULL, 0,
"do not automatically login to the remote system",
GRP + 1},
{"trace", 't', NULL, 0, "enable packet tracing", GRP + 1},
{"passive", 'p', NULL, 0,
"enable passive mode transfer, default for `pftp'", GRP + 1},
{"active", 'A', NULL, 0, "enable active mode transfer", GRP + 1},
{"prompt", OPT_PROMPT, "PROMPT", OPTION_ARG_OPTIONAL,
"print a command line PROMPT " "(optionally), even if not on a tty",
GRP + 1},
{"verbose", 'v', NULL, 0, "verbose output", GRP + 1},
{"ipv4", '4', NULL, 0, "contact IPv4 hosts", GRP + 1},
{"ipv6", '6', NULL, 0, "contact IPv6 hosts", GRP + 1},
{"netrc", 'N', "NETRC", 0, "select a specific initialization file",
GRP + 1},
#undef GRP
{NULL, 0, NULL, 0, NULL, 0}
};
static error_t
parse_opt (int key, char *arg, struct argp_state *state MAYBE_UNUSED)
{
switch (key)
{
case 'd': /* Enable debug mode. */
options |= SO_DEBUG;
debug++;
break;
case 'e':
usereadline = 0; /* No editing. */
break;
case 'g': /* No glob. */
doglob = 0;
break;
case 'i': /* No prompt. */
interactive = 0;
break;
case 'n': /* No automatic login. */
autologin = 0;
break;
case 't': /* Enable packet tracing. */
trace++;
break;
case 'v': /* Verbose. */
verbose++;
break;
case OPT_PROMPT: /* Print command line prompt. */
prompt = arg ? arg : DEFAULT_PROMPT;
break;
case 'p': /* Enable passive transfer mode. */
passivemode = 1;
break;
case 'A': /* Enable active transfer mode. */
passivemode = 0;
break;
case '4':
usefamily = AF_INET;
break;
case '6':
usefamily = AF_INET6;
break;
case 'N':
netrc = arg;
break;
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
static struct argp argp =
{ argp_options, parse_opt, args_doc, doc, NULL, NULL, NULL };
int
main (int argc, char *argv[])
{
int top;
int index;
struct passwd *pw = NULL;
char *cp;
set_program_name (argv[0]);
#ifdef HAVE_SETLOCALE
setlocale (LC_ALL, "");
#endif
doglob = 1;
interactive = 1;
autologin = 1;
passivemode = 0; /* passive mode not active */
doepsv4 = 0; /* use EPRT/EPSV for IPv4 */
usefamily = AF_UNSPEC; /* allow any address family */
usereadline = 1; /* normally using readline */
line = NULL; /* reset global input */
linelen = 0;
argbuf = NULL;
netrc = NULL;
/* Invoked as `pftp'? Then set passive mode. */
cp = strrchr (argv[0], '/');
if (cp)
cp++;
else
cp = argv[0];
if (!strcmp ("pftp", cp))
passivemode = 1;
/* Parse command line */
iu_argp_init ("ftp", default_program_authors);
argp_parse (&argp, argc, argv, 0, &index, NULL);
argc -= index;
argv += index;
fromatty = isatty (fileno (stdin));
if (fromatty)
{
verbose++;
if (!prompt)
prompt = DEFAULT_PROMPT;
cp = getenv ("TERM");
if (cp == NULL || strcmp (cp, "dumb") == 0)
usereadline = 0;
}
else
usereadline = 0;
cpend = 0; /* no pending replies */
proxy = 0; /* proxy not active */
crflag = 1; /* strip c.r. on ascii gets */
sendport = -1; /* not using ports */
/*
* Set up the home directory in case we're globbing.
*/
cp = getlogin ();
if (cp != NULL)
pw = getpwnam (cp);
if (pw == NULL)
pw = getpwuid (getuid ());
if (pw != NULL)
{
char *buf = malloc (strlen (pw->pw_dir) + 1);
if (buf)
{
strcpy (buf, pw->pw_dir);
home = buf;
}
}
if (argc > 0)
{
char *xargv[5];
int i;
if (setjmp (toplevel))
exit (EXIT_SUCCESS);
signal (SIGINT, intr);
signal (SIGPIPE, lostpeer);
xargv[0] = program_invocation_name;
for (i = 0; i < argc && i < 3; i++)
xargv[i + 1] = argv[i];
setpeer (argc + 1, xargv);
}
top = setjmp (toplevel) == 0;
if (top)
{
signal (SIGINT, intr);
signal (SIGPIPE, lostpeer);
}
for (;;)
{
cmdscanner (top);
top = 1;
}
}
void
intr (int sig MAYBE_UNUSED)
{
longjmp (toplevel, 1);
}
void
lostpeer (int sig MAYBE_UNUSED)
{
if (connected)
{
if (cout != NULL)
{
shutdown (fileno (cout), 1 + 1);
fclose (cout);
cout = NULL;
}
if (data >= 0)
{
shutdown (data, 1 + 1);
close (data);
data = -1;
}
connected = 0;
}
pswitch (1);
if (connected)
{
if (cout != NULL)
{
shutdown (fileno (cout), 1 + 1);
fclose (cout);
cout = NULL;
}
connected = 0;
}
proxflag = 0;
pswitch (0);
}
/*
char *
tail(filename)
char *filename;
{
char *s;
while (*filename) {
s = strrchr(filename, '/');
if (s == NULL)
break;
if (s[1])
return (s + 1);
*s = '\0';
}
return (filename);
}
*/
/*
* Command parser.
*/
void
cmdscanner (int top)
{
struct cmd *c;
ssize_t l;
if (!top)
putchar ('\n');
for (;;)
{
if (line)
{
free (line);
line = NULL;
}
linelen = 0;
#if HAVE_READLINE
if (usereadline)
line = readline (prompt); /* malloc'd, no NL */
else
#endif /* HAVE_READLINE */
{
if (prompt)
{
fprintf (stdout, "%s", prompt);
fflush (stdout);
}
/* `linelen' is updated to allocated amount. */
l = getline (&line, &linelen, stdin); /* includes NL */
if ((l > 0) && line)
{
char *nl = strchr (line, '\n');
if (nl)
*nl = '\0';
}
else
{
/* Allocation takes place even without input. */
free (line); /* EOF, et cetera */
line = NULL;
linelen = 0;
}
if (!fromatty && prompt)
fprintf (stdout, "%s\n", line ? line : "");
} /* !usereadline ends */
if (!line)
quit (0, 0);
l = strlen (line);
if (l == 0)
break;
#if HAVE_READLINE
if (usereadline && line && *line)
add_history (line);
#endif /* HAVE_READLINE */
makeargv ();
if (margc == 0)
continue;
c = getcmd (margv[0]);
if (c == (struct cmd *) -1)
{
printf ("?Ambiguous command\n");
continue;
}
if (c == 0)
{
printf ("?Invalid command\n");
continue;
}
if (c->c_conn && !connected)
{
printf ("Not connected.\n");
continue;
}
/* Perform the requested action. */
(*c->c_handler) (margc, margv);
if (bell && c->c_bell)
putchar ('\007');
if (c->c_handler != help)
break;
}
signal (SIGINT, intr);
signal (SIGPIPE, lostpeer);
}
/*
* Slice a string up into argc/argv.
*/
static int slrflag;
void
makeargv (void)
{
char **argp;
margc = 0; /* No content as of yet. */
/* Make sure that `argbuf' is large enough
* to contain `line'. As soon as `line' is
* invalidated, so will `argbuf' be.
**/
free (argbuf); /* Get rid of previous content. */
argbuf = malloc (strlen (line) + 4);
if (!argbuf)
{
/* `margc' is naught, which hopefully will cover our back. */
printf ("Allocation failure. Serious error.\n");
return;
}
argp = margv;
stringbase = line; /* scan from beginning of buffer */
argbase = argbuf; /* store at beginning of buffer */
slrflag = 0;
while ((margc < MAXMARGV) && (*argp++ = slurpstring ()))
margc++;
}
/*
* Parse string into argbuf;
* implemented with FSM to
* handle quoting and strings
*/
static char *
slurpstring (void)
{
int got_one = 0;
char *sb = stringbase;
char *ap = argbase;
char *tmp = argbase; /* will return this if token found */
if (*sb == '!' || *sb == '$')
{ /* recognize ! as a token for shell */
switch (slrflag) /* and $ as token for macro invoke */
{
case 0:
slrflag++;
stringbase++;
return ((*sb == '!') ? "!" : "$");
case 1:
slrflag++;
altarg = stringbase;
break;
default:
break;
}
}
S0:
switch (*sb)
{
case '\0':
goto OUT;
case ' ':
case '\t':
sb++;
goto S0;
default:
switch (slrflag)
{
case 0:
slrflag++;
break;
case 1:
slrflag++;
altarg = sb;
break;
default:
break;
}
goto S1;
}
S1:
switch (*sb)
{
case ' ':
case '\t':
case '\0':
goto OUT; /* end of token */
case '\\':
sb++;
goto S2; /* slurp next character */
case '"':
sb++;
goto S3; /* slurp quoted string */
default:
*ap++ = *sb++; /* add character to token */
got_one = 1;
goto S1;
}
S2:
switch (*sb)
{
case '\0':
goto OUT;
default:
*ap++ = *sb++;
got_one = 1;
goto S1;
}
S3:
switch (*sb)
{
case '\0':
goto OUT;
case '"':
sb++;
goto S1;
default:
*ap++ = *sb++;
got_one = 1;
goto S3;
}
OUT:
if (got_one)
*ap++ = '\0';
argbase = ap; /* update storage pointer */
stringbase = sb; /* update scan pointer */
if (got_one)
return (tmp);
switch (slrflag)
{
case 0:
slrflag++;
break;
case 1:
slrflag++;
altarg = (char *) 0;
break;
default:
break;
}
return ((char *) 0);
}