Compare commits

...

10 Commits

Author SHA1 Message Date
Alexey Sheplyakov
a300f01d08 20211007-alt1
- HTTP boot improvements (closes: #41072)
2021-10-07 16:46:06 +04:00
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
Alexey Sheplyakov
43a9d78fdd 20210922-alt1
- Support booting complete ISOs via HTTP

Closes: #40710
2021-09-22 14:25:24 +04:00
Alexey Sheplyakov
36b688d17a network boot: support loading complete ISOs via HTTP
Now I don't have to unpack (or loop mount) ISO to boot via HTTP.
Also I can boot directly from any ALT mirror, like this:

automatic=method:http,network:dhcp,server:mirror.yandex.ru,directory=/altlinux/images/p9/simply/aarch64/slinux-live-9.1.1-aarch64.iso

Closes: #40710
2021-09-17 17:26:33 +04:00
Alexey Sheplyakov
736c89620f http_download_file: fixed download of files >= 2GB size
* Use `unsigned long` to store the file size
* Return an error if the file size can not be found
* Avoid loading files >= 4GB on 32-bit systems

Related: #40710
2021-09-17 17:26:33 +04:00
Alexey Sheplyakov
e34da5300d 20210908-alt1
- Find out stage2 size at the run time instead of relying on
  the `ramdisk_size` parameter. As a result `ramdisk_size`
  parameter is not required any more.

Closes: #40629
2021-09-08 12:07:24 +04:00
Alexey Sheplyakov
77181411cd load_ramdisk_fd: load ramdisk to a tmpfs file
- No need to compute `ramdisk_size` in advance
- No need to pass `ramdisk_size` to the kernel => simpler ISO
  remastering (and network boot setup)
- If the swap is enabled rarely used parts of the loaded image
  can be paged out

Closes: #40629
2021-09-08 12:00:57 +04:00
Alexey Sheplyakov
08ec5c3a78 tools.c: added do_losetup_fd helper function
Useful to setup a loopback device backed by an anonymous file
(created by memfd_create, open(O_TMPFILE), and similar).
2021-09-08 12:00:49 +04:00
Alexey Sheplyakov
f6a430fd36 ftp_prepare: check if the image fits into (1/2 of) RAM
Related: #40710
2021-09-08 12:00:41 +04:00
Alexey Sheplyakov
0a972b5b86 http_prepare: check if the image fits into (1/2 of) RAM 2021-09-08 12:00:32 +04:00
10 changed files with 372 additions and 64 deletions

View File

@ -126,6 +126,15 @@ clean:
.depend: version.h
$(CPP) $(CFLAGS) -M $(ALLSRC) > .depend
ALL_TESTS := test_parse_content_length
test: $(ALL_TESTS)
@set -e; \
for tst in $(ALL_TESTS); do echo "$$tst"; ./$$tst; done
test_parse_content_length: test_parse_content_length.c url.c
$(CC) $(CFLAGS) -flto $(INIT_DEFS) -o $@ $<
ifeq (.depend,$(wildcard .depend))
include .depend
endif

View File

@ -23,6 +23,7 @@
#define IMAGE_LOCATION "/image"
#define STAGE2_LOCATION "/root"
#define LIVE_DEVICE "/dev/loop0"
#define LOMOUNT_DEVICE "/dev/loop3"
#define STAGE2FS "squashfs"
#define LIVEFS "squashfs"
#ifndef STAGE2_BINNAME

View File

@ -133,7 +133,7 @@ set_loop (const char *device, const char *file)
}
char * loopdev = "/dev/loop3"; /* Ugly. But do I care? */
char * loopdev = LOMOUNT_DEVICE; /* Ugly. But do I care? */
void del_loop(char *device)
{

View File

@ -890,16 +890,8 @@ enum return_type ftp_prepare(void)
char * questions_auto[] = { "server", "directory", "user", "pass", NULL };
static char ** answers = NULL;
enum return_type results;
unsigned long ramdisk_size;
update_splash("prepare");
ramdisk_size = get_ramdisk_size(NULL);
if (!ramdisk_possible(ramdisk_size)) {
stg1_error_message("FTP install needs more than %d Mbytes of memory (detected %u Mbytes).",
BYTES2MB(ramdisk_size), BYTES2MB(total_memory()));
return RETURN_ERROR;
}
results = intf_select_and_up();
if (results != RETURN_OK)
@ -957,6 +949,13 @@ enum return_type ftp_prepare(void)
continue;
}
if (!ramdisk_possible(size)) {
close(fd);
close(ftp_serv_response);
stg1_error_message("FTP install needs more than %u Mbytes of memory (detected %u Mbytes).",
BYTES2MB(size)*2, BYTES2MB(total_memory()));
return RETURN_ERROR;
}
log_message("FTP: size of download %d bytes", size);
results = load_ramdisk_fd(fd, size);
@ -986,17 +985,9 @@ enum return_type http_prepare(void)
char * questions_auto[] = { "server", "directory", NULL };
static char ** answers = NULL;
enum return_type results;
unsigned long ramdisk_size;
update_splash("prepare");
ramdisk_size = get_ramdisk_size(NULL);
if (!ramdisk_possible(ramdisk_size)) {
stg1_error_message("HTTP install needs more than %d Mbytes of memory (detected %u Mbytes).",
BYTES2MB(ramdisk_size), BYTES2MB(total_memory()));
return RETURN_ERROR;
}
results = intf_select_and_up();
if (results != RETURN_OK)
@ -1005,7 +996,9 @@ enum return_type http_prepare(void)
do {
char location_full[500];
char *tmp;
int fd, size;
int fd;
unsigned long size;
int is_iso = 0;
snprintf(location_full, sizeof(location_full),
"Please enter the name or IP address of the HTTP server, "
@ -1017,6 +1010,13 @@ enum return_type http_prepare(void)
}
strcpy(location_full, answers[1]);
log_message("HTTP: trying to retrieve %s", location_full);
fd = http_download_file(answers[0], location_full, &size);
if (fd >= 0) {
is_iso = 1;
goto download;
}
tmp = get_ramdisk_realname();
strcat(location_full, tmp);
free(tmp);
@ -1034,9 +1034,17 @@ enum return_type http_prepare(void)
continue;
}
log_message("HTTP: size of download %d bytes", size);
download:
if (!ramdisk_possible(size)) {
close(fd);
stg1_error_message("HTTP install needs more than %u Mbytes of memory (detected %u Mbytes).",
BYTES2MB(size)*2, BYTES2MB(total_memory()));
return RETURN_ERROR;
}
results = load_ramdisk_fd(fd, size);
log_message("HTTP: size of download %lu bytes", size);
results = load_ramdisk_or_iso(fd, size, is_iso);
close(fd);
if (results != RETURN_OK)
return RETURN_ERROR;

View File

@ -3,7 +3,7 @@
%def_with splash
Name: propagator
Version: 20210907
Version: 20211007
Release: alt1
Summary: 'Early userspace' set of binaries
@ -33,12 +33,32 @@ including init and various helpers for hw probing and bootstrapping.
%install
%makeinstall_std libdir=%_libdir
%check
%make_build \
%{?_with_cifs:WITH_CIFS=t} \
%{?_with_shell:WITH_SHELL=t} \
%{?_with_splash:WITH_SPLASH=t} \
version=%version-%release \
libdir=%_libdir \
test
%files
%_bindir/gencpio
%_bindir/mkmodpack
%_sbindir/propagator
%changelog
* Thu Oct 07 2021 Alexey Sheplyakov <asheplyakov@altlinux.org> 20211007-alt1
- Fixed HTTP boot when server sent more headers after Content-Length (closes: #41072)
* Thu Sep 22 2021 Alexey Sheplyakov <asheplyakov@altlinux.org> 20210922-alt1
- Support booting complete ISOs via HTTP (closes: #40710)
* Wed Sep 08 2021 Alexey Sheplyakov <asheplyakov@altlinux.org> 20210908-alt1
- Figure out stage2 size at the run time instead of relying on
ramdisk_size kernel command line parameter. As a result `ramdisk_size`
parameter is not required any more (closes: #40629)
* Tue Sep 07 2021 Alexey Sheplyakov <asheplyakov@altlinux.org> 20210907-alt1
- Improved IO errors handling when loading stage2 (closes: #40803)
- Support loading stage2 images >= 2GB (related: #40710)

View File

@ -0,0 +1,75 @@
#include <stdio.h>
#include <stdarg.h>
void log_message(const char *msg, ...) {
va_list args;
va_start(args, msg);
vfprintf(stderr, msg, args);
fprintf(stderr, "\n");
fflush(stderr);
}
#include "url.c" /* yes, include the source file */
static int test_parse_content_length(const char *headers, unsigned long expected, int shouldfail) {
int err;
unsigned long val;
err = parse_content_length(headers, &val);
if (err < 0 && !shouldfail) {
log_message("%s: unexpectedly failed on input '%s'", __func__, headers);
return 2;
}
if (err == 0 && shouldfail) {
log_message("%s: unexpectedly passed on invalid input '%s'", __func__, headers);
return 3;
}
if (!shouldfail && val != expected) {
log_message("%s: expected %lu, got %lu on input '%s'",
__func__, expected, val, headers);
return 5;
}
if (!shouldfail && val == expected) {
log_message("%s: OK: '%s'", __func__, headers);
}
return 0;
}
#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
int main(int argc, char **argv) {
int i, err = 0;
const char *invalid_inputs[] = {
"", /* empty */
"Abyrvalg: foobar", /* no Content-Length: */
"Content-Length: xyz", /* no digits */
"Content-Length: 12xyz", /* invalid characters */
"Content-Length: 12 34", /* stray symbols */
"Content-Length: 123456789012345678901234567890", /* too big */
};
const char *valid_inputs[] = {
"Content-Length: 1234",
"Content-Length: 1234",
"Content-Length: 1234 ",
"Content-Length: 1234 ",
"Content-Length: 1234 \r\n\r\n",
"Content-Length: 1234\r\n"
"Last-Modified: Sun, 12 Sep 2021 22:31:46 GMT\r\n"
"Connection: close\r\n"
"Etag: \"613e7fd2-3849a800\"\r\n"
"Accept-Ranges: bytes\r\n\r\n"
};
for (i = 0; i < ARRAY_SIZE(invalid_inputs); i++) {
err += test_parse_content_length(invalid_inputs[i], 0,
/* shouldfail = */ 1);
}
for (i = 0; i < ARRAY_SIZE(valid_inputs); i++) {
err += test_parse_content_length(valid_inputs[i], 1234,
/* shouldfail = */ 0);
}
return err;
}
/* compile command:
gcc -O2 -g -flto -o test_parse_content_length.c test_parse_content_length.c
*/

182
tools.c
View File

@ -19,6 +19,9 @@
*
*/
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
@ -33,6 +36,7 @@
#include <sys/mount.h>
#include <sys/poll.h>
#include <sys/sysinfo.h>
#include <sys/mman.h> /* memfd_create */
#include "stage1.h"
#include "log.h"
@ -41,6 +45,7 @@
#include "automatic.h"
#include <errno.h>
#include <linux/loop.h>
#include "lomount.h"
#include "tools.h"
#include "modules.h"
#include "sha256.h"
@ -293,6 +298,45 @@ static void save_stuff_for_rescue(void)
log_message("saved file %s for rescue (%d bytes)", file, i);
}
/**
* Loop mount ISO loaded to RAM, and open stage2
*
* @param ram_fd file descriptor of ISO image
* @return file descriptor of stage2 or -1 on error
*/
static int open_stage2_from_iso(int iso_fd)
{
int stage2_fd = -1;
const char *stage2 = get_ramdisk_path(IMAGE_LOCATION); /* /image/live */
if (!stage2) {
log_message("%s: get_ramdisk_size: got NULL", __func__);
goto out;
}
if (do_losetup_fd(LOMOUNT_DEVICE, iso_fd, NULL) < 0) {
log_message("%s: could not setup loopback for iso_fd", __func__);
goto out_free;
}
if (my_mount(LOMOUNT_DEVICE, IMAGE_LOCATION, "iso9660", 0)) {
log_message("%s: failed to mount ISO loopback", __func__);
goto out_del_loop;
}
stage2_fd = open(stage2, O_RDONLY);
if (stage2_fd < 0) {
log_message("%s: failed to open %s", __func__, stage2);
goto out_umount;
}
free((char *)stage2);
return stage2_fd;
out_umount:
umount(IMAGE_LOCATION);
out_del_loop:
del_loop(LOMOUNT_DEVICE);
out_free:
free((char *)stage2);
out:
return -1;
}
/**
* @brief copy exactly len bytes from buffer to file descriptor
@ -346,16 +390,46 @@ static int copy_loop(int dst_fd, int src_fd, unsigned long size)
return 0;
}
enum return_type load_ramdisk_fd(int source_fd, unsigned long size)
int make_ramfd(unsigned long size)
{
int ramfd;
ramfd = memfd_create("image", MFD_ALLOW_SEALING);
if (ramfd < 0) {
log_perror("memfd_create");
return -1;
}
/* By default memfd has no size limit. Set it to avoid OOM */
if (!size) {
/* XXX: when downloading an image via FTP size can
* be 0/unknown (the protocol makes it extremely
* difficult to figure out the file size).
* Set the limit to of 1/2 RAM in this case.
*/
size = total_memory()/2;
}
if (ftruncate(ramfd, (off_t)size) < 0) {
log_perror("ftruncate ramfd");
goto out;
}
if (fcntl(ramfd, F_ADD_SEALS, F_SEAL_GROW) < 0) {
log_perror("fcntl ramfd F_SEAL_GROW");
goto out;
}
return ramfd;
out:
close(ramfd);
return -1;
}
enum return_type load_ramdisk_or_iso(int source_fd, unsigned long size, int is_iso)
{
char * ramdisk = "/dev/ram3"; /* warning, verify that this file exists in the initrd, and that root=/dev/ram3 is actually passed to the kernel at boot time */
int ram_fd;
char * wait_msg = "Loading program into memory...";
ram_fd = open(ramdisk, O_WRONLY);
if (ram_fd == -1) {
log_perror(ramdisk);
stg1_error_message("Could not open ramdisk device file.");
ram_fd = make_ramfd(size);
if (ram_fd < 0) {
stg1_error_message("Couldn't allocate memory for image.");
return RETURN_ERROR;
}
@ -371,28 +445,62 @@ enum return_type load_ramdisk_fd(int source_fd, unsigned long size)
}
end_progression();
if (is_iso) {
int stage2_fd = open_stage2_from_iso(ram_fd);
if (stage2_fd < 0) {
close(ram_fd);
stg1_error_message("Could not open stage2 from ISO in RAM");
return RETURN_ERROR;
}
/* got a (mounted) loopback device backed by ram_fd,
* ram_fd itself is not necessary any more */
close(ram_fd);
/* to setup loopback device from stage2_fd */
ram_fd = stage2_fd;
}
if (do_losetup_fd(LIVE_DEVICE, ram_fd, NULL) < 0) {
close(ram_fd);
if (is_iso) {
umount(IMAGE_LOCATION);
del_loop(LOMOUNT_DEVICE);
}
stg1_error_message("Could not setup loopback for 2nd stage");
return RETURN_ERROR;
}
/* got a loopback device, don't need ram_fd any more */
close(ram_fd);
if (do_losetup(LIVE_DEVICE, ramdisk) == -1)
fatal_error("could not setup loopback for loaded second stage");
if (my_mount(LIVE_DEVICE, STAGE2_LOCATION, STAGE2FS, 0))
if (my_mount(LIVE_DEVICE, STAGE2_LOCATION, STAGE2FS, 0)) {
stg1_error_message("Failed to mount stage2");
if (is_iso) {
umount(IMAGE_LOCATION);
del_loop(LOMOUNT_DEVICE);
}
return RETURN_ERROR;
}
set_param(MODE_RAMDISK);
if (IS_RESCUE) {
save_stuff_for_rescue();
if (umount(STAGE2_LOCATION)) {
log_perror(ramdisk);
log_message("rescue: failed to umount " STAGE2_LOCATION);
return RETURN_ERROR;
}
if (is_iso) {
/* XXX: should I do anything special here? */
}
return RETURN_OK; /* fucksike, I lost several hours wondering why the kernel won't see the rescue if it is alreay mounted */
}
return RETURN_OK;
}
enum return_type load_ramdisk_fd(int source_fd, unsigned long size) {
return load_ramdisk_or_iso(source_fd, size, 0);
}
int splash_verbose()
{
@ -656,10 +764,12 @@ int string_array_length(char ** a)
return i;
}
int do_losetup(char * device, char * target)
/* setup loop device from file descriptor targfd */
int do_losetup_fd(char *device, int targfd, const char *name)
{
int i, loopfd, targfd;
int i, loopfd;
struct loop_info loopInfo;
memset(&loopInfo, 0, sizeof(loopInfo));
my_insmod("loop", NULL);
/* wait for udev's dust settles down */
@ -670,19 +780,10 @@ int do_losetup(char * device, char * target)
return -1;
}
targfd = open( target, O_RDONLY );
if ( targfd < 0 )
{
log_message( "losetup: error opening %s: %s", target, strerror(errno) );
close( loopfd );
return -1;
}
if ( ioctl(loopfd, LOOP_SET_FD, targfd) < 0 )
{
log_message( "losetup: error setting up loopback device: %s", strerror(errno) );
close( loopfd );
close( targfd );
return -1;
}
/* Note: LOOP_SET_FD triggers change event. While processing it
@ -693,18 +794,19 @@ int do_losetup(char * device, char * target)
*/
udev_settle();
memset(&loopInfo, 0, sizeof(loopInfo));
strcpy(loopInfo.lo_name, target);
if (name) {
strcpy(loopInfo.lo_name, name);
if ( ioctl(loopfd, LOOP_SET_STATUS, &loopInfo) < 0 )
log_message( "losetup: error setting up loopback device: %s", strerror(errno) );
if ( ioctl(loopfd, LOOP_SET_STATUS, &loopInfo) < 0 )
log_message( "losetup: error setting up loopback device: %s", strerror(errno) );
/* Same here: LOOP_SET_STATUS triggers change event, give udev
* a chance to process it without concurrent access to the loop
* device. In other words, prevent the caller of this function
* from mounting the device while udev still running blkid on it
*/
udev_settle();
/* Same here: LOOP_SET_STATUS triggers change event, give udev
* a chance to process it without concurrent access to the loop
* device. In other words, prevent the caller of this function
* from mounting the device while udev still running blkid on it
*/
udev_settle();
}
close( loopfd );
/* udev might be monitoring loopback device (with fanotify) and
@ -713,6 +815,20 @@ int do_losetup(char * device, char * target)
* (which is going to mount the newly configured loopback device)
*/
udev_settle();
close( targfd );
return 0;
}
int do_losetup(char * device, char * target)
{
int ret = 0, targfd;
targfd = open( target, O_RDONLY );
if ( targfd < 0 )
{
log_message( "losetup: error opening %s: %s", target, strerror(errno) );
return -1;
}
ret = do_losetup_fd(device, targfd, target);
close(targfd);
return ret;
}

16
tools.h
View File

@ -36,6 +36,7 @@ int ramdisk_possible(unsigned long ramdisk_size);
char * get_ramdisk_realname(void);
enum return_type load_ramdisk(char *, unsigned long size);
enum return_type load_ramdisk_fd(int ramdisk_fd, unsigned long size);
enum return_type load_ramdisk_or_iso(int ramdisk_fd, unsigned long size, int is_iso);
void * memdup(void *src, size_t size);
void add_to_env(char * name, char * value);
void handle_env(char ** env);
@ -44,6 +45,19 @@ int pass_env(int);
char ** list_directory(char * direct);
int string_array_length(char ** a);
int do_losetup(char * device, char * target);
/**
* Setup a loopback device backed by fd.
*
* @param device path to loopback device
* @param fd backing file descriptor
* @param name for pretty output from losetup, typically name of the backing file.
* @return 0 on success, otherwise -1
*
* @note: should be used instead of do_losetup for anonymous files,
* i.e. ones created by memfd_create, open(O_TMPFILE), etc
*/
int do_losetup_fd(char *device, int fd, const char *name);
char * get_ramdisk_path(const char *);
char * get_uid_file_path(const char *, const char *);
enum return_type verify_ramdisk_digest(const char *filename, const char *sha256_hash);
@ -63,6 +77,6 @@ struct param_elem
#define ptr_begins_static_str(pointer,static_str) (!strncmp(pointer,static_str,sizeof(static_str)-1))
#define streq !strcmp
#define MKDEV(ma,mi) ((ma)<<8 | (mi))
#define BYTES2MB(b) ((unsigned int)(b)/1024/1024)
#define BYTES2MB(b) ((unsigned int)((b)/1024/1024))
#endif

81
url.c
View File

@ -24,6 +24,7 @@
#include <sys/types.h>
#include <netinet/in_systm.h>
#include <limits.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
@ -394,11 +395,72 @@ int ftp_end_data_command(int sock)
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;
int http_download_file(char * hostname, char * remotename, int * size)
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;
@ -406,7 +468,6 @@ int http_download_file(char * hostname, char * remotename, int * size)
int sock;
int rc;
struct sockaddr_in destPort;
char * header_content_length = "Content-Length: ";
if ((rc = get_host_address(hostname, &serverAddress))) return rc;
@ -435,7 +496,7 @@ int http_download_file(char * hostname, char * remotename, int * size)
*nextChar = '\0';
checkedCode = 0;
while (!strstr(headers, "\r\n\r\n")) {
while (!(headers_end = strstr(headers, "\r\n\r\n"))) {
polls.fd = sock;
polls.events = POLLIN;
rc = poll(&polls, 1, TIMEOUT_SECS*1000);
@ -493,10 +554,14 @@ int http_download_file(char * hostname, char * remotename, int * size)
}
}
if ((buf = strstr(headers, header_content_length)))
*size = charstar_to_int(buf + strlen(header_content_length));
else
*size = 0;
*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;
}

2
url.h
View File

@ -26,7 +26,7 @@ int ftp_open_connection(char * host, char * name, char * password, char * proxy)
int ftp_start_download(int sock, char * remotename, int * size);
int ftp_end_data_command(int sock);
int http_download_file(char * hostname, char * remotename, int * size);
int http_download_file(char * hostname, char * remotename, unsigned long * size);
#define FTPERR_BAD_SERVER_RESPONSE -1