/* milter-callback - a sendmail milter to perform variuos sender verifications
 Copyright (C) 2008  Eugene M. Zheganin, milter-callback@norma.perm.ru
 
 This program 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 3 of the License, or
 (at your option) any later version.

 This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
*/


#ifdef WITH_SPF

#include <milter-callback.h>
#include <commonprocs.h>
#include <mcspf.h>

extern int logLevel;
extern int mcDebug;
extern SPF_server_t *SPFServer;
extern int SPFPoolLen;
extern struct SPFHeap SPFPool;

int                                                                                                                     
getSPFSrv (){                                                                                                           
    int res, i, rndSrv;
    char tmpLog[MAXLOGLINE];

    for (i = 0; i < SPFPoolLen; i++) {
        snprintf(tmpLog, MAXLOGLINE, "Trying to aquire a lock on SPF server %d.", i);
        logNow(&tmpLog[0], 3, 0);
        res = pthread_mutex_trylock(SPFPool.SPFMutexArr[i]);
        if (res == 0) {
            snprintf(tmpLog, MAXLOGLINE, "SPF server %d successfully aquired.", i);
            logNow(&tmpLog[0], 3, 0);
            return i;
        }                                                                                                               
    }
    /* all servers busy */
    /* reporting that */
    snprintf(tmpLog, MAXLOGLINE, "All SPF servers busy, consider increasing pool length from %d.", SPFPoolLen);
    logNow(&tmpLog[0], 0, 0);
    /* getting random server and breaking until it's available */
    /* rndSrv = random(SPFPoolLen - 1); */
    /* random seems to be a complete rocket science so let's just take last connection */
    rndSrv = SPFPoolLen - 1;
    snprintf(tmpLog, MAXLOGLINE, "All SPF servers busy, trying to aquire not so random server %d.", rndSrv);
    logNow(&tmpLog[0], 0, 0);
    res = pthread_mutex_lock(SPFPool.SPFMutexArr[rndSrv]);
    if (res == 0) {
        snprintf(tmpLog, MAXLOGLINE, "Random server %d successfully aquired.", rndSrv);
        logNow(&tmpLog[0], 0, 0);
        return rndSrv;
    } else {
        snprintf(tmpLog, MAXLOGLINE, "Failed to aquire random server %d, this is just wrong, aborting.", rndSrv);
        logNow(&tmpLog[0], 0, 0);
	exit(EXIT_FAILURE);
    }
}

void
unlockSPFSrv(int SPFSrvId) {
    char tmpLog[MAXLOGLINE];                                                                                            

    snprintf(tmpLog, MAXLOGLINE, "Trying to unlock mutex on SPF pool element %d.", SPFSrvId);
    logNow(&tmpLog[0], 3, 0);
    if (pthread_mutex_unlock(SPFPool.SPFMutexArr[SPFSrvId]) == 0) {
        snprintf(tmpLog, MAXLOGLINE, "Successfully unlocked mutex on SPF pool element %d.", SPFSrvId);
        logNow(&tmpLog[0], 3, 0);
    } else {
        snprintf(tmpLog, MAXLOGLINE, "Failure unlocking mutex on SPF pool element %d, this is wrong, aborting.", SPFSrvId);
        logNow(&tmpLog[0], 0, 0);
    }
}


SPF_result_t
goWithSPF (struct mlfiPriv *privData, char *envFrom) {
    SPF_request_t *SPFRequest;
    SPF_response_t *SPFResponse;
    SPF_result_t SPFResult;
    int errorCount;
    char tmpLog[MAXLOGLINE];
    int i;
    
    errorCount = 0;
    
    logNow("Entered goWithSPF().", 3, privData);
    snprintf(tmpLog, MAXLOGLINE, "Got %s as envFrom.", envFrom);
    logNow(&tmpLog[0], 3, privData);
    
    /* if we got an empty HELO/EHLO we return SPF_RESULT_SOFTFAIL */
    if (privData -> mcfi_helofrom == NULL) {
	logNow("Got empty HELO, skipping SPF check.", 0, privData);
	return(SPF_RESULT_SOFTFAIL);
    }

    i = getSPFSrv();
    SPFServer = SPFPool.SPFServerArr[i];
    
    logNow("Creating SPF request.", 3, privData);    
    SPFRequest = SPF_request_new(SPFServer);
    
    snprintf(tmpLog, MAXLOGLINE, "Setting SPF IP to %s.", privData -> relayPresIP);
    logNow(&tmpLog[0], 3, privData);
    if (SPF_request_set_ipv4_str(SPFRequest, privData -> relayPresIP)){
        logNow("Error setting SPF IP.", 0, privData);
	errorCount++;
    }
    snprintf(tmpLog, MAXLOGLINE, "Setting SPF HELO to %s.", privData -> mcfi_helofrom);
    logNow(&tmpLog[0], 3, privData);
    if (SPF_request_set_helo_dom(SPFRequest, privData -> mcfi_helofrom)){
        logNow("Error setting SPF HELO.", 0, privData);
	errorCount++;
    }
    snprintf(tmpLog, MAXLOGLINE, "Setting SPF envelope-from to %s.", envFrom);
    logNow(&tmpLog[0], 3, privData);
    if (SPF_request_set_env_from(SPFRequest, envFrom)){
        logNow("Error setting SPF envelope-from.", 0, privData);
	errorCount++;
    }
    logNow("Trying to get SPF response.", 3, privData);
    SPF_request_query_mailfrom(SPFRequest, &SPFResponse);
    SPFResult = SPF_response_result(SPFResponse);
    
    snprintf(tmpLog, MAXLOGLINE, "Got SPF result: %s:%s,%s,%s.", 
	SPF_strresult(SPF_response_result(SPFResponse)),
	SPF_response_get_smtp_comment(SPFResponse),
	SPF_response_get_header_comment(SPFResponse),
	SPF_response_get_received_spf(SPFResponse)
    );
    logNow(&tmpLog[0], 2, privData);
    
    if (errorCount == 0) {
        logNow("Trying to free() SPFResponse.", 3, privData);
	SPF_response_free(SPFResponse);
        logNow("Trying to free() SPFRequest.", 3, privData);
	SPF_request_free(SPFRequest);
    }
    unlockSPFSrv(i);
    return(SPFResult);
}

void
destroySPFServer (struct mlfiPriv *privData) {
    logNow("Freeing SPF server.", 3, privData);
    SPF_server_free(SPFServer);    
}

#endif
