/*
**
** Copyright (c) 2002-2004 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:
**
** select.c
**
** Abstract:
**
** routines to support SELECT state caching in imapproxy.
**
** Authors:
**
** Dave McMurtrie <davemcmurtrie@hotmail.com>
**
** RCS:
**
** $Source: /afs/pitt.edu/usr12/dgm/work/IMAP_Proxy/src/RCS/select.c,v $
** $Id: select.c,v 1.4 2005/06/15 12:11:12 dgm Exp $
**
** Modification History:
**
** $Log: select.c,v $
** Revision 1.4 2005/06/15 12:11:12 dgm
** Remove unused variables.
**
** Revision 1.3 2004/11/10 15:34:14 dgm
** Explictly NULL terminate all strings that are the result of strncpy.
**
** Revision 1.2 2004/03/11 15:14:06 dgm
** Behavior of calling code when Populate_Select_Cache() fails
** modified to not send error back to client.
** "safe" command list updated.
**
** Revision 1.1 2004/02/24 15:13:21 dgm
** Initial revision
**
**
**
*/
#define _REENTRANT
#include <syslog.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include "imapproxy.h"
/*
* External globals
*/
extern int errno;
extern IMAPCounter_Struct *IMAPCount;
/*
* Internal prototypes
*/
static int Send_Cached_Select_Response( ITD_Struct *, ISC_Struct *, char * );
static int Populate_Select_Cache( ITD_Struct *, ISC_Struct *, char *, char *, unsigned int );
/*
* Function definitions.
*/
/*++
* Function: Handle_Select_Command
*
* Purpose: The client sent a SELECT command. Either hit the cache,
* or get data from the imap server.
*
* Parameters: ptr to ITD -- client transaction descriptor
* ptr to ITD -- server transaction descriptor
* ptr to ISC -- imap select cache structure
* ptr to char -- The select command string from the client.
* unsigned int -- the length of the select command
*
* Returns: 0 - The caller should consider the entire SELECT
* transaction to be complete. This return code does not
* imply successful completion. Rather, it implies that
* neither the client nor the server should be sending more
* data wrt this transaction.
*
* 1 - This return code implies that this function was not able
* to really accomplish anything useful. The caller should
* attempt to send the SELECT command by an alternate means.
* The SELECT command is still in the client read buffer and
* may be proxied directly to the server without the use of
* this function.
*
* -1 - A hard failure condition. The client and server sockets
* should be shut down.
*
* Authors: Dave McMurtrie <davemcmurtrie@hotmail.com>
*
* Notes: The SELECT command string passed into here will be the
* entire command, including the tag.
*--
*/
extern int Handle_Select_Command( ITD_Struct *Client,
ITD_Struct *Server,
ISC_Struct *ISC,
char *SelectCmd,
int SelectCmdLength )
{
char *fn = "Handle_Select_Command";
char *Mailbox;
char *Tag;
char *CP;
char Buf[ BUFSIZE ];
IMAPCount->TotalSelectCommands++;
/*
* Make a local copy of the select buffer so we can chop it to hell without
* offending the caller.
*/
if ( SelectCmdLength >= BUFSIZE )
{
IMAPCount->SelectCacheMisses++;
syslog( LOG_ERR, "%s: Length of SELECT command (%d bytes) would overflow %d byte buffer.", fn, SelectCmdLength, BUFSIZE );
return( 1 );
}
memcpy( Buf, SelectCmd, SelectCmdLength );
Buf[ SelectCmdLength ] = '\0';
/*
* NULL terminate the buffer at the end of the mailbox name
*/
CP = memchr( (const void *)Buf, '\r', SelectCmdLength );
if ( ! CP )
{
IMAPCount->SelectCacheMisses++;
syslog( LOG_ERR, "%s: Sanity check failed! SELECT command from client sd [%d] has no CRLF after it.", fn, Client->conn->sd );
return( -1 );
}
*CP = '\0';
Tag = Buf;
CP = memchr( ( const void * )Buf, ' ', SelectCmdLength );
if ( ! CP )
{
IMAPCount->SelectCacheMisses++;
syslog( LOG_ERR, "%s: Sanity check failed! No tokens found in SELECT command '%s' sent from client sd [%d].", fn, Buf, Client->conn->sd );
return( 1 );
}
*CP = '\0';
CP++;
Mailbox = memchr( ( const void * )CP, ' ', SelectCmdLength -
( strlen( Tag ) + 1 ) );
if ( ! Mailbox )
{
IMAPCount->SelectCacheMisses++;
syslog( LOG_WARNING, "%s: Protocol error. Client sd [%d] sent SELECT command with no mailbox name: '%s'", fn, Client->conn->sd, SelectCmd );
snprintf( Buf, sizeof Buf - 1, "%s BAD missing required argument to SELECT command\r\n", Tag );
IMAP_Write( Client->conn, Buf, strlen( Buf ) );
return( 0 );
}
Mailbox++;
/*
* We have a valid SELECT command. See if we have a cache entry that
* isn't expired.
*/
if ( time( 0 ) > ( ISC->ISCTime + SELECT_CACHE_EXP ) )
{
/*
* The SELECT data that's cached has expired.
*/
IMAPCount->SelectCacheMisses++;
if ( Populate_Select_Cache( Server, ISC, Mailbox, SelectCmd, SelectCmdLength ) == -1 )
{
return( 1 );
}
if ( Send_Cached_Select_Response( Client, ISC, Tag ) == -1 )
{
snprintf( Buf, sizeof Buf - 1, "%s BAD internal proxy server error\r\n", Tag );
IMAP_Write( Client->conn, Buf, strlen( Buf ) );
return( 0 );
}
return( 0 );
}
/*
* Our data isn't expired, but is it the correct mailbox?
*/
if ( ! strcmp( Mailbox, ISC->MailboxName ) )
{
/*
* We have the correct mailbox selected and cached already
*/
IMAPCount->SelectCacheHits++;
if ( Send_Cached_Select_Response( Client, ISC, Tag ) == -1 )
{
snprintf( Buf, sizeof Buf - 1, "%s BAD internal proxy server error\r\n", Tag );
IMAP_Write( Client->conn, Buf, strlen( Buf ) );
return( 0 );
}
return( 0 );
}
IMAPCount->SelectCacheMisses++;
if ( Populate_Select_Cache( Server, ISC, Mailbox, SelectCmd, SelectCmdLength ) == -1 )
{
return( 1 );
}
if ( Send_Cached_Select_Response( Client, ISC, Tag ) == -1 )
{
snprintf( Buf, sizeof Buf - 1, "%s BAD internal proxy server error\r\n", Tag );
IMAP_Write( Client->conn, Buf, strlen( Buf ) );
return( 0 );
}
return( 0 );
}
/*++
* Function: Send_Cached_Select_Response
*
* Purpose: Send cached SELECT server response data back to a client.
*
* Parameters: ptr to ITD -- client transaction descriptor
* ptr to ISC -- imap select cache structure
* ptr to char -- client tag for response
*
* Returns: 0 on success
* -1 on failure
*
* Authors: Dave McMurtrie <davemcmurtrie@hotmail.com>
*
* Notes:
*--
*/
static int Send_Cached_Select_Response( ITD_Struct *Client,
ISC_Struct *ISC,
char *Tag )
{
char *fn = "Send_Cached_Select_Response()";
char SendBuf[ BUFSIZE ];
if ( IMAP_Write( Client->conn, ISC->SelectString,
strlen( ISC->SelectString ) ) == -1 )
{
syslog( LOG_WARNING, "%s: Failed to send cached SELECT string to client on sd [%d]: %s", fn, Client->conn->sd, strerror( errno ) );
return( -1 );
}
snprintf( SendBuf, sizeof SendBuf - 1, "%s %s", Tag,
ISC->SelectStatus );
if ( IMAP_Write( Client->conn, SendBuf, strlen( SendBuf ) ) == -1 )
{
syslog( LOG_WARNING, "%s: Failed to send cached SELECT status to client on sd [%d]: %s", fn, Client->conn->sd, strerror( errno ) );
return( -1 );
}
return( 0 );
}
/*++
* Function: Populate_Select_Cache
*
* Purpose: Send a SELECT command to the server and cache the response.
*
* Parameters: ptr to ITD -- server transaction descriptor
* ptr to ISC -- the cache structure to populate
* ptr to char -- the mailbox name that's being selected
* ptr to char -- The select command string from the client.
* unsigned int -- the length of the select command
*
* Returns: 0 on success
* -1 on failure
*
* Authors: Dave McMurtrie <davemcmurtrie@hotmail.com>
*
* Notes:
*--
*/
static int Populate_Select_Cache( ITD_Struct *Server,
ISC_Struct *ISC,
char *MailboxName,
char *ClientCommand,
unsigned int Length )
{
char *fn = "Populate_Select_Cache()";
int rc;
int BytesLeftInBuffer = SELECT_BUF_SIZE;
char *BufPtr;
char *CP;
char *EOS;
rc = IMAP_Write( Server->conn, ClientCommand, Length );
if ( rc == -1 )
{
syslog( LOG_ERR, "%s: Unable to send SELECT command to imap server so can't populate cache.", fn );
return( -1 );
}
BufPtr = ISC->SelectString;
for( ;; )
{
if ( Server->LiteralBytesRemaining )
{
syslog( LOG_ERR, "%s: Server response to SELECT command contains unexpected literal data on sd [%d].", fn );
/*
* Must eat the literal.
*/
while ( Server->LiteralBytesRemaining )
{
IMAP_Literal_Read( Server );
}
return( -1 );
}
rc = IMAP_Line_Read( Server );
if ( ( rc == -1 ) || ( rc == 0 ) )
{
syslog( LOG_WARNING, "%s: Unable to read SELECT response from imap server so can't populate cache.", fn );
return( -1 );
}
/*
* If it's not untagged data, we're done
*/
if ( Server->ReadBuf[0] != '*' )
break;
if ( rc >= BytesLeftInBuffer )
{
syslog( LOG_WARNING, "%s: Size of SELECT response from server exceeds max cache size of %d bytes. Unable to cache this response.", fn, SELECT_BUF_SIZE );
return( -1 );
}
memcpy( (void *)BufPtr, (const void *)Server->ReadBuf, rc );
BytesLeftInBuffer -= rc;
BufPtr += rc;
}
/*
* NULL terminate the buffer that contains the select response. Note
* that we used the >= conditional above so we'd leave one byte of
* space for this NULL
*/
*BufPtr = '\0';
/*
* The SELECT output string is filled in. Now fill in the status.
*/
CP = memchr( (const void *)Server->ReadBuf, ' ', rc );
if ( ! CP )
{
syslog( LOG_ERR, "%s: Invalid response to SELECT command. Contains no tokens.", fn );
return( -1 );
}
CP++;
EOS = memchr( (const void *)Server->ReadBuf, '\r', rc );
if ( ! EOS )
{
syslog( LOG_ERR, "%s: Invalid response to SELECT command. Not CRLF terminated.", fn );
return( -1 );
}
*EOS = '\0';
snprintf( (char *)ISC->SelectStatus, SELECT_STATUS_BUF_SIZE - 1, "%s\r\n",
CP );
*EOS = '\r';
/*
* Update the cache time
*/
ISC->ISCTime = time( 0 );
strncpy( (char *)ISC->MailboxName, (const char *)MailboxName, MAXMAILBOXNAME - 1 );
ISC->MailboxName[ MAXMAILBOXNAME - 1 ] = '\0';
return( 0 );
}
/*++
* Function: Is_Safe_Command
*
* Purpose: Determine whether a given command is "safe". A command that
* is not "safe" should cause the select cache for a given
* mailbox to be invalidated.
*
* Parameters: char ptr -- the command
*
* Returns: 1 if the command is safe
* 0 if the command is not safe
*
* Authors: Dave McMurtrie <davemcmurtrie@hotmail.com>
*
* Notes:
*--
*/
extern unsigned int Is_Safe_Command( char *Command )
{
unsigned int i;
/*
* The safety of some of these is very questionable, particularly
* APPEND. Since time also invalidates the cache, hopefully the
* contents will be accurate enough to not break stuff...
*/
char *SafeCommands[] =
{
"CAPABILITY",
"NOOP",
"LOGOUT",
"STARTTLS",
"AUTHENTICATE",
"LOGIN",
"SELECT",
"EXAMINE",
"CREATE",
"DELETE",
"RENAME",
"SUBSCRIBE",
"UNSUBSCRIBE",
"LIST",
"LSUB",
"STATUS",
"APPEND",
"CHECK",
"SEARCH",
"FETCH",
"COPY",
"UID",
NULL
};
/*
* For a list this small a simple linear search should suffice.
*/
for ( i = 0; SafeCommands[i] != 0; i++ )
{
if ( ! strncasecmp( SafeCommands[i], Command,
strlen( SafeCommands[i] ) ) )
{
return( 1 );
}
}
return( 0 );
}
/*++
* Function: Invalidate_Cache_Entry
*
* Purpose: Reset the cache time so the entry will not be valid
*
* Parameters: ptr to ISC -- imap select cache structure
*
* Returns: nothing
*
* Authors: Dave McMurtrie <davemcmurtrie@hotmail.com>
*
* Notes:
*--
*/
extern void Invalidate_Cache_Entry( ISC_Struct *ISC )
{
ISC->ISCTime = 0;
}
/*
* _________
* / |
* / |
* / ______|
* / / ________
* | | | /
* | | |_____/
* | | ______
* | | | \
* | | |______\
* \ \_______
* \ |
* \ |
* \_________|
*/
syntax highlighted by Code2HTML, v. 0.9.1