/*
**
** Copyright (c) 2002,2003 Dave McMurtrie
** Copyright (c) 2004 Martin Blapp
**
** This file is part of pop3proxy, a descendant of Dave McMurtrie's pop3proxy.
**
** pop3proxy 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.
**
** pop3proxy 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 pop3proxy; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
**
**
** Facility:
**
** request.c
**
** Abstract:
**
** Routines to handle IMAP client requests... Specifically, this module
** contains the handler thread (Handle_Request) that takes care of
** incoming client connections. It also contains one function for each
** of the 5 unauthenticated commands that the proxy server can handle
** without having to make a connection to the real IMAP server. Finally,
** the raw proxy function resides in this module, as well.
**
** Authors:
**
** Dave McMurtrie <davemcmurtrie@hotmail.com>
**
** RCS:
**
** $Source: /afs/pitt.edu/usr12/dgm/work/IMAP_Proxy/src/RCS/request.c,v $
** $Id: request.c,v 1.17 2003/10/09 14:11:13 dgm Exp $
**
** Modification History:
**
** $Log: request.c,v $
** Revision 1.17 2003/10/09 14:11:13 dgm
** bugfix: set TotalClientLogins to zero in cmd_resetcounters, submitted
** by Geoffrey Hort <g.hort@unsw.edu.au>. Changes to allow syslogging of the
** client source port. Added timestamps to protocol log entries.
**
** Revision 1.16 2003/07/14 16:26:02 dgm
** Removed erroneous newline from syslog() call to prevent compiler
** warning.
**
** Revision 1.15 2003/07/07 13:31:09 dgm
** Bugfix by Gary Windham <windhamg@email.arizona.edu>. Raw_Proxy() loop
** was not dealing with string literals correctly when the server
** responded with something other than a '+'.
**
** Revision 1.14 2003/05/20 19:11:25 dgm
** Comment changes only.
**
** Revision 1.13 2003/05/15 11:35:39 dgm
** Patch by Ken Murchison <ken@oceana.com> to clean up build process:
** conditionally include sys/param.h instead of defining MAXPATHLEN.
**
** Revision 1.12 2003/05/13 11:41:26 dgm
** Patches by Ken Murchison <ken@oceana.com> to clean up build process.
**
** Revision 1.11 2003/05/08 17:20:43 dgm
** Added code to send untagged server responses back to clients
** on LOGOUT.
**
** Revision 1.10 2003/05/06 12:11:47 dgm
** Applied patches by Ken Murchison <ken@oceana.com> to include SSL
** support.
**
** Revision 1.9 2003/02/20 12:57:26 dgm
** Raw_Proxy() sends UNSELECT instead of CLOSE if the server supports it.
**
** Revision 1.8 2003/02/19 12:57:15 dgm
** Added support for "AUTHENTICATE LOGIN".
**
** Revision 1.7 2003/02/14 18:25:40 dgm
** Fixed bug in cmd_newlog. ftruncate doesn't reset file pointer so
** I added an lseek.
**
** Revision 1.6 2003/01/22 13:03:25 dgm
** Changes to HandleRequest() and cmd_login() to support clients that
** send the password as a literal string on login.
**
** Revision 1.5 2002/08/30 13:24:43 dgm
** Added support for total client logins counter
**
** Revision 1.4 2002/08/28 15:59:25 dgm
** replaced all internal log function calls with standard syslog calls.
** Added P_RESETCOUNTERS command.
** Added logic to time out connections in the raw proxy loop.
**
** Revision 1.3 2002/08/27 20:15:16 dgm
** No longer bother doing a dns lookup before calling Get_Server_sd().
** We'll only pass the ip address string instead of possibly hostname.
**
** Revision 1.2 2002/07/18 20:43:17 dgm
** added p_dumpicc and p_newlog commands. Changed trace
** command to p_trace.
**
** Revision 1.1 2002/07/03 12:08:34 dgm
** Initial revision
**
**
*/
#define _REENTRANT
#include <errno.h>
#include <string.h>
#include "common.h"
#include "pop3proxy.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <poll.h>
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <fcntl.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <syslog.h>
#if HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif
#include <openssl/evp.h>
/*
* There are a few global variables that we care about. Make sure we know
* about them here.
*/
extern char Banner[BUFSIZE];
extern int BannerLen;
extern char Capability[BUFSIZE];
extern int CapabilityLen;
extern char Epoplist[BUFSIZE];
extern int EpoplistLen;
extern IMAPCounter_Struct *IMAPCount;
extern ISD_Struct ISD;
extern pthread_mutex_t mp;
extern pthread_mutex_t trace;
extern char TraceUser[MAXUSERNAMELEN];
extern int Tracefd;
extern ICC_Struct *ICC_HashTable[ HASH_TABLE_SIZE ];
extern ProxyConfig_Struct PC_Struct;
/*
* Function prototypes for internal entry points.
*/
static int cmd_noop( ITD_Struct *, char * );
static int cmd_logout( ITD_Struct *, char * );
static int cmd_capability( ITD_Struct *, char * );
static int cmd_authenticate_login( ITD_Struct *, char * );
static int cmd_login( ITD_Struct *, char *, char *, int, char *, unsigned char );
static int cmd_trace( ITD_Struct *, char *, char * );
static int cmd_dumpicc( ITD_Struct *, char * );
static int cmd_newlog( ITD_Struct *, char * );
static int cmd_resetcounters( ITD_Struct *, char * );
static int Raw_Proxy( ITD_Struct *, ITD_Struct * );
/*
* Function definitions.
*/
/*++
* Function: cmd_newlog
*
* Purpose: Clear the proxy trace log file.
*
* Parameters: ptr to ITD_Struct for client connection.
* char ptr to Tag sent with this command.
*
* Returns: 0 on success
* -1 on failure
*
* Authors: Dave McMurtrie <davemcmurtrie@hotmail.com>
*--
*/
static int cmd_newlog( ITD_Struct *itd, char *Tag )
{
char *fn = "cmd_newlog";
char SendBuf[BUFSIZE];
unsigned int BufLen = BUFSIZE - 1;
int rc;
SendBuf[BUFSIZE - 1] = '\0';
rc = ftruncate( Tracefd, 0 );
if ( rc != 0 )
{
syslog(LOG_ERR, "%s: ftruncate() failed: %s", fn, strerror( errno ) );
snprintf( SendBuf, BufLen, "%s NO internal server error\r\n", Tag );
if ( IMAP_Write( itd->conn, SendBuf, strlen(SendBuf) ) == -1 )
{
syslog(LOG_WARNING, "%s: IMAP_Write() failed: %s", fn, strerror(errno) );
return( -1 );
}
return( -1 );
}
/*
* bugfix. ftruncate doesn't reset the file pointer...
*/
rc = lseek( Tracefd, 0, SEEK_SET );
if ( rc < 0 )
{
syslog(LOG_ERR, "%s: lseek() failed: %s", fn, strerror( errno ) );
snprintf( SendBuf, BufLen, "%s NO internal server error\r\n", Tag );
if ( IMAP_Write( itd->conn, SendBuf, strlen(SendBuf) ) == -1 )
{
syslog(LOG_WARNING, "%s: IMAP_Write() failed: %s", fn, strerror(errno) );
return( -1 );
}
return( -1 );
}
snprintf( SendBuf, BufLen, "%s OK Completed\r\n", Tag );
if ( IMAP_Write( itd->conn, SendBuf, strlen(SendBuf) ) == -1 )
{
syslog(LOG_WARNING, "%s: IMAP_Write() failed: %s", fn, strerror(errno) );
return( -1 );
}
return( 0 );
}
/*++
* Function: cmd_resetcounters
*
* Purpose: Reset the global high-water marks and total counters.
*
* Parameters: ptr to ITD_Struct for client connection.
* char ptr to Tag sent with this command.
*
* Returns: 0 on success.
* -1 on failure.
*
* Authors: Dave McMurtrie <davemcmurtrie@hotmail.com>
*
* Notes: Always key to remember that we don't take out a mutex
* anywhere that we update these global counters. There's
* never a guarantee that they'll be exactly correct but
* for the performance penalty we'd pay to make them correct
* we just don't care.
*--
*/
static int cmd_resetcounters( ITD_Struct *itd, char *Tag )
{
char *fn = "cmd_resetcounters";
char SendBuf[BUFSIZE];
unsigned int BufLen = BUFSIZE -1;
SendBuf[BufLen] = '\0';
/*
* Bugfix by Geoffrey Hort <g.hort@unsw.edu.au> -- I forgot to zero
* out TotalClientLogins...
*/
IMAPCount->CountTime = time( 0 );
IMAPCount->PeakClientConnections = 0;
IMAPCount->PeakInUseServerConnections = 0;
IMAPCount->PeakRetainedServerConnections = 0;
IMAPCount->TotalClientConnectionsAccepted = 0;
IMAPCount->TotalServerConnectionsCreated = 0;
IMAPCount->TotalServerConnectionsReused = 0;
IMAPCount->TotalClientLogins = 0;
snprintf( SendBuf, BufLen, "%s OK Completed\r\n", Tag );
if ( IMAP_Write( itd->conn, SendBuf, strlen(SendBuf) ) == -1 )
{
syslog(LOG_WARNING, "%s: IMAP_Write() failed: %s", fn, strerror(errno) );
return( -1 );
}
return( 0 );
}
/*++
* Function: cmd_dumpicc
*
* Purpose: Dump the contents of all imap connection context structs.
*
* Parameters: ptr to ITD_Struct for client connection.
* char ptr to Tag sent with this command.
*
* Returns: 0 on success
* -1 on failure
*
* Authors: Dave McMurtrie <davemcmurtrie@hotmail.com>
*--
*/
static int cmd_dumpicc( ITD_Struct *itd, char *Tag )
{
char *fn = "cmd_dumpicc";
char SendBuf[BUFSIZE];
unsigned int HashIndex;
ICC_Struct *HashEntry;
unsigned int BufLen = BUFSIZE - 1;
SendBuf[BUFSIZE - 1] = '\0';
LockMutex( &mp );
for ( HashIndex = 0; HashIndex < HASH_TABLE_SIZE; HashIndex++ )
{
HashEntry = ICC_HashTable[ HashIndex ];
while ( HashEntry )
{
snprintf( SendBuf, BufLen, "* %d %s %s\r\n", HashEntry->server_conn->sd,
HashEntry->username,
( ( HashEntry->logouttime ) ? "Cached" : "Active" ) );
if ( IMAP_Write( itd->conn, SendBuf, strlen(SendBuf) ) == -1 )
{
UnLockMutex( &mp );
syslog(LOG_WARNING, "%s: IMAP_Write() failed: %s", fn, strerror(errno) );
return( -1 );
}
HashEntry = HashEntry->next;
}
}
UnLockMutex( &mp );
snprintf( SendBuf, BufLen, "%s OK Completed\r\n", Tag );
if ( IMAP_Write( itd->conn, SendBuf, strlen(SendBuf) ) == -1 )
{
syslog(LOG_WARNING, "%s: IMAP_Write() failed: %s", fn, strerror(errno) );
return( -1 );
}
return( 0 );
}
/*++
* Function: cmd_trace
*
* Purpose: turn on per-user tracing in the proxy server.
*
* Parameters: ptr to ITD_Struct for client connection.
* char ptr to Tag sent with this command.
* char ptr to the username we want to trace (NULL to turn
* off tracing)
*
* Returns: 0 on success
* -1 on failure
*
* Authors: Dave McMurtrie <davemcmurtrie@hotmail.com>
*--
*/
static int cmd_trace( ITD_Struct *itd, char *Tag, char *Username )
{
char *fn = "cmd_trace";
char SendBuf[BUFSIZE];
unsigned int BufLen = BUFSIZE - 1;
SendBuf[BUFSIZE - 1] = '\0';
/*
* Here are the tracing semantics:
*
* Tracing is to be limited to only one user at a time. This decision was
* made for a few different reasons. First, to conserve system resources
* such as disk space. Second, to improve overall server performance --
* tracing will slow a thread down. Third, so a sysadmin doesn't forget
* that tracing is turned on for a user (like I commonly do when I enable
* tracing in cyrus imapd). Fourth, it's just easier this way.
*/
LockMutex( &trace );
if ( !Username )
{
snprintf( SendBuf, BufLen, "\n\n-----> C= %d %s PROXY: user tracing disabled. Expect further output until client logout.\n", time( 0 ), TraceUser );
write( Tracefd, SendBuf, strlen( SendBuf ) );
memset( TraceUser, 0, sizeof TraceUser );
snprintf( SendBuf, BufLen, "%s OK Tracing disabled\r\n", Tag );
if ( IMAP_Write( itd->conn, SendBuf, strlen(SendBuf) ) == -1 )
{
syslog(LOG_WARNING, "%s: IMAP_Write() failed: %s", fn, strerror(errno) );
UnLockMutex( &trace );
return( -1 );
}
UnLockMutex( &trace );
return( 0 );
}
if ( TraceUser[0] )
{
/* guarantee no runaway strings */
TraceUser[sizeof TraceUser - 1] = '\0';
snprintf( SendBuf, BufLen, "%s BAD Tracing already enabled for user %s\r\n", Tag, TraceUser );
if ( IMAP_Write( itd->conn, SendBuf, strlen(SendBuf) ) == -1 )
{
syslog(LOG_WARNING, "%s: IMAP_Write() failed: %s", fn, strerror(errno) );
UnLockMutex( &trace );
return( -1 );
}
UnLockMutex( &trace );
return( 0 );
}
strncpy( TraceUser, Username, sizeof TraceUser - 1 );
snprintf( SendBuf, BufLen, "%s OK Tracing enabled\r\n", Tag );
if ( IMAP_Write( itd->conn, SendBuf, strlen(SendBuf) ) == -1 )
{
syslog(LOG_WARNING, "%s: IMAP_Write() failed: %s", fn, strerror(errno) );
UnLockMutex( &trace );
return( -1 );
}
snprintf( SendBuf, BufLen, "\n\n-----> C= %d %s PROXY: user tracing enabled.\n", time( 0 ), TraceUser );
write( Tracefd, SendBuf, strlen( SendBuf ) );
UnLockMutex( &trace );
return( 0 );
}
/*++
* Function: cmd_noop
*
* Purpose: implement the NOOP IMAP command.
*
* Parameters: ptr to ITD_Struct for client connection.
*
* Returns: 0 on success
* -1 on failure
*
* Authors: Dave McMurtrie <davemcmurtrie@hotmail.com>
*--
*/
static int cmd_noop( ITD_Struct *itd, char *Tag )
{
char *fn = "cmd_noop";
char SendBuf[BUFSIZE];
unsigned int BufLen = BUFSIZE - 1;
SendBuf[BUFSIZE - 1] = '\0';
snprintf( SendBuf, BufLen, "%s OK Completed\r\n", Tag );
if ( IMAP_Write( itd->conn, SendBuf, strlen(SendBuf) ) == -1 )
{
syslog(LOG_WARNING, "%s: IMAP_Write() failed: %s", fn, strerror(errno) );
return( -1 );
}
return( 0 );
}
/*++
* Function: cmd_logout
*
* Purpose: implement the LOGOUT IMAP command.
*
* Parameters: ptr to ITD_Struct for client connection.
*
* Returns: 0 on success
* -1 on failure
*
* Authors: Dave McMurtrie <davemcmurtrie@hotmail.com>
*--
*/
static int cmd_logout( ITD_Struct *itd, char *Tag )
{
char *fn = "cmd_logout";
char SendBuf[BUFSIZE];
unsigned int BufLen = BUFSIZE - 1;
SendBuf[BUFSIZE - 1] = '\0';
snprintf( SendBuf, BufLen, "+OK Pop server at virtualdomain signing off.\r\n",
Tag );
if ( IMAP_Write( itd->conn, SendBuf, strlen(SendBuf) ) == -1 )
{
syslog(LOG_WARNING, "%s: IMAP_Write() to client failed on sd [%d]: %s", fn, itd->conn->sd, strerror(errno) );
return( -1 );
}
return( 0 );
}
/*++
* Function: cmd_login
*
* Purpose: implement the LOGIN IMAP command.
*
* Parameters: ptr to ITD_Struct for client connection.
* ptr to username
* ptr to password
* int length of password buffer
* ptr to client tag
* unsigned char - flag to indicate literal password in login
* command.
*
* Returns: 0 on success prior to authentication
* 1 on success after authentication (we caught a logout)
* -1 on failure
*
* Authors: Dave McMurtrie <davemcmurtrie@hotmail.com>
*
* Note: Not too many things are really considered "failure" in the
* context of this routine, because returning failure would
* result in the client connection being closed. When most
* things fail, we'll send a failure response to the client,
* but return success to the caller. That will allow the
* client to know that something broke and try again if it
* wants to.
*
*--
*/
static int cmd_login( ITD_Struct *Client,
char *Username,
char *Password,
int passlen,
char *Tag,
unsigned char LiteralLogin )
{
char *fn = "cmd_login()";
char SendBuf[BUFSIZE];
unsigned int BufLen = BUFSIZE - 1;
ITD_Struct Server;
int rc;
ICD_Struct *conn;
char TraceFileName[ MAXPATHLEN ];
struct sockaddr_in cli_addr;
int addrlen;
char *hostaddr;
in_port_t sin_port;
memset( &Server, 0, sizeof Server );
addrlen = sizeof( struct sockaddr_in );
if ( getpeername( Client->conn->sd, (struct sockaddr *)&cli_addr, &addrlen ) < 0 )
{
syslog(LOG_INFO, "LOGIN: '%s' failed: getpeername() failed for client sd: %s", Username, strerror( errno ) );
return( -1 );
}
hostaddr = inet_ntoa( ( ( struct sockaddr_in *)&cli_addr )->sin_addr );
sin_port = ntohs( cli_addr.sin_port );
if ( !hostaddr )
{
syslog(LOG_INFO, "LOGIN: '%s' failed: inet_ntoa() failed for client sd: %s", Username, strerror( errno ) );
return( -1 );
}
conn = Get_Server_conn( Username, Password, hostaddr, sin_port, LiteralLogin, Client );
/*
* wipe out the passwd so we don't have it sitting in memory somewhere.
*/
memset( Password, 0, passlen );
if ( conn == NULL )
{
/*
* All logging is done in Get_Server_conn, so don't bother to
* log anything here.
*/
cmd_logout( Client, Tag );
/*
* close the client side socket.
*/
close( Client->conn->sd );
return( -1 );
}
Server.conn = conn;
/*
* Send a success message back to the client
* and go into raw proxy mode.
*/
snprintf( SendBuf, BufLen, "+OK %s logged in\r\n", Username );
if ( IMAP_Write( Client->conn, SendBuf, strlen(SendBuf) ) == -1 )
{
/*
* This really sux. We successfully logged the user in, but now
* we can't communicate with the client...
*/
IMAPCount->InUseServerConnections--;
close( Server.conn->sd );
syslog(LOG_ERR, "%s: Unable to send successful login message back to client: %s -- closing connection.", fn, strerror(errno) );
return( -1 );
}
IMAPCount->TotalClientLogins++;
/* turn on tracing for this session if necessary */
LockMutex( &trace );
if ( ! strcmp( Username, TraceUser ) )
{
Client->TraceOn = 1;
Server.TraceOn = 1;
}
else
{
Client->TraceOn = 0;
Server.TraceOn = 0;
}
UnLockMutex( &trace );
rc = Raw_Proxy( Client, &Server );
/*
* It's not necessary to take out the trace mutex here. The reason
* we take it out when we check above is because the trace username
* could change at any time. When we disable tracing here, we're
* doing it regardless of what the trace username is, so we don't
* take out the mutex.
*/
Client->TraceOn = 0;
Server.TraceOn = 0;
/* update the logout time for this cached connection */
ICC_Logout( Username, Server.conn );
return( rc );
}
/*++
* Function: Raw_Proxy
*
* Purpose: Start a raw proxy session to the IMAP server, catching only
* LOGOUT commands from the client.
*
* Parameters: ptr to client ITD_Struct
* ptr to server ITD_Struct
*
* Returns: 1 if we caught a logout
* -1 on failure
*
* Authors: Dave McMurtrie <davemcmurtrie@hotmail.com>
*--
*/
static int Raw_Proxy( ITD_Struct *Client, ITD_Struct *Server )
{
char *fn = "Raw_Proxy()";
struct pollfd fds[2];
nfds_t nfds;
int status, pending;
unsigned int FailCount;
int BytesSent;
char *CP;
char TraceBuf[ BUFSIZE ];
char SendBuf[ BUFSIZE ];
#define SERVER 0
#define CLIENT 1
FailCount = 0;
nfds = 2;
/*
* Set up our fds structs.
*/
fds[ SERVER ].fd = Server->conn->sd;
fds[ CLIENT ].fd = Client->conn->sd;
fds[ SERVER ].events = POLLIN;
fds[ CLIENT ].events = POLLIN;
/*
* POLL loop
*/
for ( ; ; )
{
pending = 0;
fds[ SERVER ].revents = 0;
fds[ CLIENT ].revents = 0;
#if HAVE_LIBSSL
#if USE_POP3
if ( Server->conn->tls )
{
/* See is we have any buffered input */
pending = SSL_pending( Server->conn->tls );
}
#endif
#endif
status = ( pending ? 1 : poll( fds, nfds, POLL_TIMEOUT ) );
/*
* poll returns a non-negative value on success.
* it returns 0 if it times out before any revents are modified.
* -1 is returned on failure.
*/
if ( !status )
{
/*
* We timed out. End result is that we want the server and
* client sides of this connection to close. The nicest way
* to have this happen will be to simply return failure to
* cmd_login().
* We were called by HandleRequest() & then cmd_login(). When
* we return -1 to cmd_login(), he'll call ICC_Logout() to
* update the logout time for this user on this server sd.
* Eventually, the ICC_Recycle thread will wake up and reap the
* server-side of the connection.
* cmd_login() will return our -1 to Handle_Request
* and HandleRequest will close the client-side socket.
*/
syslog( LOG_WARNING, "%s: poll() timed out. server sd [%d]. client sd [%d].", fn, Server->conn->sd, Client->conn->sd );
return( -1 );
}
if ( status < 0 )
{
/* If we were interrupted by a signal, just continue the loop. */
if ( errno == EINTR )
{
syslog(LOG_INFO, "%s: poll() was interrupted by a signal -- continuing.", fn);
continue;
}
/* resource issue -- try again. */
if ( errno == EAGAIN )
{
FailCount++;
if ( FailCount == 5 )
{
syslog(LOG_ERR, "%s: poll() returned EAGAIN. Exceeded retry limit. Returning failure.", fn );
return( -1 );
}
syslog(LOG_WARNING,"%s: poll() returned EAGAIN. Retrying.", fn );
sleep(5);
continue;
}
/* anything else, we're really jacked about it. */
syslog(LOG_ERR, "%s: poll() failed: %s -- Returning failure.", fn, strerror( errno ) );
return( -1 );
}
FailCount = 0;
/*
* PROXY LOOPS
*/
/*
* Check the server now to see if he has any data to send.
*/
if ( pending || fds[ SERVER ].revents )
{
for ( ; ; )
{
status = IMAP_Read( Server->conn, Server->ReadBuf,
sizeof Server->ReadBuf );
if ( status == -1 )
{
if ( errno == EINTR )
continue;
syslog(LOG_WARNING, "%s: IMAP_Read() failed reading from IMAP server on sd [%d]: %s", fn, Server->conn->sd, strerror( errno ) );
return( -1 );
}
break;
}
if ( status == 0 )
{
/* the server closed the connection, dammit */
syslog(LOG_ERR, "%s: IMAP server unexpectedly closed the connection on sd %d", fn, Server->conn->sd );
return( -1 );
}
/* whatever we read from the server, ship off to the client */
for ( ; ; )
{
BytesSent = IMAP_Write( Client->conn, Server->ReadBuf, status );
if ( BytesSent == -1 )
{
if ( errno == EINTR )
continue;
syslog(LOG_ERR, "%s: IMAP_Write() failed sending data to client on sd [%d]: %s", fn, Client->conn->sd, strerror( errno ) );
return( -1 );
}
break;
} /* end of infinite for loop for IMAP_Write() to client */
} /* end of if conditional -- were there server sd events? */
/*
* Then we send data from the client to the server. We have to watch
* this side a little bit closer...
*/
if ( ! fds[ CLIENT ].revents )
{
continue;
}
do
{
status = IMAP_Line_Read( Client );
if ( status == -1 )
{
syslog(LOG_NOTICE, "%s: Failed to read line from client on socket %d", fn, Client->conn->sd );
return( -1 );
}
if ( Client->TraceOn )
{
snprintf( TraceBuf, sizeof TraceBuf - 1, "\n\n-----> C= %d %s CLIENT: sd [%d]\n", time( 0 ), ( (TraceUser) ? TraceUser : "Null username" ), Client->conn->sd );
write( Tracefd, TraceBuf, strlen( TraceBuf ) );
write( Tracefd, Client->ReadBuf, status );
}
/* this is a command -- is it logout? */
CP = memchr(Client->ReadBuf, ' ',
Client->ReadBytesProcessed );
if ( Client->ReadBuf )
{
CP++;
if ( !strncasecmp( Client->ReadBuf, "QUIT", 4 ) )
{
memset( Server->ReadBuf, 0, sizeof Server->ReadBuf );
return( 1 );
} else if ( !strncasecmp( Client->ReadBuf, "FORCEDQUIT", 9 ) ) {
BytesSent = IMAP_Write( Server->conn, "QUIT\n\r", status );
if ( BytesSent == -1 )
{
if ( errno == EINTR )
continue;
syslog(LOG_ERR, "%s: IMAP_Write() failed sending data to client on sd [%d]: %s", fn, Client->conn->sd, strerror( errno ) );
return( -1 );
}
break;
}
}
/*
* it's some command other than QUIT or FORCEDQUIT ...
* just ship it over
*/
for ( ; ; )
{
BytesSent = IMAP_Write( Server->conn, Client->ReadBuf, status );
if ( BytesSent == -1 )
{
if ( errno == EINTR )
continue;
syslog(LOG_ERR, "%s: IMAP_Write() failed sending data to client on sd [%d]: %s", fn, Client->conn->sd, strerror( errno ) );
return( -1 );
}
break;
}
} while ( Client->BytesInReadBuffer > Client->ReadBytesProcessed );
}
}
/*++
* Function: HandleRequest
*
* Purpose: Handle incoming imap requests (as a thread)
*
* Parameters: int, client socket descriptor
*
* Returns: nada
*
* Authors: Dave McMurtrie <davemcmurtrie@hotmail.com>
*
* Notes: This function actually only handles unauthenticated
* traffic from an imap client. As such it can only make sense
* of the following IMAP commands (rfc 2060): CAPA, EPOP and
* QUIT. Also, it handles the commands that are internal to
* the proxy server such as P_TRACE, P_NEWLOG, P_DUMPICC and
* P_RESETCOUNTERS.
*
* None of these commands should ever have the need to send
* a boatload of data, so we avoid some error checking and
* undue complexity in this routine by just making sure that
* any given read from the client doesn't fill our read
* buffer. If it does, we just drop the connection.
*--
*/
extern void HandleRequest( int clientsd )
{
char *fn = "HandleRequest";
ITD_Struct Client;
ICD_Struct conn;
int sd;
char *Tag;
char *Command;
char *Username;
char *Password;
char *AuthMech;
char *Lasts;
char *EndOfLine;
char *CP;
char SendBuf[BUFSIZE];
int BytesRead;
int rc;
int NumRef = 0;
unsigned int BufLen = BUFSIZE - 1;
char S_UserName[MAXUSERNAMELEN];
char S_Tag[MAXTAGLEN];
char S_Password[MAXPASSWDLEN];
unsigned char LiteralFlag; /* flag to deal with passwords sent */
/* as string literals */
struct pollfd fds[1];
nfds_t nfds;
int PollFailCount;
PollFailCount = 0;
/* initialize the client ITD */
memset( &Client, 0, sizeof( ITD_Struct ) );
memset( &conn, 0, sizeof( ICD_Struct ) );
Client.conn = &conn;
Client.conn->sd = clientsd;
/* send the banner to the client */
if ( IMAP_Write( Client.conn, Banner, BannerLen ) == -1 )
{
syslog(LOG_ERR, "%s: IMAP_Write() failed: %s. Closing client connection.", fn, strerror( errno ) );
IMAPCount->CurrentClientConnections--;
close( Client.conn->sd );
return;
}
/* set up our poll fd structs */
nfds = 1;
fds[ 0 ].fd = Client.conn->sd;
fds[ 0 ].events = POLLIN;
/* start a command loop */
for ( ; ; )
{
LiteralFlag = NON_LITERAL_PASSWORD;
fds[ 0 ].revents = 0;
rc = poll( fds, nfds, POLL_TIMEOUT );
if ( !rc )
{
/*
* our client timeout was exceeded. Drop this connection.
*/
syslog(LOG_ERR, "%s: no data received from client for %d minutes. Closing client connection.", fn, POLL_TIMEOUT_MINUTES );
IMAPCount->CurrentClientConnections--;
close( Client.conn->sd );
return;
}
if ( rc < 0 )
{
/* If we were interrupted by a signal, just continue the loop. */
if ( errno == EINTR )
{
syslog(LOG_INFO, "%s: poll() was interrupted by a signal -- continuing.", fn);
continue;
}
/* resource issue -- try again. */
if ( errno == EAGAIN )
{
PollFailCount++;
if ( PollFailCount == 5 )
{
syslog(LOG_ERR, "%s: poll() returned EAGAIN. Exceeded retry limit. Closing client connection.", fn );
IMAPCount->CurrentClientConnections--;
close( Client.conn->sd );
return;
}
syslog(LOG_WARNING, "%s: poll() returned EAGAIN. Retrying.", fn );
sleep(5);
continue;
}
/* anything else, we're really jacked about it. */
syslog(LOG_ERR, "%s: poll() failed: %s -- Closing connection.", fn, strerror( errno ) );
IMAPCount->CurrentClientConnections--;
close( Client.conn->sd );
return;
}
PollFailCount = 0;
BytesRead = IMAP_Line_Read( &Client );
if ( BytesRead == -1 )
{
IMAPCount->CurrentClientConnections--;
close( Client.conn->sd );
return;
}
if ( Client.MoreData )
{
syslog( LOG_WARNING, "%s: Too much data read from unauthenticated client. Dropping the connection.", fn );
IMAPCount->CurrentClientConnections--;
close( Client.conn->sd );
return;
}
/* First grab the tag */
/* Clear the SendBuffer before we send anything ! */
memset(SendBuf, 0, sizeof(SendBuf));
EndOfLine = Client.ReadBuf + BytesRead;
Tag = memtok( Client.ReadBuf, EndOfLine, &Lasts );
if ( !Tag )
continue;
if (( !imparse_isatom( Tag ) ) ||
( Tag[0] == '*' && !Tag[1] ) )
{
if ( ! strncasecmp( (const char *)Tag, "USER", 4 ) ) {
snprintf( SendBuf, BufLen, "-ERR Zu wenig Angaben für den USER Befehl.\r\n" );
} else if ( ! strncasecmp( (const char *)Tag, "PASS", 4 ) ) {
snprintf( SendBuf, BufLen, "-ERR Zu wenig Angaben f\xfcr den PASS Befehl.\r\n" );
} else {
snprintf( SendBuf, BufLen, "-ERR Unbekannter Befehl: %s\r\n", Tag);
}
if ( IMAP_Write( Client.conn, SendBuf, strlen(SendBuf) ) == -1 )
{
IMAPCount->CurrentClientConnections--;
close( Client.conn->sd );
return;
}
continue;
}
Command = memtok( NULL, EndOfLine, &Lasts );
if ( !Command )
{
/* Tag with no command */
if ( ! strncasecmp( (const char *)Tag, "USER", 4 ) ) {
snprintf( SendBuf, BufLen, "-ERR Zu wenig Angaben f\xfcr den USER Befehl.\r\n" );
} else if ( ! strncasecmp( (const char *)Tag, "PASS", 4 )) {
snprintf( SendBuf, BufLen, "-ERR Zu wenig Angaben f\xfcr den PASS Befehl.\r\n" );
} else if ( ! strncasecmp( (const char *)Tag, "CAPA", 4)) {
/* send the capability list to the client */
if ( IMAP_Write( Client.conn, Capability, CapabilityLen) == -1 )
{
syslog(LOG_ERR, "%s: IMAP_Write() failed: %s. Closing client connection.", fn, strerror( errno ) );
IMAPCount->CurrentClientConnections--;
close( Client.conn->sd );
return;
}
} else if ( ! strncasecmp( (const char *)Tag, "EPOP", 4)) {
/* send the capability list to the client */
if ( IMAP_Write( Client.conn, Epoplist, EpoplistLen ) == -1 )
{
syslog(LOG_ERR, "%s: IMAP_Write() failed: %s. Closing client connection.", fn, strerror( errno ) );
IMAPCount->CurrentClientConnections--;
close( Client.conn->sd );
return;
}
} else if (! strncasecmp( (const char *)Tag, "QUIT", 4)) {
/*
* We caught a LOGOUT from the client. Respond with
* a successful logout back to the client.
*/
cmd_logout( &Client, Tag );
/*
* close the client side socket.
*/
IMAPCount->CurrentClientConnections--;
close( Client.conn->sd );
return;
} else
snprintf( SendBuf, BufLen, "ERR Unknown command\r\n" );
if ( IMAP_Write( Client.conn, SendBuf, strlen(SendBuf) ) == -1 )
{
IMAPCount->CurrentClientConnections--;
close( Client.conn->sd );
return;
}
continue;
}
/*
* We should have a valid tag and command now. React as
* appropriate...
*/
strncpy( S_Tag, Tag, MAXTAGLEN - 1 );
S_Tag[MAXTAGLEN - 1] = '\0';
if ( ! strcasecmp( (const char *)Tag, "USER" ) )
{
/*
* Got a LOGIN command. validate that we got all four required
* tokens (Tag, Command, Username, Password) before we waste
* a connection to the IMAP server.
*/
Username = Command;
if ( !Username )
{
/* no username -- complain back to the client */
snprintf( SendBuf, BufLen, "%s BAD Missing required argument to USER\r\n", Tag );
if ( IMAP_Write( Client.conn, SendBuf, strlen(SendBuf) ) == -1 )
{
IMAPCount->CurrentClientConnections--;
close( Client.conn->sd );
return;
}
continue;
}
snprintf( SendBuf, BufLen, "+OK Password required for %s\r\n", Username);
if ( IMAP_Write( Client.conn, SendBuf, strlen(SendBuf) ) == -1 )
{
IMAPCount->CurrentClientConnections--;
close( Client.conn->sd );
return;
}
strncpy( S_UserName, Username, sizeof S_UserName - 1 );
S_UserName[sizeof S_UserName - 1] = '\0';
}
else if ( ! strncasecmp( (const char *)Tag, "PASS", 4 ) )
{
/*
* Got a LOGIN command. validate that we got all four required
* tokens (Tag, Command, Username, Password) before we waste
* a connection to the IMAP server.
*/
Password = Command;
strncpy( S_Password, Password, sizeof S_UserName - 1 );
S_Password[sizeof S_Password - 1] = '\0';
rc = cmd_login( &Client, S_UserName, S_Password, sizeof S_Password, S_Tag, LiteralFlag );
if ( rc == 0)
continue;
if ( rc == 1)
{
/*
* We caught a LOGOUT from the client. Respond with
* a successful logout back to the client.
*/
Tag = memtok( Client.ReadBuf, EndOfLine, &Lasts );
if ( Tag )
{
strncpy( S_Tag, Tag, MAXTAGLEN - 1 );
S_Tag[MAXTAGLEN - 1] = '\0';
cmd_logout( &Client, S_Tag );
}
}
/*
* close the client side socket.
*/
IMAPCount->CurrentClientConnections--;
close( Client.conn->sd );
return;
} else {
/*
* We got a command that we don't understand. Treat this the
* same way the cyrus implementation does -- tell the client to
* log in first.
*/
snprintf( SendBuf, BufLen, "-ERR Bad command %s, please login first\r\n", Tag, Command );
if ( IMAP_Write( Client.conn, SendBuf, strlen(SendBuf) ) == -1 )
{
IMAPCount->CurrentClientConnections--;
close( Client.conn->sd );
return;
}
continue;
}
} /* End of infinite for loop */
/* should never reach this code */
IMAPCount->CurrentClientConnections--;
close( Client.conn->sd );
return;
}
/*
* _________
* / |
* / |
* / ______|
* / / ________
* | | | /
* | | |_____/
* | | ______
* | | | \
* | | |______\
* \ \_______
* \ |
* \ |
* \_________|
*/
syntax highlighted by Code2HTML, v. 0.9.1