/*****************************************************************************\
* 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;
}
syntax highlighted by Code2HTML, v. 0.9.1