/*****************************************************************************\
* 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: connection.c 1245 2004-12-09 12:01:59Z morth $ */
#include "system.h"
#include "connection.h"
#include "server.h"
#include "main.h"
#include "commands.h"
#include "utf8fs/file.h"
#include "utf8fs/memory.h"
#include "accounter.h"
#include "events.h"
#include "confparse.h"
static void *connections;
extern int reloadConfig, urgData, debug;
extern const char *localeDir, *localeSuffix;
static int accounter_reply (int sock, void *user, int urgent)
{
connection_t *conn = user;
int l;
char buf[4097], *bp, *nbp, *str;
#ifdef HAVE_LIBPAM
static struct pam_conv conv = { conv_fun };
#endif
char rhost[NI_MAXHOST], lhost[NI_MAXHOST], rport[NI_MAXSERV], lport[NI_MAXSERV];
struct sockaddr_storage lAddr, rAddr;
if (conn->sock == -1)
return 0;
l = read (sock, buf, sizeof (buf) - 1);
if (l <= 0)
{
if (l < 0 && errno == EINTR)
return 0;
close (conn->accSock);
conn->accSock = -1;
if (conn->accWait)
reply (conn, "500 Accounter error.");
close_data_connection (conn);
return 0;
}
buf[l] = 0;
for (bp = buf; bp; bp = nbp)
{
nbp = strchr (bp, '\n');
if (nbp)
*nbp++ = 0;
if (!bp[0])
continue;
if (!strncmp (bp, "MSG ", 4))
reply_spont (conn, bp + 4, 0);
else if (!strncmp (bp, "ABORT", 5))
{
if (conn->working)
{
l = close_data_connection (conn);
if (l == -1)
return 0;
if (bp[5] && bp[6])
reply (conn, "450 %s", bp + 6);
else
reply (conn, "450 Transfer aborted by administrator.");
}
}
else if (!strncmp (bp, "DISCONNECT", 10))
{
if (bp[10] && bp[11] && (str = talloc (strlen (bp + 11) + 4)))
{
strcpy (str, "421 ");
strcat (str, bp + 11);
reply_spont (conn, str, 1);
}
else
reply_spont (conn, "421 Connection closed by administrator.", 1);
}
else if (!strcmp (bp, "RELOAD"))
; // Just ignore.
else
{
switch (conn->accWait)
{
case acWelcome:
l = sizeof(lAddr);
getsockname(conn->sock, (struct sockaddr*)&lAddr, &l);
if (getnameinfo ((struct sockaddr*)&lAddr, l, lhost, sizeof (lhost), lport,
sizeof (lport), NI_NUMERICHOST | NI_NUMERICSERV))
{
strcpy (lhost, "unknown");
strcpy (lport, "0");
}
l = sizeof(rAddr);
getpeername(conn->sock, (struct sockaddr*)&rAddr, &l);
if (getnameinfo ((struct sockaddr*)&rAddr, l, rhost, sizeof (rhost), rport,
sizeof (rport), NI_NUMERICHOST | NI_NUMERICSERV))
{
strcpy (rhost, "unknown");
strcpy (rport, "0");
}
l = -1;
if (sscanf (bp, "ALLOW %d", &l) == 1)
{
#ifdef USE_SQL
if (conn->server->sql.type && conn->server->sqlConnectQuery)
{
if (sql_connect (&conn->server->sql, conn->server->sqlHost,
conn->server->sqlUser, conn->server->sqlDB,
conn->server->sqlPass, conn->server->sqlCert,
conn->server->sqlKey))
l = -2;
else
{
sql_arg_t args[5];
int res;
args[0].ch = 's';
args[0].str = tstring (sql_quote (conn->server->name));
args[1].ch = 'l';
args[1].str = tstring (sql_quote (lhost));
args[2].ch = 'r';
args[2].str = tstring (sql_quote (rhost));
args[3].ch = 'L';
args[3].str = lport;
args[4].ch = 'R';
args[4].str = rport;
res = sql_query (&conn->server->sql, conn->server->sqlConnectQuery, 5, args);
if (res <= 0)
l = -2;
else
{
const char *field, *val;
int b;
for (res = 0; (field = sql_fetch_cell (&conn->server->sql, -1, res)); res++)
{
val = sql_fetch_cell (&conn->server->sql, 0, res);
if (!val)
continue;
if (!strcasecmp (field, "ALLOW"))
{
b = parse_bool (val);
if (!b)
{
l = -2;
break;
}
}
else if (!strcasecmp (field, "LANG"))
{
str = talloc (strlen (localeDir) + strlen (val) + strlen (localeSuffix) + 1);
if (str)
{
strcpy (str, localeDir);
strcat (str, val);
strcat (str, localeSuffix);
b = open_shared (str);
if (b >= 0)
{
if (conn->langFd >= 0)
close_shared (conn->langFd);
conn->langFd = b;
pfree (conn->currLang, conn);
conn->currLang = pstring (val, conn);
}
}
}
}
}
sql_free_result (&conn->server->sql);
if (l < 0 && !conn->server->sqlRefs)
sql_disconnect (&conn->server->sql);
}
}
#endif
}
if (l >= 0)
{
conn->id = l;
#ifdef HAVE_LIBPAM
if (conn->pamh)
pam_end (conn->pamh, PAM_SUCCESS);
conv.appdata_ptr = conn;
l = pam_start (conn->server->pam_service, NULL, &conv, &conn->pamh);
if (l != PAM_SUCCESS)
{
reply (conn, "421 %s.", pam_strerror (conn->pamh, l));
disconnected (conn);
return 0;
}
#ifdef PAM_TTY
pam_set_item (conn->pamh, PAM_TTY, conn->server->pam_service);
#endif
#ifdef PAM_RHOST
if (strcmp (rhost, "unknown"))
pam_set_item (conn->pamh, PAM_RHOST, rhost);
#endif
#endif
if (conn->oldserver)
{
#ifdef USE_SQL
if (!conn->oldserver->sqlRefs)
sql_disconnect (&conn->oldserver->sql);
#endif
pfree (conn->oldserver, conn);
conn->oldserver = NULL;
}
else
{
syslog (LOG_INFO, "Connection to %s from %s (id: %d)", conn->server->name, rhost, conn->id);
// Start listening for commands.
add_read_fd (conn->sock, control_handler, conn);
}
if (conn->server->welcomeMsg)
reply_msg (conn, "220 %s", conn->server->welcomeMsg);
else
{
if (find_server ((struct sockaddr*)&lAddr, (struct sockaddr*)&rAddr, conn->server))
{
reply (conn, "220-Service available at %s (%s).", conn->server->name, PACKAGE_STRING);
reply (conn, "220 You can use the HOST command to switch to other servers.");
}
else
reply (conn, "220 Service available at %s (%s).", conn->server->name, PACKAGE_STRING);
}
}
else
{
if (l < -1 || !strcmp (bp, "DENY"))
{
pfree (conn->server, conn);
if (conn->oldserver)
{
conn->server = conn->oldserver;
conn->oldserver = NULL;
if (l < -1)
reply (conn, "500 Access denied.");
else
reply (conn, "500 Connection limit reached.");
break;
}
conn->server = pattach (find_server ((struct sockaddr*)&lAddr, (struct sockaddr*)&rAddr, conn->server), conn);
if (conn->server)
{
if (accounter (conn->accSock, "CONNECT %s %s\n", rhost, conn->server->name))
{
syslog (LOG_ERR, "Connect from %s: Error in acocunter: %m", rhost);
reply (conn, "421 Error: %s. Please try again later.", strerror (errno));
disconnected (conn);
return 0;
}
continue;
}
else
{
if (l < -1)
{
reply (conn, "421 No service available at this address.");
syslog (LOG_NOTICE, "Connect from %s: Denied by rule.", rhost);
}
else
{
reply (conn, "421 Connection limit reached.");
syslog (LOG_NOTICE, "Connect from %s: Connection limit reached.", rhost);
}
disconnected (conn);
return 0;
}
}
else
{
if (conn->oldserver)
{
pfree (conn->server, conn);
conn->server = conn->oldserver;
conn->oldserver = NULL;
reply (conn, "500 Temporary error.");
break;
}
reply (conn, "421 Temporary error. Please try again later.");
syslog (LOG_ERR, "Connect from %s: Error in acocunter reply: %s", rhost, buf);
disconnected (conn);
return 0;
}
}
break;
case acLogin:
if (!strcmp (bp, "ALLOW"))
{
if (authenticated (conn))
{
unauthenticated (conn);
reply (conn, "530 Failed to authenticate: %s (root dir inaccessible?).", strerror (errno));
break;
}
user_setup_environ (conn->user, 1, conn->extRFd, conn->extWFd);
if (conn->user->anonymous)
reply (conn, "230 Anonymous access granted.");
else
reply (conn, "230 Logged in as %s.", conn->user->name);
}
else
{
unauthenticated (conn);
if (!strcmp (bp, "DENY"))
reply (conn, "530 Login limit reached.");
else
reply (conn, "530 Accounter error. Please try again later.");
}
break;
case acSending:
if (!strcmp (bp, "ALLOW"))
{
if (open_data_connection (conn))
{
reply (conn, "425 Failed to open data connection: %s.", strerror(errno));
pfree (conn->filePath, conn);
conn->filePath = NULL;
if (close_data_connection (conn))
return 0;
}
else
{
user_setup_environ (conn->user, conn->authed, conn->extRFd, conn->extWFd);
if (conn->type == ttImage)
reply (conn, "150 Opening BINARY data connection for sending of \"%s\" (%lld bytes).",
print_path (conn->filePath), conn->dataLen);
else
reply (conn, "150 Opening ASCII data connection for sending of \"%s\".",
print_path (conn->filePath));
conn->working = 1;
}
}
else
{
if (!strcmp (bp, "DENY"))
reply (conn, "550 Permission denied.");
else
reply (conn, "550 Accounter error. Please try again later.");
pfree (conn->filePath, conn);
conn->filePath = NULL;
if (close_data_connection (conn))
return 0;
}
break;
case acGetting:
if (!strcmp (bp, "ALLOW"))
{
if (open_data_connection (conn))
{
reply (conn, "425 Failed to open data connection: %s.", strerror (errno));
pfree (conn->filePath, conn);
conn->filePath = NULL;
if(close_data_connection (conn) == -1)
return 0;
}
else
{
user_setup_environ (conn->user, conn->authed, conn->extRFd, conn->extWFd);
reply (conn, "150 Opening %s data connection for retrieval of \"%s\".",
conn->type == ttImage? "BINARY" : "ASCII", print_path (conn->filePath));
conn->working = 1;
}
}
else
{
if (!strcmp (bp, "DENY\n"))
reply (conn, "550 Permission denied.");
else
reply (conn, "550 Accounter error. Please try again later.");
pfree (conn->filePath, conn);
conn->filePath = NULL;
if (close_data_connection (conn))
return 0;
}
break;
case acList:
if (!strcmp (bp, "END"))
reply (conn, "200 End of list.");
else
{
bp[strlen (bp) - 1] = 0;
reply (conn, " %s", bp);
continue;
}
break;
case acMsg:
if (!strcmp (bp, "OK"))
reply (conn, "200 Message sent.");
else if (!strcmp (bp, "INVALID"))
reply (conn, "501 Invalid connection id.");
else
reply (conn, "500 Unknown error.");
break;
case acAbort:
if (!strcmp (bp, "OK"))
reply (conn, "200 Transfer aborted.");
else if (!strcmp (bp, "INVALID"))
reply (conn, "501 Invalid connection id.");
else
reply (conn, "500 Unknown error.");
break;
case acDisconnect:
if (!strcmp (bp, "OK"))
reply (conn, "200 Disconnected.");
else if (!strcmp (bp, "INVALID"))
reply (conn, "501 Invalid connection id.");
else
reply (conn, "500 Unknown error.");
break;
}
conn->accWait = 0;
}
}
if (!conn->accWait && !conn->sleepUntil)
run_commands (conn);
return 0;
}
ssize_t conn_plain_writer (const void *channel, const void *buf, size_t len)
{
return write ((int)channel, buf, len);
}
static ssize_t conn_plain_vecs_writer (const void *channel, struct iovec *vecs, int num)
{
struct msghdr msg = {0};
msg.msg_iov = vecs;
msg.msg_iovlen = num;
return sendmsg ((int)channel, &msg, 0);
}
int count_connections (void)
{
connection_t *conn;
int i = 0;
for (conn = pchild (connections, NULL); conn; conn = pchild (connections, conn))
i++;
return i;
}
connection_t *new_connection(int sock, server_t *serv, int accSock)
{
connection_t *res;
struct sockaddr_storage lAddr, rAddr;
int lAl, rAl;
char rhost[NI_MAXHOST], lhost[NI_MAXHOST], lport[NI_MAXSERV];
int i;
/* Check that it really is a socket and get the local address/port. */
lAl = sizeof(lAddr);
if(getsockname(sock, (struct sockaddr*)&lAddr, &lAl))
return NULL;
rAl = sizeof(lAddr);
if(getpeername(sock, (struct sockaddr*)&rAddr, &rAl))
return NULL;
if (getnameinfo ((struct sockaddr*)&rAddr, rAl, rhost, sizeof (rhost), NULL,
0, NI_NUMERICHOST))
strcpy (rhost, "unknown");
if(!connections)
{
connections = proot();
if(!connections)
return NULL;
}
res = palloc(sizeof(connection_t), connections, NULL);
if(!res)
return NULL;
res->sock = sock;
res->langFd = -1;
res->id = -1;
res->controlWrite = conn_plain_writer;
res->controlWriteVecs = conn_plain_vecs_writer;
res->controlChannel = (void*)sock;
if(serv)
res->server = pattach(serv, res);
else
{
/* Find the server running on this address/port. */
res->server = pattach(find_server((struct sockaddr*)&lAddr, (struct sockaddr*)&rAddr, NULL), res);
if(!res->server)
{
if (getnameinfo ((struct sockaddr*)&lAddr, lAl, lhost, sizeof (lhost), lport,
sizeof (lport), NI_NUMERICHOST | NI_NUMERICSERV))
{
strcpy (lhost, "unknown");
strcpy (lport, "unknown");
}
syslog (LOG_NOTICE, "Connection from %s, but no server on %s port %s", rhost, lhost, lport);
reply(res, "421 No service available at this address.");
close (res->sock);
pfree(res, connections);
if(!errno)
errno = EINVAL;
return NULL;
}
}
if (accSock >= 0)
res->accSock = accSock;
else
res->accSock = connect_accounter ();
if (res->accSock < 0)
{
reply (res, "421 Accounter is not available.");
close (res->sock);
pfree (res, connections);
if (!errno)
errno = EINVAL;
return NULL;
}
accounter (res->accSock, "SET PID %d\n", (int)getpid ());
switch(lAddr.ss_family)
{
case AF_INET:
res->activePort = ntohs (((struct sockaddr_in*)&lAddr)->sin_port) - 1;
((struct sockaddr_in*)&lAddr)->sin_port = htons (res->activePort);
break;
case AF_INET6:
res->activePort = ntohs (((struct sockaddr_in6*)&lAddr)->sin6_port) - 1;
((struct sockaddr_in6*)&lAddr)->sin6_port = htons (res->activePort);
break;
}
if(!res->server->allowLowPorts)
{
switch(rAddr.ss_family)
{
case AF_INET:
if(ntohs (((struct sockaddr_in*)&rAddr)->sin_port) < 1024)
{
syslog (LOG_NOTICE, "%d: Client port < 1024", res->sock);
reply(res, "421 Server does not accept client ports < 1024.");
close (res->sock);
pfree(res, connections);
errno = EINVAL;
return NULL;
}
break;
case AF_INET6:
if(ntohs (((struct sockaddr_in6*)&rAddr)->sin6_port) < 1024)
{
syslog (LOG_NOTICE, "%d: Client port < 1024", res->sock);
reply(res, "421 Server does not accept client ports < 1024.");
close (res->sock);
pfree(res, connections);
errno = EINVAL;
return NULL;
}
}
}
res->lDataAddr = lAddr;
res->rDataAddr = rAddr;
res->dataSock = -1;
res->fileFd = -1;
res->mlstTags = tfType | tfUnique | tfModify | tfPerm | tfSize;
res->extRFd = res->extWFd = -1;
i = -1;
setsockopt (sock, SOL_SOCKET, SO_OOBINLINE, &i, sizeof (i));
i = IPTOS_LOWDELAY;
setsockopt (sock, IPPROTO_IP, IP_TOS, &i, sizeof (i));
if (accounter (res->accSock, "CONNECT %s %s\n", rhost, res->server->name))
{
syslog (LOG_ERR, "%d: Error in acocunter: %m", res->sock);
reply (res, "421 Error: %s. Please try again later.", strerror (errno));
disconnected (res);
return NULL;
}
res->accWait = acWelcome;
/* Only add accounter fd for now. */
add_read_fd (res->accSock, accounter_reply, res);
return res;
}
void disconnected(connection_t *conn)
{
unauthenticated (conn);
conn->spontQuit = 0;
close_data_connection (conn);
remove_read_fd(conn->sock);
remove_read_fd (conn->accSock);
close (conn->accSock);
conn->accSock = -1;
syslog (LOG_INFO, "%d: Disconnected", conn->id);
#ifdef USE_TLS
if (conn->tlsControl)
{
tls_stop (conn->tlsControl);
tls_free (conn->tlsControl);
}
if (conn->tlsData)
{
tls_stop (conn->tlsData);
tls_free (conn->tlsData);
}
if (conn->tlsClientCert)
tls_free_cert (conn->tlsClientCert);
#endif
close(conn->sock);
conn->sock = -1;
if (conn->langFd >= 0)
close_shared (conn->langFd);
#ifdef HAVE_LIBPAM
if (conn->pamh)
pam_end (conn->pamh, PAM_SUCCESS);
conn->pamh = NULL;
#endif
if (conn->inLookUp)
{
#ifdef USE_SQL
if (!--conn->server->sqlRefs)
sql_disconnect (&conn->server->sql);
#endif
}
pfree(conn, connections);
}
void quit_all_connections(const char *str)
{
connection_t *conn;
if (!connections)
return;
for(conn = pchild(connections, NULL); conn; conn = pchild(connections, conn))
reply_spont(conn, str, 1);
}
void disconnect_all_connections(void)
{
connection_t *conn;
if (!connections)
return;
while((conn = pchild(connections, NULL)))
disconnected(conn);
}
void check_idle(void)
{
connection_t *conn, *nconn;
time_t currtime;
int maxIdle;
if(!connections)
return;
time(&currtime);
for(conn = pchild(connections, NULL); conn; conn = nconn)
{
nconn = pchild(connections, conn);
if (conn->accWait)
continue;
if (conn->sleepUntil && currtime >= conn->sleepUntil)
{
conn->sleepUntil = 0;
run_commands (conn);
}
if(conn->user)
maxIdle = conn->user->maxIdle;
else
maxIdle = conn->server->maxIdle;
if(maxIdle && !conn->working)
{
if(currtime > conn->lastCommand + maxIdle * 2)
disconnected(conn);
else if(currtime > conn->lastCommand + maxIdle && !conn->spontQuit)
reply_spont(conn, "421 Connection timed out.", 1);
}
}
}
void reply(connection_t *conn, const char *format, ...)
{
va_list ap;
char *str;
int doFree = 0, isLastLine = 1;
if (conn->langFd >= 0 && format)
format = scan_strings (conn->langFd, format);
if (format)
{
va_start (ap, format);
#ifdef HAVE_VASPRINTF
vasprintf (&str, format, ap);
doFree = 1;
#else
str = talloc (4097);
if (str)
vsnprintf (str, 4096, format, ap);
#endif
va_end (ap);
if (!str)
{
str = tstring (format);
doFree = 0;
}
if (str)
isLastLine = (str[0] != ' ' && str[3] == ' ');
// Change the numeric code if needed.
if (str && conn->spontReply && str[0] != ' ')
{
str[3] = '-';
if (conn->spontQuit)
strncpy (str, conn->spontReply, 3);
else
strncpy (conn->spontReply, str, 3);
}
send_telnet(conn, str, 1);
}
// Only send spontaneous reply after last line of a previous message.
if (conn->spontReply && (!format || isLastLine))
{
send_telnet(conn, conn->spontReply, 1);
pfree(conn->spontReply, conn);
conn->spontReply = NULL;
}
time(&conn->lastCommand);
#if HAVE_VASPRINTF
if(doFree)
free(str);
#endif
}
int reply_file (connection_t *conn, const char *format, const char *file, const char *lastfmt)
{
int fd = open (file, O_RDONLY);
char buf[1025], *nbp;
int l, bl;
if (fd < 0)
{
syslog (LOG_ERR, "Failed to open %s: %m.", file);
return -1;
}
bl = 0;
while ((l = read (fd, buf + bl, 1024 - bl)) > 0 || bl > 0)
{
if (l > 0)
bl += l;
buf[bl] = 0;
nbp = strchr (buf, '\n');
if (!nbp)
break;
*nbp++ = 0;
if (bl < 1024 && !*nbp) // Check for file ending with newline.
break;
reply (conn, format, buf);
if (!nbp)
break;
bl -= nbp - buf;
memmove (buf, nbp, bl);
}
if (bl)
{
if (lastfmt)
format = lastfmt;
reply (conn, format, buf);
}
close (fd);
return 0;
}
void reply_msg (connection_t *conn, const char *format, const char *msg)
{
if (msg[0] == '/')
{
char *fmt = tstring (format);
fmt[3] = '-';
reply_file (conn, fmt, msg, format);
}
else
reply (conn, format, msg);
}
void reply_spont(connection_t *conn, const char *str, int closeConn)
{
pfree (conn->spontReply, conn);
if (conn->langFd >= 0)
conn->spontReply = pstring (scan_strings (conn->langFd, str), conn);
else
conn->spontReply = pstring (str, conn);
if (closeConn)
conn->spontQuit = 1;
}
#define CHECK_VEC_SSP \
if (cvec >= svec - 1) \
{ \
svec *= 2; \
vecs = trealloc (vecs, sizeof (struct iovec) * svec); \
if (!vecs) \
return; \
} \
if (ssp < sp) \
{ \
vecs[cvec].iov_base = (void*)ssp; \
vecs[cvec++].iov_len = sp - ssp; \
}
void send_telnet(connection_t *conn, const char *str, int newline)
{
const char *sp, *ssp;
struct iovec *vecs = talloc (sizeof (struct iovec) * 4);
int cvec = 0, svec = 4;
syslog (LOG_DEBUG, "%d: SEND: %s", conn->id, str);
for (ssp = sp = str; *sp; sp++)
{
switch (*(unsigned char*)sp)
{
case '\r':
if (*++sp != '\n')
{
if (vecs)
{
CHECK_VEC_SSP;
vecs[cvec].iov_base = (void*)"";
vecs[cvec++].iov_len = 1;
}
else
conn->controlWrite (conn->controlChannel, ssp, sp - ssp);
ssp = sp--;
}
break;
case 255:
if (vecs)
{
CHECK_VEC_SSP;
vecs[cvec].iov_base = (void*)"\xFF";
vecs[cvec++].iov_len = 1;
}
else
{
if (ssp < sp)
conn->controlWrite (conn->controlChannel, ssp, sp - ssp);
conn->controlWrite (conn->controlChannel, "\xFF", 1);
}
ssp = sp;
break;
default:
break;
}
}
if (vecs)
{
CHECK_VEC_SSP;
if (newline)
{
vecs[cvec].iov_base = (void*)"\r\n";
vecs[cvec++].iov_len = 2;
}
conn->controlWriteVecs (conn->controlChannel, vecs, cvec);
}
else
{
if (ssp < sp)
conn->controlWrite (conn->controlChannel, ssp, sp - ssp);
if (newline)
conn->controlWrite (conn->controlChannel, "\r\n", 2);
}
}
int control_handler(int sock, void *user, int urgent)
{
connection_t *conn = user;
int l;
char *buffp;
if (conn->sock == -1)
return 0;
if(urgent)
{
char *tmpBuf = talloc(1024);
/*
* Discard all urgent data after telnet parsing.
*/
if(tmpBuf)
{
l = recv(sock, tmpBuf, 1024, MSG_OOB);
if(l > 0)
parse_telnet(conn, tmpBuf, l);
}
return 0;
}
if(conn->readBufferLen >= sizeof(conn->readBuffer) - 1)
{
/* Someone sent us a line longer than we can handle. Shame on them! */
syslog (LOG_NOTICE, "%d: Input line overflow.", conn->id);
conn->overFlow = 1;
conn->readBufferLen = 0;
}
/* Append what we read. */
buffp = conn->readBuffer + conn->readBufferLen;
#ifdef USE_TLS
if (conn->tlsControl)
{
l = -1;
switch (conn->tlsState)
{
case 0:
l = tls_accept (conn->tlsControl);
if (l == 1)
{
syslog (LOG_INFO, "%d: Successfully negotiated TLS.", conn->id);
conn->tlsState = 1;
conn->controlWrite = (writeFun_t)tls_write;
conn->controlWriteVecs = (writeVecsFun_t)tls_write_vecs;
conn->controlChannel = conn->tlsControl;
if (conn->tlsClientCert)
tls_free_cert (conn->tlsClientCert);
conn->tlsClientCert = tls_get_peer_cert (conn->tlsControl);
if ((conn->server->tlsOptions & tlsVerifyClient) && conn->server->loginTLS)
{
const char *clientName = tstring (tls_get_cn (conn->tlsClientCert));
if (clientName)
{
if (!conn->inLookUp)
{
conn->inLookUp = 1;
#ifdef USE_SQL
conn->server->sqlRefs++;
#endif
}
conn->user = find_user (conn, clientName, 1, conn);
if(conn->user)
{
if (conn->user->allowSecLogin)
{
#ifdef HAVE_LIBPAM
pam_set_item (conn->pamh, PAM_USER, conn->user->name);
#endif
if (authenticated (conn))
{
reply (conn, "421 Failed to authenticate: %s (root dir inaccessible?).", strerror (errno));
disconnected (conn);
return 0;
}
user_setup_environ (conn->user, 1, conn->extRFd, conn->extWFd);
return 0;
}
pfree (conn->user, conn);
conn->user = NULL;
}
syslog (LOG_NOTICE, "%d: No user for CN \"%s\".", conn->id, clientName);
}
else
syslog (LOG_NOTICE, "%d: No CN in certificate.", conn->id);
if (conn->server->tlsNoLogout)
{
reply (conn, "421 Failed to find a user for your certificate.");
disconnected (conn);
}
}
}
else if (l)
{
if (l == -1)
reply (conn, "421 TLS negotiation failed.");
syslog (LOG_NOTICE, "%d: Error in TLS negotiation: %s.", conn->id, tls_error (conn->tlsControl, l));
disconnected (conn);
}
return 0;
case 1:
l = tls_read (conn->tlsControl, buffp, sizeof (conn->readBuffer) -
conn->readBufferLen);
break;
case 2:
l = tls_stop (conn->tlsControl);
if (l == 1)
{
tls_free (conn->tlsControl);
conn->tlsControl = NULL;
}
else if (l)
{
syslog (LOG_NOTICE, "%d: Error in TLS shutdown: %s.", conn->id,
tls_error (conn->tlsControl, l));
disconnected (conn);
}
return 0;
}
}
else
#endif
l = read (sock, buffp, sizeof (conn->readBuffer) - conn->readBufferLen);
if(l < 0)
{
/* Error */
#ifdef USE_TLS
if (conn->tlsControl)
{
syslog (LOG_DEBUG, "%d: TLS read error: %s.", conn->id,
tls_error (conn->tlsControl, l));
disconnected (conn);
}
else
#endif
if(errno != EINTR)
{
syslog (LOG_DEBUG, "%d: control_handler: read: %m", conn->id);
disconnected (conn);
}
return 0;
}
if(!l)
{
syslog (LOG_DEBUG, "%d: Client suddenly disconnected.", conn->id);
/* EOF == they disconnected. */
disconnected(conn);
return 0;
}
/* Parse telnet stuff. */
conn->readBufferLen += parse_telnet(conn, buffp, l);
if (!conn->sleepUntil) // Will be run by check_idle otherwise.
run_commands (conn);
return 0;
}
void run_commands (connection_t *conn)
{
char *buffp;
/* Check for line breaks. */
while((buffp = memchr(conn->readBuffer, 0, conn->readBufferLen)))
{
if(conn->overFlow)
{
/* We can't handle this. Beginning of line is already discarded. */
conn->overFlow = 0;
reply(conn, "500 Line too long.");
}
else
{
if (debug)
{
if (!strncasecmp (conn->readBuffer, "PASS ", 5))
syslog (LOG_DEBUG, "%d: RECV: PASS (hidden)", conn->id);
else
syslog (LOG_DEBUG, "%d: RECV: %s", conn->id, conn->readBuffer);
}
if(handle_command(conn, conn->readBuffer))
return;
}
/* Discard line */
conn->readBufferLen -= ++buffp - conn->readBuffer;
memmove(conn->readBuffer, buffp, conn->readBufferLen);
}
}
int parse_telnet(connection_t *conn, char *buff, int len)
{
unsigned char *curr, *end = (unsigned char*)buff + len;
unsigned char retBuf[3];
/*
* Telnet command handling. Answer all DO with WON'T, and all WILL with DON'T
* for now. Remove all null characters and put nulls at linebreaks.
*/
for(curr = (unsigned char*)buff; curr < end;)
{
switch(conn->telnetMode)
{
case NUL:
switch(*curr)
{
case NUL:
if(conn->telnetFlags & tfCR)
{
*curr = '\r';
if(conn->telnetFlags & tfSubOpt)
{
#if 0
if(conn->suboptLen < 100)
conn->subopt[conn->suboptLen++] = *curr;
#endif
memmove(curr, curr + 1, --end - curr);
}
else
curr++;
}
else
memmove(curr, curr + 1, --end - curr);
conn->telnetFlags &= ~tfCR;
break;
case '\n':
if(conn->telnetFlags & tfCR)
*curr = 0;
conn->telnetFlags &= ~tfCR;
if(conn->telnetFlags & tfSubOpt)
{
#if 0
if(conn->suboptLen < 100)
conn->subopt[conn->suboptLen++] = *curr;
#endif
memmove(curr, curr + 1, --end - curr);
}
else
curr++;
break;
case '\r':
conn->telnetFlags |= tfCR;
memmove(curr, curr + 1, --end - curr);
/*
* CR will be ignored unless followed by either NL or NUL, but that
* what the telnet rfc says a CR must be followed by so.
*/
break;
case IAC:
conn->telnetFlags &= ~tfCR;
conn->telnetMode = IAC;
memmove(curr, curr + 1, --end - curr);
break;
default:
conn->telnetFlags &= ~tfCR;
if (conn->telnetFlags & tfSubOpt)
{
#if 0
if (conn->suboptLen < 100)
conn->subopt[conn->suboptLen++] = *curr;
#endif
memmove (curr, curr + 1, --end - curr);
}
else
curr++;
break;
}
break;
case IAC:
switch(*curr)
{
case IAC:
// Quoted character 255
if(conn->telnetFlags & tfSubOpt)
{
#if 0
if(conn->suboptLen < 100)
conn->subopt[conn->suboptLen++] = *curr;
#endif
memmove(curr, curr + 1, --end - curr);
}
else
curr++;
conn->telnetMode = NUL;
break;
case SB:
memmove(curr, curr + 1, --end - curr);
conn->telnetMode = SB;
break;
case SE:
conn->telnetMode = SE;
memmove(curr, curr + 1, --end - curr);
break;
case WILL:
case WONT:
case DO:
case DONT:
conn->telnetMode = *curr;
memmove(curr, curr + 1, --end - curr);
break;
default:
if(*curr >= 240)
memmove(curr, curr + 1, --end - curr);
else
curr++;
conn->telnetMode = NUL;
break;
}
break;
case SB:
syslog (LOG_NOTICE, "%d: Unnegotiated suboption.", conn->id);
conn->telnetMode = NUL;
if (conn->telnetFlags & tfSubOpt)
syslog (LOG_NOTICE, "%d: Nested suboption.", conn->id);
conn->telnetFlags |= tfSubOpt;
conn->sb = *curr;
memmove (curr, curr + 1, --end - curr);
break;
case SE:
conn->telnetMode = NUL;
if (!(conn->telnetFlags & tfSubOpt))
syslog (LOG_NOTICE, "%d: Suboption end without suboption start.",
conn->id);
else if(*curr == conn->sb)
{
conn->telnetFlags &= ~tfSubOpt;
#if 0
conn->suboptLen = 0;
#endif
}
else
syslog (LOG_NOTICE, "%d: Wrong suboption ended, got %d expected %d.",
conn->id, *curr, conn->sb);
memmove (curr, curr + 1, --end - curr);
break;
case WILL:
case DO:
retBuf[0] = IAC;
retBuf[1] = (conn->telnetMode == WILL? DONT : WONT);
retBuf[2] = *curr;
write(conn->sock, retBuf, 3);
conn->telnetMode = NUL;
memmove (curr, curr + 1, --end - curr);
break;
case WONT:
case DONT:
conn->telnetMode = NUL;
memmove (curr, curr + 1, --end - curr);
break;
}
}
return (char*)end - buff;
}
int authenticated(connection_t *conn)
{
conn->authed = 1;
if (conn->inLookUp)
{
conn->inLookUp = 0;
#ifdef USE_SQL
if (!--conn->server->sqlRefs)
sql_disconnect (&conn->server->sql);
#endif
}
if (conn->extRFd >= 0)
{
remove_read_fd (conn->extRFd);
close (conn->extRFd);
conn->extRFd = -1;
}
if (conn->extWFd >= 0)
{
close (conn->extWFd);
conn->extWFd = -1;
}
if(conn->user)
{
if (conn->email)
syslog (LOG_INFO, "%d: User %s (email \"%s\") logged in.", conn->id,
conn->user->name, conn->email);
else if (conn->account)
syslog (LOG_INFO, "%d: User %s (account \"%s\") logged in.", conn->id,
conn->user->name, conn->account);
else
syslog (LOG_INFO, "%d: User %s logged in.", conn->id, conn->user->name);
if (conn->user->access_program)
{
int ipipe[2], opipe[2], i;
const char *args[4], **ap = args;
const char *envs[] = { NULL };
const char *prog = conn->user->access_program;
conn->mlstTags &= ~tfPerm; // perm might be heavy if checked externally.
if (pipe (ipipe) || pipe (opipe))
conn->extRFd = conn->extWFd = -1;
else
{
*ap++ = prog;
*ap++ = conn->user->name;
if (conn->account)
*ap++ = conn->account;
*ap = NULL;
switch (vfork ())
{
case -1:
// Error
close (ipipe[0]);
close (ipipe[1]);
close (opipe[0]);
close (opipe[1]);
conn->extRFd = conn->extWFd = -1;
break;
case 0:
// Child
if (dup2 (ipipe[1], 1) == -1)
_exit (1);
if (dup2 (opipe[0], 0) == -1)
_exit (1);
for (i = 2; i < getdtablesize (); i++)
close (i);
execve (prog, (char**)args, (char**)envs);
_exit (1);
default:
close (ipipe[1]);
close (opipe[0]);
conn->extRFd = ipipe[0];
conn->extWFd = opipe[1];
break;
}
}
}
if(conn->user->chroot)
{
const char *new_root;
super_privs (0);
new_root = set_root (conn->user->chroot);
if (!new_root)
{
syslog (LOG_ERR, "%d: Failed to chroot: %m.", conn->id);
return -1;
}
if (strcmp (new_root, conn->user->chroot))
{
pfree (conn->user->chroot, conn->user);
conn->user->chroot = pstring (new_root, conn->user);
}
}
if(conn->user->home)
conn->cwd = pstring (set_cwd ("~", conn->user->home), conn);
}
else
syslog (LOG_NOTICE, "%d: Unknown user logged in.", conn->id);
#ifdef HAVE_LIBPAM
super_privs (0);
// TODO: Check return values.
pam_setcred (conn->pamh, PAM_ESTABLISH_CRED);
pam_open_session (conn->pamh, PAM_SILENT);
#endif
return 0;
}
void unauthenticated(connection_t *conn)
{
#ifdef HAVE_LIBPAM
super_privs (0);
// TODO: Check return values.
if (conn->pamh)
{
pam_close_session (conn->pamh, PAM_SILENT);
pam_setcred (conn->pamh, PAM_DELETE_CRED);
}
#endif
conn->authed = 0;
pfree(conn->user, conn);
conn->user = NULL;
pfree(conn->email, conn);
conn->email = NULL;
pfree (conn->account, conn);
conn->account = NULL;
if (conn->extRFd >= 0)
{
remove_read_fd (conn->extRFd);
close (conn->extRFd);
conn->extRFd = -1;
}
if (conn->extWFd >= 0)
{
close (conn->extWFd);
conn->extWFd = -1;
}
}
syntax highlighted by Code2HTML, v. 0.9.1