/* ftp.c * ftp:// processing * (c) 2002 Mikulas Patocka * This file is a part of the Links program, released under GPL. */ #include "links.h" #define FTP_BUF 16384 struct ftp_connection_info { int pending_commands; int opc; int pasv; int eprt_epsv; int dir; int rest_sent; int we_are_in_root; int conn_st; int d; int dpos; int buf_pos; unsigned char ftp_buffer[FTP_BUF]; unsigned char cmdbuf[1]; }; static void ftp_get_banner(struct connection *); static void ftp_got_banner(struct connection *, struct read_buffer *); static void ftp_login(struct connection *); static void ftp_logged(struct connection *); static void ftp_sent_passwd(struct connection *); static void ftp_got_user_info(struct connection *, struct read_buffer *); static void ftp_pass_info(struct connection *, struct read_buffer *); static void ftp_send_retr_req(struct connection *, int, int); static struct ftp_connection_info *add_file_cmd_to_str(struct connection *, int); static void ftp_retr_1(struct connection *); static void ftp_retr_file(struct connection *, struct read_buffer *); static void ftp_got_final_response(struct connection *, struct read_buffer *); static void created_data_connection(struct connection *); static void got_something_from_data_connection(void *); static void ftp_end_request(struct connection *, int); static int get_ftp_response(struct connection *, struct read_buffer *, int); static int ftp_process_dirlist(struct cache_entry *, off_t *, int *, unsigned char *, int, int, int, int *); static int get_ftp_response(struct connection *c, struct read_buffer *rb, int part) { int l; set_connection_timeout(c); again: for (l = 0; l < rb->len; l++) if (rb->data[l] == 10) { char *e; long k = strtoul(cast_const_char rb->data, &e, 10); if (cast_uchar e != rb->data + 3 || k < 100 || k >= 1000) return -1; if (*e == '-') { int i; for (i = 0; i < rb->len - 5; i++) { if (rb->data[i] == 10 && !memcmp(rb->data+i+1, rb->data, 3) && rb->data[i+4] == ' ') { for (i++; i < rb->len; i++) if (rb->data[i] == 10) goto ok; return 0; } } return 0; ok: l = i; } if (!part && k >= 100 && k < 200) { kill_buffer_data(rb, l + 1); goto again; } if (part == 2) return (int)k; kill_buffer_data(rb, l + 1); return (int)k; } return 0; } void ftp_func(struct connection *c) { int we_are_in_root; unsigned char *de, *d; int del, bad_url; d = get_url_data(c->url); de = init_str(), del = 0; add_conv_str(&de, &del, d, (int)strcspn(cast_const_char d, POST_CHAR_STRING), -2); bad_url = !!strchr(cast_const_char de, 10); mem_free(de); if (bad_url) { setcstate(c, S_BAD_URL); abort_connection(c); return; } if (get_keepalive_socket(c, &we_are_in_root)) { int p; if ((p = get_port(c->url)) == -1) { setcstate(c, S_BAD_URL); abort_connection(c); return; } make_connection(c, p, &c->sock1, ftp_get_banner); } else { ftp_send_retr_req(c, S_SENT, we_are_in_root); } } static void ftp_get_banner(struct connection *c) { struct read_buffer *rb; set_connection_timeout(c); setcstate(c, S_SENT); if (!(rb = alloc_read_buffer(c))) return; read_from_socket(c, c->sock1, rb, ftp_got_banner); } static void ftp_got_banner(struct connection *c, struct read_buffer *rb) { int g = get_ftp_response(c, rb, 0); if (g == -1) { setcstate(c, S_FTP_ERROR); abort_connection(c); return; } if (!g) { read_from_socket(c, c->sock1, rb, ftp_got_banner); return; } if (g >= 400) { setcstate(c, S_FTP_UNAVAIL); retry_connection(c); return; } ftp_login(c); } static unsigned char *get_ftp_password(struct connection *c) { unsigned char *u; if ((u = get_pass(c->url))) { if (*u) return u; mem_free(u); } if (SCRUB_HEADERS) return stracpy(cast_uchar "mozilla@example.com"); return stracpy(ftp_options.anon_pass); } static void ftp_login(struct connection *c) { unsigned char *login; unsigned char *u; int logl = 0; set_connection_timeout(c); login = init_str(); add_to_str(&login, &logl, cast_uchar "USER "); if ((u = get_user_name(c->url)) && *u) add_to_str(&login, &logl, u); else add_to_str(&login, &logl, cast_uchar "anonymous"); if (u) mem_free(u); add_to_str(&login, &logl, cast_uchar "\r\n"); write_to_socket(c, c->sock1, login, logl, ftp_logged); mem_free(login); setcstate(c, S_SENT); } static void ftp_logged(struct connection *c) { struct read_buffer *rb; if (!(rb = alloc_read_buffer(c))) return; ftp_got_user_info(c, rb); } static void ftp_got_user_info(struct connection *c, struct read_buffer *rb) { int g = get_ftp_response(c, rb, 0); if (g == -1) { setcstate(c, S_FTP_ERROR); abort_connection(c); return; } if (!g) { read_from_socket(c, c->sock1, rb, ftp_got_user_info); return; } if (g >= 530 && g < 540) { setcstate(c, S_FTP_LOGIN); retry_connection(c); return; } if (g >= 400) { setcstate(c, S_FTP_UNAVAIL); retry_connection(c); return; } if (g >= 200 && g < 300) { ftp_send_retr_req(c, S_GETH, 0); } else { unsigned char *login; unsigned char *u; int logl = 0; login = init_str(); add_to_str(&login, &logl, cast_uchar "PASS "); u = get_ftp_password(c); add_to_str(&login, &logl, u); mem_free(u); add_to_str(&login, &logl, cast_uchar "\r\n"); write_to_socket(c, c->sock1, login, logl, ftp_sent_passwd); mem_free(login); setcstate(c, S_LOGIN); } } static void ftp_sent_passwd(struct connection *c) { struct read_buffer *rb; if (!(rb = alloc_read_buffer(c))) return; read_from_socket(c, c->sock1, rb, ftp_pass_info); } static void ftp_pass_info(struct connection *c, struct read_buffer *rb) { int g = get_ftp_response(c, rb, 0); if (g == -1) { setcstate(c, S_FTP_ERROR); abort_connection(c); return; } if (!g) { read_from_socket(c, c->sock1, rb, ftp_pass_info); setcstate(c, S_LOGIN); return; } if (g >= 530 && g < 540) { setcstate(c, S_FTP_LOGIN); abort_connection(c); return; } if (g >= 400) { setcstate(c, S_FTP_UNAVAIL); abort_connection(c); return; } ftp_send_retr_req(c, S_GETH, 0); } static void add_port_pasv(unsigned char **s, int *l, struct ftp_connection_info *inf, unsigned char *port_string) { if (!inf->pasv) { if (inf->eprt_epsv) { add_to_str(s, l, cast_uchar "EPRT "); add_to_str(s, l, port_string); } else { add_to_str(s, l, cast_uchar "PORT "); add_to_str(s, l, port_string); } } else { if (inf->eprt_epsv) { add_to_str(s, l, cast_uchar "EPSV"); } else { add_to_str(s, l, cast_uchar "PASV"); } } add_to_str(s, l, cast_uchar "\r\n"); } static struct ftp_connection_info *add_file_cmd_to_str(struct connection *c, int we_are_in_root) { unsigned char *d = get_url_data(c->url); unsigned char *dd, *de; int del; unsigned char port_string[50]; struct ftp_connection_info *inf, *inf2; unsigned char *s; int l; de = init_str(), del = 0; add_conv_str(&de, &del, d, (int)strcspn(cast_const_char d, POST_CHAR_STRING), -2); d = de; inf = mem_alloc(sizeof(struct ftp_connection_info)); memset(inf, 0, sizeof(struct ftp_connection_info)); l = 0; s = init_str(); inf->we_are_in_root = we_are_in_root; inf->pasv = ftp_options.passive_ftp; #ifdef LINKS_2 if (*c->socks_proxy) inf->pasv = 1; if (ftp_options.eprt_epsv || is_ipv6(c->sock1)) inf->eprt_epsv = 1; #endif c->info = inf; if (!inf->pasv) { int ps; #ifdef SUPPORT_IPV6 if (is_ipv6(c->sock1)) { ps = get_pasv_socket_ipv6(c, c->sock1, &c->sock2, port_string); if (ps) { mem_free(d); mem_free(s); return NULL; } } else #endif { unsigned char pc[6]; ps = get_pasv_socket(c, c->sock1, &c->sock2, pc); if (ps) { mem_free(d); mem_free(s); return NULL; } if (inf->eprt_epsv) sprintf(cast_char port_string, "|1|%d.%d.%d.%d|%d|", pc[0], pc[1], pc[2], pc[3], (pc[4] << 8) | pc[5]); else sprintf(cast_char port_string, "%d,%d,%d,%d,%d,%d", pc[0], pc[1], pc[2], pc[3], pc[4], pc[5]); } if (strlen(cast_const_char port_string) >= sizeof(port_string)) internal_error("buffer overflow in get_pasv_socket_ipv6: %d > %d", (int)strlen(cast_const_char port_string), (int)sizeof(port_string)); #ifdef HAVE_IPTOS if (ftp_options.set_tos) { int rx; int on = IPTOS_THROUGHPUT; EINTRLOOP(rx, setsockopt(c->sock2, IPPROTO_IP, IP_TOS, (char *)&on, sizeof(int))); } #endif } dd = d; while (*dd == '/') dd++; de = cast_uchar strchr(cast_const_char dd, 0); if (dd == de || de[-1] == '/') { inf->dir = 1; inf->pending_commands = 3; add_to_str(&s, &l, cast_uchar "TYPE A\r\n"); add_port_pasv(&s, &l, inf, port_string); if (!inf->we_are_in_root) { add_to_str(&s, &l, cast_uchar "CWD /\r\n"); inf->we_are_in_root = 1; inf->pending_commands++; } if (dd != de) { add_to_str(&s, &l, cast_uchar "CWD "); add_bytes_to_str(&s, &l, dd, de - 1 - dd); add_to_str(&s, &l, cast_uchar "\r\n"); inf->we_are_in_root = 0; inf->pending_commands++; } add_to_str(&s, &l, cast_uchar "LIST\r\n"); c->from = 0; } else { inf->dir = 0; inf->pending_commands = 3; add_to_str(&s, &l, cast_uchar "TYPE I\r\n"); add_port_pasv(&s, &l, inf, port_string); if (!inf->we_are_in_root) { add_to_str(&s, &l, cast_uchar "CWD /\r\n"); inf->we_are_in_root = 1; inf->pending_commands++; } if (c->from && c->no_cache < NC_IF_MOD) { add_to_str(&s, &l, cast_uchar "REST "); add_num_to_str(&s, &l, c->from); add_to_str(&s, &l, cast_uchar "\r\n"); inf->rest_sent = 1; inf->pending_commands++; } else { c->from = 0; } add_to_str(&s, &l, cast_uchar "RETR "); add_bytes_to_str(&s, &l, dd, de - dd); add_to_str(&s, &l, cast_uchar "\r\n"); } inf->opc = inf->pending_commands; if ((unsigned)l > MAXINT - sizeof(struct ftp_connection_info) - 1) overalloc(); inf2 = mem_realloc(inf, sizeof(struct ftp_connection_info) + l + 1); strcpy(cast_char (inf = inf2)->cmdbuf, cast_const_char s); mem_free(s); c->info = inf; mem_free(d); return inf; } static void ftp_send_retr_req(struct connection *c, int state, int we_are_in_root) { struct ftp_connection_info *fi; unsigned char *login; int logl = 0; set_connection_timeout_keepal(c); login = init_str(); if (!c->info && !(fi = add_file_cmd_to_str(c, we_are_in_root))) { mem_free(login); return; } else { fi = c->info; } if (0) { a:add_to_str(&login, &logl, fi->cmdbuf); } else { unsigned char *nl = cast_uchar strchr(cast_const_char fi->cmdbuf, '\n'); if (!nl) goto a; nl++; add_bytes_to_str(&login, &logl, fi->cmdbuf, nl - fi->cmdbuf); memmove(fi->cmdbuf, nl, strlen(cast_const_char nl) + 1); } write_to_socket(c, c->sock1, login, logl, ftp_retr_1); mem_free(login); setcstate(c, state); } static void ftp_retr_1(struct connection *c) { struct read_buffer *rb; if (!(rb = alloc_read_buffer(c))) return; read_from_socket(c, c->sock1, rb, ftp_retr_file); } static void ftp_retr_file(struct connection *c, struct read_buffer *rb) { int g; struct ftp_connection_info *inf = c->info; if (0) { rep: ftp_send_retr_req(c, S_GETH, inf->we_are_in_root); return; } if (inf->pending_commands > 1) { unsigned char pc[6] = { 0, 0, 0, 0, 0, 0 }; /* against warning */ if (inf->pasv && inf->opc - (inf->pending_commands - 1) == 2) { int i, j; i = 3; if (!inf->eprt_epsv) while (i < rb->len) { if (rb->data[i] >= '0' && rb->data[i] <= '9') { for (j = 0; j < 6; j++) { int n = 0; while (rb->data[i] >= '0' && rb->data[i] <= '9') { n = n * 10 + rb->data[i] - '0'; if (n >= 256) goto no_pasv; if (++i >= rb->len) goto no_pasv; } pc[j] = (unsigned char)n; if (j != 5) { if (rb->data[i] != ',') goto xa; if (++i >= rb->len) goto xa; if (rb->data[i] < '0' || rb->data[i] > '9') { xa: if (j != 1) goto no_pasv; pc[4] = pc[0]; pc[5] = pc[1]; pc[0] = pc[1] = pc[2] = pc[3] = 0; goto pasv_ok; } } } goto pasv_ok; } i++; } no_pasv: i = 3; while (i < rb->len - 5) { if (rb->data[i] == '(' && (rb->data[i + 1] < '0' || rb->data[i + 1] > '9') && rb->data[i + 1] == rb->data[i + 2] && rb->data[i + 2] == rb->data[i + 3]) { unsigned char delim = rb->data[i + 1]; int n = 0; i += 4; while (rb->data[i] >= '0' && rb->data[i] <= '9') { n = n * 10 + rb->data[i] - '0'; if (n >= 65536) goto no_epsv; if (++i >= rb->len) goto no_epsv; } if (rb->data[i] != delim) goto no_epsv; pc[4] = n >> 8; pc[5] = n & 0xff; pc[0] = pc[1] = pc[2] = pc[3] = 0; goto pasv_ok; } i++; } no_epsv: memset(pc, 0, sizeof pc); pasv_ok:; } g = get_ftp_response(c, rb, 0); if (g == -1) { setcstate(c, S_FTP_ERROR); abort_connection(c); return; } if (!g) { read_from_socket(c, c->sock1, rb, ftp_retr_file); setcstate(c, S_GETH); return; } inf->pending_commands--; switch (inf->opc - inf->pending_commands) { case 1: /* TYPE */ goto rep; case 2: /* PORT */ if (g >= 400) { setcstate(c, S_FTP_PORT); abort_connection(c); return; } if (inf->pasv) { if (!pc[4] && !pc[5]) { setcstate(c, S_FTP_ERROR); retry_connection(c); return; } make_connection(c, (pc[4] << 8) + pc[5], &c->sock2, created_data_connection); } goto rep; case 3: /* REST / CWD */ case 4: if (g >= 400) { if (!inf->dir && c->from && inf->pending_commands == 1) c->from = 0; else { setcstate(c, S_FTP_NO_FILE); abort_connection(c); return; } } goto rep; } internal_error("WHAT???"); } g = get_ftp_response(c, rb, 2); if (!g) { read_from_socket(c, c->sock1, rb, ftp_retr_file); setcstate(c, S_GETH); return; } if (g >= 100 && g < 200) { unsigned char *d = rb->data; int i, p = 0; for (i = 0; i < rb->len && d[i] != 10; i++) if (d[i] == '(') p = i; if (!p || p == rb->len - 1) goto nol; p++; if (d[p] < '0' || d[p] > '9') goto nol; for (i = p; i < rb->len; i++) if (d[i] < '0' || d[i] > '9') goto quak; goto nol; quak: for (; i < rb->len; i++) if (d[i] != ' ') break; if (i + 4 > rb->len) goto nol; if (casecmp(&d[i], cast_uchar "byte", 4)) goto nol; /* when resuming file transfer, some servers return total size, others return the number of remaining bytes. So it is not reliable to guess file size in this case */ if (c->from) goto nol; { my_strtoll_t est = my_strtoll(&d[p], NULL); if (est < 0 || (off_t)est < 0 || (off_t)est != est) est = 0; if (est) c->est_length = est + c->from; } nol:; } if (!inf->pasv) set_handlers(c->sock2, got_something_from_data_connection, NULL, c); /*read_from_socket(c, c->sock1, rb, ftp_got_final_response);*/ ftp_got_final_response(c, rb); } static void ftp_got_final_response(struct connection *c, struct read_buffer *rb) { struct ftp_connection_info *inf = c->info; int g = get_ftp_response(c, rb, 0); if (g == -1) { setcstate(c, S_FTP_ERROR); abort_connection(c); return; } if (!g) { read_from_socket(c, c->sock1, rb, ftp_got_final_response); if (c->state != S_TRANS) setcstate(c, S_GETH); return; } if (g == 425 || g == 450 || g == 500 || g == 501 || g == 550) { if (c->url[strlen(cast_const_char c->url) - 1] == '/') goto skip_redir; if (!c->cache) { if (get_connection_cache_entry(c)) { setcstate(c, S_OUT_OF_MEM); abort_connection(c); return; } c->cache->refcount--; } if (c->cache->redirect) mem_free(c->cache->redirect); c->cache->redirect = stracpy(c->url); add_to_strn(&c->cache->redirect, cast_uchar "/"); c->cache->incomplete = 0; /*setcstate(c, S_FTP_NO_FILE);*/ setcstate(c, S__OK); abort_connection(c); return; } skip_redir: if (g >= 400) { setcstate(c, S_FTP_FILE_ERROR); abort_connection(c); return; } if (inf->conn_st == 2) { ftp_end_request(c, S__OK); } else { inf->conn_st = 1; if (c->state != S_TRANS) setcstate(c, S_GETH); } } static int is_date(unsigned char *data) /* can touch at most data[-4] --- "n 12 "<--if fed with this --- if you change it, fix the caller */ { /* fix for ftp://ftp.su.se/ */ if (*data == ' ') data--; if (data[0] >= '0' && data[0] <= '9' && data[-1] >= '0' && data[-1] <= '9') data -= 2; else if (data[0] >= '1' && data[0] <= '9' && data[-1] == ' ') data -= 1 + (data[-2] == ' '); else return 0; if (data[0] == ':') return 1; if (data[0] != ' ') return 0; if ((data[-1] < 'a' || data[-1] > 'z') && (data[-1] < 'A' || data[-1] > 'Z')) return 0; return 1; } #define PARSE_MODE_VMS -1 static int ftp_process_dirlist(struct cache_entry *ce, off_t *pos, int *d, unsigned char *bf, int ln, int fin, int root, int *tr) { unsigned char *str, *buf; int sl; int ret = 0; int p; int len; int f; int a; int dir; int sp; int ee; again: buf = bf + ret; len = ln - ret; for (p = 0; p < len; p++) if (buf[p] == '\n') goto lb; if (p && (fin || len >= FTP_BUF)) { ret += p; goto pl; } return ret; lb: ret += p + 1; if (p && buf[p - 1] == '\r') p--; pl: str = init_str(); sl = 0; /*add_to_str(&str, &sl, cast_uchar " ");*/ dir = 0; if (!*d || *d == PARSE_MODE_VMS) for (ee = 0; p - ee > 1 && !WHITECHAR(buf[ee]); ee++) { if (buf[ee] == ';' && buf[ee + 1] >= '1' && buf[ee + 1] <= '9') { if (!ee) goto skip_vms; if (ee >= 4 && buf[ee - 4] == '.' && buf[ee - 3] == 'D' && buf[ee - 2] == 'I' && buf[ee - 1] == 'R') { if (ee == 4) goto raw; dir = 1, ee -= 4; } if (*d != PARSE_MODE_VMS && !root) { add_to_str(&str, &sl, cast_uchar "..\n"); } sp = 0; *d = PARSE_MODE_VMS; goto put_entry; } } skip_vms: if (*d < 0) goto raw; f = *d; if (*d && *d < p && WHITECHAR(buf[*d - 1])) { sp = *d; ppp: for (ee = sp; ee <= p - 4; ee++) if (!memcmp(buf + ee, cast_uchar " -> ", 4)) goto syml; ee = p; syml: if (!f && !root) { if ((ee - sp != 1 || buf[sp] != '.') && (ee - sp != 2 || buf[sp] != '.' || buf[sp + 1] != '.')) { int i; for (i = 0; i < sp; i++) add_to_str(&str, &sl, cast_uchar " "); add_to_str(&str, &sl, cast_uchar "..\n"); } } dir = buf[0] == 'd'; if (!dir) { unsigned char *p = memacpy(buf, sp); if (strstr(cast_const_char p, "")) dir = 1; mem_free(p); }; put_entry: add_conv_str(&str, &sl, buf, sp, 0); add_to_str(&str, &sl, cast_uchar ""); add_conv_str(&str, &sl, buf + sp, ee - sp, 0); add_to_str(&str, &sl, cast_uchar ""); add_conv_str(&str, &sl, buf + ee, p - ee, 0); } else { int pp, ppos; int bp, bn; if (p > 5 && !casecmp(buf, cast_uchar "total", 5)) goto raw; if (p > 10 && !memcmp(buf, cast_uchar "Directory ", 10)) goto raw; for (pp = p - 1; pp >= 0; pp--) if (!WHITECHAR(buf[pp])) break; if (pp < 0) goto raw; if (pp < p - 1) pp++; ppos = -1; for (; pp >= 10; pp--) if (WHITECHAR(buf[pp])) { if (is_date(&buf[pp - 6]) && buf[pp - 5] == ' ' && ((buf[pp - 4] == '2' && buf[pp - 3] == '0') || (buf[pp - 4] == '1' && buf[pp - 3] == '9')) && buf[pp - 2] >= '0' && buf[pp - 2] <= '9' && buf[pp - 1] >= '0' && buf[pp - 1] <= '9') { if (pp < p - 2 && buf[pp + 1] == ' ' && buf[pp + 2] != ' ') ppos = pp + 1; else ppos = pp; } if (buf[pp - 6] == ' ' && ((buf[pp - 5] >= '0' && buf[pp - 5] <= '2') || buf[pp - 5] == ' ') && buf[pp - 4] >= '0' && buf[pp - 4] <= '9' && buf[pp - 3] == ':' && buf[pp - 2] >= '0' && buf[pp - 2] <= '5' && buf[pp - 1] >= '0' && buf[pp - 1] <= '9') { ppos = pp; if (p - pp > 2 && buf[pp + 1] == ' ' && buf[pp + 2] != ' ') ppos++; } } if (ppos != -1) { pp = ppos; goto done; } for (pp = 0; p - pp >= 5; pp++) if (!casecmp(&buf[pp], cast_uchar "", 5)) { pp += 4; while (p - pp > 1 && WHITECHAR(buf[pp + 1])) pp++; if (p - pp > 1) goto done; } bn = -1; bp = 0; /* warning, go away */ for (pp = 0; pp < p; ) { if (buf[pp] >= '0' && buf[pp] <= '9') { int i; for (i = pp; i < p; i++) if (buf[i] < '0' || buf[i] > '9') break; if (i < p && WHITECHAR(buf[i])) { if (i - pp > bn) { bn = i - pp; bp = pp; } } pp = i; } while (pp < p && !WHITECHAR(buf[pp])) pp++; while (pp < p && WHITECHAR(buf[pp])) pp++; } if (bn >= 0) { pp = bp + bn; while (p - pp > 1 && WHITECHAR(buf[pp + 1])) pp++; if (p - pp > 1) goto done; } for (pp = p - 1; pp >= 0; pp--) if (!WHITECHAR(buf[pp])) break; if (pp < 0) goto raw; for (; pp >= 0; pp--) if (WHITECHAR(buf[pp]) && (pp < 3 || memcmp(buf + pp - 3, cast_uchar " -> ", 4)) && (pp > p - 4 || memcmp(buf + pp, cast_uchar " -> ", 4))) break; done: sp = *d = pp + 1; goto ppp; raw: add_conv_str(&str, &sl, buf, p, 0); } add_chr_to_str(&str, &sl, '\n'); a = add_fragment(ce, *pos, str, sl); if (a < 0) return a; if (a == 1) *tr = 0; *pos += sl; mem_free(str); goto again; } static void created_data_connection(struct connection *c) { struct ftp_connection_info *inf = c->info; #ifdef HAVE_IPTOS if (ftp_options.set_tos) { int rx; int on = IPTOS_THROUGHPUT; EINTRLOOP(rx, setsockopt(c->sock2, IPPROTO_IP, IP_TOS, (char *)&on, sizeof(int))); } #endif inf->d = 1; set_handlers(c->sock2, got_something_from_data_connection, NULL, c); } static void got_something_from_data_connection(void *c_) { struct connection *c = (struct connection *)c_; struct ftp_connection_info *inf = c->info; int l; int m; int rs; set_connection_timeout(c); if (!inf->d) { int ns; inf->d = 1; set_handlers(c->sock2, NULL, NULL, NULL); ns = c_accept(c->sock2, NULL, NULL); if (ns == -1) goto e; set_nonblock(ns); EINTRLOOP(rs, close(c->sock2)); c->sock2 = ns; set_handlers(ns, got_something_from_data_connection, NULL, c); return; } if (!c->cache) { if (get_connection_cache_entry(c)) { setcstate(c, S_OUT_OF_MEM); abort_connection(c); return; } c->cache->refcount--; } if (inf->dir && !c->from) { unsigned char *ud; unsigned char *s0; int s0l; int err = 0; static const unsigned char ftp_head[] = "/"; static const unsigned char ftp_head2[] = "

Directory /"; static const unsigned char ftp_head3[] = "

";
#define A(s)							\
do {								\
	m = add_fragment(c->cache, c->from, s, strlen(cast_const_char s));\
	if (m < 0 && !err) err = m;				\
	c->from += strlen(cast_const_char s);			\
} while (0)
		A(ftp_head);
		ud = stracpy(get_url_data(c->url));
		if (strchr(cast_const_char ud, POST_CHAR)) *strchr(cast_const_char ud, POST_CHAR) = 0;
		s0 = init_str();
		s0l = 0;
		add_conv_str(&s0, &s0l, ud, (int)strlen(cast_const_char ud), -1);
		mem_free(ud);
		A(s0);
		A(ftp_head2);
		A(s0);
		A(ftp_head3);
		mem_free(s0);
		if (!c->cache->head) c->cache->head = stracpy(cast_uchar "\r\n");
		add_to_strn(&c->cache->head, cast_uchar "Content-Type: text/html\r\n");
		if (err) {
			setcstate(c, err);
			abort_connection(c);
			return;
		}
#undef A
	}
	EINTRLOOP(l, (int)read(c->sock2, inf->ftp_buffer + inf->buf_pos, FTP_BUF - inf->buf_pos));
	if (l == -1) {
		e:
		if (inf->conn_st != 1 && !inf->dir && !c->from) {
			close_socket(&c->sock2);
			inf->conn_st = 2;
			return;
		}
		setcstate(c, get_error_from_errno(errno));
		retry_connection(c);
		return;
	}
	if (l > 0) {
		if (!inf->dir) {
			if ((off_t)(0UL + c->from + l) < 0) {
				setcstate(c, S_LARGE_FILE);
				abort_connection(c);
				return;
			}
			c->received += l;
			m = add_fragment(c->cache, c->from, inf->ftp_buffer, l);
			if (m < 0) {
				setcstate(c, m);
				abort_connection(c);
				return;
			}
			if (m == 1) c->tries = 0;
			c->from += l;
		} else {
			c->received += l;
			m = ftp_process_dirlist(c->cache, &c->from, &inf->dpos, inf->ftp_buffer, l + inf->buf_pos, 0, inf->we_are_in_root, &c->tries);
			if (m < 0) {
				setcstate(c, m);
				abort_connection(c);
				return;
			}
			memmove(inf->ftp_buffer, inf->ftp_buffer + m, inf->buf_pos + l - m);
			inf->buf_pos += l - m;
		}
		setcstate(c, S_TRANS);
		return;
	}
	m = ftp_process_dirlist(c->cache, &c->from, &inf->dpos, inf->ftp_buffer, inf->buf_pos, 1, inf->we_are_in_root, &c->tries);
	if (m < 0) {
		setcstate(c, m);
		abort_connection(c);
		return;
	}
	close_socket(&c->sock2);
	if (inf->conn_st == 1) {
		ftp_end_request(c, S__OK);
	} else {
		inf->conn_st = 2;
	}
}

static void ftp_end_request(struct connection *c, int state)
{
	struct ftp_connection_info *inf = c->info;
	if (state == S__OK) {
		if (c->cache) {
			truncate_entry(c->cache, c->from, 1);
			c->cache->incomplete = 0;
		}
	}
	setcstate(c, state);
	add_keepalive_socket(c, FTP_KEEPALIVE_TIMEOUT, inf->we_are_in_root);
}