Beispiel #1
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 );
}
Beispiel #2
0
/*++
 * Function:	Get_Server_sd
 *
 * Purpose:	When a client login attempt is made, fetch a usable server
 *              socket descriptor.  This means that either we reuse an
 *              existing sd, 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)
 *
 * Returns:	int sd on success
 *              -1 on failure
 *
 * Authors:	dgm
 *
 *--
 */
extern int Get_Server_sd( char *Username,
                          char *Password,
                          const char *ClientAddr )
{
    char *fn = "Get_Server_sd()";
    unsigned int HashIndex;
    ICC_Struct *HashEntry = NULL;
    char SendBuf[BUFSIZE];
    unsigned int BufLen = BUFSIZE - 1;
    char md5pw[16];
    char *tokenptr;
    char *endptr;
    char *last;
    ICC_Struct *ICC_Active;
    ICC_Struct *ICC_tptr;
    ITD_Struct Server;
    int rc;
    unsigned int Expiration;

    Expiration = PC_Struct.cache_expiration_time;
    memset( &Server, 0, sizeof Server );

    /* need to md5 the passwd regardless, so do that now */
    md5_calc( md5pw, Password, strlen( Password ) );

    /* 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 ) )
            {
                /* the passwords don't match.  Shake this guy. */
                UnLockMutex( &mp );
                syslog(LOG_INFO, "LOGIN: '******' (%s) failed: password incorrect", Username, ClientAddr );
                return( -1 );
            }

            /*
             * 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_sd, F_SETFL,
                   fcntl( ICC_Active->server_sd, F_GETFL, 0) | O_NONBLOCK );

            while ( ( rc = recv( ICC_Active->server_sd, Server.ReadBuf,
                                 sizeof Server.ReadBuf, 0 ) ) > 0 );

            if ( !rc )
            {
                syslog(LOG_NOTICE, "%s: Unable to reuse server sd [%d] for user '%s' (%s).  Connection closed by server.", fn, ICC_Active->server_sd, Username, ClientAddr );
                ICC_Active->logouttime = 1;
                continue;
            }

            if ( errno != EWOULDBLOCK )
            {
                syslog(LOG_NOTICE, "%s: Unable to reuse server sd [%d] for user '%s' (%s). recv() error: %s", fn, ICC_Active->server_sd, Username, ClientAddr, strerror( errno ) );
                ICC_Active->logouttime = 1;
                continue;
            }


            fcntl( ICC_Active->server_sd, F_SETFL,
                   fcntl( ICC_Active->server_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) on existing sd [%d]", Username, ClientAddr, ICC_Active->server_sd );
            return( ICC_Active->server_sd );
        }
    }

    UnLockMutex( &mp );

    /*
     * We don't have an active connection for this user.
     * Open a connection to the IMAP server so we can attempt to login
     */
    Server.sd = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
    if ( Server.sd == -1 )
    {
        syslog(LOG_INFO, "LOGIN: '******' (%s) failed: Unable to open server socket: %s",
               Username, ClientAddr, strerror( errno ) );
        return( -1 );
    }

    if ( connect( Server.sd, (struct sockaddr *)&ISD.srv,
                  sizeof(ISD.srv) ) == -1 )
    {
        syslog(LOG_INFO, "LOGIN: '******' (%s) failed: Unable to connect to IMAP server: %s", Username, ClientAddr, strerror( errno ) );
        close( Server.sd );
        return( -1 );
    }


    /* Read & throw away the banner line from the server */

    if ( IMAP_Line_Read( &Server ) == -1 )
    {
        syslog(LOG_INFO, "LOGIN: '******' (%s) failed: No banner line received from IMAP server",
               Username, ClientAddr );
        close( Server.sd );
        return( -1 );
    }

    /*
     * Send the login command off to the IMAP server.
     */
    snprintf( SendBuf, BufLen, "A0001 LOGIN %s %s\r\n",
              Username, Password );

    if ( send( Server.sd, SendBuf, strlen(SendBuf), 0 ) == -1 )
    {
        syslog(LOG_INFO, "LOGIN: '******' (%s) failed: send() failed attempting to send LOGIN command to IMAP server: %s", Username, ClientAddr, strerror( errno ) );
        close( Server.sd );
        return( -1 );
    }

    /*
     * Read the server response
     */
    if ( ( rc = IMAP_Line_Read( &Server ) ) == -1 )
    {
        syslog(LOG_INFO, "LOGIN: '******' (%s) failed: No response from IMAP server after sending LOGIN command", Username, ClientAddr );
        close( Server.sd );
        return( -1 );
    }


    /*
     * 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) failed: server response to LOGIN command contained no tokens.", Username, ClientAddr );
        close( Server.sd );
        return( -1 );
    }

    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) failed: server response to LOGIN command contained non-matching tag.", Username, ClientAddr );
        close( Server.sd );
        return( -1 );
    }


    /*
     * 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) failed: Malformed server response to LOGIN command", Username, ClientAddr );
        close( Server.sd );
        return( -1 );
    }

    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) failed: non-OK server response to LOGIN command", Username, ClientAddr );
        close( Server.sd );
        return( -1 );
    }

    /*
     * 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_sd = Server.sd;

            UnLockMutex( &mp );

            IMAPCount->InUseServerConnections++;
            IMAPCount->TotalServerConnectionsCreated++;

            if ( IMAPCount->InUseServerConnections >
                    IMAPCount->PeakInUseServerConnections )
                IMAPCount->PeakInUseServerConnections = IMAPCount->InUseServerConnections;
            syslog(LOG_INFO, "LOGIN: '******' (%s) on new sd [%d]", Username, ClientAddr, Server.sd );
            return( Server.sd );
        }

        /*
         * 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) failed: Out of free ICC structs.", Username, ClientAddr );
            close( Server.sd );
            return( -1 );
        }

        ICC_Recycle( Expiration );

    }

}