static int parse_version(char *version_str, unsigned int *version, unsigned int *version_status) { register unsigned int i; char c, *ptr, *tmp; int have_suffix = FALSE; size_t revision_len = 0; /* Parse the given version string. We expect to see: * * major[.minor[.revision[suffix]]] * * Examples: * * "1" * "1.3" * "1.3.3" * "1.3.3rc1" * "1.3.3a" */ /* Quick sanity check */ if (!PR_ISDIGIT(version_str[0])) { return -1; } /* Parse the major number */ ptr = strchr(version_str, '.'); if (ptr) { *ptr = '\0'; tmp = NULL; version[0] = (int) strtoul(version_str, &tmp, 10); if (tmp && *tmp) { *ptr = '.'; return -1; } *ptr = '.'; /* Make sure that there is a character following the period. */ if (*(ptr + 1) != '\0') { version_str = ptr + 1; } else { return -1; } } else { tmp = NULL; version[0] = (int) strtoul(version_str, &tmp, 10); if (tmp && *tmp) { return -1; } return 0; } /* Parse the minor number */ ptr = strchr(version_str, '.'); if (ptr) { *ptr = '\0'; tmp = NULL; version[1] = (int) strtoul(version_str, &tmp, 10); if (tmp && *tmp) { *ptr = '.'; return -1; } *ptr = '.'; /* Make sure that there is a character following the period. */ if (*(ptr + 1) != '\0') { version_str = ptr + 1; } else { return -1; } } else { tmp = NULL; version[1] = (int) strtoul(version_str, &tmp, 10); if (tmp && *tmp) { return -1; } return 0; } /* Parse the revision number. This is trickier, since we have to also * account for the suffix, and there is no delimiter between the revision * number and the suffix characters. * * We thus scan every character from here on out. If they are all digits, * then it is a "stable" release (no suffix). Otherwise, it's either an * RC release, a maintenance release, or it's a badly formatted version * string. */ for (i = 0; i < strlen(version_str); i++) { if (!PR_ISDIGIT(version_str[i])) { if (i > 0) { have_suffix = TRUE; break; } /* Syntax error */ return -1; } else { /* Keep track of the number of characters in the revision number; this * is handy for afterwards, assuming we do have a suffix. */ revision_len++; } } if (!have_suffix) { tmp = NULL; version[2] = (int) strtoul(version_str, &tmp, 10); if (tmp && *tmp) { return -1; } /* Stable release */ *version_status = IFVERSION_STATUS_STABLE; return 0; } ptr = version_str + revision_len; c = *ptr; *ptr = '\0'; tmp = NULL; version[2] = (int) strtoul(version_str, &tmp, 10); if (tmp && *tmp) { *ptr = c; return -1; } *ptr = c; /* We already know, based on the suffix check, that there are characters * after the revision number. */ version_str = ptr; /* If the next two characters are "rc" (case-insensitive) followed by * digits, it's an RC release. (If there are no digits, it's a syntax * error.) * * If there only a single character left, it is a maintenance release. */ if (strlen(version_str) == 1) { if (!PR_ISALPHA(version_str[0])) { /* Syntax error */ return -1; } /* Maintenance release. */ c = toupper(version_str[0]); *version_status = IFVERSION_STATUS_STABLE + (c - 'A'); return 0; } if (strncasecmp(version_str, "rc", 2) != 0) { return -1; } /* RC release */ *version_status = IFVERSION_STATUS_RC; if (strlen(version_str) == 2) { return 0; } version_str += 2; for (i = 0; i < strlen(version_str); i++) { if (!PR_ISDIGIT(version_str[i])) { /* Syntax error */ return -1; } } tmp = NULL; *version_status += strtoul(version_str, &tmp, 10); if (tmp && *tmp) { return -1; } return 0; }
const pr_netaddr_t *proxy_ftp_msg_parse_ext_addr(pool *p, const char *msg, const pr_netaddr_t *addr, int cmd_id, const char *net_proto) { pr_netaddr_t *res = NULL, na; int family = 0; unsigned short port = 0; char delim, *msg_str, *ptr; size_t msglen; if (p == NULL || msg == NULL || addr == NULL) { errno = EINVAL; return NULL; } if (cmd_id == PR_CMD_EPSV_ID) { /* First, find the opening '(' character. */ ptr = strchr(msg, '('); if (ptr == NULL) { pr_trace_msg(trace_channel, 12, "missing starting '(' character for extended address in '%s'", msg); errno = EINVAL; return NULL; } /* Make sure that the last character is a closing ')'. */ msglen = strlen(ptr); if (ptr[msglen-1] != ')') { pr_trace_msg(trace_channel, 12, "missing ending ')' character for extended address in '%s'", msg); errno = EINVAL; return NULL; } msg_str = pstrndup(p, ptr+1, msglen-2); } else { msg_str = pstrdup(p, msg); } /* Format is <d>proto<d>ip address<d>port<d> (ASCII in network order), * where <d> is an arbitrary delimiter character. */ delim = *msg_str++; /* If the network protocol string (e.g. sent by client in EPSV command) is * null, then determine the protocol family from the address family we were * given. */ /* XXX Hack to skip "all", e.g. "EPSV ALL" commands. */ if (net_proto != NULL) { if (strncasecmp(net_proto, "all", 4) == 0) { net_proto = NULL; } } if (net_proto == NULL) { if (*msg_str == delim) { switch (pr_netaddr_get_family(addr)) { case AF_INET: family = 1; break; #ifdef PR_USE_IPV6 case AF_INET6: if (pr_netaddr_use_ipv6()) { family = 2; break; } #endif /* PR_USE_IPV6 */ default: break; } } else { family = atoi(msg_str); } } else { family = atoi(net_proto); } switch (family) { case 1: pr_trace_msg(trace_channel, 19, "parsed IPv4 address from '%s'", msg); break; #ifdef PR_USE_IPV6 case 2: pr_trace_msg(trace_channel, 19, "parsed IPv6 address from '%s'", msg); if (pr_netaddr_use_ipv6()) { break; } #endif /* PR_USE_IPV6 */ default: pr_trace_msg(trace_channel, 12, "unsupported network protocol %d", family); errno = EPROTOTYPE; return NULL; } /* Now, skip past those numeric characters that atoi() used. */ while (PR_ISDIGIT(*msg_str)) { msg_str++; } /* If the next character is not the delimiter, it's a badly formatted * parameter. */ if (*msg_str == delim) { msg_str++; } else { pr_trace_msg(trace_channel, 17, "rejecting badly formatted message '%s'", msg_str); errno = EPERM; return NULL; } pr_netaddr_clear(&na); /* If the next character IS the delimiter, then the address portion is * omitted (which is permissible). */ if (*msg_str == delim) { pr_netaddr_set_family(&na, pr_netaddr_get_family(addr)); pr_netaddr_set_sockaddr(&na, pr_netaddr_get_sockaddr(addr)); msg_str++; } else { ptr = strchr(msg_str, delim); if (ptr == NULL) { /* Badly formatted message. */ errno = EINVAL; return NULL; } /* Twiddle the string so that just the address portion will be processed * by pr_inet_pton(). */ *ptr = '\0'; /* Use pr_inet_pton() to translate the address string into the address * value. */ switch (family) { case 1: { struct sockaddr *sa = NULL; pr_netaddr_set_family(&na, AF_INET); sa = pr_netaddr_get_sockaddr(&na); if (sa) { sa->sa_family = AF_INET; } if (pr_inet_pton(AF_INET, msg_str, pr_netaddr_get_inaddr(&na)) <= 0) { pr_trace_msg(trace_channel, 2, "error converting IPv4 address '%s': %s", msg_str, strerror(errno)); errno = EPERM; return NULL; } break; } case 2: { struct sockaddr *sa = NULL; pr_netaddr_set_family(&na, AF_INET6); sa = pr_netaddr_get_sockaddr(&na); if (sa) { sa->sa_family = AF_INET6; } if (pr_inet_pton(AF_INET6, msg_str, pr_netaddr_get_inaddr(&na)) <= 0) { pr_trace_msg(trace_channel, 2, "error converting IPv6 address '%s': %s", msg_str, strerror(errno)); errno = EPERM; return NULL; } break; } } /* Advance past the address portion of the argument. */ msg_str = ++ptr; } port = atoi(msg_str); while (PR_ISDIGIT(*msg_str)) { msg_str++; } /* If the next character is not the delimiter, it's a badly formatted * parameter. */ if (*msg_str != delim) { pr_trace_msg(trace_channel, 17, "rejecting badly formatted message '%s'", msg_str); errno = EPERM; return NULL; } /* XXX Use a pool other than session.pool here, in the future. */ res = pr_netaddr_dup(session.pool, &na); pr_netaddr_set_port(res, htons(port)); return res; }
pr_response_t *proxy_ftp_ctrl_recv_resp(pool *p, conn_t *ctrl_conn, unsigned int *nlines) { char buf[PR_TUNABLE_BUFFER_SIZE]; pr_response_t *resp = NULL; int multiline = FALSE; unsigned int count = 0; while (TRUE) { char c, *ptr; int resp_code; size_t buflen; pr_signals_handle(); memset(buf, '\0', sizeof(buf)); if (ftp_telnet_gets(buf, sizeof(buf)-1, ctrl_conn->instrm, ctrl_conn) == NULL) { return NULL; } buflen = strlen(buf); /* Remove any trailing CRs, LFs. */ while (buflen > 0 && (buf[buflen-1] == '\r' || buf[buflen-1] == '\n')) { pr_signals_handle(); buf[buflen-1] = '\0'; buflen--; } /* If we are the first line of the response, the first three characters * MUST be numeric, followed by a hypen. Anything else is nonconformant * with RFC 959. * * If we are NOT the first line of the response, then we are probably * handling a multiline response. If the first character is a space, then * this is a continuation line. Otherwise, the first three characters * MUST be numeric, AND MUST match the numeric code from the first line. * This indicates the last line in the multiline response -- and the * character after the numerics MUST be a space. * */ if (resp == NULL) { /* First line of a possibly multiline response (or just the only line). */ if (buflen < 4) { pr_trace_msg(trace_channel, 12, "read %lu characters of response, needed at least %d", (unsigned long) buflen, 4); errno = EINVAL; return NULL; } if (!PR_ISDIGIT((int) buf[0]) || !PR_ISDIGIT((int) buf[1]) || !PR_ISDIGIT((int) buf[2])) { pr_trace_msg(trace_channel, 1, "non-numeric characters in start of response data: '%c%c%c'", buf[0], buf[1], buf[2]); errno = EINVAL; return NULL; } /* If this is a space, then we have a single line response. If it * is a hyphen, then this is the first line of a multiline response. */ if (buf[3] != ' ' && buf[3] != '-') { pr_trace_msg(trace_channel, 1, "unexpected character '%c' following numeric response code", buf[3]); errno = EINVAL; return NULL; } if (buf[3] == '-') { multiline = TRUE; } count++; resp = (pr_response_t *) pcalloc(p, sizeof(pr_response_t)); } else { if (buflen >= 1) { if (buf[0] == ' ') { /* Continuation line; append it the existing response. */ if (buflen > 1) { resp->msg = pstrcat(p, resp->msg, "\r\n", buf, NULL); } count++; continue; } else { /* Possible ending line of multiline response. */ if (buflen < 4) { errno = EINVAL; return NULL; } if (!PR_ISDIGIT((int) buf[0]) || !PR_ISDIGIT((int) buf[1]) || !PR_ISDIGIT((int) buf[2])) { pr_trace_msg(trace_channel, 1, "non-numeric characters in end of response data: '%c%c%c'", buf[0], buf[1], buf[2]); errno = EINVAL; return NULL; } if (buf[3] != ' ') { errno = EINVAL; return NULL; } count++; } } } ptr = &(buf[3]); c = *ptr; *ptr = '\0'; resp_code = atoi(buf); if (resp_code < 100 || resp_code >= 700) { /* Outside of the expected/defined FTP response code range. */ pr_trace_msg(trace_channel, 1, "invalid FTP response code %d received", resp_code); errno = EINVAL; return NULL; } if (resp->num == NULL) { resp->num = pstrdup(p, buf); } else { /* Make sure the last line of the multiline response uses the same * response code. */ if (strncmp(resp->num, buf, 3) != 0) { pr_trace_msg(trace_channel, 1, "invalid multiline FTP response: mismatched starting response " "code (%s) and ending response code (%s)", resp->num, buf); errno = EINVAL; return NULL; } } if (resp->msg == NULL) { if (buflen > 4) { if (multiline) { *ptr = c; resp->msg = pstrdup(p, ptr); *ptr = '\0'; } else { resp->msg = pstrdup(p, ptr + 1); } } else { resp->msg = ""; } /* If the character after the response code was a space, then this is * a single line response; we can be done now. */ if (c == ' ') { break; } } else { if (buflen > 4) { if (multiline) { *ptr = c; /* This the last line of a multiline response, which means we * need the ENTIRE line, including the response code. */ resp->msg = pstrcat(p, resp->msg, "\r\n", buf, NULL); } else { resp->msg = pstrcat(p, resp->msg, "\r\n", ptr + 1, NULL); } } break; } } *nlines = count; pr_trace_msg(trace_channel, 9, "received '%s%s%s' response from backend to frontend", resp->num, multiline ? "" : " ", resp->msg); return resp; }