/*
* $Id: ftp_command.c,v 1.19 2004/03/25 20:46:48 shane Exp $
*/
#include <config.h>
#include <string.h>
#include <ctype.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include "ftp_command.h"
#include "af_portability.h"
#include "daemon_assert.h"
/* argument types */
#define ARG_NONE 0
#define ARG_STRING 1
#define ARG_OPTIONAL_STRING 2
#define ARG_HOST_PORT 3
#define ARG_TYPE 4
#define ARG_STRUCTURE 5
#define ARG_MODE 6
#define ARG_OFFSET 7
#define ARG_HOST_PORT_LONG 8
#define ARG_HOST_PORT_EXT 9
#define ARG_OPTIONAL_NUMBER 10
/* our FTP commands */
struct {
char *name;
int arg_type;
} command_def[] = {
{ "USER", ARG_STRING },
{ "PASS", ARG_STRING },
{ "CWD", ARG_STRING },
{ "CDUP", ARG_NONE },
{ "QUIT", ARG_NONE },
{ "PORT", ARG_HOST_PORT },
{ "LPRT", ARG_HOST_PORT_LONG },
{ "EPRT", ARG_HOST_PORT_EXT },
{ "PASV", ARG_NONE },
{ "LPSV", ARG_NONE },
{ "EPSV", ARG_OPTIONAL_NUMBER },
{ "TYPE", ARG_TYPE },
{ "STRU", ARG_STRUCTURE },
{ "MODE", ARG_MODE },
{ "RETR", ARG_STRING },
{ "STOR", ARG_STRING },
{ "PWD", ARG_NONE },
{ "LIST", ARG_OPTIONAL_STRING },
{ "NLST", ARG_OPTIONAL_STRING },
{ "SYST", ARG_NONE },
{ "HELP", ARG_OPTIONAL_STRING },
{ "NOOP", ARG_NONE },
{ "REST", ARG_OFFSET },
{ "SIZE", ARG_STRING },
{ "MDTM", ARG_STRING }
};
#define NUM_COMMAND (sizeof(command_def) / sizeof(command_def[0]))
/* prototypes */
static const char *copy_string(char *dst, const char *src);
static const char *parse_host_port(struct sockaddr_in *addr, const char *s);
static const char *parse_number(int *num, const char *s, int max_num);
static const char *parse_offset(off_t *ofs, const char *s);
static const char *parse_host_port_long(sockaddr_storage_t *sa, const char *s);
static const char *parse_host_port_ext(sockaddr_storage_t *sa, const char *s);
int ftp_command_parse(const char *input, ftp_command_t *cmd)
{
int i;
int len;
int match;
ftp_command_t tmp;
int c;
const char *optional_number;
daemon_assert(input != NULL);
daemon_assert(cmd != NULL);
/* see if our input starts with a valid command */
match = -1;
for (i=0; (i<NUM_COMMAND) && (match == -1); i++) {
len = strlen(command_def[i].name);
if (strncasecmp(input, command_def[i].name, len) == 0) {
match = i;
}
}
/* if we didn't find a match, return error */
if (match == -1) {
return 0;
}
daemon_assert(match >= 0);
daemon_assert(match < NUM_COMMAND);
/* copy our command */
strcpy(tmp.command, command_def[match].name);
/* advance input past the command */
input += strlen(command_def[match].name);
/* now act based on the command */
switch (command_def[match].arg_type) {
case ARG_NONE:
tmp.num_arg = 0;
break;
case ARG_STRING:
if (*input != ' ') {
return 0;
}
++input;
input = copy_string(tmp.arg[0].string, input);
tmp.num_arg = 1;
break;
case ARG_OPTIONAL_STRING:
if (*input == ' ') {
++input;
input = copy_string(tmp.arg[0].string, input);
tmp.num_arg = 1;
} else {
tmp.num_arg = 0;
}
break;
case ARG_HOST_PORT:
if (*input != ' ') {
return 0;
}
input++;
/* parse the host & port information (if any) */
input = parse_host_port(&tmp.arg[0].host_port, input);
if (input == NULL) {
return 0;
}
tmp.num_arg = 1;
break;
case ARG_HOST_PORT_LONG:
if (*input != ' ') {
return 0;
}
input++;
/* parse the host & port information (if any) */
input = parse_host_port_long(&tmp.arg[0].host_port, input);
if (input == NULL) {
return 0;
}
tmp.num_arg = 1;
break;
case ARG_HOST_PORT_EXT:
if (*input != ' ') {
return 0;
}
input++;
/* parse the host & port information (if any) */
input = parse_host_port_ext(&tmp.arg[0].host_port, input);
if (input == NULL) {
return 0;
}
tmp.num_arg = 1;
break;
/* the optional number may also be "ALL" */
case ARG_OPTIONAL_NUMBER:
if (*input == ' ') {
++input;
optional_number = parse_number(&tmp.arg[0].num, input, 255);
if (optional_number != NULL) {
input = optional_number;
} else {
if ((tolower(input[0]) == 'a') &&
(tolower(input[1]) == 'l') &&
(tolower(input[2]) == 'l'))
{
tmp.arg[0].num = EPSV_ALL;
input += 3;
} else {
return 0;
}
}
tmp.num_arg = 1;
} else {
tmp.num_arg = 0;
}
break;
case ARG_TYPE:
if (*input != ' ') {
return 0;
}
input++;
c = toupper(*input);
if ((c == 'A') || (c == 'E')) {
tmp.arg[0].string[0] = c;
tmp.arg[0].string[1] = '\0';
input++;
if (*input == ' ') {
input++;
c = toupper(*input);
if ((c != 'N') && (c != 'T') && (c != 'C')) {
return 0;
}
tmp.arg[1].string[0] = c;
tmp.arg[1].string[1] = '\0';
input++;
tmp.num_arg = 2;
} else {
tmp.num_arg = 1;
}
} else if (c == 'I') {
tmp.arg[0].string[0] = 'I';
tmp.arg[0].string[1] = '\0';
input++;
tmp.num_arg = 1;
} else if (c == 'L') {
tmp.arg[0].string[0] = 'L';
tmp.arg[0].string[1] = '\0';
input++;
input = parse_number(&tmp.arg[1].num, input, 255);
if (input == NULL) {
return 0;
}
tmp.num_arg = 2;
} else {
return 0;
}
break;
case ARG_STRUCTURE:
if (*input != ' ') {
return 0;
}
input++;
c = toupper(*input);
if ((c != 'F') && (c != 'R') && (c != 'P')) {
return 0;
}
input++;
tmp.arg[0].string[0] = c;
tmp.arg[0].string[1] = '\0';
tmp.num_arg = 1;
break;
case ARG_MODE:
if (*input != ' ') {
return 0;
}
input++;
c = toupper(*input);
if ((c != 'S') && (c != 'B') && (c != 'C')) {
return 0;
}
input++;
tmp.arg[0].string[0] = c;
tmp.arg[0].string[1] = '\0';
tmp.num_arg = 1;
break;
case ARG_OFFSET:
if (*input != ' ') {
return 0;
}
input++;
input = parse_offset(&tmp.arg[0].offset, input);
if (input == NULL) {
return 0;
}
tmp.num_arg = 1;
break;
default:
daemon_assert(0);
}
/* check for our ending newline */
if (*input != '\n') {
return 0;
}
/* return our result */
*cmd = tmp;
return 1;
}
/* copy a string terminated with a newline */
static const char *copy_string(char *dst, const char *src)
{
int i;
daemon_assert(dst != NULL);
daemon_assert(src != NULL);
for (i=0; (i<=MAX_STRING_LEN) && (src[i]!='\0') && (src[i]!='\n'); i++) {
dst[i] = src[i];
}
dst[i] = '\0';
return src+i;
}
static const char *parse_host_port(struct sockaddr_in *addr, const char *s)
{
int i;
int octets[6];
char addr_str[16];
int port;
struct in_addr in_addr;
daemon_assert(addr != NULL);
daemon_assert(s != NULL);
/* scan in 5 pairs of "#," */
for (i=0; i<5; i++) {
s = parse_number(&octets[i], s, 255);
if (s == NULL) {
return NULL;
}
if (*s != ',') {
return NULL;
}
s++;
}
/* scan in ending "#" */
s = parse_number(&octets[5], s, 255);
if (s == NULL) {
return NULL;
}
daemon_assert(octets[0] >= 0);
daemon_assert(octets[0] <= 255);
daemon_assert(octets[1] >= 0);
daemon_assert(octets[1] <= 255);
daemon_assert(octets[2] >= 0);
daemon_assert(octets[2] <= 255);
daemon_assert(octets[3] >= 0);
daemon_assert(octets[3] <= 255);
/* convert our number to a IP/port */
sprintf(addr_str, "%d.%d.%d.%d",
octets[0], octets[1], octets[2], octets[3]);
port = (octets[4] << 8) | octets[5];
#ifdef HAVE_INET_ATON
if (inet_aton(addr_str, &in_addr) == 0) {
return NULL;
}
#else
in_addr.s_addr = inet_addr(addr_str);
if (in_addr.s_addr == -1) {
return NULL;
}
#endif
addr->sin_family = AF_INET;
addr->sin_addr = in_addr;
addr->sin_port = htons(port);
/* return success */
return s;
}
/* note: returns success even for unknown address families */
/* this is okay, as long as subsequent uses VERIFY THE FAMILY first */
static const char *parse_host_port_long(sockaddr_storage_t *sa, const char *s)
{
int i;
int family;
int tmp;
int addr_len;
unsigned char addr[255];
int port_len;
unsigned char port[255];
/* we are family */
s = parse_number(&family, s, 255);
if (s == NULL) {
return NULL;
}
if (*s != ',') {
return NULL;
}
s++;
/* parse host length */
s = parse_number(&addr_len, s, 255);
if (s == NULL) {
return NULL;
}
if (*s != ',') {
return NULL;
}
s++;
/* parse address */
for (i=0; i<addr_len; i++) {
daemon_assert(i < sizeof(addr)/sizeof(addr[0]));
s = parse_number(&tmp, s, 255);
addr[i] = tmp;
if (s == NULL) {
return NULL;
}
if (*s != ',') {
return NULL;
}
s++;
}
/* parse port length */
s = parse_number(&port_len, s, 255);
if (s == NULL) {
return NULL;
}
/* parse port */
for (i=0; i<port_len; i++) {
if (*s != ',') {
return NULL;
}
s++;
daemon_assert(i < sizeof(port)/sizeof(port[0]));
s = parse_number(&tmp, s, 255);
port[i] = tmp;
}
/* okay, everything parses, load the address if possible */
if (family == 4) {
SAFAM(sa) = AF_INET;
if (addr_len != sizeof(struct in_addr)) {
return NULL;
}
if (port_len != 2) {
return NULL;
}
memcpy(&SINADDR(sa), addr, addr_len);
SINPORT(sa) = htons((port[0] << 8) + port[1]);
}
#ifdef INET6
else if (family == 6) {
SAFAM(sa) = AF_INET6;
if (addr_len != sizeof(struct in6_addr)) {
return NULL;
}
if (port_len != 2) {
return NULL;
}
memcpy(&SIN6ADDR(sa), addr, addr_len);
SINPORT(sa) = htons((port[0] << 8) + port[1]);
}
#endif
else {
SAFAM(sa) = -1;
}
/* return new pointer */
return s;
}
static const char *parse_host_port_ext(sockaddr_storage_t *sa, const char *s)
{
int delimeter;
int family;
char *p;
int len;
char addr_str[256];
int port;
/* get delimeter character */
if ((*s < 33) || (*s > 126)) {
return NULL;
}
delimeter = *s;
s++;
/* get address family */
if (*s == '1') {
family = AF_INET;
}
#ifdef INET6
else if (*s == '2') {
family = AF_INET6;
}
#endif
else {
return NULL;
}
s++;
if (*s != delimeter) {
return NULL;
}
s++;
/* get address string */
p = strchr(s, delimeter);
if (p == NULL) {
return NULL;
}
len = p - s;
if (len >= sizeof(addr_str)) {
return NULL;
}
memcpy(addr_str, s, len);
addr_str[len] = '\0';
s = p+1;
/* get port */
s = parse_number(&port, s, 65535);
if (s == NULL) {
return NULL;
}
if (*s != delimeter) {
return NULL;
}
s++;
/* now parse the value passed */
#ifndef INET6
{
struct in_addr in_addr;
#ifdef HAVE_INET_ATON
if (inet_aton(addr_str, &in_addr) == 0) {
return NULL;
}
#else
in_addr.s_addr = inet_addr(addr_str);
if (in_addr.s_addr == -1) {
return NULL;
}
#endif /* HAVE_INET_ATON */
SIN4ADDR(sa) = in_addr;
}
#else
{
struct addrinfo hints;
struct *res;
memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_NUMERICHOST;
hints.ai_family = family;
if (getaddrinfo(addr_str, NULL, &hints, &res) != 0) {
return NULL;
}
memcpy(sa, res->ai_addr, res->ai_addrlen);
freeaddrinfo(res);
}
#endif /* INET6 */
SAFAM(sa) = family;
SINPORT(sa) = htons(port);
/* return new pointer */
return s;
}
/* scan the string for a number from 0 to max_num */
/* returns the next non-numberic character */
/* returns NULL if not at least one digit */
static const char *parse_number(int *num, const char *s, int max_num)
{
int tmp;
int cur_digit;
daemon_assert(s != NULL);
daemon_assert(num != NULL);
/* handle first character */
if (!isdigit(*s)) {
return NULL;
}
tmp = (*s - '0');
s++;
/* handle subsequent characters */
while (isdigit(*s)) {
cur_digit = (*s - '0');
/* check for overflow */
if ((max_num - cur_digit) < (tmp * 10)) {
return NULL;
}
tmp *= 10;
tmp += cur_digit;
s++;
}
daemon_assert(tmp >= 0);
daemon_assert(tmp <= max_num);
/* return the result */
*num = tmp;
return s;
}
static const char *parse_offset(off_t *ofs, const char *s)
{
off_t tmp_ofs;
int cur_digit;
off_t max_ofs;
daemon_assert(ofs != NULL);
daemon_assert(s != NULL);
/* calculate maximum allowable offset based on size of off_t */
if (sizeof(off_t) == 8) {
max_ofs = (off_t)9223372036854775807LL;
} else if (sizeof(off_t) == 4) {
max_ofs = (off_t)2147483647LL;
} else if (sizeof(off_t) == 2) {
max_ofs = (off_t)32767LL;
} else {
max_ofs = 0;
}
daemon_assert(max_ofs != 0);
/* handle first character */
if (!isdigit(*s)) {
return NULL;
}
tmp_ofs = (*s - '0');
s++;
/* handle subsequent characters */
while (isdigit(*s)) {
cur_digit = (*s - '0');
/* check for overflow */
if ((max_ofs - cur_digit) < (tmp_ofs * 10)) {
return NULL;
}
tmp_ofs *= 10;
tmp_ofs += cur_digit;
s++;
}
/* return */
*ofs = tmp_ofs;
return s;
}
syntax highlighted by Code2HTML, v. 0.9.1