/* * $Id: ftp_command.c,v 1.19 2004/03/25 20:46:48 shane Exp $ */ #include #include #include #include #include #include #include #include #include #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= 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 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; }