/*
**
** Copyright (c) 2002,2003 Dave McMurtrie
**
** This file is part of imapproxy.
**
** imapproxy 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.
**
** imapproxy 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 imapproxy; 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.21 2005/07/06 11:53:19 dgm Exp $
**
** Modification History:
**
** $Log: request.c,v $
** Revision 1.21 2005/07/06 11:53:19 dgm
** Added support for enable_admin_commands config option.
**
** Revision 1.20 2005/06/15 12:07:12 dgm
** Remove unused variables. Fixed snprintf argument lists.
** Include missing config.h directive.
**
** Revision 1.19 2004/11/10 15:33:04 dgm
** Explictly NULL terminate all strings that are the result
** of strncpy. Also enforce checking of LiteralBytesRemaining
** after any call to IMAP_Line_Read.
**
** Revision 1.18 2004/02/24 15:19:06 dgm
** Added support for SELECT caching.
**
** 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 <config.h>
#include <errno.h>
#include <string.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>
#include "common.h"
#include "imapproxy.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 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 *, ISC_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';
if ( ! PC_Struct.enable_admin_commands )
{
snprintf( SendBuf, BufLen, "%s BAD Unrecognized command\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 );
}
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';
if ( ! PC_Struct.enable_admin_commands )
{
snprintf( SendBuf, BufLen, "%s BAD Unrecognized command\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 );
}
/*
* 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';
if ( ! PC_Struct.enable_admin_commands )
{
snprintf( SendBuf, BufLen, "%s BAD Unrecognized command\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 );
}
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';
if ( ! PC_Struct.enable_admin_commands )
{
snprintf( SendBuf, BufLen, "%s BAD Unrecognized command\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 );
}
/*
* 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 );
TraceUser[ sizeof TraceUser - 1 ] = '\0';
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, "* BYE LOGOUT received\r\n%s OK Completed\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_capability
*
* Purpose: implement the CAPABILITY 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_capability( ITD_Struct *itd, char *Tag )
{
char *fn = "cmd_capability";
char SendBuf[BUFSIZE];
unsigned int BufLen = BUFSIZE - 1;
SendBuf[BUFSIZE - 1] = '\0';
snprintf( SendBuf, BufLen, "%s%s OK Completed\r\n",Capability, 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_authenticate_login
*
* Purpose: implement the AUTHENTICATE LOGIN mechanism
*
* Parameters: ptr to ITD_Struct for client connection.
* ptr to client tag
*
* 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>
*
* Notes:
*--
*/
static int cmd_authenticate_login( ITD_Struct *Client,
char *Tag )
{
char *fn = "cmd_authenticate_login()";
char SendBuf[BUFSIZE];
char Username[MAXUSERNAMELEN];
char EncodedUsername[BUFSIZE];
char Password[MAXPASSWDLEN];
char EncodedPassword[BUFSIZE];
ICD_Struct *conn;
int rc;
ITD_Struct Server;
int BytesRead;
struct sockaddr_in cli_addr;
int addrlen;
char *hostaddr;
in_port_t sin_port;
unsigned int BufLen = BUFSIZE - 1;
memset ( &Server, 0, sizeof Server );
addrlen = sizeof( struct sockaddr_in );
/*
* send a base64 encoded username prompt to the client. Note that we're
* using our Username and EncodedUsername buffers temporarily here to
* avoid allocating additional buffers. Keep this in mind for future
* code modification...
*/
snprintf( Username, BufLen, "Username:" );
EVP_EncodeBlock( EncodedUsername, Username, strlen( Username ) );
snprintf( SendBuf, BufLen, "+ %s\r\n", EncodedUsername );
if ( IMAP_Write( Client->conn, SendBuf, strlen(SendBuf) ) == -1 )
{
syslog(LOG_ERR, "%s: Unable to send base64 encoded username prompt to client: %s", fn, strerror(errno) );
return( -1 );
}
/*
* The response from the client should be a base64 encoded version of the
* username.
*/
BytesRead = IMAP_Line_Read( Client );
if ( BytesRead == -1 )
{
syslog( LOG_NOTICE, "%s: Failed to read base64 encoded username from client on socket %d", fn, Client->conn->sd );
return( -1 );
}
/*
* Don't accept literals from the client here.
*/
if ( Client->LiteralBytesRemaining )
{
syslog( LOG_NOTICE, "%s: Read unexpected literal specifier from client on socket %d", fn, Client->conn->sd );
return( -1 );
}
/*
* Easy, but not perfect sanity check. If the client sent enough data
* to fill our entire buffer, we're not even going to bother looking at it.
*/
if ( Client->MoreData ||
BytesRead > BufLen )
{
syslog( LOG_NOTICE, "%s: Base64 encoded username sent from client on sd %d is too large.", fn, Client->conn->sd );
return( -1 );
}
/*
* copy BytesRead -2 so we don't include the CRLF.
*/
memcpy( (void *)EncodedUsername, (const void *)Client->ReadBuf,
BytesRead - 2 );
rc = EVP_DecodeBlock( Username, EncodedUsername, BytesRead - 2 );
Username[rc] = '\0';
/*
* Same drill all over again, except this time it's for the password.
*/
snprintf( Password, BufLen, "Password:" );
EVP_EncodeBlock( EncodedPassword, Password, strlen( Password ) );
snprintf( SendBuf, BufLen, "+ %s\r\n", EncodedPassword );
if ( IMAP_Write( Client->conn, SendBuf, strlen(SendBuf) ) == -1 )
{
syslog(LOG_ERR, "%s: Unable to send base64 encoded password prompt to client: %s", fn, strerror(errno) );
return( -1 );
}
BytesRead = IMAP_Line_Read( Client );
if ( Client->LiteralBytesRemaining )
{
syslog( LOG_ERR, "%s: received unexpected literal specifier from client on socket %d", fn, Client->conn->sd );
return( -1 );
}
if ( BytesRead == -1 )
{
syslog( LOG_NOTICE, "%s: Failed to read base64 encoded password from client on socket %d", fn, Client->conn->sd );
return( -1 );
}
if ( Client->MoreData ||
BytesRead > BufLen )
{
syslog( LOG_NOTICE, "%s: Base64 encoded password sent from client on sd %d is too large.", fn, Client->conn->sd );
return( -1 );
}
memcpy( (void *)EncodedPassword, (const void *)Client->ReadBuf,
BytesRead - 2 );
rc = EVP_DecodeBlock( Password, EncodedPassword, BytesRead - 2 );
Password[rc] = '\0';
if ( getpeername( Client->conn->sd, (struct sockaddr *)&cli_addr, &addrlen ) < 0 )
{
syslog( LOG_WARNING, "AUTH_LOGIN: 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_WARNING, "AUTH_LOGIN: '%s' failed: inet_ntoa() failed for client sd: %s", Username, strerror( errno ) );
return( -1 );
}
/*
* Tell Get_Server_conn() to send the password as a string literal if
* he needs to login. This is just in case there are any special
* characters in the password that we decoded.
*/
conn = Get_Server_conn( Username, Password, hostaddr, sin_port, LITERAL_PASSWORD );
/*
* all the code from here to the end is basically identical to that
* in cmd_login().
*/
memset( Password, 0, MAXPASSWDLEN );
if ( conn == NULL )
{
snprintf( SendBuf, BufLen, "%s NO AUTHENTICATE failed\r\n", Tag );
if ( IMAP_Write( Client->conn, SendBuf, strlen(SendBuf) ) == -1 )
{
syslog( LOG_ERR, "%s: Unable to send failure message back to client: %s", fn, strerror( errno ) );
return( -1 );
}
return( 0 );
}
Server.conn = conn;
snprintf( SendBuf, BufLen, "%s OK User authenticated\r\n", Tag );
if ( IMAP_Write( Client->conn, SendBuf, strlen( SendBuf ) ) == -1 )
{
IMAPCount->InUseServerConnections--;
close( Server.conn->sd );
syslog( LOG_ERR, "%s: Unable to send successful authentication message back to client: %s -- closing connection.", fn, strerror( errno ) );
return( -1 );
}
IMAPCount->TotalClientLogins++;
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, &Server.conn->ISC );
Client->TraceOn = 0;
Server.TraceOn = 0;
ICC_Logout( Username, Server.conn );
return( rc );
}
/*++
* 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;
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 );
/*
* 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.
*/
snprintf( SendBuf, BufLen, "%s NO LOGIN failed\r\n", Tag );
if ( IMAP_Write( Client->conn, SendBuf, strlen(SendBuf) ) == -1 )
{
syslog(LOG_ERR, "%s: Unable to send failure message back to client: %s", fn, strerror(errno) );
return( -1 );
}
return( 0 );
}
Server.conn = conn;
/*
* Send a success message back to the client
* and go into raw proxy mode.
*/
snprintf( SendBuf, BufLen, "%s OK User logged in\r\n", Tag );
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, &Server.conn->ISC );
/*
* 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,
ISC_Struct *ISC )
{
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 ];
int rc;
#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 ( Server->conn->tls )
{
/* See is we have any buffered input */
pending = SSL_pending( Server->conn->tls );
}
#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 first to see if he has any data to send. I don't
* know if the order (client or server) matters, but my logic is that
* the client should always be ready to hear from the server since the
* server is allowed to send unsolicited data and the client has to
* be able to deal with it.
*/
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 );
}
if ( Server->TraceOn )
{
snprintf( TraceBuf, sizeof TraceBuf - 1, "\n\n-----> C= %d %s SERVER: sd [%d]\n", time( 0 ), ( (TraceUser) ? TraceUser : "Null username" ), Server->conn->sd );
write( Tracefd, TraceBuf, strlen( TraceBuf ) );
write( Tracefd, Server->ReadBuf, status );
}
/* 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? */
/*
* Now we proxy from the client to the server. We have to watch
* this side a little bit closer...
*/
if ( ! fds[ CLIENT ].revents )
{
continue;
}
do
{
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. What command is it?
*/
CP = memchr( Client->ReadBuf, ' ',
Client->ReadBytesProcessed );
if ( CP )
{
CP++;
if ( !strncasecmp( CP, "LOGOUT", 6 ) )
{
/*
* Since we want to potentially reuse this server
* connection, we want to return it to an unselected
* state. Use UNSELECT if the server supports it.
* Otherwise, EXAMINE a null mailbox. If SELECT
* caching is enabled, don't do this.
*/
if ( ! PC_Struct.enable_select_cache )
{
snprintf( SendBuf, sizeof SendBuf - 1,
"C64 %s\r\n", ( (PC_Struct.support_unselect) ? "UNSELECT" : "EXAMINE \"\"" ) );
IMAP_Write( Server->conn, SendBuf,
strlen(SendBuf) );
/*
* To be more correct, we should send any untagged
* data back to the client before we're done.
*/
for( ;; )
{
/*
* If the server wants to send a literal for
* some reason, bag it...
*/
if ( Server->LiteralBytesRemaining )
break;
status = IMAP_Line_Read( Server );
/*
* If there's an error reading from the server,
* we'll catch it when (if) we try to reuse this
* connection.
*/
if ( ( status == -1 ) || ( status == 0 ) )
break;
/*
* If it's not untagged data, we're done.
*/
if ( Server->ReadBuf[0] != '*' )
break;
BytesSent = IMAP_Write( Client->conn,
Server->ReadBuf, status );
if ( BytesSent == -1 )
{
syslog( LOG_ERR, "%s: IMAP_Write() failed sending data to client on sd [%d]: %s", fn, Client->conn->sd, strerror( errno ) );
}
}
} /* if ( ! PC_Struct.enable_select_cache ) */
memset( Server->ReadBuf, 0, sizeof Server->ReadBuf );
return( 1 );
}
/*
* it's some command other than a LOGOUT...
* If we care about SELECT caching, do that now.
*/
if ( PC_Struct.enable_select_cache )
{
if ( !strncasecmp( CP, "SELECT ", 7 ) )
{
rc = Handle_Select_Command( Client, Server,
ISC, Client->ReadBuf,
status );
if ( rc == 0 )
continue;
if ( rc == -1 )
return( -1 );
/*
* if Handle_Select_Command() returned 1,
* fall through the rest of the logic and the
* SELECT command should be proxied without
* looking at the cache.
*/
} /* if the command is SELECT */
/*
* SELECT caching is enabled and we've encountered
* a command other than SELECT. See if we should
* invalidate the SELECT cache or not.
*/
if ( ! Is_Safe_Command( CP ) )
{
Invalidate_Cache_Entry( ISC );
}
} /* if ( PC_Struct.enable_select_cache ) */
} /* if ( CP ) */
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 server on sd [%d]: %s", fn, Server->conn->sd, strerror( errno ) );
return( -1 );
}
break;
}
/*
* Don't just rely on our do/while condition. There
* may still be stuff in our buffer, but it could be
* literal data. Check and break out of the loop if that's
* the case.
*/
if ( Client->LiteralBytesRemaining )
break;
} while ( Client->BytesInReadBuffer > Client->ReadBytesProcessed );
/*
* If there are literal bytes to read, get them and blast them
* off to the server. Only do this, however, if it's a non
* synchronous literal since the server has to send a "go ahead"
* otherwise.
*/
if ( ! Client->LiteralBytesRemaining )
continue;
/*
* Do we have to wait for a "go-ahead" from the server?
*/
if ( ! Client->NonSyncLiteral )
{
/* we have to wait for a go-ahead */
status = IMAP_Line_Read( Server );
if ( Server->TraceOn )
{
snprintf( TraceBuf, sizeof TraceBuf - 1, "\n\n-----> C= %d %s SERVER: sd [%d]\n", time( 0 ), ( (TraceUser) ? TraceUser : "Null username" ), Server->conn->sd );
write( Tracefd, TraceBuf, strlen( TraceBuf ) );
write( Tracefd, Server->ReadBuf, status );
}
if ( Server->ReadBuf[0] != '+' )
Client->LiteralBytesRemaining = 0;
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;
}
}
while ( Client->LiteralBytesRemaining )
{
status = IMAP_Literal_Read( Client );
if ( status == -1 )
{
syslog(LOG_NOTICE, "%s: Failed to read string literal 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 );
}
/* send any literal data back to the server */
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): NOOP, CAPABILITY,
* AUTHENTICATE, LOGIN, and LOGOUT. 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;
char *Tag;
char *Command;
char *Username;
char *AuthMech;
char *Lasts;
char *EndOfLine;
char *CP;
char SendBuf[BUFSIZE];
int BytesRead;
int rc;
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 */
EndOfLine = Client.ReadBuf + BytesRead;
Tag = memtok( Client.ReadBuf, EndOfLine, &Lasts );
if ( ( !Tag ) ||
( !imparse_isatom( Tag ) ) ||
( Tag[0] == '*' && !Tag[1] ) )
{
snprintf( SendBuf, BufLen, "* BAD Invalid tag\r\n" );
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 */
snprintf( SendBuf, BufLen, "%s BAD Null command\r\n", Tag );
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 *)Command, "NOOP" ) )
{
if ( Client.LiteralBytesRemaining )
{
syslog( LOG_ERR, "%s: Unexpected literal specifier read from client on socket %d as part of NOOP command -- disconnecting client", fn, Client.conn->sd );
IMAPCount->CurrentClientConnections--;
close( Client.conn->sd );
return;
}
cmd_noop( &Client, S_Tag );
continue;
}
else if ( ! strcasecmp( (const char *)Command, "CAPABILITY" ) )
{
if ( Client.LiteralBytesRemaining )
{
syslog( LOG_ERR, "%s: Unexpected literal specifier read from client on socket %d as part of CAPABILITY command -- disconnecting client", fn, Client.conn->sd );
IMAPCount->CurrentClientConnections--;
close( Client.conn->sd );
return;
}
cmd_capability( &Client, S_Tag );
continue;
}
else if ( ! strcasecmp( (const char *)Command, "AUTHENTICATE" ) )
{
if ( Client.LiteralBytesRemaining )
{
syslog( LOG_ERR, "%s: Unexpected literal specifier read from client on socket %d as part of AUTHENTICATE command -- disconnecting client", fn, Client.conn->sd );
IMAPCount->CurrentClientConnections--;
close( Client.conn->sd );
return;
}
AuthMech = memtok( NULL, EndOfLine, &Lasts );
if ( !AuthMech )
{
snprintf( SendBuf, BufLen, "%s BAD Missing required argument to Authenticate\r\n", Tag );
if ( IMAP_Write( Client.conn, SendBuf, strlen(SendBuf) ) == -1 )
{
IMAPCount->CurrentClientConnections--;
close( Client.conn->sd );
return;
}
continue;
}
if ( !strcasecmp( (const char *)AuthMech, "LOGIN" ) )
{
rc = cmd_authenticate_login( &Client, S_Tag );
if ( rc == 0 )
continue;
if ( rc == 1 )
{
/* caught a logout */
Tag = memtok( Client.ReadBuf, EndOfLine, &Lasts );
if ( Tag )
{
strncpy( S_Tag, Tag, MAXTAGLEN - 1 );
S_Tag[ MAXTAGLEN - 1 ] = '\0';
cmd_logout( &Client, S_Tag );
}
}
IMAPCount->CurrentClientConnections--;
close( Client.conn->sd );
return;
}
else
{
/*
* an auth mechanism we can't handle.
*/
snprintf( SendBuf, BufLen, "%s NO no mechanism available\r\n", Tag );
if ( IMAP_Write( Client.conn, SendBuf, strlen(SendBuf) ) == -1 )
{
IMAPCount->CurrentClientConnections--;
close( Client.conn->sd );
return;
}
continue;
}
}
else if ( ! strcasecmp( (const char *)Command, "LOGOUT" ) )
{
if ( Client.LiteralBytesRemaining )
{
syslog( LOG_ERR, "%s: Unexpected literal specifier read from client on socket %d as part of LOGOUT command -- disconnecting client", fn, Client.conn->sd );
IMAPCount->CurrentClientConnections--;
close( Client.conn->sd );
return;
}
cmd_logout( &Client, S_Tag );
IMAPCount->CurrentClientConnections--;
close( Client.conn->sd );
return;
}
else if ( ! strcasecmp( (const char *)Command, "P_TRACE" ) )
{
if ( Client.LiteralBytesRemaining )
{
syslog( LOG_ERR, "%s: Unexpected literal specifier read from client on socket %d as part of P_TRACE command -- disconnecting client", fn, Client.conn->sd );
IMAPCount->CurrentClientConnections--;
close( Client.conn->sd );
return;
}
Username = memtok( NULL, EndOfLine, &Lasts );
cmd_trace( &Client, S_Tag, Username );
continue;
}
else if ( ! strcasecmp( (const char *)Command, "P_DUMPICC" ) )
{
if ( Client.LiteralBytesRemaining )
{
syslog( LOG_ERR, "%s: Unexpected literal specifier read from client on socket %d as part of P_DUMPICC command -- disconnecting client", fn, Client.conn->sd );
IMAPCount->CurrentClientConnections--;
close( Client.conn->sd );
return;
}
cmd_dumpicc( &Client, S_Tag );
continue;
}
else if ( ! strcasecmp( (const char *)Command, "P_RESETCOUNTERS" ) )
{
if ( Client.LiteralBytesRemaining )
{
syslog( LOG_ERR, "%s: Unexpected literal specifier read from client on socket %d as part of P_RESETCOUNTERS command -- disconnecting client", fn, Client.conn->sd );
IMAPCount->CurrentClientConnections--;
close( Client.conn->sd );
return;
}
cmd_resetcounters( &Client, S_Tag );
continue;
}
else if ( ! strcasecmp( (const char *)Command, "P_NEWLOG" ) )
{
if ( Client.LiteralBytesRemaining )
{
syslog( LOG_ERR, "%s: Unexpected literal specifier read from client on socket %d as part of P_NEWLOG command -- disconnecting client", fn, Client.conn->sd );
IMAPCount->CurrentClientConnections--;
close( Client.conn->sd );
return;
}
cmd_newlog( &Client, S_Tag );
continue;
}
else if ( ! strcasecmp( (const char *)Command, "LOGIN" ) )
{
/*
* 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 = memtok( NULL, EndOfLine, &Lasts );
if ( !Username )
{
/* no username -- complain back to the client */
snprintf( SendBuf, BufLen, "%s BAD Missing required argument to Login\r\n", Tag );
if ( IMAP_Write( Client.conn, SendBuf, strlen(SendBuf) ) == -1 )
{
IMAPCount->CurrentClientConnections--;
close( Client.conn->sd );
return;
}
continue;
}
strncpy( S_UserName, Username, sizeof S_UserName - 1 );
S_UserName[ sizeof S_UserName - 1 ] = '\0';
/*
* Clients can send the password as a literal bytestream. Check
* for that here.
*/
if ( Client.LiteralBytesRemaining )
{
if ( ( sizeof S_Password - 1 ) < Client.LiteralBytesRemaining )
{
syslog( LOG_ERR, "%s: password length would cause buffer overflow.", fn );
/*
* we have to at least eat the literal bytestream because
* of the way our I/O routines work.
*/
memset( &Client.ReadBuf, 0, sizeof Client.ReadBuf );
Client.BytesInReadBuffer = 0;
Client.ReadBytesProcessed = 0;
Client.LiteralBytesRemaining = 0;
Client.NonSyncLiteral = 0;
Client.MoreData = 0;
snprintf( SendBuf, BufLen, "%s NO LOGIN failed\r\n", Tag );
if ( IMAP_Write( Client.conn, SendBuf, strlen(SendBuf) ) == -1 )
{
IMAPCount->CurrentClientConnections--;
close( Client.conn->sd );
return;
}
continue;
}
LiteralFlag = LITERAL_PASSWORD;
CP = S_Password;
if ( ! Client.NonSyncLiteral )
{
sprintf( SendBuf, "+ go ahead\r\n" );
if ( IMAP_Write( Client.conn, SendBuf, strlen(SendBuf) ) == -1 )
{
IMAPCount->CurrentClientConnections--;
close( Client.conn->sd );
return;
}
}
while ( Client.LiteralBytesRemaining )
{
BytesRead = IMAP_Literal_Read( &Client );
if ( BytesRead == -1 )
{
syslog( LOG_NOTICE, "%s: Failed to read string literal from client on login." );
snprintf( SendBuf, BufLen, "%s NO LOGIN failed\r\n", Tag );
if ( IMAP_Write( Client.conn, SendBuf, strlen(SendBuf) ) == -1 )
{
IMAPCount->CurrentClientConnections--;
close( Client.conn->sd );
return;
}
continue;
}
memcpy ( (void *)CP, (const void *)Client.ReadBuf, BytesRead );
CP += BytesRead;
}
*CP = '\0';
/*
* I'm not sure if IMAP_Literal_Read() is written entirely
* in a correct fashion. There will be a CRLF at the end
* of the literal bytestream that it doesn't deal with.
* If we don't eat that here, it will be read as a separate
* (Null) command... Reading it here is more of a hack than
* a real solution, but I hesitate to fiddle with
* IMAP_Literal_Read() right now since it works properly
* otherwise.
*/
rc = IMAP_Line_Read( &Client );
}
else
{
/*
* The password is just being sent as a plain old string.
* Can't use memtok() because it uses a single space as the
* token delimeter and any password with a space in it would
* break.
*/
CP = EndOfLine - 2;
Lasts++;
if ( Lasts >= CP )
{
/* no password -- complain back to the client */
snprintf( SendBuf, BufLen, "%s BAD Missing required argument to Login\r\n", Tag );
if ( IMAP_Write( Client.conn, SendBuf, strlen(SendBuf) ) == -1 )
{
IMAPCount->CurrentClientConnections--;
close( Client.conn->sd );
return;
}
continue;
}
*CP = '\0';
strncpy( S_Password, Lasts, sizeof S_Password - 1 );
S_Password[ sizeof S_Password - 1 ] = '\0';
}
/*
* wipe out the the client read buffer since a copy of the
* password lives in there.
*/
memset( &Client.ReadBuf, 0, sizeof Client.ReadBuf );
Client.BytesInReadBuffer = 0;
Client.ReadBytesProcessed = 0;
Client.LiteralBytesRemaining = 0;
Client.NonSyncLiteral = 0;
Client.MoreData = 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.
*/
if ( Client.LiteralBytesRemaining )
{
syslog( LOG_ERR, "%s: Unexpected literal specifier read from client on socket %d as part of unknown command -- disconnecting client", fn, Client.conn->sd );
IMAPCount->CurrentClientConnections--;
close( Client.conn->sd );
return;
}
snprintf( SendBuf, BufLen, "%s BAD Please login first\r\n", Tag );
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