int main(int argc, char *argv[]) { pool *p; const char *remote_name; pr_netaddr_t *remote_addr; conn_t *client_conn, *ctrl_conn, *data_conn; unsigned int connect_timeout, remote_port; struct proxy_ftp_client *ftp; int res, timerno; char buf[1024]; /* Seed the random number generator. */ /* XXX Use random(3) in the future? */ srand((unsigned int) (time(NULL) * getpid())); init_pools(); init_privs(); init_log(); init_regexp(); init_inet(); init_netio(); init_netaddr(); init_fs(); init_class(); init_config(); init_stash(); pr_log_setdebuglevel(10); log_stderr(TRUE); pr_trace_use_stderr(TRUE); pr_trace_set_levels("DEFAULT", 1, 20); p = make_sub_pool(permanent_pool); pr_pool_tag(p, "FTP Client Pool"); remote_name = "ftp.proftpd.org"; remote_addr = pr_netaddr_get_addr(p, remote_name, NULL); if (remote_addr == NULL) { fprintf(stderr, "Failed to get addr for '%s': %s\n", remote_name, strerror(errno)); destroy_pool(p); return 1; } fprintf(stdout, "Resolved name '%s' to IP address '%s'\n", remote_name, pr_netaddr_get_ipstr(remote_addr)); remote_port = 21; connect_timeout = 5; ftp = proxy_ftp_connect(p, remote_addr, remote_port, connect_timeout, NULL); if (ftp == NULL) { fprintf(stderr, "Error connecting to FTP server: %s\n", strerror(errno)); destroy_pool(p); return 1; } fprintf(stdout, "Successfully connected to %s:%d from %s:%d\n", remote_name, remote_port, pr_netaddr_get_ipstr(client_conn->local_addr), ntohs(pr_netaddr_get_port(client_conn->local_addr))); res = proxy_ftp_disconnect(ftp); if (res < 0) { fprintf(stderr, "Error disconnecting from FTP server: %s\n", strerror(errno)); destroy_pool(p); return 1; } ctrl_conn = pr_inet_openrw(p, client_conn, NULL, PR_NETIO_STRM_OTHR, -1, -1, -1, FALSE); if (ctrl_conn == NULL) { fprintf(stderr, "Error opening control connection: %s\n", strerror(errno)); pr_inet_close(p, client_conn); destroy_pool(p); return 1; } fprintf(stdout, "Reading response from %s:%d\n", remote_name, remote_port); /* Read the response */ memset(buf, '\0', sizeof(buf)); /* XXX We need to write our own version of netio_telnet_gets(), with * the buffering to handle reassembly of a full FTP response out of * multiple TCP packets. Not sure why the existing netio_telnet_gets() * is not sufficient. But we don't need the handling of Telnet codes * in our reading. But DO generate the 'core.ctrl-read' event, so that * any event listeners get a chance to process the data we've received. * (Or maybe use 'mod_proxy.server-read', and differentiate between * client and server reads/writes?) */ if (pr_netio_read(ctrl_conn->instrm, buf, sizeof(buf)-1, 5) < 0) { fprintf(stderr, "Error reading response from server: %s\n", strerror(errno)); } else { fprintf(stdout, "Response: \"%s\"\n", buf); } /* Disconnect */ res = pr_netio_printf(ctrl_conn->outstrm, "%s\r\n", C_QUIT); if (res < 0) { fprintf(stderr, "Error writing command to server: %s", strerror(errno)); } pr_inet_close(p, ctrl_conn); pr_inet_close(p, client_conn); destroy_pool(p); return 0; }
char *pr_netio_telnet_gets(char *buf, size_t buflen, pr_netio_stream_t *in_nstrm, pr_netio_stream_t *out_nstrm) { char *bp = buf; unsigned char cp; static unsigned char mode = 0; int toread; pr_buffer_t *pbuf = NULL; buflen--; if (in_nstrm->strm_buf) pbuf = in_nstrm->strm_buf; else pbuf = netio_buffer_alloc(in_nstrm); while (buflen) { /* Is the buffer empty? */ if (!pbuf->current || pbuf->remaining == pbuf->buflen) { toread = pr_netio_read(in_nstrm, pbuf->buf, (buflen < pbuf->buflen ? buflen : pbuf->buflen), 1); if (toread <= 0) { if (bp != buf) { *bp = '\0'; return buf; } else return NULL; } pbuf->remaining = pbuf->buflen - toread; pbuf->current = pbuf->buf; } else toread = pbuf->buflen - pbuf->remaining; while (buflen && toread > 0 && *pbuf->current != '\n' && toread--) { cp = *pbuf->current++; pbuf->remaining++; switch (mode) { case IAC: switch (cp) { case WILL: case WONT: case DO: case DONT: mode = cp; continue; case IAC: mode = 0; break; default: /* Ignore */ mode = 0; continue; } break; case WILL: case WONT: pr_netio_printf(out_nstrm, "%c%c%c", IAC, DONT, cp); mode = 0; continue; case DO: case DONT: pr_netio_printf(out_nstrm, "%c%c%c", IAC, WONT, cp); mode = 0; continue; default: if (cp == IAC) { mode = cp; continue; } break; } *bp++ = cp; buflen--; } if (buflen && toread && *pbuf->current == '\n') { buflen--; toread--; *bp++ = *pbuf->current++; pbuf->remaining++; break; } if (!toread) pbuf->current = NULL; } *bp = '\0'; return buf; }
char *pr_ident_lookup(pool *p, conn_t *c) { char *ret = "UNKNOWN"; pool *tmp_pool = NULL; conn_t *ident_conn = NULL, *ident_io = NULL; char buf[256] = {'\0'}, *tok = NULL, *tmp = NULL; int timerno, i = 0; int ident_port = pr_inet_getservport(p, "ident", "tcp"); tmp_pool = make_sub_pool(p); ident_timeout = 0; nstrm = NULL; if (ident_port == -1) { destroy_pool(tmp_pool); return pstrdup(p, ret); } /* Set up our timer before going any further. */ timerno = pr_timer_add(PR_TUNABLE_TIMEOUTIDENT, -1, NULL, (callback_t) ident_timeout_cb, "ident lookup"); if (timerno <= 0) { destroy_pool(tmp_pool); return pstrdup(p, ret); } ident_conn = pr_inet_create_connection(tmp_pool, NULL, -1, c->local_addr, INPORT_ANY, FALSE); pr_inet_set_nonblock(tmp_pool, ident_conn); i = pr_inet_connect_nowait(tmp_pool, ident_conn, c->remote_addr, ident_port); if (i < 0) { int xerrno = errno; pr_timer_remove(timerno, ANY_MODULE); pr_inet_close(tmp_pool, ident_conn); pr_trace_msg(trace_channel, 5, "connection to %s, port %d failed: %s", pr_netaddr_get_ipstr(c->remote_addr), ident_port, strerror(xerrno)); destroy_pool(tmp_pool); errno = xerrno; return pstrdup(p, ret); } if (!i) { /* Not yet connected. */ nstrm = pr_netio_open(p, PR_NETIO_STRM_OTHR, ident_conn->listen_fd, PR_NETIO_IO_RD); pr_netio_set_poll_interval(nstrm, 1); switch (pr_netio_poll(nstrm)) { /* Aborted, timed out */ case 1: { if (ident_timeout) { pr_timer_remove(timerno, ANY_MODULE); pr_netio_close(nstrm); pr_inet_close(tmp_pool, ident_conn); pr_trace_msg(trace_channel, 5, "lookup timed out, returning '%s'", ret); destroy_pool(tmp_pool); return pstrdup(p, ret); } break; } /* Error. */ case -1: { int xerrno = errno; pr_timer_remove(timerno, ANY_MODULE); pr_netio_close(nstrm); pr_inet_close(tmp_pool, ident_conn); pr_trace_msg(trace_channel, 6, "lookup failed (%s), returning '%s'", strerror(xerrno), ret); destroy_pool(tmp_pool); errno = xerrno; return pstrdup(p, ret); } /* Connected. */ default: { ident_conn->mode = CM_OPEN; if (pr_inet_get_conn_info(ident_conn, ident_conn->listen_fd) < 0) { int xerrno = errno; pr_timer_remove(timerno, ANY_MODULE); pr_netio_close(nstrm); pr_inet_close(tmp_pool, ident_conn); pr_trace_msg(trace_channel, 2, "lookup timed out (%s), returning '%s'", strerror(xerrno), ret); destroy_pool(tmp_pool); errno = xerrno; return pstrdup(p, ret); } break; } } } ident_io = pr_inet_openrw(tmp_pool, ident_conn, NULL, PR_NETIO_STRM_OTHR, -1, -1, -1, FALSE); if (ident_io == NULL) { int xerrno = errno; pr_timer_remove(timerno, ANY_MODULE); pr_inet_close(tmp_pool, ident_conn); pr_trace_msg(trace_channel, 3, "failed opening read/write connection: %s", strerror(xerrno)); destroy_pool(tmp_pool); errno = xerrno; return pstrdup(p, ret); } nstrm = ident_io->instrm; pr_inet_set_nonblock(tmp_pool, ident_io); pr_netio_set_poll_interval(ident_io->instrm, 1); pr_netio_set_poll_interval(ident_io->outstrm, 1); pr_netio_printf(ident_io->outstrm, "%d, %d\r\n", c->remote_port, c->local_port); /* If the timer fires while in netio_gets(), netio_gets() will simply return * either a partial string, or NULL. This works because ident_timeout_cb * aborts the stream from which we are reading. netio_set_poll_interval() is * used to make sure significant delays don't occur on systems that * automatically restart syscalls after the SIGALRM signal. */ pr_trace_msg(trace_channel, 4, "reading response from remote ident server"); if (pr_netio_gets(buf, sizeof(buf), ident_io->instrm)) { strip_end(buf, "\r\n"); pr_trace_msg(trace_channel, 6, "received '%s' from remote ident server", buf); tmp = buf; tok = get_token(&tmp, ":"); if (tok && (tok = get_token(&tmp, ":"))) { while (*tok && isspace((int) *tok)) { pr_signals_handle(); tok++; } strip_end(tok, " \t"); if (strcasecmp(tok, "ERROR") == 0) { if (tmp) { while (*tmp && isspace((int) *tmp)) { pr_signals_handle(); tmp++; } strip_end(tmp, " \t"); if (strcasecmp(tmp, "HIDDEN-USER") == 0) ret = "HIDDEN-USER"; } } else if (strcasecmp(tok, "USERID") == 0) { if (tmp && (tok = get_token(&tmp, ":"))) { if (tmp) { while (*tmp && isspace((int) *tmp)) { pr_signals_handle(); tmp++; } strip_end(tmp, " \t"); ret = tmp; } } } } } pr_timer_remove(timerno, ANY_MODULE); pr_inet_close(tmp_pool, ident_io); pr_inet_close(tmp_pool, ident_conn); destroy_pool(tmp_pool); return pstrdup(p, ret); }
char *pr_netio_telnet_gets(char *buf, size_t buflen, pr_netio_stream_t *in_nstrm, pr_netio_stream_t *out_nstrm) { char *bp = buf; unsigned char cp; int toread, handle_iac = TRUE, saw_newline = FALSE; pr_buffer_t *pbuf = NULL; if (buflen == 0 || in_nstrm == NULL || out_nstrm == NULL) { errno = EINVAL; return NULL; } #ifdef PR_USE_NLS handle_iac = pr_encode_supports_telnet_iac(); #endif /* PR_USE_NLS */ buflen--; if (in_nstrm->strm_buf) { pbuf = in_nstrm->strm_buf; } else { pbuf = pr_netio_buffer_alloc(in_nstrm); } while (buflen > 0) { pr_signals_handle(); /* Is the buffer empty? */ if (pbuf->current == NULL || pbuf->remaining == pbuf->buflen) { toread = pr_netio_read(in_nstrm, pbuf->buf, (buflen < pbuf->buflen ? buflen : pbuf->buflen), 1); if (toread <= 0) { if (bp != buf) { *bp = '\0'; return buf; } return NULL; } pbuf->remaining = pbuf->buflen - toread; pbuf->current = pbuf->buf; /* Before we begin iterating through the data read in from the * network, handing any Telnet characters and such, generate an event * for any listeners which may want to examine this data as well. */ pr_event_generate("core.ctrl-read", pbuf); } toread = pbuf->buflen - pbuf->remaining; while (buflen > 0 && toread > 0 && *pbuf->current != '\n' && toread--) { pr_signals_handle(); cp = *pbuf->current++; pbuf->remaining++; if (handle_iac == TRUE) { switch (telnet_mode) { case TELNET_IAC: switch (cp) { case TELNET_WILL: case TELNET_WONT: case TELNET_DO: case TELNET_DONT: case TELNET_IP: case TELNET_DM: /* Why do we do this crazy thing where we set the "telnet mode" * to be the action, and let the while loop, on the next pass, * handle that action? It's because we don't know, right now, * whether there actually a "next byte" in the input buffer. * There _should_ be -- but we can't be sure. And that next * byte is needed for properly responding with WONT/DONT * responses. */ telnet_mode = cp; continue; case TELNET_IAC: /* In this case, we know that the previous byte was TELNET_IAC, * and that the current byte is another TELNET_IAC. The * first TELNET_IAC thus "escapes" the second, telling us * that the current byte (TELNET_IAC) should be written out * as is (Bug#3697). */ telnet_mode = 0; break; default: /* In this case, we know that the previous byte was TELNET_IAC, * but the current byte is not a value we care about. So * write the TELNET_IAC into the output buffer, break out of * of the switch, and let that handle the writing of the * current byte into the output buffer. */ *bp++ = TELNET_IAC; buflen--; telnet_mode = 0; break; } break; case TELNET_WILL: case TELNET_WONT: pr_netio_printf(out_nstrm, "%c%c%c", TELNET_IAC, TELNET_DONT, cp); telnet_mode = 0; continue; case TELNET_DO: case TELNET_DONT: pr_netio_printf(out_nstrm, "%c%c%c", TELNET_IAC, TELNET_WONT, cp); telnet_mode = 0; continue; case TELNET_IP: case TELNET_DM: default: if (cp == TELNET_IAC) { telnet_mode = cp; continue; } break; } } /* In the situation where the previous byte was an IAC, we wrote IAC * into the output buffer, and decremented buflen (size of the output * buffer remaining). Thus we need to check here if buflen is zero, * before trying to decrement buflen again (and possibly underflowing * the buflen size_t data type). */ if (buflen == 0) { break; } *bp++ = cp; buflen--; } if (buflen > 0 && toread > 0 && *pbuf->current == '\n') { buflen--; toread--; *bp++ = *pbuf->current++; pbuf->remaining++; saw_newline = TRUE; break; } if (toread == 0) { /* No more input? Set pbuf->current to null, so that at the top of * the loop, we read more. */ pbuf->current = NULL; } } if (!saw_newline) { /* If we haven't seen a newline, then assume the client is deliberately * sending a too-long command, trying to exploit buffer sizes and make * the server make some possibly bad assumptions. */ properly_terminated_prev_command = FALSE; errno = E2BIG; return NULL; } if (!properly_terminated_prev_command) { properly_terminated_prev_command = TRUE; pr_log_pri(PR_LOG_NOTICE, "client sent too-long command, ignoring"); errno = E2BIG; return NULL; } properly_terminated_prev_command = TRUE; *bp = '\0'; return buf; }