mirror of
https://git.savannah.gnu.org/git/inetutils.git
synced 2026-01-12 00:19:39 +08:00
643 lines
13 KiB
C
643 lines
13 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.
|
||
*/
|
||
|
||
/*
|
||
* 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);
|
||
}
|