/*++ * 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 <*****@*****.**> * * 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: 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 <*****@*****.**> *-- */ 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 <*****@*****.**> * * 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'; IMAPCount->CountTime = time( 0 ); IMAPCount->PeakClientConnections = 0; IMAPCount->PeakInUseServerConnections = 0; IMAPCount->PeakRetainedServerConnections = 0; IMAPCount->TotalClientConnectionsAccepted = 0; IMAPCount->TotalServerConnectionsCreated = 0; IMAPCount->TotalServerConnectionsReused = 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 <*****@*****.**> *-- */ 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_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 <*****@*****.**> *-- */ 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_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 <*****@*****.**> *-- */ 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_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 <*****@*****.**> * * 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; 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:"******"+ %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 ); } /* * 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:"******"+ %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 ); }
/*++ * 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 <*****@*****.**> *-- */ 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= %s PROXY: user tracing disabled. Expect further output until client logout.\n", 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= %s PROXY: user tracing enabled.\n", TraceUser ); write( Tracefd, SendBuf, strlen( SendBuf ) ); UnLockMutex( &trace ); 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 <*****@*****.**> * * 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: 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 <*****@*****.**> * * 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: Get_Server_conn * * Purpose: When a client login attempt is made, fetch a usable server * connection descriptor. This means that either we reuse an * existing ICD, or we open a new one. Hide that abstraction from * the caller... * * Parameters: ptr to username string * ptr to password string * const ptr to client hostname or IP string (for logging only) * in_port_t, client port number (for logging only) * unsigned char - flag to indicate that the client sent the * password as a string literal. * * Returns: ICD * on success * NULL on failure * * Authors: Dave McMurtrie <*****@*****.**> * * Credit: Major SSL additions by Ken Murchison <*****@*****.**> * *-- */ extern ICD_Struct *Get_Server_conn( char *Username, char *Password, const char *ClientAddr, in_port_t sin_port, unsigned char LiteralPasswd ) { char *fn = "Get_Server_conn()"; unsigned int HashIndex; ICC_Struct *HashEntry = NULL; char SendBuf[BUFSIZE]; unsigned int BufLen = BUFSIZE - 1; char md5pw[MD5_DIGEST_LENGTH]; char *tokenptr; char *endptr; char *last; ICC_Struct *ICC_Active; ICC_Struct *ICC_tptr; ITD_Struct Server; int rc; unsigned int Expiration; EVP_MD_CTX mdctx; int md_len; Expiration = PC_Struct.cache_expiration_time; memset( &Server, 0, sizeof Server ); /* need to md5 the passwd regardless, so do that now */ EVP_DigestInit(&mdctx, EVP_md5()); EVP_DigestUpdate(&mdctx, Password, strlen(Password)); EVP_DigestFinal(&mdctx, md5pw, &md_len); /* see if we have a reusable connection available */ ICC_Active = NULL; HashIndex = Hash( Username, HASH_TABLE_SIZE ); LockMutex( &mp ); /* * Now we just iterate through the linked list at this hash index until * we either find the string we're looking for or we find a NULL. */ for ( HashEntry = ICC_HashTable[ HashIndex ]; HashEntry; HashEntry = HashEntry->next ) { if ( ( strcmp( Username, HashEntry->username ) == 0 ) && ( HashEntry->logouttime > 1 ) ) { ICC_Active = HashEntry; /* * we found this username in our hash table. Need to know if * the password matches. */ if ( memcmp( md5pw, ICC_Active->hashedpw, sizeof md5pw ) ) { syslog( LOG_NOTICE, "%s: Unable to reuse server sd [%d] for user '%s' (%s:%d) because password doesn't match.", fn, ICC_Active->server_conn->sd, Username, ClientAddr, sin_port ); ICC_Active->logouttime = 1; } else { /* * We found a matching password on an inactive server socket. * We can use this guy. Before we release the mutex, set the * logouttime such that we mark this connection as "active" * again. */ ICC_Active->logouttime = 0; /* * The fact that we have this stored in a table as an open * server socket doesn't really mean that it's open. The * server could've closed it on us. * We need a speedy way to make sure this is still open. * We'll set the fd to non-blocking and try to read from it. * If we get a zero back, the connection is closed. If we get * EWOULDBLOCK (or some data) we know it's still open. If we * do read data, make sure we read all the data so we "drain" * any puss that may be left on this socket. */ fcntl( ICC_Active->server_conn->sd, F_SETFL, fcntl( ICC_Active->server_conn->sd, F_GETFL, 0) | O_NONBLOCK ); while ( ( rc = IMAP_Read( ICC_Active->server_conn, Server.ReadBuf, sizeof Server.ReadBuf ) ) > 0 ); if ( !rc ) { syslog(LOG_NOTICE, "%s: Unable to reuse server sd [%d] for user '%s' (%s:%d). Connection closed by server.", fn, ICC_Active->server_conn->sd, Username, ClientAddr, sin_port ); ICC_Active->logouttime = 1; continue; } if ( errno != EWOULDBLOCK ) { syslog(LOG_NOTICE, "%s: Unable to reuse server sd [%d] for user '%s' (%s:%d). IMAP_read() error: %s", fn, ICC_Active->server_conn->sd, Username, ClientAddr, sin_port, strerror( errno ) ); ICC_Active->logouttime = 1; continue; } fcntl( ICC_Active->server_conn->sd, F_SETFL, fcntl( ICC_Active->server_conn->sd, F_GETFL, 0) & ~O_NONBLOCK ); /* now release the mutex and return the sd to the caller */ UnLockMutex( &mp ); /* * We're reusing an existing server socket. There are a few * counters we have to deal with. */ IMAPCount->RetainedServerConnections--; IMAPCount->InUseServerConnections++; IMAPCount->TotalServerConnectionsReused++; if ( IMAPCount->InUseServerConnections > IMAPCount->PeakInUseServerConnections ) IMAPCount->PeakInUseServerConnections = IMAPCount->InUseServerConnections; syslog(LOG_INFO, "LOGIN: '******' (%s:%d) on existing sd [%d]", Username, ClientAddr, sin_port, ICC_Active->server_conn->sd ); return( ICC_Active->server_conn ); } } } UnLockMutex( &mp ); /* * We don't have an active connection for this user, or the password * didn't match. * Open a connection to the IMAP server so we can attempt to login */ Server.conn = ( ICD_Struct * ) malloc( sizeof ( ICD_Struct ) ); memset( Server.conn, 0, sizeof ( ICD_Struct ) ); Server.conn->sd = socket( PF_INET, SOCK_STREAM, IPPROTO_TCP ); if ( Server.conn->sd == -1 ) { syslog(LOG_INFO, "LOGIN: '******' (%s:%d) failed: Unable to open server socket: %s", Username, ClientAddr, sin_port, strerror( errno ) ); goto fail; } if ( PC_Struct.send_tcp_keepalives ) { int onoff = 1; setsockopt( Server.conn->sd, SOL_SOCKET, SO_KEEPALIVE, &onoff, sizeof onoff ); } if ( connect( Server.conn->sd, (struct sockaddr *)&ISD.srv, sizeof(ISD.srv) ) == -1 ) { syslog(LOG_INFO, "LOGIN: '******' (%s:%d) failed: Unable to connect to IMAP server: %s", Username, ClientAddr, sin_port, strerror( errno ) ); goto fail; } /* Read & throw away the banner line from the server */ if ( IMAP_Line_Read( &Server ) == -1 ) { syslog(LOG_INFO, "LOGIN: '******' (%s:%d) failed: No banner line received from IMAP server", Username, ClientAddr, sin_port ); goto fail; } /* * Do STARTTLS if necessary. */ #if HAVE_LIBSSL if ( PC_Struct.login_disabled ) { snprintf( SendBuf, BufLen, "S0001 STARTTLS\r\n" ); if ( IMAP_Write( Server.conn, SendBuf, strlen(SendBuf) ) == -1 ) { syslog(LOG_INFO, "STARTTLS failed: IMAP_Write() failed attempting to send STARTTLS command to IMAP server: %s", strerror( errno ) ); goto fail; } /* * Read the server response */ if ( ( rc = IMAP_Line_Read( &Server ) ) == -1 ) { syslog(LOG_INFO, "STARTTLS failed: No response from IMAP server after sending STARTTLS command" ); goto fail; } /* * Try to match up the tag in the server response to the client tag. */ endptr = Server.ReadBuf + rc; tokenptr = memtok( Server.ReadBuf, endptr, &last ); if ( !tokenptr ) { /* * no tokens found in server response? Not likely, but we still * have to check. */ syslog(LOG_INFO, "STARTTLS failed: server response to STARTTLS command contained no tokens." ); goto fail; } if ( memcmp( (const void *)tokenptr, (const void *)"S0001", strlen( tokenptr ) ) ) { /* * non-matching tag read back from the server... Lord knows what this * is, so we'll fail. */ syslog(LOG_INFO, "STARTTLS failed: server response to STARTTLS command contained non-matching tag." ); goto fail; } /* * Now that we've matched the tags up, see if the response was 'OK' */ tokenptr = memtok( NULL, endptr, &last ); if ( !tokenptr ) { /* again, not likely but we still have to check... */ syslog(LOG_INFO, "STARTTLS failed: Malformed server response to STARTTLS command" ); goto fail; } if ( memcmp( (const void *)tokenptr, "OK", 2 ) ) { /* * If the server sent back a "NO" or "BAD", we can look at the actual * server logs to figure out why. We don't have to break our ass here * putting the string back together just for the sake of logging. */ syslog(LOG_INFO, "STARTTLS failed: non-OK server response to STARTTLS command" ); goto fail; } Server.conn->tls = SSL_new( tls_ctx ); if ( Server.conn->tls == NULL ) { syslog(LOG_INFO, "STARTTLS failed: SSL_new() failed" ); goto fail; } SSL_clear( Server.conn->tls ); rc = SSL_set_fd( Server.conn->tls, Server.conn->sd ); if ( rc == 0 ) { syslog(LOG_INFO, "STARTTLS failed: SSL_set_fd() failed: %d", SSL_get_error( Server.conn->tls, rc ) ); goto fail; } SSL_set_connect_state( Server.conn->tls ); rc = SSL_connect( Server.conn->tls ); if ( rc <= 0 ) { syslog(LOG_INFO, "STARTTLS failed: SSL_connect() failed, %d: %s", SSL_get_error( Server.conn->tls, rc ), SSLerrmessage() ); goto fail; } /* XXX Should we grab the session id for later reuse? */ } #endif /* HAVE_LIBSSL */ /* * Send the login command off to the IMAP server. Have to treat a literal * password different. */ if ( LiteralPasswd ) { snprintf( SendBuf, BufLen, "A0001 LOGIN %s {%d}\r\n", Username, strlen( Password ) ); if ( IMAP_Write( Server.conn, SendBuf, strlen(SendBuf) ) == -1 ) { syslog(LOG_INFO, "LOGIN: '******' (%s:%d) failed: IMAP_Write() failed attempting to send LOGIN command to IMAP server: %s", Username, ClientAddr, sin_port, strerror( errno ) ); goto fail; } /* * the server response should be a go ahead */ if ( ( rc = IMAP_Line_Read( &Server ) ) == -1 ) { syslog(LOG_INFO, "LOGIN: '******' (%s:%d) failed: Failed to receive go-ahead from IMAP server after sending LOGIN command", Username, ClientAddr, sin_port ); goto fail; } if ( Server.ReadBuf[0] != '+' ) { syslog( LOG_INFO, "LOGIN: '******' (%s:%d) failed: bad response from server after sending string literal specifier", Username, ClientAddr, sin_port ); goto fail; } /* * now send the password */ snprintf( SendBuf, BufLen, "%s\r\n", Password ); if ( IMAP_Write( Server.conn, SendBuf, strlen( SendBuf ) ) == -1 ) { syslog(LOG_INFO, "LOGIN: '******' (%s:%d) failed: IMAP_Write() failed attempting to send literal password to IMAP server: %s", Username, ClientAddr, sin_port, strerror( errno ) ); goto fail; } } else { /* * just send the login command via normal means. */ snprintf( SendBuf, BufLen, "A0001 LOGIN %s %s\r\n", Username, Password ); if ( IMAP_Write( Server.conn, SendBuf, strlen(SendBuf) ) == -1 ) { syslog(LOG_INFO, "LOGIN: '******' (%s:%d) failed: IMAP_Write() failed attempting to send LOGIN command to IMAP server: %s", Username, ClientAddr, sin_port, strerror( errno ) ); goto fail; } } /* * Read the server response. From RFC 3501: * * A server MAY include a CAPABILITY response code in the tagged OK * response to a successful LOGIN command in order to send * capabilities automatically. It is unnecessary for a client to * send a separate CAPABILITY command if it recognizes these * automatic capabilities. * * We have to be ready for the possibility that this might be an * untagged response... In an ideal world, we'd want to pass the * untagged stuff back to the client. For now, since the RFC doesn't * mandate that behaviour, we're not going to since we don't have a client * socket descriptor to send it to. */ for ( ;; ) { if ( ( rc = IMAP_Line_Read( &Server ) ) == -1 ) { syslog(LOG_INFO, "LOGIN: '******' (%s:%d) failed: No response from IMAP server after sending LOGIN command", Username, ClientAddr, sin_port ); goto fail; } if ( Server.ReadBuf[0] != '*' ) break; } /* * Try to match up the tag in the server response to the client tag. */ endptr = Server.ReadBuf + rc; tokenptr = memtok( Server.ReadBuf, endptr, &last ); if ( !tokenptr ) { /* * no tokens found in server response? Not likely, but we still * have to check. */ syslog(LOG_INFO, "LOGIN: '******' (%s:%d) failed: server response to LOGIN command contained no tokens.", Username, ClientAddr, sin_port ); goto fail; } if ( memcmp( (const void *)tokenptr, (const void *)"A0001", strlen( tokenptr ) ) ) { /* * non-matching tag read back from the server... Lord knows what this * is, so we'll fail. */ syslog(LOG_INFO, "LOGIN: '******' (%s:%d) failed: server response to LOGIN command contained non-matching tag.", Username, ClientAddr, sin_port ); goto fail; } /* * Now that we've matched the tags up, see if the response was 'OK' */ tokenptr = memtok( NULL, endptr, &last ); if ( !tokenptr ) { /* again, not likely but we still have to check... */ syslog(LOG_INFO, "LOGIN: '******' (%s:%d) failed: Malformed server response to LOGIN command", Username, ClientAddr, sin_port ); goto fail; } if ( memcmp( (const void *)tokenptr, "OK", 2 ) ) { /* * If the server sent back a "NO" or "BAD", we can look at the actual * server logs to figure out why. We don't have to break our ass here * putting the string back together just for the sake of logging. */ syslog(LOG_INFO, "LOGIN: '******' (%s:%d) failed: non-OK server response to LOGIN command", Username, ClientAddr, sin_port ); goto fail; } /* * put this in our used list and remove it from the free list */ for( ; ; ) { LockMutex( &mp ); if ( ICC_free->next ) { /* generate the hash index */ HashIndex = Hash( Username, HASH_TABLE_SIZE ); /* temporarily store the address of the next free structure */ ICC_tptr = ICC_free->next; /* * We want to add the newest "used" structure at the front of * the list at the hash index. */ ICC_free->next = ICC_HashTable[ HashIndex ]; ICC_HashTable[ HashIndex ] = ICC_free; /* * less typing and more readability, set an "active" pointer. */ ICC_Active = ICC_free; /* now point the free listhead to the next available free struct */ ICC_free = ICC_tptr; /* fill in the newest used (oxymoron?) structure */ strncpy( ICC_Active->username, Username, sizeof ICC_Active->username ); memcpy( ICC_Active->hashedpw, md5pw, sizeof ICC_Active->hashedpw ); ICC_Active->logouttime = 0; /* zero means, "it's active". */ ICC_Active->server_conn = Server.conn; UnLockMutex( &mp ); IMAPCount->InUseServerConnections++; IMAPCount->TotalServerConnectionsCreated++; if ( IMAPCount->InUseServerConnections > IMAPCount->PeakInUseServerConnections ) IMAPCount->PeakInUseServerConnections = IMAPCount->InUseServerConnections; syslog(LOG_INFO, "LOGIN: '******' (%s:%d) on new sd [%d]", Username, ClientAddr, sin_port, Server.conn->sd ); return( Server.conn ); } /* * There weren't any free ICC structs. Try to free one. Make sure * we unlock the mutex, since ICC_Recycle needs to obtain it. */ UnLockMutex( &mp ); Expiration = abs( Expiration / 2 ); /* * Eventually, we have to fail */ if ( Expiration <= 2 ) { syslog(LOG_INFO, "LOGIN: '******' (%s:%d) failed: Out of free ICC structs.", Username, ClientAddr, sin_port ); goto fail; } ICC_Recycle( Expiration ); } fail: #if HAVE_LIBSSL if ( Server.conn->tls ) { SSL_shutdown( Server.conn->tls ); SSL_free( Server.conn->tls ); } #endif close( Server.conn->sd ); free( Server.conn ); return( NULL ); }
/*++ * Function: SetBannerAndCapability * * Purpose: Connect to an IMAP server as a client and fetch the initial * banner string and the output from a CAPABILITY command. * * Parameters: none * * Returns: nuttin -- exits if there's a problem * * Authors: Dave McMurtrie <*****@*****.**> * * Notes: All AUTH mechanisms will be stripped from the capability * string. AUTH=LOGIN will be added. * The support_unselect flag in the global copy of the * ProxyConfig struct will be set in this function depending on * whether the server supports UNSELECT or not. *-- */ static void SetBannerAndCapability( void ) { int sd; ITD_Struct itd; ICD_Struct conn; int BytesRead; char *fn = "SetBannerAndCapability()"; int NumRef = 0; /* initialize some stuff */ memset( &itd, 0, sizeof itd ); for ( ;; ) { sd = socket( PF_INET, SOCK_STREAM, IPPROTO_TCP ); if ( sd == -1 ) { syslog(LOG_ERR, "%s: socket() failed: %s -- exiting", fn, strerror(errno) ); exit( 1 ); } if ( connect( sd, (struct sockaddr *)&ISD.srv, sizeof(ISD.srv) ) == -1 ) { syslog(LOG_ERR, "%s: connect() to imap server on socket [%d] failed: %s", fn, sd, strerror(errno)); close( sd ); if ( errno == ECONNREFUSED && ++NumRef < 10 ) { sleep( 60 ); /* IMAP server may not be started yet. */ continue; } syslog( LOG_ERR, "%s: unable to connect() to imap server and retry limit exceeded -- exiting.", fn ); exit( 1 ); } break; /* Success */ } memset( &conn, 0, sizeof ( ICD_Struct ) ); itd.conn = &conn; itd.conn->sd = sd; /* * The first thing we get back from the server should be the * banner string. */ BytesRead = IMAP_Line_Read( &itd ); if ( BytesRead == -1 ) { syslog( LOG_ERR, "%s: Error reading banner line from server on initial connection: %s -- Exiting.", fn, strerror( errno ) ); close( itd.conn->sd ); exit( 1 ); } if ( itd.LiteralBytesRemaining ) { syslog( LOG_ERR, "%s: Server sent unexpected literal specifier in banner response -- Exiting.", fn ); exit( 1 ); } BannerLen = ParseBannerAndCapability( Banner, sizeof Banner - 1, itd.ReadBuf, BytesRead ); /* * See if the string we got back starts with "* OK" by comparing the * first 4 characters of the buffer. */ if ( strncasecmp( Banner, IMAP_UNTAGGED_OK, strlen(IMAP_UNTAGGED_OK)) ) { syslog(LOG_ERR, "%s: Unexpected response from imap server on initial connection: %s -- Exiting.", fn, Banner); close( itd.conn->sd ); exit( 1 ); } /* Now we send a CAPABILITY command to the server. */ if ( IMAP_Write( itd.conn, "1 CAPABILITY\r\n", strlen("1 CAPABILITY\r\n") ) == -1 ) { syslog(LOG_ERR, "%s: Unable to send capability command to server: %s -- exiting.", fn, strerror(errno) ); close( itd.conn->sd ); exit( 1 ); } /* * From RFC2060: * The server MUST send a single untagged * CAPABILITY response with "IMAP4rev1" as one of the listed * capabilities before the (tagged) OK response. * * The means we should read exactly 2 lines of data back from the server. * The first will be the untagged capability line. * The second will be the OK response with the tag in it. */ BytesRead = IMAP_Line_Read( &itd ); if ( BytesRead == -1 ) { syslog( LOG_ERR, "%s: Failed to read capability response from server: %s -- exiting.", fn, strerror( errno ) ); close( itd.conn->sd ); exit( 1 ); } if ( itd.LiteralBytesRemaining ) { syslog( LOG_ERR, "%s: Server sent unexpected literal specifier in CAPABILITY response -- Exiting.", fn ); close( itd.conn->sd ); exit ( 1 ); } CapabilityLen = ParseBannerAndCapability( Capability, sizeof Capability - 1, itd.ReadBuf, BytesRead ); /* Now read the tagged response and make sure it's OK */ BytesRead = IMAP_Line_Read( &itd ); if ( BytesRead == -1 ) { syslog( LOG_ERR, "%s: Failed to read capability response from server: %s -- exiting.", fn, strerror( errno ) ); close( itd.conn->sd ); exit( 1 ); } if ( itd.LiteralBytesRemaining ) { syslog( LOG_ERR, "%s: Server sent unexpected literal specifier in tagged CAPABILITY response -- exiting.", fn ); exit( 1 ); } if ( strncasecmp( itd.ReadBuf, IMAP_TAGGED_OK, strlen(IMAP_TAGGED_OK) ) ) { syslog(LOG_ERR, "%s: Received non-OK tagged reponse from imap server on CAPABILITY command -- exiting.", fn ); close( itd.conn->sd ); exit( 1 ); } /* Be nice and logout */ if ( IMAP_Write( itd.conn, "2 LOGOUT\r\n", strlen("2 LOGOUT\r\n") ) == -1 ) { syslog(LOG_WARNING, "%s: IMAP_Write() failed on LOGOUT: %s -- Ignoring", fn, strerror(errno) ); close( itd.conn->sd ); return; } /* read the final OK logout */ BytesRead = IMAP_Line_Read( &itd ); if ( BytesRead == -1 ) { syslog(LOG_WARNING, "%s: IMAP_Line_Read() failed on LOGOUT -- Ignoring", fn ); } if ( itd.LiteralBytesRemaining ) { syslog( LOG_WARNING, "%s: Server sent unexpected literal specifier in LOGOUT response -- Ignoring", fn ); } close( itd.conn->sd ); return; }
/*++ * Function: _ICC_Recycle * * Purpose: core logic to implement the ICC_Recycle() & ICC_Recycle_Loop() * functions. * * Parameters: unsigned int -- ICC expiration time * * Returns: nada * * Authors: Dave McMurtrie <*****@*****.**> *-- */ static void _ICC_Recycle( unsigned int Expiration ) { char *fn = "_ICC_Recycle()"; time_t CurrentTime; int rc; unsigned int HashIndex; ICC_Struct *HashEntry; ICC_Struct *Previous; CurrentTime = time(0); LockMutex( &mp ); /* * Need to iterate through every single item in our hash table * to decide if we can free it or not. */ for ( HashIndex = 0; HashIndex < HASH_TABLE_SIZE; HashIndex++ ) { Previous = NULL; HashEntry = ICC_HashTable[ HashIndex ]; while ( HashEntry ) { /* * If the last logout time is non-zero, and it's been logged * out for longer than our default expiration time, free it. * Note that this allows for the logouttime to be explicitly * set to 1 (such as in the Get_Server_conn code) if we want to * reap a connection before waiting the normal expiration * cycle. */ if ( HashEntry->logouttime && ( ( CurrentTime - HashEntry->logouttime ) > Expiration ) ) { syslog(LOG_INFO, "Expiring server sd [%d]", HashEntry->server_conn->sd); /* Logout of the imap server and close the server socket. */ IMAP_Write( HashEntry->server_conn, "VIC20 LOGOUT\r\n", strlen( "VIC20 LOGOUT\r\n" ) ); #if HAVE_LIBSSL if ( HashEntry->server_conn->tls ) { SSL_shutdown( HashEntry->server_conn->tls ); SSL_free( HashEntry->server_conn->tls ); } #endif close( HashEntry->server_conn->sd ); free( HashEntry->server_conn ); /* * This was being counted as a "retained" connection. It was * open, but not in use. Now that we're closing it, we have * to decrement the number of retained connections. */ IMAPCount->RetainedServerConnections--; if ( Previous ) { Previous->next = HashEntry->next; HashEntry->next = ICC_free; ICC_free = HashEntry; HashEntry = Previous->next; } else { ICC_HashTable[ HashIndex ] = HashEntry->next; HashEntry->next = ICC_free; ICC_free = HashEntry; HashEntry = ICC_HashTable[ HashIndex ]; } } else { Previous = HashEntry; HashEntry = HashEntry->next; } } } UnLockMutex( &mp ); }
/*++ * Function: SetBannerAndCapability * * Purpose: Connect to an IMAP server as a client and fetch the initial * banner string and the output from a CAPABILITY command. * * Parameters: none * * Returns: 0 on success * -1 on failure * * Authors: Dave McMurtrie <*****@*****.**> * * Notes: All AUTH mechanisms will be stripped from the capability * string. AUTH=LOGIN will be added. * The support_unselect flag in the global copy of the * ProxyConfig struct will be set in this function depending on * whether the server supports UNSELECT or not. *-- */ static int SetBannerAndCapability( void ) { int sd; ITD_Struct itd; ICD_Struct conn; int BytesRead; char *fn = "SetBannerAndCapability()"; char *CP; int NumRef = 0; /* initialize some stuff */ memset( &itd, 0, sizeof itd ); for ( ;; ) { sd = socket( PF_INET, SOCK_STREAM, IPPROTO_TCP ); if ( sd == -1 ) { syslog(LOG_ERR, "%s: socket() failed: %s", fn, strerror(errno) ); return( -1 ); } if ( connect( sd, (struct sockaddr *)&ISD.srv, sizeof(ISD.srv) ) == -1 ) { syslog(LOG_ERR, "%s: connect() to imap server on socket [%d] failed: %s", fn, sd, strerror(errno)); close( sd ); if ( errno == ECONNREFUSED && ++NumRef < 10 ) { sleep( 60 ); /* IMAP server may not be started yet. */ continue; } return( -1 ); } break; /* Success */ } memset( &conn, 0, sizeof ( ICD_Struct ) ); itd.conn = &conn; itd.conn->sd = sd; /* * The first thing we get back from the server should be the * banner string. */ BytesRead = IMAP_Line_Read( &itd ); if ( BytesRead == -1 ) { close( itd.conn->sd ); return( -1 ); } if ( sizeof Banner < BytesRead ) { syslog(LOG_ERR, "%s: Storing %d byte banner string from IMAP server would cause buffer overflow.", fn, BytesRead ); close( itd.conn->sd ); return( -1 ); } memcpy( Banner, itd.ReadBuf, BytesRead ); BannerLen = BytesRead; /* * See if the string we got back starts with "* OK" by comparing the * first 4 characters of the buffer. */ if ( strncasecmp( Banner, IMAP_UNTAGGED_OK, strlen(IMAP_UNTAGGED_OK)) ) { syslog(LOG_ERR, "%s: Unexpected response from imap server on initial connection: %s", fn, Banner); close( itd.conn->sd ); return( -1 ); } /* Now we send a CAPABILITY command to the server. */ if ( IMAP_Write( itd.conn, "1 CAPABILITY\r\n", strlen("1 CAPABILITY\r\n") ) == -1 ) { syslog(LOG_ERR, "%s: IMAP_Write() failed: %s", fn, strerror(errno) ); close( itd.conn->sd ); return( -1 ); } /* * From RFC2060: * The server MUST send a single untagged * CAPABILITY response with "IMAP4rev1" as one of the listed * capabilities before the (tagged) OK response. * * The means we should read exactly 2 lines of data back from the server. * The first will be the untagged capability line. * The second will be the OK response with the tag in it. */ BytesRead = IMAP_Line_Read( &itd ); if ( BytesRead == -1 ) { close( itd.conn->sd ); return( -1 ); } /* * The read buffer should now contain the * untagged response line. */ if ( sizeof Capability < BytesRead ) { syslog(LOG_ERR, "%s: Storing %d byte capability string from IMAP server would cause buffer overflow.", fn, BytesRead ); close( itd.conn->sd ); return( -1 ); } /* * strip out all of the AUTH mechanisms except the ones that we support. * Right now, this is just AUTH=LOGIN. Note that the use of * non-MT safe strtok is okay here. This function is called before any * other threads are launched and should never be called again. */ itd.ReadBuf[BytesRead - 2] = '\0'; CP = strtok( itd.ReadBuf, " " ); if ( !CP ) { syslog( LOG_ERR, "%s: No tokens found in capability string sent from IMAP server.", fn); close( itd.conn->sd ); return( -1 ); } sprintf( Capability, CP ); /* * initially assume that the server doesn't support UNSELECT. */ PC_Struct.support_unselect = UNSELECT_NOT_SUPPORTED; /* * initially assume that the server doesn't support STARTTLS. */ PC_Struct.support_starttls = STARTTLS_NOT_SUPPORTED; /* * initially assume that the server doesn't disable LOGIN. */ PC_Struct.login_disabled = LOGIN_NOT_DISABLED; for( ; ; ) { CP = strtok( NULL, " " ); if ( !CP ) break; if ( !strncasecmp( CP, "UNSELECT", strlen( "UNSELECT" ) ) ) { PC_Struct.support_unselect = UNSELECT_SUPPORTED; } /* * If this token happens to be an auth mechanism, we want to * discard it unless it's an auth mechanism we can support. */ if ( ! strncasecmp( CP, "AUTH=", strlen( "AUTH=" ) ) && ( strncasecmp( CP, "AUTH=LOGIN", strlen( "AUTH=LOGIN" ) ) ) ) { continue; } /* * If this token happens to be SASL, we want to discard it * since we don't support any auth mechs that can use it. */ if ( !strncasecmp( CP, "SASL", strlen( "SASL" ) ) ) { continue; } /* * If this token happens to be STARTTLS, we want to discard it * since we don't support it on the client-side. */ if ( ! strncasecmp( CP, "STARTTLS", strlen( "STARTTLS" ) ) ) { PC_Struct.support_starttls = STARTTLS_SUPPORTED; continue; } /* * If this token happens to be LOGINDISABLED, we want to discard it * since we don't support it on the client-side. */ if ( ! strncasecmp( CP, "LOGINDISABLED", strlen( "LOGINDISABLED" ) ) ) { PC_Struct.login_disabled = LOGIN_DISABLED; continue; } strcat( Capability, " "); strcat( Capability, CP ); } strcat( Capability, "\r\n" ); CapabilityLen = strlen( Capability ); /* Now read the tagged response and make sure it's OK */ BytesRead = IMAP_Line_Read( &itd ); if ( BytesRead == -1 ) { close( itd.conn->sd ); return( -1 ); } if ( strncasecmp( itd.ReadBuf, IMAP_TAGGED_OK, strlen(IMAP_TAGGED_OK) ) ) { syslog(LOG_ERR, "%s: Received non-OK tagged reponse from imap server on CAPABILITY command", fn ); close( itd.conn->sd ); return( -1 ); } /* Be nice and logout */ if ( IMAP_Write( itd.conn, "2 LOGOUT\r\n", strlen("2 LOGOUT\r\n") ) == -1 ) { syslog(LOG_WARNING, "%s: IMAP_Write() failed on LOGOUT: %s -- Returning success anyway.", fn, strerror(errno) ); close( itd.conn->sd ); return( 0 ); } /* read the final OK logout */ BytesRead = IMAP_Line_Read( &itd ); if ( BytesRead == -1 ) { syslog(LOG_WARNING, "%s: IMAP_Line_Read() failed on LOGOUT. Returning success anyway.", fn ); } close( itd.conn->sd ); return( 0 ); }
/*++ * Function: SetBannerAndCapability * * Purpose: Connect to an IMAP server as a client and fetch the initial * banner string and the output from a CAPABILITY command. * * Parameters: none * * Returns: nuttin -- exits if there's a problem * * Authors: Dave McMurtrie <*****@*****.**> * * Notes: All AUTH mechanisms will be stripped from the capability * string. AUTH=LOGIN will be added. * The support_unselect flag in the global copy of the * ProxyConfig struct will be set in this function depending on * whether the server supports UNSELECT or not. *-- */ static void SetBannerAndCapability( void ) { int sd; ITD_Struct itd; ICD_Struct conn; int BytesRead; char *fn = "SetBannerAndCapability()"; /* initialize some stuff */ memset( &itd, 0, sizeof itd ); for ( ;; ) { sd = socket( ISD.srv->ai_family, ISD.srv->ai_socktype, ISD.srv->ai_protocol ); if ( sd == -1 ) { syslog(LOG_ERR, "%s: socket() failed: %s -- exiting", fn, strerror(errno) ); exit( 1 ); } if ( connect( sd, (struct sockaddr *)ISD.srv->ai_addr, ISD.srv->ai_addrlen ) == -1 ) { syslog(LOG_ERR, "%s: connect() to IMAP server on socket [%d] failed: %s -- retrying", fn, sd, strerror(errno)); close( sd ); sleep( 15 ); /* IMAP server may not be started yet. */ } else { break; /* Success */ } } memset( &conn, 0, sizeof ( ICD_Struct ) ); itd.conn = &conn; itd.conn->sd = sd; /* * The first thing we get back from the server should be the * banner string. */ BytesRead = IMAP_Line_Read( &itd ); if ( BytesRead == -1 ) { syslog( LOG_ERR, "%s: Error reading banner line from server on initial connection: %s -- Exiting.", fn, strerror( errno ) ); close( itd.conn->sd ); exit( 1 ); } if ( itd.LiteralBytesRemaining ) { syslog( LOG_ERR, "%s: Server sent unexpected literal specifier in banner response -- Exiting.", fn ); exit( 1 ); } BannerLen = ParseBannerAndCapability( Banner, sizeof Banner - 1, itd.ReadBuf, BytesRead, 0 ); /* * See if the string we got back starts with "* OK" by comparing the * first 4 characters of the buffer. */ if ( strncasecmp( Banner, IMAP_UNTAGGED_OK, strlen(IMAP_UNTAGGED_OK)) ) { syslog(LOG_ERR, "%s: Unexpected response from IMAP server on initial connection: %s -- Exiting.", fn, Banner); close( itd.conn->sd ); exit( 1 ); } /* Now we send a CAPABILITY command to the server. */ if ( IMAP_Write( itd.conn, "1 CAPABILITY\r\n", strlen("1 CAPABILITY\r\n") ) == -1 ) { syslog(LOG_ERR, "%s: Unable to send capability command to server: %s -- exiting.", fn, strerror(errno) ); close( itd.conn->sd ); exit( 1 ); } /* * From RFC2060: * The server MUST send a single untagged * CAPABILITY response with "IMAP4rev1" as one of the listed * capabilities before the (tagged) OK response. * * The means we should read exactly 2 lines of data back from the server. * The first will be the untagged capability line. * The second will be the OK response with the tag in it. */ BytesRead = IMAP_Line_Read( &itd ); if ( BytesRead == -1 ) { syslog( LOG_ERR, "%s: Failed to read capability response from server: %s -- exiting.", fn, strerror( errno ) ); close( itd.conn->sd ); exit( 1 ); } if ( itd.LiteralBytesRemaining ) { syslog( LOG_ERR, "%s: Server sent unexpected literal specifier in CAPABILITY response -- Exiting.", fn ); close( itd.conn->sd ); exit ( 1 ); } CapabilityLen = ParseBannerAndCapability( Capability, sizeof Capability - 1, itd.ReadBuf, BytesRead, 1 ); /* Now read the tagged response and make sure it's OK */ BytesRead = IMAP_Line_Read( &itd ); if ( BytesRead == -1 ) { syslog( LOG_ERR, "%s: Failed to read capability response from server: %s -- exiting.", fn, strerror( errno ) ); close( itd.conn->sd ); exit( 1 ); } if ( itd.LiteralBytesRemaining ) { syslog( LOG_ERR, "%s: Server sent unexpected literal specifier in tagged CAPABILITY response -- exiting.", fn ); exit( 1 ); } if ( strncasecmp( itd.ReadBuf, IMAP_TAGGED_OK, strlen(IMAP_TAGGED_OK) ) ) { syslog(LOG_ERR, "%s: Received non-OK tagged reponse from IMAP server on CAPABILITY command -- exiting.", fn ); close( itd.conn->sd ); exit( 1 ); } /* * If we're using it, attempt STARTTLS before doing one more * CAPABILITY so our capability string is accurate */ if ( PC_Struct.login_disabled || PC_Struct.force_tls ) { #if HAVE_LIBSSL if ( PC_Struct.support_starttls != STARTTLS_NOT_SUPPORTED ) { if ( Attempt_STARTTLS( &itd ) != 0 ) { syslog(LOG_ERR, "%s: STARTTLS failed for CAPABILITY check -- exiting.", fn ); close( itd.conn->sd ); exit( 1 ); } else { /* * STARTTLS was successful, so we can proceed * to get the new CAPABILITY list - first, * send a CAPABILITY command to the server. */ if ( IMAP_Write( itd.conn, "1 CAPABILITY\r\n", strlen("1 CAPABILITY\r\n") ) == -1 ) { syslog(LOG_ERR, "%s: Unable to send capability command to server: %s -- exiting.", fn, strerror(errno) ); close( itd.conn->sd ); exit( 1 ); } /* * From RFC2060: * The server MUST send a single untagged * CAPABILITY response with "IMAP4rev1" as one of the listed * capabilities before the (tagged) OK response. * * The means we should read exactly 2 lines of data back from the server. * The first will be the untagged capability line. * The second will be the OK response with the tag in it. */ BytesRead = IMAP_Line_Read( &itd ); if ( BytesRead == -1 ) { syslog( LOG_ERR, "%s: Failed to read capability response from server: %s -- exiting.", fn, strerror( errno ) ); close( itd.conn->sd ); exit( 1 ); } if ( itd.LiteralBytesRemaining ) { syslog( LOG_ERR, "%s: Server sent unexpected literal specifier in CAPABILITY response -- Exiting.", fn ); close( itd.conn->sd ); exit ( 1 ); } CapabilityLen = ParseBannerAndCapability( Capability, sizeof Capability - 1, itd.ReadBuf, BytesRead, 1 ); /* Now read the tagged response and make sure it's OK */ BytesRead = IMAP_Line_Read( &itd ); if ( BytesRead == -1 ) { syslog( LOG_ERR, "%s: Failed to read capability response from server: %s -- exiting.", fn, strerror( errno ) ); close( itd.conn->sd ); exit( 1 ); } if ( itd.LiteralBytesRemaining ) { syslog( LOG_ERR, "%s: Server sent unexpected literal specifier in tagged CAPABILITY response -- exiting.", fn ); exit( 1 ); } if ( strncasecmp( itd.ReadBuf, IMAP_TAGGED_OK, strlen(IMAP_TAGGED_OK) ) ) { syslog(LOG_ERR, "%s: Received non-OK tagged reponse from imap server on CAPABILITY command -- exiting.", fn ); close( itd.conn->sd ); exit( 1 ); } } } else #endif /* HAVE_LIBSSL */ { /* We're screwed! We won't be able to login without SASL */ syslog(LOG_ERR, "%s: IMAP server has LOGINDISABLED and we can't do STARTTLS. Exiting.", fn); close( itd.conn->sd ); exit( 1 ); } } else { //syslog( LOG_ERR, "%s: Not trying STARTTLS and second CAPABILITY.", fn ); } /* Be nice and logout */ if ( IMAP_Write( itd.conn, "2 LOGOUT\r\n", strlen("2 LOGOUT\r\n") ) == -1 ) { syslog(LOG_WARNING, "%s: IMAP_Write() failed on LOGOUT: %s -- Ignoring", fn, strerror(errno) ); close( itd.conn->sd ); return; } /* read the final OK logout */ BytesRead = IMAP_Line_Read( &itd ); if ( BytesRead == -1 ) { syslog(LOG_WARNING, "%s: IMAP_Line_Read() failed on LOGOUT -- Ignoring", fn ); } if ( itd.LiteralBytesRemaining ) { syslog( LOG_WARNING, "%s: Server sent unexpected literal specifier in LOGOUT response -- Ignoring", fn ); } close( itd.conn->sd ); return; }