1876 lines
38 KiB
Text
1876 lines
38 KiB
Text
|
/* $NetBSD: ftpcmd.y,v 1.93 2011/09/16 16:13:17 plunky Exp $ */
|
||
|
|
||
|
/*-
|
||
|
* Copyright (c) 1997-2009 The NetBSD Foundation, Inc.
|
||
|
* All rights reserved.
|
||
|
*
|
||
|
* This code is derived from software contributed to The NetBSD Foundation
|
||
|
* by Luke Mewburn.
|
||
|
*
|
||
|
* 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.
|
||
|
*
|
||
|
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
* Copyright (c) 1985, 1988, 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.
|
||
|
*
|
||
|
* @(#)ftpcmd.y 8.3 (Berkeley) 4/6/94
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
* Grammar for FTP commands.
|
||
|
* See RFC 959.
|
||
|
*/
|
||
|
|
||
|
%{
|
||
|
#include <sys/cdefs.h>
|
||
|
|
||
|
#ifndef lint
|
||
|
#if 0
|
||
|
static char sccsid[] = "@(#)ftpcmd.y 8.3 (Berkeley) 4/6/94";
|
||
|
#else
|
||
|
__RCSID("$NetBSD: ftpcmd.y,v 1.93 2011/09/16 16:13:17 plunky Exp $");
|
||
|
#endif
|
||
|
#endif /* not lint */
|
||
|
|
||
|
#include <sys/param.h>
|
||
|
#include <sys/socket.h>
|
||
|
#include <sys/stat.h>
|
||
|
|
||
|
#include <netinet/in.h>
|
||
|
#include <arpa/ftp.h>
|
||
|
#include <arpa/inet.h>
|
||
|
|
||
|
#include <ctype.h>
|
||
|
#include <errno.h>
|
||
|
#include <pwd.h>
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
#include <syslog.h>
|
||
|
#include <time.h>
|
||
|
#include <tzfile.h>
|
||
|
#include <unistd.h>
|
||
|
#include <netdb.h>
|
||
|
|
||
|
#ifdef KERBEROS5
|
||
|
#include <krb5/krb5.h>
|
||
|
#endif
|
||
|
|
||
|
#include "extern.h"
|
||
|
#include "version.h"
|
||
|
|
||
|
static int cmd_type;
|
||
|
static int cmd_form;
|
||
|
static int cmd_bytesz;
|
||
|
|
||
|
char cbuf[FTP_BUFLEN];
|
||
|
char *cmdp;
|
||
|
char *fromname;
|
||
|
|
||
|
extern int epsvall;
|
||
|
struct tab sitetab[];
|
||
|
|
||
|
static int check_write(const char *, int);
|
||
|
static void help(struct tab *, const char *);
|
||
|
static void port_check(const char *, int);
|
||
|
int yylex(void);
|
||
|
|
||
|
%}
|
||
|
|
||
|
%union {
|
||
|
struct {
|
||
|
LLT ll;
|
||
|
int i;
|
||
|
} u;
|
||
|
char *s;
|
||
|
const char *cs;
|
||
|
}
|
||
|
|
||
|
%token
|
||
|
A B C E F I
|
||
|
L N P R S T
|
||
|
|
||
|
SP CRLF COMMA ALL
|
||
|
|
||
|
USER PASS ACCT CWD CDUP SMNT
|
||
|
QUIT REIN PORT PASV TYPE STRU
|
||
|
MODE RETR STOR STOU APPE ALLO
|
||
|
REST RNFR RNTO ABOR DELE RMD
|
||
|
MKD PWD LIST NLST SITE SYST
|
||
|
STAT HELP NOOP
|
||
|
|
||
|
AUTH ADAT PROT PBSZ CCC MIC
|
||
|
CONF ENC
|
||
|
|
||
|
FEAT OPTS
|
||
|
|
||
|
SIZE MDTM MLST MLSD
|
||
|
|
||
|
LPRT LPSV EPRT EPSV
|
||
|
|
||
|
MAIL MLFL MRCP MRSQ MSAM MSND
|
||
|
MSOM
|
||
|
|
||
|
CHMOD IDLE RATEGET RATEPUT UMASK
|
||
|
|
||
|
LEXERR
|
||
|
|
||
|
%token <s> STRING
|
||
|
%token <u> NUMBER
|
||
|
|
||
|
%type <u.i> check_login octal_number byte_size
|
||
|
%type <u.i> struct_code mode_code type_code form_code decimal_integer
|
||
|
%type <s> pathstring pathname password username
|
||
|
%type <s> mechanism_name base64data prot_code
|
||
|
|
||
|
%start cmd_sel
|
||
|
|
||
|
%%
|
||
|
|
||
|
cmd_sel
|
||
|
: cmd
|
||
|
{
|
||
|
REASSIGN(fromname, NULL);
|
||
|
restart_point = (off_t) 0;
|
||
|
}
|
||
|
|
||
|
| rcmd
|
||
|
|
||
|
;
|
||
|
|
||
|
cmd
|
||
|
/* RFC 959 */
|
||
|
: USER SP username CRLF
|
||
|
{
|
||
|
user($3);
|
||
|
free($3);
|
||
|
}
|
||
|
|
||
|
| PASS SP password CRLF
|
||
|
{
|
||
|
pass($3);
|
||
|
memset($3, 0, strlen($3));
|
||
|
free($3);
|
||
|
}
|
||
|
|
||
|
| CWD check_login CRLF
|
||
|
{
|
||
|
if ($2)
|
||
|
cwd(homedir);
|
||
|
}
|
||
|
|
||
|
| CWD check_login SP pathname CRLF
|
||
|
{
|
||
|
if ($2 && $4 != NULL)
|
||
|
cwd($4);
|
||
|
if ($4 != NULL)
|
||
|
free($4);
|
||
|
}
|
||
|
|
||
|
| CDUP check_login CRLF
|
||
|
{
|
||
|
if ($2)
|
||
|
cwd("..");
|
||
|
}
|
||
|
|
||
|
| QUIT CRLF
|
||
|
{
|
||
|
if (logged_in) {
|
||
|
reply(-221, "%s", "");
|
||
|
reply(0,
|
||
|
"Data traffic for this session was " LLF " byte%s in " LLF " file%s.",
|
||
|
(LLT)total_data, PLURAL(total_data),
|
||
|
(LLT)total_files, PLURAL(total_files));
|
||
|
reply(0,
|
||
|
"Total traffic for this session was " LLF " byte%s in " LLF " transfer%s.",
|
||
|
(LLT)total_bytes, PLURAL(total_bytes),
|
||
|
(LLT)total_xfers, PLURAL(total_xfers));
|
||
|
}
|
||
|
reply(221,
|
||
|
"Thank you for using the FTP service on %s.",
|
||
|
hostname);
|
||
|
if (logged_in && logging) {
|
||
|
syslog(LOG_INFO,
|
||
|
"Data traffic: " LLF " byte%s in " LLF " file%s",
|
||
|
(LLT)total_data, PLURAL(total_data),
|
||
|
(LLT)total_files, PLURAL(total_files));
|
||
|
syslog(LOG_INFO,
|
||
|
"Total traffic: " LLF " byte%s in " LLF " transfer%s",
|
||
|
(LLT)total_bytes, PLURAL(total_bytes),
|
||
|
(LLT)total_xfers, PLURAL(total_xfers));
|
||
|
}
|
||
|
|
||
|
dologout(0);
|
||
|
}
|
||
|
|
||
|
| PORT check_login SP host_port CRLF
|
||
|
{
|
||
|
if ($2)
|
||
|
port_check("PORT", AF_INET);
|
||
|
}
|
||
|
|
||
|
| LPRT check_login SP host_long_port4 CRLF
|
||
|
{
|
||
|
if ($2)
|
||
|
port_check("LPRT", AF_INET);
|
||
|
}
|
||
|
|
||
|
| LPRT check_login SP host_long_port6 CRLF
|
||
|
{
|
||
|
#ifdef INET6
|
||
|
if ($2)
|
||
|
port_check("LPRT", AF_INET6);
|
||
|
#else
|
||
|
reply(500, "IPv6 support not available.");
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
| EPRT check_login SP STRING CRLF
|
||
|
{
|
||
|
if ($2) {
|
||
|
if (extended_port($4) == 0)
|
||
|
port_check("EPRT", -1);
|
||
|
}
|
||
|
free($4);
|
||
|
}
|
||
|
|
||
|
| PASV check_login CRLF
|
||
|
{
|
||
|
if ($2) {
|
||
|
if (CURCLASS_FLAGS_ISSET(passive))
|
||
|
passive();
|
||
|
else
|
||
|
reply(500, "PASV mode not available.");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
| LPSV check_login CRLF
|
||
|
{
|
||
|
if ($2) {
|
||
|
if (CURCLASS_FLAGS_ISSET(passive)) {
|
||
|
if (epsvall)
|
||
|
reply(501,
|
||
|
"LPSV disallowed after EPSV ALL");
|
||
|
else
|
||
|
long_passive("LPSV", PF_UNSPEC);
|
||
|
} else
|
||
|
reply(500, "LPSV mode not available.");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
| EPSV check_login SP NUMBER CRLF
|
||
|
{
|
||
|
if ($2) {
|
||
|
if (CURCLASS_FLAGS_ISSET(passive))
|
||
|
long_passive("EPSV",
|
||
|
epsvproto2af($4.i));
|
||
|
else
|
||
|
reply(500, "EPSV mode not available.");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
| EPSV check_login SP ALL CRLF
|
||
|
{
|
||
|
if ($2) {
|
||
|
if (CURCLASS_FLAGS_ISSET(passive)) {
|
||
|
reply(200,
|
||
|
"EPSV ALL command successful.");
|
||
|
epsvall++;
|
||
|
} else
|
||
|
reply(500, "EPSV mode not available.");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
| EPSV check_login CRLF
|
||
|
{
|
||
|
if ($2) {
|
||
|
if (CURCLASS_FLAGS_ISSET(passive))
|
||
|
long_passive("EPSV", PF_UNSPEC);
|
||
|
else
|
||
|
reply(500, "EPSV mode not available.");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
| TYPE check_login SP type_code CRLF
|
||
|
{
|
||
|
if ($2) {
|
||
|
|
||
|
switch (cmd_type) {
|
||
|
|
||
|
case TYPE_A:
|
||
|
if (cmd_form == FORM_N) {
|
||
|
reply(200, "Type set to A.");
|
||
|
type = cmd_type;
|
||
|
form = cmd_form;
|
||
|
} else
|
||
|
reply(504, "Form must be N.");
|
||
|
break;
|
||
|
|
||
|
case TYPE_E:
|
||
|
reply(504, "Type E not implemented.");
|
||
|
break;
|
||
|
|
||
|
case TYPE_I:
|
||
|
reply(200, "Type set to I.");
|
||
|
type = cmd_type;
|
||
|
break;
|
||
|
|
||
|
case TYPE_L:
|
||
|
#if NBBY == 8
|
||
|
if (cmd_bytesz == 8) {
|
||
|
reply(200,
|
||
|
"Type set to L (byte size 8).");
|
||
|
type = cmd_type;
|
||
|
} else
|
||
|
reply(504, "Byte size must be 8.");
|
||
|
#else /* NBBY == 8 */
|
||
|
UNIMPLEMENTED for NBBY != 8
|
||
|
#endif /* NBBY == 8 */
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
| STRU check_login SP struct_code CRLF
|
||
|
{
|
||
|
if ($2) {
|
||
|
switch ($4) {
|
||
|
|
||
|
case STRU_F:
|
||
|
reply(200, "STRU F ok.");
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
reply(504, "Unimplemented STRU type.");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
| MODE check_login SP mode_code CRLF
|
||
|
{
|
||
|
if ($2) {
|
||
|
switch ($4) {
|
||
|
|
||
|
case MODE_S:
|
||
|
reply(200, "MODE S ok.");
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
reply(502, "Unimplemented MODE type.");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
| RETR check_login SP pathname CRLF
|
||
|
{
|
||
|
if ($2 && $4 != NULL)
|
||
|
retrieve(NULL, $4);
|
||
|
if ($4 != NULL)
|
||
|
free($4);
|
||
|
}
|
||
|
|
||
|
| STOR SP pathname CRLF
|
||
|
{
|
||
|
if (check_write($3, 1))
|
||
|
store($3, "w", 0);
|
||
|
if ($3 != NULL)
|
||
|
free($3);
|
||
|
}
|
||
|
|
||
|
| STOU SP pathname CRLF
|
||
|
{
|
||
|
if (check_write($3, 1))
|
||
|
store($3, "w", 1);
|
||
|
if ($3 != NULL)
|
||
|
free($3);
|
||
|
}
|
||
|
|
||
|
| APPE SP pathname CRLF
|
||
|
{
|
||
|
if (check_write($3, 1))
|
||
|
store($3, "a", 0);
|
||
|
if ($3 != NULL)
|
||
|
free($3);
|
||
|
}
|
||
|
|
||
|
| ALLO check_login SP NUMBER CRLF
|
||
|
{
|
||
|
if ($2)
|
||
|
reply(202, "ALLO command ignored.");
|
||
|
}
|
||
|
|
||
|
| ALLO check_login SP NUMBER SP R SP NUMBER CRLF
|
||
|
{
|
||
|
if ($2)
|
||
|
reply(202, "ALLO command ignored.");
|
||
|
}
|
||
|
|
||
|
| RNTO SP pathname CRLF
|
||
|
{
|
||
|
if (check_write($3, 0)) {
|
||
|
if (fromname) {
|
||
|
renamecmd(fromname, $3);
|
||
|
REASSIGN(fromname, NULL);
|
||
|
} else {
|
||
|
reply(503, "Bad sequence of commands.");
|
||
|
}
|
||
|
}
|
||
|
if ($3 != NULL)
|
||
|
free($3);
|
||
|
}
|
||
|
|
||
|
| ABOR check_login CRLF
|
||
|
{
|
||
|
if (is_oob)
|
||
|
abor();
|
||
|
else if ($2)
|
||
|
reply(225, "ABOR command successful.");
|
||
|
}
|
||
|
|
||
|
| DELE SP pathname CRLF
|
||
|
{
|
||
|
if (check_write($3, 0))
|
||
|
delete($3);
|
||
|
if ($3 != NULL)
|
||
|
free($3);
|
||
|
}
|
||
|
|
||
|
| RMD SP pathname CRLF
|
||
|
{
|
||
|
if (check_write($3, 0))
|
||
|
removedir($3);
|
||
|
if ($3 != NULL)
|
||
|
free($3);
|
||
|
}
|
||
|
|
||
|
| MKD SP pathname CRLF
|
||
|
{
|
||
|
if (check_write($3, 0))
|
||
|
makedir($3);
|
||
|
if ($3 != NULL)
|
||
|
free($3);
|
||
|
}
|
||
|
|
||
|
| PWD check_login CRLF
|
||
|
{
|
||
|
if ($2)
|
||
|
pwd();
|
||
|
}
|
||
|
|
||
|
| LIST check_login CRLF
|
||
|
{
|
||
|
const char *argv[] = { INTERNAL_LS, "-lgA", NULL };
|
||
|
|
||
|
if (CURCLASS_FLAGS_ISSET(hidesymlinks))
|
||
|
argv[1] = "-LlgA";
|
||
|
if ($2)
|
||
|
retrieve(argv, "");
|
||
|
}
|
||
|
|
||
|
| LIST check_login SP pathname CRLF
|
||
|
{
|
||
|
const char *argv[] = { INTERNAL_LS, "-lgA", NULL, NULL };
|
||
|
|
||
|
if (CURCLASS_FLAGS_ISSET(hidesymlinks))
|
||
|
argv[1] = "-LlgA";
|
||
|
if ($2 && $4 != NULL) {
|
||
|
argv[2] = $4;
|
||
|
retrieve(argv, $4);
|
||
|
}
|
||
|
if ($4 != NULL)
|
||
|
free($4);
|
||
|
}
|
||
|
|
||
|
| NLST check_login CRLF
|
||
|
{
|
||
|
if ($2)
|
||
|
send_file_list(".");
|
||
|
}
|
||
|
|
||
|
| NLST check_login SP pathname CRLF
|
||
|
{
|
||
|
if ($2)
|
||
|
send_file_list($4);
|
||
|
free($4);
|
||
|
}
|
||
|
|
||
|
| SITE SP HELP CRLF
|
||
|
{
|
||
|
help(sitetab, NULL);
|
||
|
}
|
||
|
|
||
|
| SITE SP CHMOD SP octal_number SP pathname CRLF
|
||
|
{
|
||
|
if (check_write($7, 0)) {
|
||
|
if (($5 == -1) || ($5 > 0777))
|
||
|
reply(501,
|
||
|
"CHMOD: Mode value must be between 0 and 0777");
|
||
|
else if (chmod($7, $5) < 0)
|
||
|
perror_reply(550, $7);
|
||
|
else
|
||
|
reply(200, "CHMOD command successful.");
|
||
|
}
|
||
|
if ($7 != NULL)
|
||
|
free($7);
|
||
|
}
|
||
|
|
||
|
| SITE SP HELP SP STRING CRLF
|
||
|
{
|
||
|
help(sitetab, $5);
|
||
|
free($5);
|
||
|
}
|
||
|
|
||
|
| SITE SP IDLE check_login CRLF
|
||
|
{
|
||
|
if ($4) {
|
||
|
reply(200,
|
||
|
"Current IDLE time limit is " LLF
|
||
|
" seconds; max " LLF,
|
||
|
(LLT)curclass.timeout,
|
||
|
(LLT)curclass.maxtimeout);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
| SITE SP IDLE check_login SP NUMBER CRLF
|
||
|
{
|
||
|
if ($4) {
|
||
|
if ($6.i < 30 || $6.i > curclass.maxtimeout) {
|
||
|
reply(501,
|
||
|
"IDLE time limit must be between 30 and "
|
||
|
LLF " seconds",
|
||
|
(LLT)curclass.maxtimeout);
|
||
|
} else {
|
||
|
curclass.timeout = $6.i;
|
||
|
(void) alarm(curclass.timeout);
|
||
|
reply(200,
|
||
|
"IDLE time limit set to "
|
||
|
LLF " seconds",
|
||
|
(LLT)curclass.timeout);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
| SITE SP RATEGET check_login CRLF
|
||
|
{
|
||
|
if ($4) {
|
||
|
reply(200,
|
||
|
"Current RATEGET is " LLF " bytes/sec",
|
||
|
(LLT)curclass.rateget);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
| SITE SP RATEGET check_login SP STRING CRLF
|
||
|
{
|
||
|
char errbuf[100];
|
||
|
char *p = $6;
|
||
|
LLT rate;
|
||
|
|
||
|
if ($4) {
|
||
|
rate = strsuftollx("RATEGET", p, 0,
|
||
|
curclass.maxrateget
|
||
|
? curclass.maxrateget
|
||
|
: LLTMAX, errbuf, sizeof(errbuf));
|
||
|
if (errbuf[0])
|
||
|
reply(501, "%s", errbuf);
|
||
|
else {
|
||
|
curclass.rateget = rate;
|
||
|
reply(200,
|
||
|
"RATEGET set to " LLF " bytes/sec",
|
||
|
(LLT)curclass.rateget);
|
||
|
}
|
||
|
}
|
||
|
free($6);
|
||
|
}
|
||
|
|
||
|
| SITE SP RATEPUT check_login CRLF
|
||
|
{
|
||
|
if ($4) {
|
||
|
reply(200,
|
||
|
"Current RATEPUT is " LLF " bytes/sec",
|
||
|
(LLT)curclass.rateput);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
| SITE SP RATEPUT check_login SP STRING CRLF
|
||
|
{
|
||
|
char errbuf[100];
|
||
|
char *p = $6;
|
||
|
LLT rate;
|
||
|
|
||
|
if ($4) {
|
||
|
rate = strsuftollx("RATEPUT", p, 0,
|
||
|
curclass.maxrateput
|
||
|
? curclass.maxrateput
|
||
|
: LLTMAX, errbuf, sizeof(errbuf));
|
||
|
if (errbuf[0])
|
||
|
reply(501, "%s", errbuf);
|
||
|
else {
|
||
|
curclass.rateput = rate;
|
||
|
reply(200,
|
||
|
"RATEPUT set to " LLF " bytes/sec",
|
||
|
(LLT)curclass.rateput);
|
||
|
}
|
||
|
}
|
||
|
free($6);
|
||
|
}
|
||
|
|
||
|
| SITE SP UMASK check_login CRLF
|
||
|
{
|
||
|
int oldmask;
|
||
|
|
||
|
if ($4) {
|
||
|
oldmask = umask(0);
|
||
|
(void) umask(oldmask);
|
||
|
reply(200, "Current UMASK is %03o", oldmask);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
| SITE SP UMASK check_login SP octal_number CRLF
|
||
|
{
|
||
|
int oldmask;
|
||
|
|
||
|
if ($4 && check_write("", 0)) {
|
||
|
if (($6 == -1) || ($6 > 0777)) {
|
||
|
reply(501, "Bad UMASK value");
|
||
|
} else {
|
||
|
oldmask = umask($6);
|
||
|
reply(200,
|
||
|
"UMASK set to %03o (was %03o)",
|
||
|
$6, oldmask);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
| SYST CRLF
|
||
|
{
|
||
|
if (EMPTYSTR(version))
|
||
|
reply(215, "UNIX Type: L%d", NBBY);
|
||
|
else
|
||
|
reply(215, "UNIX Type: L%d Version: %s", NBBY,
|
||
|
version);
|
||
|
}
|
||
|
|
||
|
| STAT check_login SP pathname CRLF
|
||
|
{
|
||
|
if ($2 && $4 != NULL)
|
||
|
statfilecmd($4);
|
||
|
if ($4 != NULL)
|
||
|
free($4);
|
||
|
}
|
||
|
|
||
|
| STAT CRLF
|
||
|
{
|
||
|
if (is_oob)
|
||
|
statxfer();
|
||
|
else
|
||
|
statcmd();
|
||
|
}
|
||
|
|
||
|
| HELP CRLF
|
||
|
{
|
||
|
help(cmdtab, NULL);
|
||
|
}
|
||
|
|
||
|
| HELP SP STRING CRLF
|
||
|
{
|
||
|
char *cp = $3;
|
||
|
|
||
|
if (strncasecmp(cp, "SITE", 4) == 0) {
|
||
|
cp = $3 + 4;
|
||
|
if (*cp == ' ')
|
||
|
cp++;
|
||
|
if (*cp)
|
||
|
help(sitetab, cp);
|
||
|
else
|
||
|
help(sitetab, NULL);
|
||
|
} else
|
||
|
help(cmdtab, $3);
|
||
|
free($3);
|
||
|
}
|
||
|
|
||
|
| NOOP CRLF
|
||
|
{
|
||
|
reply(200, "NOOP command successful.");
|
||
|
}
|
||
|
|
||
|
/* RFC 2228 */
|
||
|
| AUTH SP mechanism_name CRLF
|
||
|
{
|
||
|
reply(502, "RFC 2228 authentication not implemented.");
|
||
|
free($3);
|
||
|
}
|
||
|
|
||
|
| ADAT SP base64data CRLF
|
||
|
{
|
||
|
reply(503,
|
||
|
"Please set authentication state with AUTH.");
|
||
|
free($3);
|
||
|
}
|
||
|
|
||
|
| PROT SP prot_code CRLF
|
||
|
{
|
||
|
reply(503,
|
||
|
"Please set protection buffer size with PBSZ.");
|
||
|
free($3);
|
||
|
}
|
||
|
|
||
|
| PBSZ SP decimal_integer CRLF
|
||
|
{
|
||
|
reply(503,
|
||
|
"Please set authentication state with AUTH.");
|
||
|
}
|
||
|
|
||
|
| CCC CRLF
|
||
|
{
|
||
|
reply(533, "No protection enabled.");
|
||
|
}
|
||
|
|
||
|
| MIC SP base64data CRLF
|
||
|
{
|
||
|
reply(502, "RFC 2228 authentication not implemented.");
|
||
|
free($3);
|
||
|
}
|
||
|
|
||
|
| CONF SP base64data CRLF
|
||
|
{
|
||
|
reply(502, "RFC 2228 authentication not implemented.");
|
||
|
free($3);
|
||
|
}
|
||
|
|
||
|
| ENC SP base64data CRLF
|
||
|
{
|
||
|
reply(502, "RFC 2228 authentication not implemented.");
|
||
|
free($3);
|
||
|
}
|
||
|
|
||
|
/* RFC 2389 */
|
||
|
| FEAT CRLF
|
||
|
{
|
||
|
|
||
|
feat();
|
||
|
}
|
||
|
|
||
|
| OPTS SP STRING CRLF
|
||
|
{
|
||
|
|
||
|
opts($3);
|
||
|
free($3);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* RFC 3659 */
|
||
|
|
||
|
/*
|
||
|
* Return size of file in a format suitable for
|
||
|
* using with RESTART (we just count bytes).
|
||
|
*/
|
||
|
| SIZE check_login SP pathname CRLF
|
||
|
{
|
||
|
if ($2 && $4 != NULL)
|
||
|
sizecmd($4);
|
||
|
if ($4 != NULL)
|
||
|
free($4);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Return modification time of file as an ISO 3307
|
||
|
* style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
|
||
|
* where xxx is the fractional second (of any precision,
|
||
|
* not necessarily 3 digits)
|
||
|
*/
|
||
|
| MDTM check_login SP pathname CRLF
|
||
|
{
|
||
|
if ($2 && $4 != NULL) {
|
||
|
struct stat stbuf;
|
||
|
if (stat($4, &stbuf) < 0)
|
||
|
perror_reply(550, $4);
|
||
|
else if (!S_ISREG(stbuf.st_mode)) {
|
||
|
reply(550, "%s: not a plain file.", $4);
|
||
|
} else {
|
||
|
struct tm *t;
|
||
|
|
||
|
t = gmtime(&stbuf.st_mtime);
|
||
|
reply(213,
|
||
|
"%04d%02d%02d%02d%02d%02d",
|
||
|
TM_YEAR_BASE + t->tm_year,
|
||
|
t->tm_mon+1, t->tm_mday,
|
||
|
t->tm_hour, t->tm_min, t->tm_sec);
|
||
|
}
|
||
|
}
|
||
|
if ($4 != NULL)
|
||
|
free($4);
|
||
|
}
|
||
|
|
||
|
| MLST check_login SP pathname CRLF
|
||
|
{
|
||
|
if ($2 && $4 != NULL)
|
||
|
mlst($4);
|
||
|
if ($4 != NULL)
|
||
|
free($4);
|
||
|
}
|
||
|
|
||
|
| MLST check_login CRLF
|
||
|
{
|
||
|
mlst(NULL);
|
||
|
}
|
||
|
|
||
|
| MLSD check_login SP pathname CRLF
|
||
|
{
|
||
|
if ($2 && $4 != NULL)
|
||
|
mlsd($4);
|
||
|
if ($4 != NULL)
|
||
|
free($4);
|
||
|
}
|
||
|
|
||
|
| MLSD check_login CRLF
|
||
|
{
|
||
|
mlsd(NULL);
|
||
|
}
|
||
|
|
||
|
| error CRLF
|
||
|
{
|
||
|
yyerrok;
|
||
|
}
|
||
|
;
|
||
|
|
||
|
rcmd
|
||
|
: REST check_login SP NUMBER CRLF
|
||
|
{
|
||
|
if ($2) {
|
||
|
REASSIGN(fromname, NULL);
|
||
|
restart_point = (off_t)$4.ll;
|
||
|
reply(350,
|
||
|
"Restarting at " LLF ". Send STORE or RETRIEVE to initiate transfer.",
|
||
|
(LLT)restart_point);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
| RNFR SP pathname CRLF
|
||
|
{
|
||
|
restart_point = (off_t) 0;
|
||
|
if (check_write($3, 0)) {
|
||
|
REASSIGN(fromname, NULL);
|
||
|
fromname = renamefrom($3);
|
||
|
}
|
||
|
if ($3 != NULL)
|
||
|
free($3);
|
||
|
}
|
||
|
;
|
||
|
|
||
|
username
|
||
|
: STRING
|
||
|
;
|
||
|
|
||
|
password
|
||
|
: /* empty */
|
||
|
{
|
||
|
$$ = (char *)calloc(1, sizeof(char));
|
||
|
}
|
||
|
|
||
|
| STRING
|
||
|
;
|
||
|
|
||
|
byte_size
|
||
|
: NUMBER
|
||
|
{
|
||
|
$$ = $1.i;
|
||
|
}
|
||
|
;
|
||
|
|
||
|
host_port
|
||
|
: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
|
||
|
NUMBER COMMA NUMBER
|
||
|
{
|
||
|
char *a, *p;
|
||
|
|
||
|
memset(&data_dest, 0, sizeof(data_dest));
|
||
|
data_dest.su_len = sizeof(struct sockaddr_in);
|
||
|
data_dest.su_family = AF_INET;
|
||
|
p = (char *)&data_dest.su_port;
|
||
|
p[0] = $9.i; p[1] = $11.i;
|
||
|
a = (char *)&data_dest.su_addr;
|
||
|
a[0] = $1.i; a[1] = $3.i; a[2] = $5.i; a[3] = $7.i;
|
||
|
}
|
||
|
;
|
||
|
|
||
|
host_long_port4
|
||
|
: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
|
||
|
NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
|
||
|
NUMBER
|
||
|
{
|
||
|
char *a, *p;
|
||
|
|
||
|
memset(&data_dest, 0, sizeof(data_dest));
|
||
|
data_dest.su_len = sizeof(struct sockaddr_in);
|
||
|
data_dest.su_family = AF_INET;
|
||
|
p = (char *)&data_dest.su_port;
|
||
|
p[0] = $15.i; p[1] = $17.i;
|
||
|
a = (char *)&data_dest.su_addr;
|
||
|
a[0] = $5.i; a[1] = $7.i; a[2] = $9.i; a[3] = $11.i;
|
||
|
|
||
|
/* reject invalid LPRT command */
|
||
|
if ($1.i != 4 || $3.i != 4 || $13.i != 2)
|
||
|
memset(&data_dest, 0, sizeof(data_dest));
|
||
|
}
|
||
|
;
|
||
|
|
||
|
host_long_port6
|
||
|
: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
|
||
|
NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
|
||
|
NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
|
||
|
NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
|
||
|
NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
|
||
|
NUMBER
|
||
|
{
|
||
|
#ifdef INET6
|
||
|
unsigned char buf[16];
|
||
|
|
||
|
(void)memset(&data_dest, 0, sizeof(data_dest));
|
||
|
data_dest.su_len = sizeof(struct sockaddr_in6);
|
||
|
data_dest.su_family = AF_INET6;
|
||
|
buf[0] = $39.i; buf[1] = $41.i;
|
||
|
(void)memcpy(&data_dest.su_port, buf,
|
||
|
sizeof(data_dest.su_port));
|
||
|
buf[0] = $5.i; buf[1] = $7.i;
|
||
|
buf[2] = $9.i; buf[3] = $11.i;
|
||
|
buf[4] = $13.i; buf[5] = $15.i;
|
||
|
buf[6] = $17.i; buf[7] = $19.i;
|
||
|
buf[8] = $21.i; buf[9] = $23.i;
|
||
|
buf[10] = $25.i; buf[11] = $27.i;
|
||
|
buf[12] = $29.i; buf[13] = $31.i;
|
||
|
buf[14] = $33.i; buf[15] = $35.i;
|
||
|
(void)memcpy(&data_dest.si_su.su_sin6.sin6_addr,
|
||
|
buf, sizeof(data_dest.si_su.su_sin6.sin6_addr));
|
||
|
if (his_addr.su_family == AF_INET6) {
|
||
|
/* XXX: more sanity checks! */
|
||
|
data_dest.su_scope_id = his_addr.su_scope_id;
|
||
|
}
|
||
|
#else
|
||
|
memset(&data_dest, 0, sizeof(data_dest));
|
||
|
#endif /* INET6 */
|
||
|
/* reject invalid LPRT command */
|
||
|
if ($1.i != 6 || $3.i != 16 || $37.i != 2)
|
||
|
memset(&data_dest, 0, sizeof(data_dest));
|
||
|
}
|
||
|
;
|
||
|
|
||
|
form_code
|
||
|
: N
|
||
|
{
|
||
|
$$ = FORM_N;
|
||
|
}
|
||
|
|
||
|
| T
|
||
|
{
|
||
|
$$ = FORM_T;
|
||
|
}
|
||
|
|
||
|
| C
|
||
|
{
|
||
|
$$ = FORM_C;
|
||
|
}
|
||
|
;
|
||
|
|
||
|
type_code
|
||
|
: A
|
||
|
{
|
||
|
cmd_type = TYPE_A;
|
||
|
cmd_form = FORM_N;
|
||
|
}
|
||
|
|
||
|
| A SP form_code
|
||
|
{
|
||
|
cmd_type = TYPE_A;
|
||
|
cmd_form = $3;
|
||
|
}
|
||
|
|
||
|
| E
|
||
|
{
|
||
|
cmd_type = TYPE_E;
|
||
|
cmd_form = FORM_N;
|
||
|
}
|
||
|
|
||
|
| E SP form_code
|
||
|
{
|
||
|
cmd_type = TYPE_E;
|
||
|
cmd_form = $3;
|
||
|
}
|
||
|
|
||
|
| I
|
||
|
{
|
||
|
cmd_type = TYPE_I;
|
||
|
}
|
||
|
|
||
|
| L
|
||
|
{
|
||
|
cmd_type = TYPE_L;
|
||
|
cmd_bytesz = NBBY;
|
||
|
}
|
||
|
|
||
|
| L SP byte_size
|
||
|
{
|
||
|
cmd_type = TYPE_L;
|
||
|
cmd_bytesz = $3;
|
||
|
}
|
||
|
|
||
|
/* this is for a bug in the BBN ftp */
|
||
|
| L byte_size
|
||
|
{
|
||
|
cmd_type = TYPE_L;
|
||
|
cmd_bytesz = $2;
|
||
|
}
|
||
|
;
|
||
|
|
||
|
struct_code
|
||
|
: F
|
||
|
{
|
||
|
$$ = STRU_F;
|
||
|
}
|
||
|
|
||
|
| R
|
||
|
{
|
||
|
$$ = STRU_R;
|
||
|
}
|
||
|
|
||
|
| P
|
||
|
{
|
||
|
$$ = STRU_P;
|
||
|
}
|
||
|
;
|
||
|
|
||
|
mode_code
|
||
|
: S
|
||
|
{
|
||
|
$$ = MODE_S;
|
||
|
}
|
||
|
|
||
|
| B
|
||
|
{
|
||
|
$$ = MODE_B;
|
||
|
}
|
||
|
|
||
|
| C
|
||
|
{
|
||
|
$$ = MODE_C;
|
||
|
}
|
||
|
;
|
||
|
|
||
|
pathname
|
||
|
: pathstring
|
||
|
{
|
||
|
/*
|
||
|
* Problem: this production is used for all pathname
|
||
|
* processing, but only gives a 550 error reply.
|
||
|
* This is a valid reply in some cases but not in
|
||
|
* others.
|
||
|
*/
|
||
|
if (logged_in && $1 && *$1 == '~') {
|
||
|
char *path, *home, *result;
|
||
|
size_t len;
|
||
|
|
||
|
path = strchr($1 + 1, '/');
|
||
|
if (path != NULL)
|
||
|
*path++ = '\0';
|
||
|
if ($1[1] == '\0')
|
||
|
home = homedir;
|
||
|
else {
|
||
|
struct passwd *hpw;
|
||
|
|
||
|
if ((hpw = getpwnam($1 + 1)) != NULL)
|
||
|
home = hpw->pw_dir;
|
||
|
else
|
||
|
home = $1;
|
||
|
}
|
||
|
len = strlen(home) + 1;
|
||
|
if (path != NULL)
|
||
|
len += strlen(path) + 1;
|
||
|
if ((result = malloc(len)) == NULL)
|
||
|
fatal("Local resource failure: malloc");
|
||
|
strlcpy(result, home, len);
|
||
|
if (path != NULL) {
|
||
|
strlcat(result, "/", len);
|
||
|
strlcat(result, path, len);
|
||
|
}
|
||
|
$$ = result;
|
||
|
free($1);
|
||
|
} else
|
||
|
$$ = $1;
|
||
|
}
|
||
|
;
|
||
|
|
||
|
pathstring
|
||
|
: STRING
|
||
|
;
|
||
|
|
||
|
octal_number
|
||
|
: NUMBER
|
||
|
{
|
||
|
int ret, dec, multby, digit;
|
||
|
|
||
|
/*
|
||
|
* Convert a number that was read as decimal number
|
||
|
* to what it would be if it had been read as octal.
|
||
|
*/
|
||
|
dec = $1.i;
|
||
|
multby = 1;
|
||
|
ret = 0;
|
||
|
while (dec) {
|
||
|
digit = dec%10;
|
||
|
if (digit > 7) {
|
||
|
ret = -1;
|
||
|
break;
|
||
|
}
|
||
|
ret += digit * multby;
|
||
|
multby *= 8;
|
||
|
dec /= 10;
|
||
|
}
|
||
|
$$ = ret;
|
||
|
}
|
||
|
;
|
||
|
|
||
|
mechanism_name
|
||
|
: STRING
|
||
|
;
|
||
|
|
||
|
base64data
|
||
|
: STRING
|
||
|
;
|
||
|
|
||
|
prot_code
|
||
|
: STRING
|
||
|
;
|
||
|
|
||
|
decimal_integer
|
||
|
: NUMBER
|
||
|
{
|
||
|
$$ = $1.i;
|
||
|
}
|
||
|
;
|
||
|
|
||
|
check_login
|
||
|
: /* empty */
|
||
|
{
|
||
|
if (logged_in)
|
||
|
$$ = 1;
|
||
|
else {
|
||
|
reply(530, "Please login with USER and PASS.");
|
||
|
$$ = 0;
|
||
|
hasyyerrored = 1;
|
||
|
}
|
||
|
}
|
||
|
;
|
||
|
|
||
|
%%
|
||
|
|
||
|
#define CMD 0 /* beginning of command */
|
||
|
#define ARGS 1 /* expect miscellaneous arguments */
|
||
|
#define STR1 2 /* expect SP followed by STRING */
|
||
|
#define STR2 3 /* expect STRING */
|
||
|
#define OSTR 4 /* optional SP then STRING */
|
||
|
#define ZSTR1 5 /* SP then optional STRING */
|
||
|
#define ZSTR2 6 /* optional STRING after SP */
|
||
|
#define SITECMD 7 /* SITE command */
|
||
|
#define NSTR 8 /* Number followed by a string */
|
||
|
#define NOARGS 9 /* No arguments allowed */
|
||
|
#define EOLN 10 /* End of line */
|
||
|
|
||
|
struct tab cmdtab[] = {
|
||
|
/* From RFC 959, in order defined (5.3.1) */
|
||
|
{ "USER", USER, STR1, 1, "<sp> username", 0, },
|
||
|
{ "PASS", PASS, ZSTR1, 1, "<sp> password", 0, },
|
||
|
{ "ACCT", ACCT, STR1, 0, "(specify account)", 0, },
|
||
|
{ "CWD", CWD, OSTR, 1, "[ <sp> directory-name ]", 0, },
|
||
|
{ "CDUP", CDUP, NOARGS, 1, "(change to parent directory)", 0, },
|
||
|
{ "SMNT", SMNT, ARGS, 0, "(structure mount)", 0, },
|
||
|
{ "QUIT", QUIT, NOARGS, 1, "(terminate service)", 0, },
|
||
|
{ "REIN", REIN, NOARGS, 0, "(reinitialize server state)", 0, },
|
||
|
{ "PORT", PORT, ARGS, 1, "<sp> b0, b1, b2, b3, b4, b5", 0, },
|
||
|
{ "LPRT", LPRT, ARGS, 1, "<sp> af, hal, h1, h2, h3,..., pal, p1, p2...", 0, },
|
||
|
{ "EPRT", EPRT, STR1, 1, "<sp> |af|addr|port|", 0, },
|
||
|
{ "PASV", PASV, NOARGS, 1, "(set server in passive mode)", 0, },
|
||
|
{ "LPSV", LPSV, ARGS, 1, "(set server in passive mode)", 0, },
|
||
|
{ "EPSV", EPSV, ARGS, 1, "[<sp> af|ALL]", 0, },
|
||
|
{ "TYPE", TYPE, ARGS, 1, "<sp> [ A | E | I | L ]", 0, },
|
||
|
{ "STRU", STRU, ARGS, 1, "(specify file structure)", 0, },
|
||
|
{ "MODE", MODE, ARGS, 1, "(specify transfer mode)", 0, },
|
||
|
{ "RETR", RETR, STR1, 1, "<sp> file-name", 0, },
|
||
|
{ "STOR", STOR, STR1, 1, "<sp> file-name", 0, },
|
||
|
{ "STOU", STOU, STR1, 1, "<sp> file-name", 0, },
|
||
|
{ "APPE", APPE, STR1, 1, "<sp> file-name", 0, },
|
||
|
{ "ALLO", ALLO, ARGS, 1, "allocate storage (vacuously)", 0, },
|
||
|
{ "REST", REST, ARGS, 1, "<sp> offset (restart command)", 0, },
|
||
|
{ "RNFR", RNFR, STR1, 1, "<sp> file-name", 0, },
|
||
|
{ "RNTO", RNTO, STR1, 1, "<sp> file-name", 0, },
|
||
|
{ "ABOR", ABOR, NOARGS, 4, "(abort operation)", 0, },
|
||
|
{ "DELE", DELE, STR1, 1, "<sp> file-name", 0, },
|
||
|
{ "RMD", RMD, STR1, 1, "<sp> path-name", 0, },
|
||
|
{ "MKD", MKD, STR1, 1, "<sp> path-name", 0, },
|
||
|
{ "PWD", PWD, NOARGS, 1, "(return current directory)", 0, },
|
||
|
{ "LIST", LIST, OSTR, 1, "[ <sp> path-name ]", 0, },
|
||
|
{ "NLST", NLST, OSTR, 1, "[ <sp> path-name ]", 0, },
|
||
|
{ "SITE", SITE, SITECMD, 1, "site-cmd [ <sp> arguments ]", 0, },
|
||
|
{ "SYST", SYST, NOARGS, 1, "(get type of operating system)", 0, },
|
||
|
{ "STAT", STAT, OSTR, 4, "[ <sp> path-name ]", 0, },
|
||
|
{ "HELP", HELP, OSTR, 1, "[ <sp> <string> ]", 0, },
|
||
|
{ "NOOP", NOOP, NOARGS, 2, "", 0, },
|
||
|
|
||
|
/* From RFC 2228, in order defined */
|
||
|
{ "AUTH", AUTH, STR1, 1, "<sp> mechanism-name", 0, },
|
||
|
{ "ADAT", ADAT, STR1, 1, "<sp> base-64-data", 0, },
|
||
|
{ "PROT", PROT, STR1, 1, "<sp> prot-code", 0, },
|
||
|
{ "PBSZ", PBSZ, ARGS, 1, "<sp> decimal-integer", 0, },
|
||
|
{ "CCC", CCC, NOARGS, 1, "(Disable data protection)", 0, },
|
||
|
{ "MIC", MIC, STR1, 4, "<sp> base64data", 0, },
|
||
|
{ "CONF", CONF, STR1, 4, "<sp> base64data", 0, },
|
||
|
{ "ENC", ENC, STR1, 4, "<sp> base64data", 0, },
|
||
|
|
||
|
/* From RFC 2389, in order defined */
|
||
|
{ "FEAT", FEAT, NOARGS, 1, "(display extended features)", 0, },
|
||
|
{ "OPTS", OPTS, STR1, 1, "<sp> command [ <sp> options ]", 0, },
|
||
|
|
||
|
/* From RFC 3659, in order defined */
|
||
|
{ "MDTM", MDTM, OSTR, 1, "<sp> path-name", 0, },
|
||
|
{ "SIZE", SIZE, OSTR, 1, "<sp> path-name", 0, },
|
||
|
{ "MLST", MLST, OSTR, 2, "[ <sp> path-name ]", 0, },
|
||
|
{ "MLSD", MLSD, OSTR, 1, "[ <sp> directory-name ]", 0, },
|
||
|
|
||
|
/* obsolete commands */
|
||
|
{ "MAIL", MAIL, OSTR, 0, "(mail to user)", 0, },
|
||
|
{ "MLFL", MLFL, OSTR, 0, "(mail file)", 0, },
|
||
|
{ "MRCP", MRCP, STR1, 0, "(mail recipient)", 0, },
|
||
|
{ "MRSQ", MRSQ, OSTR, 0, "(mail recipient scheme question)", 0, },
|
||
|
{ "MSAM", MSAM, OSTR, 0, "(mail send to terminal and mailbox)", 0, },
|
||
|
{ "MSND", MSND, OSTR, 0, "(mail send to terminal)", 0, },
|
||
|
{ "MSOM", MSOM, OSTR, 0, "(mail send to terminal or mailbox)", 0, },
|
||
|
{ "XCUP", CDUP, NOARGS, 1, "(change to parent directory)", 0, },
|
||
|
{ "XCWD", CWD, OSTR, 1, "[ <sp> directory-name ]", 0, },
|
||
|
{ "XMKD", MKD, STR1, 1, "<sp> path-name", 0, },
|
||
|
{ "XPWD", PWD, NOARGS, 1, "(return current directory)", 0, },
|
||
|
{ "XRMD", RMD, STR1, 1, "<sp> path-name", 0, },
|
||
|
|
||
|
{ NULL, 0, 0, 0, 0, 0, }
|
||
|
};
|
||
|
|
||
|
struct tab sitetab[] = {
|
||
|
{ "CHMOD", CHMOD, NSTR, 1, "<sp> mode <sp> file-name", 0, },
|
||
|
{ "HELP", HELP, OSTR, 1, "[ <sp> <string> ]", 0, },
|
||
|
{ "IDLE", IDLE, ARGS, 1, "[ <sp> maximum-idle-time ]", 0, },
|
||
|
{ "RATEGET", RATEGET,OSTR, 1, "[ <sp> get-throttle-rate ]", 0, },
|
||
|
{ "RATEPUT", RATEPUT,OSTR, 1, "[ <sp> put-throttle-rate ]", 0, },
|
||
|
{ "UMASK", UMASK, ARGS, 1, "[ <sp> umask ]", 0, },
|
||
|
{ NULL, 0, 0, 0, 0, 0, }
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* Check if a filename is allowed to be modified (isupload == 0) or
|
||
|
* uploaded (isupload == 1), and if necessary, check the filename is `sane'.
|
||
|
* If the filename is NULL, fail.
|
||
|
* If the filename is "", don't do the sane name check.
|
||
|
*/
|
||
|
static int
|
||
|
check_write(const char *file, int isupload)
|
||
|
{
|
||
|
if (file == NULL)
|
||
|
return (0);
|
||
|
if (! logged_in) {
|
||
|
reply(530, "Please login with USER and PASS.");
|
||
|
return (0);
|
||
|
}
|
||
|
/* checking modify */
|
||
|
if (! isupload && ! CURCLASS_FLAGS_ISSET(modify)) {
|
||
|
reply(502, "No permission to use this command.");
|
||
|
return (0);
|
||
|
}
|
||
|
/* checking upload */
|
||
|
if (isupload && ! CURCLASS_FLAGS_ISSET(upload)) {
|
||
|
reply(502, "No permission to use this command.");
|
||
|
return (0);
|
||
|
}
|
||
|
|
||
|
/* checking sanenames */
|
||
|
if (file[0] != '\0' && CURCLASS_FLAGS_ISSET(sanenames)) {
|
||
|
const char *p;
|
||
|
|
||
|
if (file[0] == '.')
|
||
|
goto insane_name;
|
||
|
for (p = file; *p; p++) {
|
||
|
if (isalnum((unsigned char)*p) || *p == '-' || *p == '+' ||
|
||
|
*p == ',' || *p == '.' || *p == '_')
|
||
|
continue;
|
||
|
insane_name:
|
||
|
reply(553, "File name `%s' not allowed.", file);
|
||
|
return (0);
|
||
|
}
|
||
|
}
|
||
|
return (1);
|
||
|
}
|
||
|
|
||
|
struct tab *
|
||
|
lookup(struct tab *p, const char *cmd)
|
||
|
{
|
||
|
|
||
|
for (; p->name != NULL; p++)
|
||
|
if (strcasecmp(cmd, p->name) == 0)
|
||
|
return (p);
|
||
|
return (0);
|
||
|
}
|
||
|
|
||
|
#include <arpa/telnet.h>
|
||
|
|
||
|
/*
|
||
|
* get_line - a hacked up version of fgets to ignore TELNET escape codes.
|
||
|
* `s' is the buffer to read into.
|
||
|
* `n' is the 1 less than the size of the buffer, to allow trailing NUL
|
||
|
* `iop' is the FILE to read from.
|
||
|
* Returns 0 on success, -1 on EOF, -2 if the command was too long.
|
||
|
*/
|
||
|
int
|
||
|
get_line(char *s, int n, FILE *iop)
|
||
|
{
|
||
|
int c;
|
||
|
char *cs;
|
||
|
|
||
|
cs = s;
|
||
|
/* tmpline may contain saved command from urgent mode interruption */
|
||
|
for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
|
||
|
*cs++ = tmpline[c];
|
||
|
if (tmpline[c] == '\n') {
|
||
|
*cs++ = '\0';
|
||
|
if (ftpd_debug)
|
||
|
syslog(LOG_DEBUG, "command: %s", s);
|
||
|
tmpline[0] = '\0';
|
||
|
return(0);
|
||
|
}
|
||
|
if (c == 0)
|
||
|
tmpline[0] = '\0';
|
||
|
}
|
||
|
while ((c = getc(iop)) != EOF) {
|
||
|
total_bytes++;
|
||
|
total_bytes_in++;
|
||
|
c &= 0377;
|
||
|
if (c == IAC) {
|
||
|
if ((c = getc(iop)) != EOF) {
|
||
|
total_bytes++;
|
||
|
total_bytes_in++;
|
||
|
c &= 0377;
|
||
|
switch (c) {
|
||
|
case WILL:
|
||
|
case WONT:
|
||
|
c = getc(iop);
|
||
|
total_bytes++;
|
||
|
total_bytes_in++;
|
||
|
cprintf(stdout, "%c%c%c", IAC, DONT, 0377&c);
|
||
|
(void) fflush(stdout);
|
||
|
continue;
|
||
|
case DO:
|
||
|
case DONT:
|
||
|
c = getc(iop);
|
||
|
total_bytes++;
|
||
|
total_bytes_in++;
|
||
|
cprintf(stdout, "%c%c%c", IAC, WONT, 0377&c);
|
||
|
(void) fflush(stdout);
|
||
|
continue;
|
||
|
case IAC:
|
||
|
break;
|
||
|
default:
|
||
|
continue; /* ignore command */
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
*cs++ = c;
|
||
|
if (--n <= 0) {
|
||
|
/*
|
||
|
* If command doesn't fit into buffer, discard the
|
||
|
* rest of the command and indicate truncation.
|
||
|
* This prevents the command to be split up into
|
||
|
* multiple commands.
|
||
|
*/
|
||
|
if (ftpd_debug)
|
||
|
syslog(LOG_DEBUG,
|
||
|
"command too long, last char: %d", c);
|
||
|
while (c != '\n' && (c = getc(iop)) != EOF)
|
||
|
continue;
|
||
|
return (-2);
|
||
|
}
|
||
|
if (c == '\n')
|
||
|
break;
|
||
|
}
|
||
|
if (c == EOF && cs == s)
|
||
|
return (-1);
|
||
|
*cs++ = '\0';
|
||
|
if (ftpd_debug) {
|
||
|
if ((curclass.type != CLASS_GUEST &&
|
||
|
strncasecmp(s, "PASS ", 5) == 0) ||
|
||
|
strncasecmp(s, "ACCT ", 5) == 0) {
|
||
|
/* Don't syslog passwords */
|
||
|
syslog(LOG_DEBUG, "command: %.4s ???", s);
|
||
|
} else {
|
||
|
char *cp;
|
||
|
int len;
|
||
|
|
||
|
/* Don't syslog trailing CR-LF */
|
||
|
len = strlen(s);
|
||
|
cp = s + len - 1;
|
||
|
while (cp >= s && (*cp == '\n' || *cp == '\r')) {
|
||
|
--cp;
|
||
|
--len;
|
||
|
}
|
||
|
syslog(LOG_DEBUG, "command: %.*s", len, s);
|
||
|
}
|
||
|
}
|
||
|
return (0);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
ftp_handle_line(char *cp)
|
||
|
{
|
||
|
|
||
|
cmdp = cp;
|
||
|
yyparse();
|
||
|
}
|
||
|
|
||
|
void
|
||
|
ftp_loop(void)
|
||
|
{
|
||
|
int ret;
|
||
|
|
||
|
while (1) {
|
||
|
(void) alarm(curclass.timeout);
|
||
|
ret = get_line(cbuf, sizeof(cbuf)-1, stdin);
|
||
|
(void) alarm(0);
|
||
|
if (ret == -1) {
|
||
|
reply(221, "You could at least say goodbye.");
|
||
|
dologout(0);
|
||
|
} else if (ret == -2) {
|
||
|
reply(500, "Command too long.");
|
||
|
} else {
|
||
|
ftp_handle_line(cbuf);
|
||
|
}
|
||
|
}
|
||
|
/*NOTREACHED*/
|
||
|
}
|
||
|
|
||
|
int
|
||
|
yylex(void)
|
||
|
{
|
||
|
static int cpos, state;
|
||
|
char *cp, *cp2;
|
||
|
struct tab *p;
|
||
|
int n;
|
||
|
char c;
|
||
|
|
||
|
switch (state) {
|
||
|
|
||
|
case CMD:
|
||
|
hasyyerrored = 0;
|
||
|
if ((cp = strchr(cmdp, '\r'))) {
|
||
|
*cp = '\0';
|
||
|
#if defined(HAVE_SETPROCTITLE)
|
||
|
if (strncasecmp(cmdp, "PASS", 4) != 0 &&
|
||
|
strncasecmp(cmdp, "ACCT", 4) != 0)
|
||
|
setproctitle("%s: %s", proctitle, cmdp);
|
||
|
#endif /* defined(HAVE_SETPROCTITLE) */
|
||
|
*cp++ = '\n';
|
||
|
*cp = '\0';
|
||
|
}
|
||
|
if ((cp = strpbrk(cmdp, " \n")))
|
||
|
cpos = cp - cmdp;
|
||
|
if (cpos == 0)
|
||
|
cpos = 4;
|
||
|
c = cmdp[cpos];
|
||
|
cmdp[cpos] = '\0';
|
||
|
p = lookup(cmdtab, cmdp);
|
||
|
cmdp[cpos] = c;
|
||
|
if (p != NULL) {
|
||
|
if (is_oob && ! CMD_OOB(p)) {
|
||
|
/* command will be handled in-band */
|
||
|
return (0);
|
||
|
} else if (! CMD_IMPLEMENTED(p)) {
|
||
|
reply(502, "%s command not implemented.",
|
||
|
p->name);
|
||
|
hasyyerrored = 1;
|
||
|
break;
|
||
|
}
|
||
|
state = p->state;
|
||
|
yylval.cs = p->name;
|
||
|
return (p->token);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case SITECMD:
|
||
|
if (cmdp[cpos] == ' ') {
|
||
|
cpos++;
|
||
|
return (SP);
|
||
|
}
|
||
|
cp = &cmdp[cpos];
|
||
|
if ((cp2 = strpbrk(cp, " \n")))
|
||
|
cpos = cp2 - cmdp;
|
||
|
c = cmdp[cpos];
|
||
|
cmdp[cpos] = '\0';
|
||
|
p = lookup(sitetab, cp);
|
||
|
cmdp[cpos] = c;
|
||
|
if (p != NULL) {
|
||
|
if (!CMD_IMPLEMENTED(p)) {
|
||
|
reply(502, "SITE %s command not implemented.",
|
||
|
p->name);
|
||
|
hasyyerrored = 1;
|
||
|
break;
|
||
|
}
|
||
|
state = p->state;
|
||
|
yylval.cs = p->name;
|
||
|
return (p->token);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case OSTR:
|
||
|
if (cmdp[cpos] == '\n') {
|
||
|
state = EOLN;
|
||
|
return (CRLF);
|
||
|
}
|
||
|
/* FALLTHROUGH */
|
||
|
|
||
|
case STR1:
|
||
|
case ZSTR1:
|
||
|
dostr1:
|
||
|
if (cmdp[cpos] == ' ') {
|
||
|
cpos++;
|
||
|
state = state == OSTR ? STR2 : state+1;
|
||
|
return (SP);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case ZSTR2:
|
||
|
if (cmdp[cpos] == '\n') {
|
||
|
state = EOLN;
|
||
|
return (CRLF);
|
||
|
}
|
||
|
/* FALLTHROUGH */
|
||
|
|
||
|
case STR2:
|
||
|
cp = &cmdp[cpos];
|
||
|
n = strlen(cp);
|
||
|
cpos += n - 1;
|
||
|
/*
|
||
|
* Make sure the string is nonempty and \n terminated.
|
||
|
*/
|
||
|
if (n > 1 && cmdp[cpos] == '\n') {
|
||
|
cmdp[cpos] = '\0';
|
||
|
yylval.s = ftpd_strdup(cp);
|
||
|
cmdp[cpos] = '\n';
|
||
|
state = ARGS;
|
||
|
return (STRING);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case NSTR:
|
||
|
if (cmdp[cpos] == ' ') {
|
||
|
cpos++;
|
||
|
return (SP);
|
||
|
}
|
||
|
if (isdigit((unsigned char)cmdp[cpos])) {
|
||
|
cp = &cmdp[cpos];
|
||
|
while (isdigit((unsigned char)cmdp[++cpos]))
|
||
|
;
|
||
|
c = cmdp[cpos];
|
||
|
cmdp[cpos] = '\0';
|
||
|
yylval.u.i = atoi(cp);
|
||
|
cmdp[cpos] = c;
|
||
|
state = STR1;
|
||
|
return (NUMBER);
|
||
|
}
|
||
|
state = STR1;
|
||
|
goto dostr1;
|
||
|
|
||
|
case ARGS:
|
||
|
if (isdigit((unsigned char)cmdp[cpos])) {
|
||
|
cp = &cmdp[cpos];
|
||
|
while (isdigit((unsigned char)cmdp[++cpos]))
|
||
|
;
|
||
|
c = cmdp[cpos];
|
||
|
cmdp[cpos] = '\0';
|
||
|
yylval.u.i = atoi(cp);
|
||
|
yylval.u.ll = STRTOLL(cp, NULL, 10);
|
||
|
cmdp[cpos] = c;
|
||
|
return (NUMBER);
|
||
|
}
|
||
|
if (strncasecmp(&cmdp[cpos], "ALL", 3) == 0
|
||
|
&& !isalnum((unsigned char)cmdp[cpos + 3])) {
|
||
|
cpos += 3;
|
||
|
return (ALL);
|
||
|
}
|
||
|
switch (cmdp[cpos++]) {
|
||
|
|
||
|
case '\n':
|
||
|
state = EOLN;
|
||
|
return (CRLF);
|
||
|
|
||
|
case ' ':
|
||
|
return (SP);
|
||
|
|
||
|
case ',':
|
||
|
return (COMMA);
|
||
|
|
||
|
case 'A':
|
||
|
case 'a':
|
||
|
return (A);
|
||
|
|
||
|
case 'B':
|
||
|
case 'b':
|
||
|
return (B);
|
||
|
|
||
|
case 'C':
|
||
|
case 'c':
|
||
|
return (C);
|
||
|
|
||
|
case 'E':
|
||
|
case 'e':
|
||
|
return (E);
|
||
|
|
||
|
case 'F':
|
||
|
case 'f':
|
||
|
return (F);
|
||
|
|
||
|
case 'I':
|
||
|
case 'i':
|
||
|
return (I);
|
||
|
|
||
|
case 'L':
|
||
|
case 'l':
|
||
|
return (L);
|
||
|
|
||
|
case 'N':
|
||
|
case 'n':
|
||
|
return (N);
|
||
|
|
||
|
case 'P':
|
||
|
case 'p':
|
||
|
return (P);
|
||
|
|
||
|
case 'R':
|
||
|
case 'r':
|
||
|
return (R);
|
||
|
|
||
|
case 'S':
|
||
|
case 's':
|
||
|
return (S);
|
||
|
|
||
|
case 'T':
|
||
|
case 't':
|
||
|
return (T);
|
||
|
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case NOARGS:
|
||
|
if (cmdp[cpos] == '\n') {
|
||
|
state = EOLN;
|
||
|
return (CRLF);
|
||
|
}
|
||
|
c = cmdp[cpos];
|
||
|
cmdp[cpos] = '\0';
|
||
|
reply(501, "'%s' command does not take any arguments.", cmdp);
|
||
|
hasyyerrored = 1;
|
||
|
cmdp[cpos] = c;
|
||
|
break;
|
||
|
|
||
|
case EOLN:
|
||
|
state = CMD;
|
||
|
return (0);
|
||
|
|
||
|
default:
|
||
|
fatal("Unknown state in scanner.");
|
||
|
}
|
||
|
yyerror(NULL);
|
||
|
state = CMD;
|
||
|
return (0);
|
||
|
}
|
||
|
|
||
|
/* ARGSUSED */
|
||
|
void
|
||
|
yyerror(const char *s)
|
||
|
{
|
||
|
char *cp;
|
||
|
|
||
|
if (hasyyerrored || is_oob)
|
||
|
return;
|
||
|
if ((cp = strchr(cmdp,'\n')) != NULL)
|
||
|
*cp = '\0';
|
||
|
reply(500, "'%s': command not understood.", cmdp);
|
||
|
hasyyerrored = 1;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
help(struct tab *ctab, const char *s)
|
||
|
{
|
||
|
struct tab *c;
|
||
|
int width, NCMDS;
|
||
|
const char *htype;
|
||
|
|
||
|
if (ctab == sitetab)
|
||
|
htype = "SITE ";
|
||
|
else
|
||
|
htype = "";
|
||
|
width = 0, NCMDS = 0;
|
||
|
for (c = ctab; c->name != NULL; c++) {
|
||
|
int len = strlen(c->name);
|
||
|
|
||
|
if (len > width)
|
||
|
width = len;
|
||
|
NCMDS++;
|
||
|
}
|
||
|
width = (width + 8) &~ 7;
|
||
|
if (s == 0) {
|
||
|
int i, j, w;
|
||
|
int columns, lines;
|
||
|
|
||
|
reply(-214, "%s", "");
|
||
|
reply(0, "The following %scommands are recognized.", htype);
|
||
|
reply(0, "(`-' = not implemented, `+' = supports options)");
|
||
|
columns = 76 / width;
|
||
|
if (columns == 0)
|
||
|
columns = 1;
|
||
|
lines = (NCMDS + columns - 1) / columns;
|
||
|
for (i = 0; i < lines; i++) {
|
||
|
cprintf(stdout, " ");
|
||
|
for (j = 0; j < columns; j++) {
|
||
|
c = ctab + j * lines + i;
|
||
|
cprintf(stdout, "%s", c->name);
|
||
|
w = strlen(c->name);
|
||
|
if (! CMD_IMPLEMENTED(c)) {
|
||
|
CPUTC('-', stdout);
|
||
|
w++;
|
||
|
}
|
||
|
if (CMD_HAS_OPTIONS(c)) {
|
||
|
CPUTC('+', stdout);
|
||
|
w++;
|
||
|
}
|
||
|
if (c + lines >= &ctab[NCMDS])
|
||
|
break;
|
||
|
while (w < width) {
|
||
|
CPUTC(' ', stdout);
|
||
|
w++;
|
||
|
}
|
||
|
}
|
||
|
cprintf(stdout, "\r\n");
|
||
|
}
|
||
|
(void) fflush(stdout);
|
||
|
reply(214, "Direct comments to ftp-bugs@%s.", hostname);
|
||
|
return;
|
||
|
}
|
||
|
c = lookup(ctab, s);
|
||
|
if (c == (struct tab *)0) {
|
||
|
reply(502, "Unknown command '%s'.", s);
|
||
|
return;
|
||
|
}
|
||
|
if (CMD_IMPLEMENTED(c))
|
||
|
reply(214, "Syntax: %s%s %s", htype, c->name, c->help);
|
||
|
else
|
||
|
reply(504, "%s%-*s\t%s; not implemented.", htype, width,
|
||
|
c->name, c->help);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Check that the structures used for a PORT, LPRT or EPRT command are
|
||
|
* valid (data_dest, his_addr), and if necessary, detect ftp bounce attacks.
|
||
|
* If family != -1 check that his_addr.su_family == family.
|
||
|
*/
|
||
|
static void
|
||
|
port_check(const char *cmd, int family)
|
||
|
{
|
||
|
char h1[NI_MAXHOST], h2[NI_MAXHOST];
|
||
|
char s1[NI_MAXHOST], s2[NI_MAXHOST];
|
||
|
#ifdef NI_WITHSCOPEID
|
||
|
const int niflags = NI_NUMERICHOST | NI_NUMERICSERV | NI_WITHSCOPEID;
|
||
|
#else
|
||
|
const int niflags = NI_NUMERICHOST | NI_NUMERICSERV;
|
||
|
#endif
|
||
|
|
||
|
if (epsvall) {
|
||
|
reply(501, "%s disallowed after EPSV ALL", cmd);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (family != -1 && his_addr.su_family != family) {
|
||
|
port_check_fail:
|
||
|
reply(500, "Illegal %s command rejected", cmd);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (data_dest.su_family != his_addr.su_family)
|
||
|
goto port_check_fail;
|
||
|
|
||
|
/* be paranoid, if told so */
|
||
|
if (CURCLASS_FLAGS_ISSET(checkportcmd)) {
|
||
|
#ifdef INET6
|
||
|
/*
|
||
|
* be paranoid, there are getnameinfo implementation that does
|
||
|
* not present scopeid portion
|
||
|
*/
|
||
|
if (data_dest.su_family == AF_INET6 &&
|
||
|
data_dest.su_scope_id != his_addr.su_scope_id)
|
||
|
goto port_check_fail;
|
||
|
#endif
|
||
|
|
||
|
if (getnameinfo((struct sockaddr *)&data_dest, data_dest.su_len,
|
||
|
h1, sizeof(h1), s1, sizeof(s1), niflags))
|
||
|
goto port_check_fail;
|
||
|
if (getnameinfo((struct sockaddr *)&his_addr, his_addr.su_len,
|
||
|
h2, sizeof(h2), s2, sizeof(s2), niflags))
|
||
|
goto port_check_fail;
|
||
|
|
||
|
if (atoi(s1) < IPPORT_RESERVED || strcmp(h1, h2) != 0)
|
||
|
goto port_check_fail;
|
||
|
}
|
||
|
|
||
|
usedefault = 0;
|
||
|
if (pdata >= 0) {
|
||
|
(void) close(pdata);
|
||
|
pdata = -1;
|
||
|
}
|
||
|
reply(200, "%s command successful.", cmd);
|
||
|
}
|