/* * ProFTPD - FTP server daemon * Copyright (c) 1997, 1998 Public Flood Software * Copyright (c) 1999, 2000 MacGyver aka Habeeb J. Dihu * Copyright (c) 2001-2004 The ProFTPD Project team * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. * * As a special exemption, Public Flood Software/MacGyver aka Habeeb J. Dihu * and other respective copyright holders give permission to link this program * with OpenSSL, and distribute the resulting executable, without including * the source code for OpenSSL in the source distribution. */ /* * Data connection management functions * $Id: data.c,v 1.89 2006/02/08 01:25:46 castaglia Exp $ */ #include "conf.h" #include #ifdef HAVE_SYS_SENDFILE_H #include #endif /* HAVE_SYS_SENDFILE_H */ #ifdef HAVE_SYS_UIO_H #include #endif /* HAVE_SYS_UIO_H */ /* local macro */ #define MODE_STRING (session.sf_flags & (SF_ASCII|SF_ASCII_OVERRIDE) ? \ "ASCII" : "BINARY") /* Internal usage: pointer to current data connection stream in use (may be * in either read or write mode) */ static pr_netio_stream_t *nstrm = NULL; static long timeout_linger = PR_TUNABLE_TIMEOUTLINGER; /* Called if the "Stalled" timer goes off */ static int stalled_timeout_cb(CALLBACK_FRAME) { pr_event_generate("core.timeout-stalled", NULL); pr_log_pri(PR_LOG_NOTICE, "Data transfer stall timeout: %d seconds", TimeoutStalled); end_login(1); /* Prevent compiler warning. */ return 0; } /* this signal is raised if we get OOB data on the control connection, and * a data transfer is in progress */ static RETSIGTYPE data_urgent(int sig) { if (nstrm) { session.sf_flags |= SF_ABORT; pr_netio_abort(nstrm); } signal(SIGURG, data_urgent); } static int xfrm_ascii_read(char *buf, int *bufsize, int *adjlen) { char *dest = buf,*src = buf; int thislen = *bufsize; *adjlen = 0; while (thislen--) { if (*src != '\r') *dest++ = *src++; else { if (thislen == 0) { /* copy, but save it for later */ *dest++ = *src++; (*adjlen)++; (*bufsize)--; } else { if (*(src+1) == '\n') { /* skip */ (*bufsize)--; src++; } else *dest++ = *src++; } } } return *bufsize; } /* this function rewrites the contents of the given buffer, making sure that * each LF has a preceding CR, as required by RFC959: * * buf = pointer to a buffer * buflen = length of data in buffer * bufsize = total size of buffer * expand = will contain the number of expansion bytes (CRs) added, * and should be the difference between buflen's original * value and its value when this function returns */ static int have_dangling_cr = FALSE; static unsigned int xfrm_ascii_write(char **buf, unsigned int *buflen, unsigned int bufsize) { char *tmpbuf = *buf; unsigned int tmplen = *buflen; unsigned int lfcount = 0; unsigned int added = 0; int res = 0; register unsigned int i = 0; /* First, determine how many bare LFs are present. */ if (!have_dangling_cr && tmpbuf[0] == '\n') lfcount++; for (i = 1; i < tmplen; i++) if (tmpbuf[i] == '\n' && tmpbuf[i-1] != '\r') lfcount++; /* If the last character in the buffer is CR, then we have a dangling CR. * The first character in the next buffer could be an LF, and without * this flag, that LF would be treated as a bare LF, thus resulting in * an added extraneous CR in the stream. */ have_dangling_cr = (tmpbuf[tmplen-1] == '\r') ? TRUE : FALSE; if (lfcount == 0) /* No translation needed. */ return 0; /* Assume that for each LF (including a leading LF), space for another * char (a '\r') is needed. Determine whether there is enough space in * the buffer for the adjusted data. If not, allocate a new buffer that is * large enough. The new buffer is allocated from session.xfer.p, which is * fine; this pool has a lifetime only for this current data transfer, and * will be cleared after the transfer is done, either having succeeded or * failed. * * Note: the res variable is needed in order to force signedness of the * resulting difference. Without it, this condition would never evaluate * to true, as C's promotion rules would ensure that the resulting value * would be of the same type as the operands: an unsigned int (which will * never be less than zero). */ if ((res = (bufsize - tmplen - lfcount)) <= 0) { char *copybuf = malloc(tmplen); if (!copybuf) { pr_log_pri(PR_LOG_ERR, "fatal: memory exhausted"); exit(1); } memcpy(copybuf, tmpbuf, tmplen); /* Allocate a new session.xfer.buf of the needed size. */ session.xfer.bufsize = tmplen + lfcount + 1; session.xfer.buf = pcalloc(session.xfer.p, session.xfer.bufsize); memcpy(session.xfer.buf, copybuf, tmplen); free(copybuf); copybuf = NULL; tmpbuf = session.xfer.buf; bufsize = session.xfer.bufsize; } if (tmpbuf[0] == '\n') { /* Shift everything in the buffer to the right one character, making * space for a '\r' */ memmove(&(tmpbuf[1]), &(tmpbuf[0]), tmplen); tmpbuf[0] = '\r'; /* Increment the number of added characters, and decrement the number * of bare LFs. */ added++; lfcount--; } for (i = 1; i < bufsize && (lfcount > 0); i++) { if (tmpbuf[i] == '\n' && tmpbuf[i-1] != '\r') { memmove(&(tmpbuf[i+1]), &(tmpbuf[i]), bufsize - i - 1); tmpbuf[i] = '\r'; added++; lfcount--; } } *buf = tmpbuf; *buflen = tmplen + added; return added; } static void data_new_xfer(char *filename, int direction) { if (session.xfer.p) { destroy_pool(session.xfer.p); memset(&session.xfer, 0, sizeof(session.xfer)); } session.xfer.p = make_sub_pool(session.pool); pr_pool_tag(session.xfer.p, "session.xfer pool"); session.xfer.filename = pstrdup(session.xfer.p, filename); session.xfer.direction = direction; session.xfer.bufsize = PR_TUNABLE_XFER_BUFFER_SIZE; session.xfer.buf = pcalloc(session.xfer.p, PR_TUNABLE_XFER_BUFFER_SIZE + 1); session.xfer.buf++; /* leave room for ascii translation */ session.xfer.buflen = 0; } static int data_pasv_open(char *reason, off_t size) { conn_t *c; int rev; if (!reason && session.xfer.filename) reason = session.xfer.filename; /* Set the "stalled" timer, if any, to prevent the connection * open from taking too long */ if (TimeoutStalled) pr_timer_add(TimeoutStalled, TIMER_STALLED, NULL, stalled_timeout_cb); /* We save the state of our current disposition for doing reverse * lookups, and then set it to what the configuration wants it to * be. */ rev = pr_netaddr_set_reverse_dns(ServerUseReverseDNS); /* Protocol and socket options should be set before handshaking. */ if (session.xfer.direction == PR_NETIO_IO_RD) { pr_inet_set_socket_opts(session.d->pool, session.d, (main_server->tcp_rcvbuf_override ? main_server->tcp_rcvbuf_len : 0), 0); pr_inet_set_proto_opts(session.pool, session.d, main_server->tcp_mss_len, 0, 0, 1, 1); } else { pr_inet_set_socket_opts(session.d->pool, session.d, 0, (main_server->tcp_sndbuf_override ? main_server->tcp_sndbuf_len : 0)); pr_inet_set_proto_opts(session.pool, session.d, main_server->tcp_mss_len, 0, 0, 1, 1); } c = pr_inet_accept(session.xfer.p, session.d, session.c, -1, -1, TRUE); pr_netaddr_set_reverse_dns(rev); if (c && c->mode != CM_ERROR) { pr_inet_close(session.pool, session.d); pr_inet_set_nonblock(session.pool, c); session.d = c; pr_log_debug(DEBUG4, "passive data connection opened - local : %s:%d", pr_netaddr_get_ipstr(session.d->local_addr), session.d->local_port); pr_log_debug(DEBUG4, "passive data connection opened - remote : %s:%d", pr_netaddr_get_ipstr(session.d->remote_addr), session.d->remote_port); if (session.xfer.xfer_type != STOR_UNIQUE) { if (size) pr_response_send(R_150, "Opening %s mode data connection for %s " "(%" PR_LU " bytes)", MODE_STRING, reason, (pr_off_t) size); else pr_response_send(R_150, "Opening %s mode data connection for %s", MODE_STRING, reason); } else { /* Format of 150 responses for STOU is explicitly dictated by * RFC 1123: * * 4.1.2.9 STOU Command: RFC-959 Section 4.1.3 * * The STOU command stores into a uniquely named file. When it * receives an STOU command, a Server-FTP MUST return the * actual file name in the "125 Transfer Starting" or the "150 * Opening Data Connection" message that precedes the transfer * (the 250 reply code mentioned in RFC-959 is incorrect). The * exact format of these messages is hereby defined to be as * follows: * * 125 FILE: pppp * 150 FILE: pppp * * where pppp represents the unique pathname of the file that * will be written. */ pr_response_send(R_150, "FILE: %s", reason); } return 0; } /* Check for error conditions. */ if (c && c->mode == CM_ERROR) pr_log_pri(PR_LOG_ERR, "Error: unable to accept an incoming data " "connection (%s)", strerror(c->xerrno)); pr_response_add_err(R_425, "Unable to build data connection: %s", strerror(session.d->xerrno)); destroy_pool(session.d->pool); session.d = NULL; return -1; } static int data_active_open(char *reason, off_t size) { conn_t *c; int rev; if (!reason && session.xfer.filename) reason = session.xfer.filename; session.d = pr_inet_create_connection(session.pool, NULL, -1, session.c->local_addr, session.c->local_port-1, TRUE); /* Set the "stalled" timer, if any, to prevent the connection * open from taking too long */ if (TimeoutStalled) pr_timer_add(TimeoutStalled, TIMER_STALLED, NULL, stalled_timeout_cb); rev = pr_netaddr_set_reverse_dns(ServerUseReverseDNS); /* Protocol and socket options should be set before handshaking. */ if (session.xfer.direction == PR_NETIO_IO_RD) { pr_inet_set_socket_opts(session.d->pool, session.d, (main_server->tcp_rcvbuf_override ? main_server->tcp_rcvbuf_len : 0), 0); pr_inet_set_proto_opts(session.pool, session.d, main_server->tcp_mss_len, 0, 0, 1, 1); } else { pr_inet_set_socket_opts(session.d->pool, session.d, 0, (main_server->tcp_sndbuf_override ? main_server->tcp_sndbuf_len : 0)); pr_inet_set_proto_opts(session.pool, session.d, main_server->tcp_mss_len, 0, 0, 1, 1); } if (pr_inet_connect(session.d->pool, session.d, &session.data_addr, session.data_port) == -1) { pr_response_add_err(R_425, "Unable to build data connection: %s", strerror(session.d->xerrno)); destroy_pool(session.d->pool); session.d = NULL; return -1; } c = pr_inet_openrw(session.pool, session.d, NULL, PR_NETIO_STRM_DATA, session.d->listen_fd, -1, -1, TRUE); pr_netaddr_set_reverse_dns(rev); if (c) { pr_log_debug(DEBUG4, "active data connection opened - local : %s:%d", pr_netaddr_get_ipstr(session.d->local_addr), session.d->local_port); pr_log_debug(DEBUG4, "active data connection opened - remote : %s:%d", pr_netaddr_get_ipstr(session.d->remote_addr), session.d->remote_port); if (session.xfer.xfer_type != STOR_UNIQUE) { if (size) pr_response_send(R_150, "Opening %s mode data connection for %s " "(%" PR_LU " bytes)", MODE_STRING, reason, (pr_off_t) size); else pr_response_send(R_150, "Opening %s mode data connection for %s", MODE_STRING, reason); } else { /* Format of 150 responses for STOU is explicitly dictated by * RFC 1123: * * 4.1.2.9 STOU Command: RFC-959 Section 4.1.3 * * The STOU command stores into a uniquely named file. When it * receives an STOU command, a Server-FTP MUST return the * actual file name in the "125 Transfer Starting" or the "150 * Opening Data Connection" message that precedes the transfer * (the 250 reply code mentioned in RFC-959 is incorrect). The * exact format of these messages is hereby defined to be as * follows: * * 125 FILE: pppp * 150 FILE: pppp * * where pppp represents the unique pathname of the file that * will be written. */ pr_response_send(R_150, "FILE: %s", reason); } pr_inet_close(session.pool, session.d); pr_inet_set_nonblock(session.pool, session.d); session.d = c; return 0; } pr_response_add_err(R_425, "Unable to build data connection: %s", strerror(session.d->xerrno)); destroy_pool(session.d->pool); session.d = NULL; return -1; } void pr_data_set_linger(long linger) { timeout_linger = linger; } void pr_data_reset(void) { if (session.d && session.d->pool) destroy_pool(session.d->pool); session.d = NULL; session.sf_flags &= (SF_ALL^(SF_ABORT|SF_XFER|SF_PASSIVE|SF_ASCII_OVERRIDE|SF_EPSV_ALL)); } void pr_data_init(char *filename, int direction) { if (!session.xfer.p) { data_new_xfer(filename, direction); } else { if (!(session.sf_flags & SF_PASSIVE)) pr_log_debug(DEBUG0, "data_init oddity: session.xfer exists in non-PASV mode."); session.xfer.direction = direction; } } int pr_data_open(char *filename, char *reason, int direction, off_t size) { int res = 0; if (!session.xfer.p) data_new_xfer(filename, direction); else session.xfer.direction = direction; if (!reason) reason = filename; /* Passive data transfers... */ if (session.sf_flags & SF_PASSIVE || session.sf_flags & SF_EPSV_ALL) { if (!session.d) { pr_log_pri(PR_LOG_ERR, "Internal error: PASV mode set, but no data " "connection listening."); end_login(1); } res = data_pasv_open(reason, size); /* Active data transfers... */ } else { if (session.d) { pr_log_pri(PR_LOG_ERR, "Internal error: non-PASV mode, yet data " "connection already exists?!?"); end_login(1); } res = data_active_open(reason, size); } if (res >= 0) { struct sigaction act; if (pr_netio_postopen(session.d->instrm) < 0) { pr_response_add_err(R_425, "Unable to build data connection: %s", strerror(session.d->xerrno)); destroy_pool(session.d->pool); session.d = NULL; return -1; } if (pr_netio_postopen(session.d->outstrm) < 0) { pr_response_add_err(R_425, "Unable to build data connection: %s", strerror(session.d->xerrno)); destroy_pool(session.d->pool); session.d = NULL; return -1; } memset(&session.xfer.start_time, '\0', sizeof(session.xfer.start_time)); gettimeofday(&session.xfer.start_time, NULL); if (session.xfer.direction == PR_NETIO_IO_RD) nstrm = session.d->instrm; else nstrm = session.d->outstrm; session.sf_flags |= SF_XFER; if (TimeoutNoXfer) pr_timer_reset(TIMER_NOXFER, ANY_MODULE); /* Allow aborts. */ /* Set the current NetIO stream to allow interrupted syscalls, so our * SIGURG handler can interrupt it */ pr_netio_set_poll_interval(nstrm, 1); /* PORTABILITY: sigaction is used here to allow us to indicate * (w/ POSIX at least) that we want SIGURG to interrupt syscalls. Put * in whatever is necessary for your arch here; probably not necessary * as the only _important_ interrupted syscall is select(), which on * any sensible system is interrupted. */ act.sa_handler = data_urgent; sigemptyset(&act.sa_mask); act.sa_flags = 0; #ifdef SA_INTERRUPT act.sa_flags |= SA_INTERRUPT; #endif sigaction(SIGURG, &act, NULL); #ifdef HAVE_SIGINTERRUPT /* This is the BSD way of ensuring interruption. * Linux uses it too (??) */ siginterrupt(SIGURG, 1); #endif } return res; } /* close == successful transfer */ void pr_data_close(int quiet) { nstrm = NULL; if (session.d) { pr_inet_lingering_close(session.pool, session.d, timeout_linger); session.d = NULL; } /* Aborts no longer necessary */ signal(SIGURG, SIG_IGN); if (TimeoutNoXfer) pr_timer_reset(TIMER_NOXFER, ANY_MODULE); if (TimeoutStalled) pr_timer_remove(TIMER_STALLED, ANY_MODULE); session.sf_flags &= (SF_ALL^SF_PASSIVE); session.sf_flags &= (SF_ALL^(SF_ABORT|SF_XFER|SF_PASSIVE|SF_ASCII_OVERRIDE)); session_set_idle(); if (!quiet) pr_response_add(R_226, "Transfer complete."); } /* Note: true_abort may be false in real abort situations, because * some ftp clients close the data connection at the same time as they * send the OOB byte (which results in a broken pipe on our * end). Thus, it's a race between the OOB data and the tcp close * finishing. Either way, it's ok (client will see either "Broken pipe" * error or "Aborted"). cmd_abor in mod_xfer cleans up the session * flags in any case. session flags will end up have SF_POST_ABORT * set if the OOB byte won the race. */ void pr_data_cleanup(void) { /* sanity check */ if (session.d) { pr_inet_lingering_close(session.pool, session.d, timeout_linger); session.d = NULL; } if (session.xfer.p) destroy_pool(session.xfer.p); memset(&session.xfer,0,sizeof(session.xfer)); } /* In order to avoid clearing the transfer counters in session.xfer, we don't * clear session.xfer here, it should be handled by the appropriate * LOG_CMD/LOG_CMD_ERR handler calling pr_data_cleanup(). */ void pr_data_abort(int err, int quiet) { int true_abort = XFER_ABORTED; nstrm = NULL; if (session.d) { if (!true_abort) pr_inet_lingering_close(session.pool, session.d, timeout_linger); else pr_inet_lingering_abort(session.pool, session.d, timeout_linger); session.d = NULL; } if (TimeoutNoXfer) pr_timer_reset(TIMER_NOXFER, ANY_MODULE); if (TimeoutStalled) pr_timer_remove(TIMER_STALLED, ANY_MODULE); session.sf_flags &= (SF_ALL^SF_PASSIVE); session.sf_flags &= (SF_ALL^(SF_XFER|SF_PASSIVE|SF_ASCII_OVERRIDE)); session_set_idle(); /* Aborts no longer necessary */ signal(SIGURG, SIG_IGN); if (TimeoutNoXfer) pr_timer_reset(TIMER_NOXFER, ANY_MODULE); if (!quiet) { char *respcode = R_426; char *fmt = NULL; char *msg = NULL; char msgbuf[64]; switch (err) { case 0: respcode = R_426; msg = "Data connection closed."; break; #ifdef ENXIO case ENXIO: respcode = R_451; msg = "Unexpected streams hangup."; break; #endif #ifdef EAGAIN case EAGAIN: /* FALLTHROUGH */ #endif #ifdef ENOMEM case ENOMEM: #endif #if defined(EAGAIN) || defined(ENOMEM) respcode = R_451; msg = "Insufficient memory or file locked."; break; #endif #ifdef ETXTBSY case ETXTBSY: /* FALLTHROUGH */ #endif #ifdef EBUSY case EBUSY: #endif #if defined(ETXTBSY) || defined(EBUSY) respcode = R_451; break; #endif #ifdef ENOSPC case ENOSPC: respcode = R_452; break; #endif #ifdef EDQUOT case EDQUOT: /* FALLTHROUGH */ #endif #ifdef EFBIG case EFBIG: #endif #if defined(EDQUOT) || defined(EFBIG) respcode = R_552; break; #endif #ifdef ECOMM case ECOMM: /* FALLTHROUGH */ #endif #ifdef EDEADLK case EDEADLK: /* FALLTHROUGH */ #endif #ifdef EDEADLOCK # if !defined(EDEADLK) || (EDEADLOCK != EDEADLK) case EDEADLOCK: /* FALLTHROUGH */ # endif #endif #ifdef EXFULL case EXFULL: /* FALLTHROUGH */ #endif #ifdef ENOSR case ENOSR: /* FALLTHROUGH */ #endif #ifdef EPROTO case EPROTO: /* FALLTHROUGH */ #endif #ifdef ETIME case ETIME: /* FALLTHROUGH */ #endif #ifdef EIO case EIO: /* FALLTHROUGH */ #endif #ifdef EFAULT case EFAULT: /* FALLTHROUGH */ #endif #ifdef ESPIPE case ESPIPE: /* FALLTHROUGH */ #endif #ifdef EPIPE case EPIPE: #endif #if defined(ECOMM) || defined(EDEADLK) || defined(EDEADLOCK) \ || defined(EXFULL) || defined(ENOSR) || defined(EPROTO) \ || defined(ETIME) || defined(EIO) || defined(EFAULT) \ || defined(ESPIPE) || defined(EPIPE) respcode = R_451; break; #endif #ifdef EREMCHG case EREMCHG: /* FALLTHROUGH */ #endif #ifdef ESRMNT case ESRMNT: /* FALLTHROUGH */ #endif #ifdef ESTALE case ESTALE: /* FALLTHROUGH */ #endif #ifdef ENOLINK case ENOLINK: /* FALLTHROUGH */ #endif #ifdef ENOLCK case ENOLCK: /* FALLTHROUGH */ #endif #ifdef ENETRESET case ENETRESET: /* FALLTHROUGH */ #endif #ifdef ECONNABORTED case ECONNABORTED: /* FALLTHROUGH */ #endif #ifdef ECONNRESET case ECONNRESET: /* FALLTHROUGH */ #endif #ifdef ETIMEDOUT case ETIMEDOUT: #endif #if defined(EREMCHG) || defined(ESRMNT) || defined(ESTALE) \ || defined(ENOLINK) || defined(ENOLCK) || defined(ENETRESET) \ || defined(ECONNABORTED) || defined(ECONNRESET) || defined(ETIMEDOUT) respcode = R_450; msg = "Link to file server lost."; break; #endif } if (msg == NULL && (msg = strerror(err)) == NULL ) { if (snprintf(msgbuf, sizeof(msgbuf), "Unknown or out of range errno [%d]", err) > 0) msg = msgbuf; } pr_log_pri(PR_LOG_NOTICE, "notice: user %s: aborting transfer: %s", session.user, msg); /* If we are aborting, then a 426 response has already been sent, * and we don't want to add another to the error queue. */ if (!true_abort) pr_response_add_err(respcode, fmt ? fmt : "Transfer aborted. %s", msg ? msg : ""); } if (true_abort) session.sf_flags |= SF_POST_ABORT; } /* pr_data_xfer() actually transfers the data on the data connection .. * ASCII translation is performed if necessary. direction set * when data connection was opened determine if the client buffer * is read from or written to. return 0 if reading and data connection * closes, or -1 if error */ int pr_data_xfer(char *cl_buf, int cl_size) { int len = 0; int total = 0; if (session.xfer.direction == PR_NETIO_IO_RD) { char *buf = session.xfer.buf; if (session.d) { if (session.sf_flags & (SF_ASCII|SF_ASCII_OVERRIDE)) { int adjlen, buflen; do { buflen = session.xfer.buflen; /* how much remains in buf */ adjlen = 0; len = pr_netio_read(session.d->instrm, buf + buflen, session.xfer.bufsize - buflen, 1); if (len < 0) return -1; if (len > 0) { buflen += len; if (TimeoutStalled) pr_timer_reset(TIMER_STALLED, ANY_MODULE); } /* If buflen > 0, data remains in the buffer to be copied. */ if (len >= 0 && buflen > 0) { /* Perform translation: * * buflen is returned as the modified buffer length after * translation * adjlen is returned as the number of characters unprocessed in * the buffer (to be dealt with later) * * We skip the call to xfrm_ascii_read() in one case: * when we have one character in the buffer and have reached * end of data, this is so that xfrm_ascii_read() won't sit * forever waiting for the next character after a final '\r'. */ if (len > 0 || buflen > 1) xfrm_ascii_read(buf, &buflen, &adjlen); /* Now copy everything we can into cl_buf */ if (buflen > cl_size) { /* Because we have to cut our buffer short, make sure this * is made up for later by increasing adjlen. */ adjlen += (buflen - cl_size); buflen = cl_size; } memcpy(cl_buf, buf, buflen); /* Copy whatever remains at the end of session.xfer.buf to the * head of the buffer and adjust buf accordingly. * * adjlen is now the total bytes still waiting in buf, if * anything remains, copy it to the start of the buffer. */ if (adjlen > 0) memcpy(buf, buf+buflen, adjlen); /* Store everything back in session.xfer. */ session.xfer.buflen = adjlen; total += buflen; } /* Restart if data was returned by pr_netio_read() (len > 0) but * no data was copied to the client buffer (buflen = 0). * This indicates that xfrm_ascii_read() needs more data * in order to translate, so we need to call pr_netio_read() again. */ } while (len > 0 && buflen == 0); /* Return how much data we actually copied into the client buffer. */ len = buflen; } else if ((len = pr_netio_read(session.d->instrm, cl_buf, cl_size, 1)) > 0) { /* Non-ASCII mode doesn't need to use session.xfer.buf */ if (TimeoutStalled) pr_timer_reset(TIMER_STALLED, ANY_MODULE); total += len; } } } else { /* PR_NETIO_IO_WR */ while (cl_size) { int buflen = cl_size; unsigned int xferbuflen; if (buflen > PR_TUNABLE_XFER_BUFFER_SIZE) buflen = PR_TUNABLE_XFER_BUFFER_SIZE; xferbuflen = buflen; /* Fill up our internal buffer. */ memcpy(session.xfer.buf, cl_buf, buflen); if (session.sf_flags & (SF_ASCII|SF_ASCII_OVERRIDE)) { /* Scan the internal buffer, looking for LFs with no preceding CRs. * Add CRs (and expand the internal buffer) as necessary. xferbuflen * will be adjusted so that it contains the length of data in * the internal buffer, including any added CRs. */ xfrm_ascii_write(&session.xfer.buf, &xferbuflen, session.xfer.bufsize); } if (pr_netio_write(session.d->outstrm, session.xfer.buf, xferbuflen) < 0) return -1; if (TimeoutStalled) pr_timer_reset(TIMER_STALLED, ANY_MODULE); cl_size -= buflen; cl_buf += buflen; total += buflen; } len = total; } if (total && TimeoutIdle) pr_timer_reset(TIMER_IDLE, ANY_MODULE); session.xfer.total_bytes += total; session.total_bytes += total; return (len < 0 ? -1 : len); } #ifdef HAVE_SENDFILE /* pr_data_sendfile() actually transfers the data on the data connection. * ASCII translation is not performed. * return 0 if reading and data connection closes, or -1 if error */ pr_sendfile_t pr_data_sendfile(int retr_fd, off_t *offset, off_t count) { int flags, error; pr_sendfile_t len = 0, total = 0; #if defined(HAVE_AIX_SENDFILE) struct sf_parms parms; int rc; #endif /* HAVE_AIX_SENDFILE */ if (session.xfer.direction == PR_NETIO_IO_RD) return -1; flags = fcntl(PR_NETIO_FD(session.d->outstrm), F_GETFL); if (flags == -1) return -1; /* Set fd to blocking-mode for sendfile() */ if (flags & O_NONBLOCK) if (fcntl(PR_NETIO_FD(session.d->outstrm), F_SETFL, flags^O_NONBLOCK) == -1) return -1; for (;;) { #if defined(HAVE_LINUX_SENDFILE) || defined(HAVE_SOLARIS_SENDFILE) off_t orig_offset = *offset; /* Linux semantics are fairly straightforward in a glibc 2.x world: * * #include * * ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count) * * Unfortunately, this API does not allow for an off_t number of bytes * to be sent, only a size_t. This means we need to make sure that * the given count does not exceed the maximum value for a size_t. Even * worse, the return value is a ssize_t, not a size_t, which means * the maximum value used can only be of the maximum _signed_ value, * not the maximum unsigned value. This means calling sendfile() more * times. How annoying. */ #if defined(HAVE_LINUX_SENDFILE) if (count > INT_MAX) count = INT_MAX; #elif defined(HAVE_SOLARIS_SENDFILE) # if SIZEOF_SIZE_T == SIZEOF_INT if (count > INT_MAX) count = INT_MAX; # elif SIZEOF_SIZE_T == SIZEOF_LONG if (count > LONG_MAX) count = LONG_MAX; # elif SIZEOF_SIZE_T == SIZEOF_LONG_LONG if (count > LLONG_MAX) count = LLONG_MAX; # endif #endif /* !HAVE_SOLARIS_SENDFILE */ len = sendfile(PR_NETIO_FD(session.d->outstrm), retr_fd, offset, count); if (len != -1 && len < count) { /* Under Linux semantics, this occurs when a signal has interrupted * sendfile(). */ count -= len; /* Only reset the timers if data have actually been written out. */ if (len > 0) { if (TimeoutStalled) pr_timer_reset(TIMER_STALLED, ANY_MODULE); if (TimeoutIdle) pr_timer_reset(TIMER_IDLE, ANY_MODULE); } session.xfer.total_bytes += len; session.total_bytes += len; total += len; pr_signals_handle(); continue; } else if (len == -1) { /* Linux updates offset on error, not len like BSD, fix up so * BSD-based code works. */ len = *offset - orig_offset; *offset = orig_offset; #elif defined(HAVE_BSD_SENDFILE) /* BSD semantics for sendfile are flexible...it'd be nice if we could * standardize on something like it. The semantics are: * * #include * #include * #include * * int sendfile(int in_fd, int out_fd, off_t offset, size_t count, * struct sf_hdtr *hdtr, off_t *len, int flags) * * The comments above, about size_t versus off_t, apply here as * well. Except that BSD's sendfile() uses an off_t * for returning * the number of bytes sent, so we can use the maximum unsigned * value. */ #if SIZEOF_SIZE_T == SIZEOF_INT if (count > UINT_MAX) count = UINT_MAX; #elif SIZEOF_SIZE_T == SIZEOF_LONG if (count > ULONG_MAX) count = ULONG_MAX; #elif SIZEOF_SIZE_T == SIZEOF_LONG_LONG if (count > ULLONG_MAX) count = ULLONG_MAX; #endif if (sendfile(retr_fd, PR_NETIO_FD(session.d->outstrm), *offset, count, NULL, &len, 0) == -1) { #elif defined(HAVE_AIX_SENDFILE) memset(&parms, 0, sizeof(parms)); parms.file_descriptor = retr_fd; parms.file_offset = (uint64_t) *offset; parms.file_bytes = (int64_t) count; rc = send_file(&(PR_NETIO_FD(session.d->outstrm)), &(parms), (uint_t)0); len = (int) parms.bytes_sent; if (rc == -1 || rc == 1) { #endif /* HAVE_AIX_SENDFILE */ /* IMO, BSD's semantics are warped. Apparently, since we have our * alarms tagged SA_INTERRUPT (allowing system calls to be * interrupted - primarily for select), BSD will interrupt a * sendfile operation as well, so we have to catch and handle this * case specially. It should also be noted that the sendfile(2) man * page doesn't state any of this. * * HP/UX has the same semantics, however, EINTR is well documented * as a side effect in the sendfile(2) man page. HP/UX, however, * is implemented horribly wrong. If a signal would result in * -1 being returned and EINTR being set, what ACTUALLY happens is * that errno is cleared and the number of bytes written is returned. * * For obvious reasons, HP/UX sendfile is not supported yet. */ if (errno == EINTR) { pr_signals_handle(); /* If we got everything in this transaction, we're done. */ if (len >= count) break; else count -= len; *offset += len; if (TimeoutStalled) pr_timer_reset(TIMER_STALLED, ANY_MODULE); if (TimeoutIdle) pr_timer_reset(TIMER_IDLE, ANY_MODULE); session.xfer.total_bytes += len; session.total_bytes += len; total += len; continue; } error = errno; fcntl(PR_NETIO_FD(session.d->outstrm), F_SETFL, flags); errno = error; return -1; } break; } if (flags & O_NONBLOCK) fcntl(PR_NETIO_FD(session.d->outstrm), F_SETFL, flags); if (TimeoutStalled) pr_timer_reset(TIMER_STALLED, ANY_MODULE); if (TimeoutIdle) pr_timer_reset(TIMER_IDLE, ANY_MODULE); session.xfer.total_bytes += len; session.total_bytes += len; total += len; return total; } #endif /* HAVE_SENDFILE */