propagator/url.c
Alexey Sheplyakov 24db85c5ab http: fixed Content-Length header validation
Content-Length is not necesserily the last header, more headers
can follow it.

Closes: #41072
2021-10-07 16:43:36 +04:00

568 lines
12 KiB
C

/*
* Guillaume Cottenceau (gc@mandrakesoft.com)
*
* Copyright 2000 MandrakeSoft
*
* This software may be freely redistributed under the terms of the GNU
* public license.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
/*
* Portions from Erik Troan <ewt@redhat.com> and Matt Wilson <msw@redhat.com>
*
* Copyright 1999 Red Hat, Inc.
*
*/
#include <alloca.h>
// #include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in_systm.h>
#include <limits.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// #include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/poll.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include "dns.h"
#include "log.h"
#include "tools.h"
#include "url.h"
#define TIMEOUT_SECS 60
#define BUFFER_SIZE 4096
static int ftp_check_response(int sock, char ** str)
{
static char buf[BUFFER_SIZE + 1];
int bufLength = 0;
struct pollfd polls;
char * chptr, * start;
int bytesRead, rc = 0;
int doesContinue = 1;
char errorCode[4];
errorCode[0] = '\0';
do {
polls.fd = sock;
polls.events = POLLIN;
if (poll(&polls, 1, TIMEOUT_SECS*1000) != 1)
return FTPERR_BAD_SERVER_RESPONSE;
bytesRead = read(sock, buf + bufLength, sizeof(buf) - bufLength - 1);
bufLength += bytesRead;
buf[bufLength] = '\0';
/* divide the response into lines, checking each one to see if
we are finished or need to continue */
start = chptr = buf;
do {
while (*chptr != '\n' && *chptr) chptr++;
if (*chptr == '\n') {
*chptr = '\0';
if (*(chptr - 1) == '\r') *(chptr - 1) = '\0';
if (str) *str = start;
if (errorCode[0]) {
if (!strncmp(start, errorCode, 3) && start[3] == ' ')
doesContinue = 0;
} else {
strncpy(errorCode, start, 3);
errorCode[3] = '\0';
if (start[3] != '-') {
doesContinue = 0;
}
}
start = chptr + 1;
chptr++;
} else {
chptr++;
}
} while (*chptr);
if (doesContinue && chptr > start) {
memcpy(buf, start, chptr - start - 1);
bufLength = chptr - start - 1;
} else {
bufLength = 0;
}
} while (doesContinue);
if (*errorCode == '4' || *errorCode == '5') {
if (!strncmp(errorCode, "550", 3)) {
return FTPERR_FILE_NOT_FOUND;
}
return FTPERR_BAD_SERVER_RESPONSE;
}
if (rc) return rc;
return 0;
}
static int ftp_command(int sock, char * command, char * param)
{
char buf[500];
int rc;
strcpy(buf, command);
if (param) {
strcat(buf, " ");
strcat(buf, param);
}
strcat(buf, "\r\n");
if (write(sock, buf, strlen(buf)) != strlen(buf)) {
return FTPERR_SERVER_IO_ERROR;
}
if ((rc = ftp_check_response(sock, NULL)))
return rc;
return 0;
}
static int get_host_address(char * host, struct in_addr * address)
{
if (isdigit(host[0])) {
if (!inet_aton(host, address)) {
return FTPERR_BAD_HOST_ADDR;
}
} else {
if (mygethostbyname(host, address))
return FTPERR_BAD_HOSTNAME;
}
return 0;
}
int ftp_open_connection(char * host, char * name, char * password, char * proxy)
{
int sock;
struct in_addr serverAddress;
struct sockaddr_in destPort;
char * buf;
int rc;
int port = 21;
if (!strcmp(name, "")) {
name = "anonymous";
password = "altinst@";
}
if (strcmp(proxy, "")) {
buf = alloca(strlen(name) + strlen(host) + 5);
sprintf(buf, "%s@%s", name, host);
name = buf;
host = proxy;
}
if ((rc = get_host_address(host, &serverAddress))) return rc;
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
if (sock < 0) {
return FTPERR_FAILED_CONNECT;
}
destPort.sin_family = AF_INET;
destPort.sin_port = htons(port);
destPort.sin_addr = serverAddress;
if (connect(sock, (struct sockaddr *) &destPort, sizeof(destPort))) {
close(sock);
return FTPERR_FAILED_CONNECT;
}
/* ftpCheckResponse() assumes the socket is nonblocking */
if (fcntl(sock, F_SETFL, O_NONBLOCK)) {
close(sock);
return FTPERR_FAILED_CONNECT;
}
if ((rc = ftp_check_response(sock, NULL))) {
return rc;
}
if ((rc = ftp_command(sock, "USER", name))) {
close(sock);
return rc;
}
if ((rc = ftp_command(sock, "PASS", password))) {
close(sock);
return rc;
}
if ((rc = ftp_command(sock, "TYPE", "I"))) {
close(sock);
return rc;
}
return sock;
}
int ftp_data_command(int sock, char * command, char * param)
{
int dataSocket;
struct sockaddr_in dataAddress;
int i, j;
char * passReply;
char * chptr;
char retrCommand[500];
int rc;
if (write(sock, "PASV\r\n", 6) != 6) {
return FTPERR_SERVER_IO_ERROR;
}
if ((rc = ftp_check_response(sock, &passReply)))
return FTPERR_PASSIVE_ERROR;
chptr = passReply;
while (*chptr && *chptr != '(') chptr++;
if (*chptr != '(') return FTPERR_PASSIVE_ERROR;
chptr++;
passReply = chptr;
while (*chptr && *chptr != ')') chptr++;
if (*chptr != ')') return FTPERR_PASSIVE_ERROR;
*chptr-- = '\0';
while (*chptr && *chptr != ',') chptr--;
if (*chptr != ',') return FTPERR_PASSIVE_ERROR;
chptr--;
while (*chptr && *chptr != ',') chptr--;
if (*chptr != ',') return FTPERR_PASSIVE_ERROR;
*chptr++ = '\0';
/* now passReply points to the IP portion, and chptr points to the
port number portion */
dataAddress.sin_family = AF_INET;
if (sscanf(chptr, "%d,%d", &i, &j) != 2) {
return FTPERR_PASSIVE_ERROR;
}
dataAddress.sin_port = htons((i << 8) + j);
chptr = passReply;
while (*chptr++) {
if (*chptr == ',') *chptr = '.';
}
if (!inet_aton(passReply, &dataAddress.sin_addr))
return FTPERR_PASSIVE_ERROR;
dataSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
if (dataSocket < 0) {
return FTPERR_FAILED_CONNECT;
}
if (!param)
sprintf(retrCommand, "%s\r\n", command);
else
sprintf(retrCommand, "%s %s\r\n", command, param);
i = strlen(retrCommand);
if (write(sock, retrCommand, i) != i) {
close(dataSocket);
return FTPERR_SERVER_IO_ERROR;
}
if (connect(dataSocket, (struct sockaddr *) &dataAddress,
sizeof(dataAddress))) {
close(dataSocket);
return FTPERR_FAILED_DATA_CONNECT;
}
if ((rc = ftp_check_response(sock, NULL))) {
close(dataSocket);
return rc;
}
return dataSocket;
}
static int ftp_get_filesize(int sock, char * remotename)
{
int size = 0;
char buf[2000];
char file[500];
char * ptr;
int fd, rc, tot;
int i;
strcpy(buf, remotename);
ptr = strrchr(buf, '/');
if (!*ptr)
return -1;
*ptr = '\0';
strcpy(file, ptr+1);
if ((rc = ftp_command(sock, "CWD", buf))) {
return -1;
}
fd = ftp_data_command(sock, "LIST", NULL);
if (fd <= 0) {
close(sock);
return -1;
}
ptr = buf;
while ((tot = read(fd, ptr, sizeof(buf) - (ptr - buf))) != 0)
ptr += tot;
*ptr = '\0';
close(fd);
if (!(ptr = strstr(buf, file))) {
log_message("FTP/get_filesize: Bad mood, directory does not contain searched file (%s)", file);
if (ftp_end_data_command(sock))
close(sock);
return -1;
}
for (i=0; i<4; i++) {
while (*ptr && *ptr != ' ')
ptr--;
while (*ptr && *ptr == ' ')
ptr--;
}
while (*ptr && *ptr != ' ')
ptr--;
if (ptr)
size = charstar_to_int(ptr+1);
else
size = 0;
if (ftp_end_data_command(sock)) {
close(sock);
return -1;
}
return size;
}
int ftp_start_download(int sock, char * remotename, int * size)
{
if ((*size = ftp_get_filesize(sock, remotename)) == -1) {
log_message("FTP: could not get filesize (trying to continue)");
*size = 0;
}
return ftp_data_command(sock, "RETR", remotename);
}
int ftp_end_data_command(int sock)
{
if (ftp_check_response(sock, NULL))
return FTPERR_BAD_SERVER_RESPONSE;
return 0;
}
static int parse_content_length(const char *headers, unsigned long *size) {
const char *header_content_length = "Content-Length: ";
const char *hdr = NULL, *ptr = NULL, *start = NULL, *end = NULL;
const char *nexthdr = NULL;
hdr = strstr(headers, header_content_length);
if (!hdr) {
log_message("%s: error: Content-Length header not found", __func__);
return -1;
}
start = hdr + strlen(header_content_length);
nexthdr = strstr(start, "\r\n");
errno = 0;
*size = strtoul(start, (char **)&end, 10);
/* HTTP headers are pretty standard, hence reject a bogus header
* instead of trying to download file of unknown length.
* Also bail out if the file is too long (i.e. size can't be
* represented by unsigned long).
* As a side effect this prevents 32-bit platforms from
* loading large (>= 4GB) files into RAM
*/
if (errno != 0 && errno != ERANGE) {
log_message("%s: error: invalid Content-Length header '%s', parse error: %s", __func__, hdr, strerror(errno));
return -1;
}
if (ERANGE == errno || *size == ULONG_MAX) {
log_message("%s: error: length '%s' is too big (>= %lu)", __func__, start, ULONG_MAX);
return -1;
}
/* XXX: on non-numerical inputs strtoul returns 0 *without*
* setting errno. Therefore validate the header manually: only
* a sequence of decimal digits surrounded by whitspace is OK.
* Note: endptr points first non-digit/space character or
* end of the string
*/
for (ptr = end; nexthdr ? ptr < nexthdr : *ptr != '\0'; ptr++) {
if (!isspace(*ptr) && !isdigit(*ptr)) {
log_message("%s: error: invalid character %c in Content-Length header '%s'", __func__, *ptr, hdr);
return -1;
}
if (isdigit(*ptr)) {
/* Content-Length: 123 456
* *end = '4'
*/
log_message("%s: error: stray data '%s' in Content-Length header: '%s'", __func__, ptr, hdr);
return -1;
}
}
if (*size == 0) {
log_message("%s: length is zero", __func__);
return -1;
}
return 0;
}
int http_download_file(char * hostname, char * remotename, unsigned long * size)
{
char * buf;
char headers[4096];
char *headers_end = NULL; /* points to 1st \r in \r\n\r\n */
char * nextChar = headers;
int checkedCode;
struct in_addr serverAddress;
struct pollfd polls;
int sock;
int rc;
struct sockaddr_in destPort;
if ((rc = get_host_address(hostname, &serverAddress))) return rc;
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
if (sock < 0) {
return FTPERR_FAILED_CONNECT;
}
destPort.sin_family = AF_INET;
destPort.sin_port = htons(80);
destPort.sin_addr = serverAddress;
if (connect(sock, (struct sockaddr *) &destPort, sizeof(destPort))) {
close(sock);
return FTPERR_FAILED_CONNECT;
}
buf = alloca(strlen(remotename) + strlen(hostname) + 48);
sprintf(buf, "GET %s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n", remotename, hostname);
rc = write(sock, buf, strlen(buf));
/* This is fun; read the response a character at a time until we:
1) Get our first \r\n; which lets us check the return code
2) Get a \r\n\r\n, which means we're done */
*nextChar = '\0';
checkedCode = 0;
while (!(headers_end = strstr(headers, "\r\n\r\n"))) {
polls.fd = sock;
polls.events = POLLIN;
rc = poll(&polls, 1, TIMEOUT_SECS*1000);
if (rc == 0) {
close(sock);
return FTPERR_SERVER_TIMEOUT;
} else if (rc < 0) {
close(sock);
return FTPERR_SERVER_IO_ERROR;
}
if (read(sock, nextChar, 1) != 1) {
close(sock);
return FTPERR_SERVER_IO_ERROR;
}
nextChar++;
*nextChar = '\0';
if (nextChar - headers == sizeof(headers)) {
close(sock);
return FTPERR_SERVER_IO_ERROR;
}
if (!checkedCode && strstr(headers, "\r\n")) {
char * start, * end;
checkedCode = 1;
start = headers;
while (!isspace(*start) && *start) start++;
if (!*start) {
close(sock);
return FTPERR_SERVER_IO_ERROR;
}
start++;
end = start;
while (!isspace(*end) && *end) end++;
if (!*end) {
close(sock);
return FTPERR_SERVER_IO_ERROR;
}
*end = '\0';
if (!strcmp(start, "404")) {
close(sock);
return FTPERR_FILE_NOT_FOUND;
} else if (strcmp(start, "200")) {
close(sock);
return FTPERR_BAD_SERVER_RESPONSE;
}
*end = ' ';
}
}
*headers_end = '\0'; /* skip \r\n\r\n */
if (parse_content_length(headers, size) < 0) {
goto out_err;
}
return sock;
out_err:
*size = 0; /* unknown/error */
close(sock);
return FTPERR_SERVER_IO_ERROR;
}