/*****************************************************************************\ * Copyright (c) 2002-2003 Pelle Johansson. * * All rights reserved. * * * * This file is part of the moftpd package. Use and distribution of * * this software is governed by the terms in the file LICENCE, which * * should have come with this package. * \*****************************************************************************/ /* $moftpd: transfer.c 1264 2005-04-06 13:32:27Z morth $ */ #include "system.h" #include "connection.h" #include "utf8fs/memory.h" #include "main.h" #include "accounter.h" #include "events.h" int xferBufferSize = 1024; char *xferBuffer; extern int forkConnections, urgData, forker; int passive_acceptor(int sock, void *user, int urgent) { connection_t *conn = user; struct sockaddr_storage addr; int al = sizeof(addr); int i; if (conn->sock == -1) return 0; set_locale (conn->currLang); remove_read_fd(sock); conn->dataSock = accept(sock, (struct sockaddr*)&addr, &al); if(conn->dataSock == -1) { reply(conn, "451 Acception of connection failed: %s.", strerror(errno)); close_data_connection (conn); close(sock); return 0; } close(sock); if(!conn->user->allowForeign) { if (!same_addr((struct sockaddr*)&addr, (struct sockaddr*)&conn->rDataAddr, 0)) { syslog (LOG_NOTICE, "%d: Data connection from foreign address", conn->id); reply(conn, "501 Foreign address not allowed."); close_data_connection (conn); return 0; } } else if (!conn->user->allowOutOfRange && conn->server->numRanges) { for (i = 0; i < conn->server->numRanges; i++) { if (check_range ((struct sockaddr*)&addr, (struct sockaddr*)&conn->server->ranges[i].addr, (struct sockaddr*)&conn->server->ranges[i].mask)) break; } if (i == conn->server->numRanges) { syslog (LOG_NOTICE, "%d: Data connection out of range", conn->id); reply (conn, "501 Address out of allowed range."); } } if(fcntl(conn->dataSock, F_SETFL, O_NONBLOCK)) { reply(conn, "451 Failed to make socket nonblocking: %s.", strerror(errno)); close_data_connection (conn); return 0; } i = IPTOS_THROUGHPUT; setsockopt(conn->dataSock, IPPROTO_IP, IP_TOS, &i, sizeof(i)); conn->passiveAccepted = 1; if(conn->working && open_data_connection(conn)) { reply(conn, "421 %s.", strerror(errno)); close_data_connection (conn); } return 0; } int open_data_connection(connection_t *conn) { int i; if(!xferBuffer) { xferBuffer = palloc(xferBufferSize, NULL, NULL); if(!xferBuffer) return -1; } if(conn->passive) { if(!conn->passiveAccepted) return 0; } else { if(conn->dataSock >= 0) { close(conn->dataSock); remove_read_fd(conn->dataSock); remove_write_fd(conn->dataSock); } conn->dataSock = socket(conn->rDataAddr.ss_family, SOCK_STREAM, IPPROTO_TCP); if(conn->dataSock < 0) return -1; if(fcntl(conn->dataSock, F_SETFL, O_NONBLOCK)) { close(conn->dataSock); conn->dataSock = -1; return -1; } super_privs(0); switch(conn->rDataAddr.ss_family) { case AF_INET: if(conn->lDataAddr.ss_family == AF_INET) { ((struct sockaddr_in*)&conn->lDataAddr)->sin_port = htons (conn->activePort); bind(conn->dataSock, (struct sockaddr*)&conn->lDataAddr, sizeof(struct sockaddr_in)); } else if(conn->activePort) { struct sockaddr_in addr = {}; addr.sin_family = AF_INET; #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN addr.sin_len = sizeof(addr); #endif addr.sin_port = htons (conn->activePort); bind(conn->dataSock, (struct sockaddr*)&addr, sizeof(addr)); } if(connect(conn->dataSock, (struct sockaddr*)&conn->rDataAddr, sizeof(struct sockaddr_in)) && errno != EINPROGRESS) return -1; break; case AF_INET6: if(conn->lDataAddr.ss_family == AF_INET6) { ((struct sockaddr_in6*)&conn->lDataAddr)->sin6_port = htons (conn->activePort); bind(conn->dataSock, (struct sockaddr*)&conn->lDataAddr, sizeof(struct sockaddr_in6)); } else if(conn->activePort) { struct sockaddr_in6 addr = {}; addr.sin6_family = AF_INET6; #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN addr.sin6_len = sizeof(addr); #endif addr.sin6_port = htons (conn->activePort); bind(conn->dataSock, (struct sockaddr*)&addr, sizeof(addr)); } if(connect(conn->dataSock, (struct sockaddr*)&conn->rDataAddr, sizeof(struct sockaddr_in6)) && errno != EINPROGRESS) return -1; break; default: errno = EINVAL; return -1; } } i = IPTOS_THROUGHPUT; setsockopt(conn->dataSock, IPPROTO_IP, IP_TOS, &i, sizeof(i)); i = xferBufferSize; setsockopt(conn->dataSock, SOL_SOCKET, SO_SNDLOWAT, &i, sizeof(i)); conn->transferStart = 0; #ifdef SO_NOSIGPIPE i = -1; setsockopt (conn->dataSock, SOL_SOCKET, SO_NOSIGPIPE, &i, sizeof (i)); #endif conn->dataWrite = conn_plain_writer; conn->dataChannel = (void*)conn->dataSock; #ifdef USE_TLS conn->tlsDataState = 0; if (conn->protLevel) { conn->tlsData = tls_open (conn->dataSock, conn->server->tlsOptions, conn->server->tlsCert, conn->server->tlsKey); if (!conn->tlsData) { reply (conn, "431 TLS initialisation failed."); close_data_connection (conn); return 0; } tls_start (conn->tlsData); add_read_fd (conn->dataSock, data_receiver, conn); return 0; } #endif if (conn->sending) { /* * Block on sending if forking connections. We should get EINTR on SIGURG, * and since we set SNDLOWAT to the xfer buffer size it should only apply * to mmap() sends. */ if (forkConnections && forker != getpid ()) fcntl (conn->dataSock, F_SETFL, 0); add_write_fd (conn->dataSock, data_sender, conn); } else add_read_fd (conn->dataSock, data_receiver, conn); return 0; } int close_data_connection (connection_t *conn) { int res = 0; if (conn->filePath) { time_t now; char rhost[NI_MAXHOST]; int al; switch (conn->rDataAddr.ss_family) { case AF_INET: al = sizeof (struct sockaddr_in); break; case AF_INET6: al = sizeof (struct sockaddr_in6); break; default: al = sizeof (conn->rDataAddr); break; } if (getnameinfo ((struct sockaddr*)&conn->rDataAddr, al, rhost, sizeof (rhost), NULL, 0, NI_NUMERICHOST)) strcpy (rhost, "unknown"); time (&now); /* * Fields are: * connection id (as usual): * current time * transfer time in seconds * remote host * transfered bytes * file path * transfer mode, a for ascii, b for binary * Special action, _ for no action, S for secured data connection. * transfer direction, o for outgoing, i for incoming * user type, a for anonymous, g (guest) for server user, * r (real) for system user. * user name, email for anonymous users, account if entered, otherwise * user. * Service name, FTPS for a secure connection, FTP for nonsecure. * This reflects the control connection, not the data connection. * authentication-method, always 0 for none right now. * authenticated-user-id, always * for n/a right now. * success flag, c for complete transfer, i for incomplete. */ if (!conn->data) conn->restart = 0; syslog (LOG_INFO, "%d: %.24s %.0f %s %llu %s %c %c %c %c %s %s 0 * %c", conn->id, ctime (&now), difftime (now, conn->transferStart), rhost, conn->dataOffset - conn->restart, conn->filePath, conn->type == ttText? 'a' : 'b', #ifdef USE_TLS conn->tlsData? 'S' : #endif '_', conn->sending? 'o' : 'i', conn->user && conn->user->anonymous? 'a' : conn->account || (conn->user && conn->user->spec_flags)? 'g' : 'r', conn->email? conn->email : conn->account? conn->account : conn->user? conn->user->name : "unknown", #ifdef USE_TLS conn->tlsControl? "FTPS" : #endif "FTP", conn->dataOffset >= conn->dataLen? 'c' : 'i'); if (conn->sending) accounter (conn->accSock, "SENT %lld %s", conn->dataOffset - conn->restart, conn->filePath); else accounter (conn->accSock, "GOT %lld %s", conn->dataOffset, conn->filePath); pfree (conn->filePath, conn); conn->filePath = NULL; } conn->passive = 0; conn->passiveAccepted = 0; conn->working = 0; if(conn->data) { if(conn->fileFd >= 0) munmap(conn->data, conn->dataLen); else pfree(conn->data, conn); conn->data = NULL; } conn->dataOffset = 0; conn->dataLen = 0; if(conn->fileFd >= 0) { close(conn->fileFd); conn->fileFd = -1; } #ifdef USE_TLS if (conn->tlsData) { tls_stop (conn->tlsData); tls_free (conn->tlsData); conn->tlsData = NULL; } #endif if(conn->dataSock >= 0) { close(conn->dataSock); remove_read_fd(conn->dataSock); remove_write_fd(conn->dataSock); conn->dataSock = -1; res = 1; } if(conn->spontQuit) { conn->spontQuit = 0; disconnected(conn); return -1; } return res; } int check_secure_data (connection_t *conn) { #ifdef USE_TLS int l; if (conn->tlsData) { switch (conn->tlsDataState) { case 0: l = tls_accept (conn->tlsData); if (l == 1) { syslog (LOG_INFO, "%d: Successfully negotiated data TLS.", conn->id); conn->tlsDataState = 1; conn->dataWrite = (writeFun_t)tls_write; conn->dataChannel = conn->tlsData; if (conn->sending) { remove_read_fd (conn->dataSock); add_write_fd (conn->dataSock, data_sender, conn); } if (conn->server->tlsOptions & tlsVerifyClient) { tlscert_t clientDataCert = tls_get_peer_cert (conn->tlsData); if (tls_compare_certs (conn->tlsClientCert, clientDataCert)) { syslog (LOG_NOTICE, "%d: Control/data certificate mismatch.", conn->id); reply (conn, "535 Must use same certificate on control and data connections."); close_data_connection (conn); } tls_free_cert (clientDataCert); } } else if (l) { syslog (LOG_NOTICE, "%d: Error in data TLS negotiation: %s.", conn->id, tls_error (conn->tlsData, l)); close_data_connection (conn); } return 1; case 2: l = tls_stop (conn->tlsData); if (l == 1) close_data_connection (conn); else if (l) { syslog (LOG_NOTICE, "%d: Error in TLS shutdown: %s.", conn->id, tls_error (conn->tlsData, l)); close_data_connection (conn); } return 1; } } #endif return 0; } int data_receiver(int sock, void *user, int urgent) { connection_t *conn = user; int rl, wl; char *bp1, *bp2; if (conn->sock == -1) return 0; set_locale (conn->currLang); if (conn->protLevel && check_secure_data (conn)) return 0; if (!conn->transferStart) time (&conn->transferStart); #ifdef USE_TLS if (conn->tlsData) rl = tls_read (conn->tlsData, xferBuffer, xferBufferSize); else #endif rl = recv(sock, xferBuffer, xferBufferSize, 0); if(rl < 0) { reply(conn, "451 socket error: %s.", #ifdef USE_TLS conn->tlsData? tls_error (conn->tlsData, rl) : #endif strerror(errno)); close_data_connection (conn); return 0; } if(!rl) { /* EOF */ if (!conn->dataLen) conn->dataLen = conn->dataOffset; reply(conn, "226 Transfer complete."); close_data_connection (conn); return 0; } if(conn->type == ttText) { wl = 0; for(bp1 = xferBuffer; (bp2 = memchr(bp1, '\r', rl)); bp1 = bp2 + 1) { wl = write(conn->fileFd, bp1, bp2 - bp1); if(wl == -1) break; conn->dataOffset += wl + 1; rl -= wl + 1; } if(rl && wl != -1) { wl = write(conn->fileFd, bp1, rl); conn->dataOffset += rl; } } else { wl = write(conn->fileFd, xferBuffer, rl); conn->dataOffset += rl; } if(wl == -1) { if(errno == ENOSPC) reply(conn, "452 Out of disk space."); else if(errno == EDQUOT) reply(conn, "552 Quota exceeded."); else reply(conn, "451 write failed: %s.", strerror(errno)); conn->dataLen = conn->dataOffset + 1; // To indicate "incomplete" close_data_connection (conn); } return 0; } int data_sender(int sock, void *user, int urgent) { connection_t *conn = user; int rl, wl, fc; char *bp1, *bp2; if (conn->sock == -1) return 0; set_locale (conn->currLang); if (!conn->transferStart) time (&conn->transferStart); rl = sizeof(errno); if(!getsockopt(sock, SOL_SOCKET, SO_ERROR, &errno, &rl)) { if(errno) { if (errno == EPIPE) reply (conn, "451 Remote end closed connection."); else reply (conn, "451 socket error: %s.", strerror (errno)); close_data_connection (conn); return 0; } } #ifdef USE_TLS if (conn->tlsControl) urgData = 1; // Force exit after one loop. #endif fc = (forkConnections && forker != getpid ()); // Store locally so a smart compiler knows it doesn't change. if(conn->data) { do { if(conn->dataOffset == conn->dataLen) { /* EOF */ reply(conn, "226 Transfer complete."); close_data_connection (conn); return 0; } wl = conn->dataWrite (conn->dataChannel, (char*)conn->data + conn->dataOffset, conn->dataLen - conn->dataOffset); if(wl >= 0) conn->dataOffset += wl; else { #ifdef USE_TLS if (conn->tlsData) { reply (conn, "451 Write to socket failed: %s.", tls_error (conn->tlsData, wl)); close_data_connection (conn); } else #endif if (errno != EINTR && errno != EAGAIN) { if (errno == EPIPE) reply (conn, "451 Remote end closed connection."); else reply (conn, "451 Write to socket failed: %s.", strerror(errno)); close_data_connection (conn); } return 0; } } while(fc && !urgData); } else if(conn->type == ttText) { do { if (conn->endOffset) { rl = conn->endOffset - conn->restart - conn->dataOffset; if (rl > xferBufferSize) rl = xferBufferSize; else if (rl < 0) rl = 0; } else rl = xferBufferSize; if (rl) rl = read(conn->fileFd, xferBuffer, rl); if(rl == -1) { reply(conn, "451 read failed: %s.", strerror(errno)); close_data_connection (conn); return 0; } if(!rl) { /* EOF */ conn->dataLen = conn->dataOffset; reply(conn, "226 Transfer complete."); close_data_connection (conn); return 0; } conn->dataOffset += rl; wl = 0; for(bp1 = xferBuffer; (bp2 = memchr(bp1, '\n', rl)); bp1 = bp2 + 1) { wl = conn->dataWrite (conn->dataChannel, bp1, bp2 - bp1); if(wl < 0) break; rl -= wl + 1; wl = conn->dataWrite (conn->dataChannel, "\r\n", 2); if(wl < 0) break; conn->dataOffset++; } if (rl && wl >= 0) wl = conn->dataWrite (conn->dataChannel, bp1, rl); if (wl < 0) { #ifdef USE_TLS if (conn->tlsData) { reply (conn, "451 Write to socket failed: %s.", tls_error (conn->tlsData, wl)); close_data_connection (conn); } else #endif if (errno != EINTR && errno != EAGAIN) { if (errno == EPIPE) reply (conn, "451 Remote end closed connection."); else reply (conn, "451 Write to socket failed: %s.", strerror(errno)); close_data_connection (conn); } else { lseek (conn->fileFd, SEEK_CUR, -rl); conn->dataOffset -= rl; } return 0; } } while(fc && !urgData); } else { do { if (conn->endOffset) { rl = conn->endOffset - conn->restart - conn->dataOffset; if (rl > xferBufferSize) rl = xferBufferSize; else if (rl < 0) rl = 0; } else rl = xferBufferSize; rl = read(conn->fileFd, xferBuffer, rl); if(rl == -1) { reply(conn, "451 read failed: %s.", strerror(errno)); close_data_connection (conn); return 0; } if(!rl) { /* EOF */ conn->dataLen = conn->dataOffset; reply(conn, "226 Transfer complete."); close_data_connection (conn); return 0; } conn->dataOffset += rl; wl = conn->dataWrite (conn->dataChannel, xferBuffer, rl); if (wl < 0) { #ifdef USE_TLS if (conn->tlsData) { reply (conn, "451 Write to socket failed: %s.", tls_error (conn->tlsData, wl)); close_data_connection (conn); } else #endif if (errno != EINTR && errno != EAGAIN) { if (errno == EPIPE) reply (conn, "451 Remote end closed connection."); else reply (conn, "451 Write to socket failed: %s.", strerror (errno)); close_data_connection (conn); } else { lseek (conn->fileFd, SEEK_CUR, -rl); conn->dataOffset -= rl; } return 0; } else if(wl < rl) { lseek(conn->fileFd, SEEK_CUR, wl - rl); conn->dataOffset += wl - rl; } } while(fc && !urgData); } return 0; }