Logo Search packages:      
Sourcecode: mailfilter version File versions  Download package

PopAccount.cc

// PopAccount.cc - source file for the mailfilter program
// Copyright (c) 2000 - 2004  Andreas Bauer <baueran@in.tum.de>
//
// 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 2 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, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
// USA.

#include <string>
#include <vector>
#include <strstream>
extern "C" {
#include <sys/time.h>
#include <sys/types.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <stdio.h>
#include <ctype.h>
#include <regex.h>
#include <fcntl.h>
#include <string.h>
#include "md5.h"
}
#include "Account.hh"
#include "PopAccount.hh"
#include "Header.hh"
#include "Preferences.hh"
#include "Feedback.hh"
#include "mailfilter.hh"
#include "SocketConnection.hh"
#include "Checker.hh"
#include "RFC822.hh"
#include "i18n.hh"

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

using namespace std;
using namespace msg;

namespace acc {
  
  PopAccount::PopAccount(const string& nserver,
                   const string& nuser,
                   const string& npassword,
                   int nprotocol,
                   int nport,
                   pref::Preferences* newPrefs,
                   fb::Feedback* newFeedback) : Account(newFeedback) {
    server = nserver;
    user = nuser;
    pass = npassword;
    protocol = nprotocol;
    port = nport;
    prefs = newPrefs;
  }
  
  
  PopAccount::~PopAccount() {
  }
  
  
  bool PopAccount::successful(const string& result) {
    if (result.length()) {
      if (result.find_first_of("+") == 0) {
      report->message(6, (string)PACKAGE_NAME + (string)": Server responded: " + result.c_str());
      return true;
      }
      else
      report->message(6, (string)PACKAGE_NAME + (string)": Server issued error: " + result.c_str());
    }
    else
      report->message(6, (string)PACKAGE_NAME + (string)": No response from server received.\n");
    
    return false;
  }
  

  // Checks a pop account for spam.  Returns 0 if no messages were on the server,
  // the number of scanned messages if there are any, and a predefined error value
  // if something went from, e.g. a communication and network error.
  int PopAccount::check(void) {
    rfc::RFC822 rfc822;
    string result;
    char cmd[CMD_LENGTH], status[MAX_BYTES];
    strstream msgSize, sizeLimit, totScore;
    int messages = 0,
      size = 0,
      connectError = 0,
      loginError = 0,
      checkError = 0,
      returnValue = 0,
      tmpScore = 0;
    check::Checker checker(prefs);

    try {
      // Establish server connection and login
      if ( (connectError = Account::connectHost(server, port, prefs->getTimeOut())) == 0 ) {
      if ( (loginError = loginHost()) == 0 ) {
        
        // Determine number of pending messages -- STAT
        if ( (Account::sendHost("STAT\r\n") > 0) && 
             ((result = Account::receiveHost(SINGLE_LINE)).length() > 0) && 
             (successful(result)) ) {
          returnValue = messages = atoi(getWord(result, 1).c_str());
          snprintf(status, sizeof(status), _("%s: Examining %d message(s).\n"), PACKAGE_NAME, messages);
          report->message(3, status);
        }
        else {
          Account::disconnectHost();
          return CMD_STAT_FAILED;
        }
        
        // Now scan message by message until we're through with the pile of pending messages
        for (int i = 0; i < messages; i++) {
          checkError = 0;
          
#ifdef DEBUG
          cout << "Sending LIST" << (i+1) << endl;
#endif
          // Get the message size -- LIST
          snprintf(cmd, sizeof(cmd), "LIST %d\r\n", i + 1);
          if ( (Account::sendHost(cmd) > 0) && ((result = Account::receiveHost(SINGLE_LINE)).length() > 0) && successful(result) )
            size = atoi(getWord(result, 2).c_str());
          else {
            Account::disconnectHost();
            return CMD_LIST_FAILED;
          }
          
#ifdef DEBUG
          cout << "Sending TOP " << (i+1) << " 0" << endl;
#endif
          // Receive entire message header -- command (PREVIEW_COMMAND) is defined in config.h
          snprintf(cmd, sizeof(cmd), PREVIEW_COMMAND, i + 1);
          
          int sendStatus = Account::sendHost(cmd);
          result = Account::receiveHost(MULTI_LINE);

          if ( sendStatus > 0 && result.length() > 0 && successful(result) ) {
            Header curMessage;

            // Clean previous strings and filters from buffer
            checker.cleanMatchingFilters();
            checker.cleanMatchingStrings();
          
            // Set current total SPAM score to 0
            totScore.rdbuf()->freeze(0); totScore.seekp(0); tmpScore = 0;

            // Show the header files if requested explicitly by SHOW_HEADERS.
            // "VERBOSE = 6" automatically prints headers, as the entire server response of "TOP x 0"
            // is being shown in a call of successful(result);
            if ( (prefs->getShowHeaders())  &&  (prefs->getVerboseLevel() < 6) )
            report->message(0, result);
          
            curMessage = rfc822.extract(result, i+1, size, prefs->isNormal());

            if ( checker.duplicates(&curMessage, &msgIDs) == 0 && removeMessage(i+1) == 0 ) {
            report->message(2, (string)PACKAGE_NAME + (string)": Deleted " + curMessage.sender().c_str() + (string)": " +
                        curMessage.subject().c_str() + (string)", " + curMessage.date().c_str() + (string)"." +
                        (string)" [Message was duplicate]\n");
            continue;
            }
          
            if ( checker.lineLength(&curMessage) == 0 && removeMessage(i+1) == 0 ) {
            report->message(2, (string)PACKAGE_NAME + (string)": Deleted " + curMessage.sender().c_str() + (string)": " +
                        curMessage.subject().c_str() + (string)", " + curMessage.date().c_str() + (string)"." +
                        (string)" [Maximum header-line-length in '" + checker.getMatchingStrings()->front() + (string)"' exceeded]\n");
            continue;
            }

            checkError = checker.friends(&curMessage);
            if ( checkError == 1 ) {
            // A friend; leave message alone and move on to the next one
            report->message(5, (string)PACKAGE_NAME + (string)": Approved " + curMessage.sender().c_str() + (string)": " +
                        curMessage.subject().c_str() + (string)", " + curMessage.date().c_str() + (string)"." +
                        (string)" [Applied rule: '" + checker.getMatchingFilters()->front() + (string)"' to '" +
                        checker.getMatchingStrings()->front() + (string)"']\n");
            continue;
            }
            else if ( checkError == 0 && removeMessage(i+1) == 0 ) {
            msgSize << curMessage.size() << ends; sizeLimit << prefs->getMaxsizeFriends() << ends;
            report->message(2, (string)PACKAGE_NAME + (string)": Deleted " + curMessage.sender().c_str() + (string)": " +
                        curMessage.subject().c_str() + (string)", " + curMessage.date().c_str() + (string)"." +
                        (string)" [Size limit MAXSIZE_ALLOW exceeded, " +
                        msgSize.str() + (string)"/" + sizeLimit.str() + (string)"]\n");
            msgSize.rdbuf()->freeze(0); msgSize.seekp(0);
            sizeLimit.rdbuf()->freeze(0); sizeLimit.seekp(0);
            continue;
            }
                
            if ( checker.size(&curMessage) == 0 && removeMessage(i+1) == 0 ) {
            msgSize << curMessage.size() << ends; sizeLimit << prefs->getMaxsize() << ends;
            report->message(2, (string)PACKAGE_NAME + (string)": Deleted " + curMessage.sender().c_str() + (string)": " +
                        curMessage.subject().c_str() + (string)", " + curMessage.date().c_str() + (string)"." +
                        (string)" [Size limit MAXSIZE_DENY exceeded, " +
                        msgSize.str() + (string)"/" + sizeLimit.str() + (string)"]\n");
            msgSize.rdbuf()->freeze(0); msgSize.seekp(0);
            sizeLimit.rdbuf()->freeze(0); sizeLimit.seekp(0);
            continue;
            }
          
            checkError = checker.filters(&curMessage);
            if ( checkError == 0 && removeMessage(i+1) == 0 ) {        // Unmodified string matched
            if (prefs->getVerboseLevel() < 5) {
              report->message(2, (string)PACKAGE_NAME + (string)": Deleted " + curMessage.sender().c_str() + (string)": " +
                          curMessage.subject().c_str() + (string)", " + curMessage.date().c_str() + (string)"." +
                          (string)" [Applied filter: '" + checker.getMatchingFilters()->front() + (string)"']\n");
            }
            else {
              report->message(5, (string)PACKAGE_NAME + (string)": Deleted " + curMessage.sender().c_str() + (string)": " +
                          curMessage.subject().c_str() + (string)", " + curMessage.date().c_str() + (string)"." +
                          (string)" [Applied filter: '" + checker.getMatchingFilters()->front() + (string)"' to '" +
                          checker.getMatchingStrings()->front() + (string)"']\n");
            }
            continue;
            }
            else if ( checkError == 1 && removeMessage(i+1) == 0 ) {   // Normalised string matched
            if (prefs->getVerboseLevel() < 5) {
              report->message(2, (string)PACKAGE_NAME + (string)": Deleted " + curMessage.sender().c_str() + (string)": " +
                          curMessage.subject().c_str() + (string)", " + curMessage.date().c_str() + (string)"." +
                          (string)" [Applied filter: '" + checker.getMatchingFilters()->front() + (string)"']\n");
            }
            else {
              report->message(5, (string)PACKAGE_NAME + (string)": Deleted " + curMessage.sender().c_str() + (string)": " +
                          curMessage.subject().c_str() + (string)", " + curMessage.date().c_str() + (string)"." +
                          (string)" [Applied filter: '" + checker.getMatchingFilters()->front() + (string)"' to '" +
                          checker.getMatchingStrings()->front() + (string)"']\n");
            }
            continue;
            }
          
            if ( checker.negFilters(&curMessage) == 0 && removeMessage(i+1) == 0 ) {
            report->message(2, (string)PACKAGE_NAME + (string)": Deleted " + curMessage.sender().c_str() + (string)": " +
                        curMessage.subject().c_str() + (string)", " + curMessage.date().c_str() + (string)"." +
                        (string)" [Applied filter: '<>" + checker.getMatchingFilters()->front() + (string)"']\n");
            continue;
            }

            tmpScore = checker.scores(&curMessage) + checker.negScores(&curMessage) + checker.maxSizeScore(&curMessage);
            totScore << tmpScore << ends;
            if (tmpScore >= prefs->getHighscore() && removeMessage(i+1) == 0) {
            report->message(2, (string)PACKAGE_NAME + (string)": Deleted " + curMessage.sender().c_str() + (string)": " +
                        curMessage.subject().c_str() + (string)", " + curMessage.date().c_str() + (string)"." +
                        (string)" [Score: " + totScore.str() + (string)"]\n");
            }
          }
          else {
            // TOP has failed (probably an error in the POP server)
            // but we might as well log out to delete the spam detected
            // so far
            logoutHost();
            Account::disconnectHost();
            return CMD_TOP_FAILED;
          }
        }
      }
      else
        returnValue = loginError;
      }
      else
      return connectError;
    }
    catch(...) {
      throw;
    }

    // Clean up and return status
    logoutHost();
    Account::disconnectHost();
    return returnValue;
  }

  
  // Login to pop server
  int PopAccount::loginHost(void) {
    char command[CMD_LENGTH], md5hash[33];
    string result = "", greet = "";
    char *ts,*p;
    
    try {
      // Check whether we received the server's greeting message,
      // i.e. grab any string that initially comes from the server
      // In APOP's case it would be part of the encryption key
      if ( successful(greet = Account::receiveHost(SINGLE_LINE)) ) {
      
      if (protocol == APOP) {
        // Extract the timestamp from the saved input
        ts = index(&greet[0], '<');
        p = index(ts, '>');
        p[1] = '\0';
        
        // Calculate the hash
        snprintf(command, sizeof(command), "%s", pass.c_str());
        getHash(md5hash, ts, command);
        
        // Send the APOP command as in the username/password code below
        snprintf(command, sizeof(command), "APOP %s %s\r\n", user.c_str(), md5hash);
        
        // If successful return 0 from here
          if (Account::sendHost(command) > 0) { 
            if (!successful(Account::receiveHost(SINGLE_LINE)))
              return AUTHENTICATION_FAILURE;
          }
        else
          return NO_REPLY_FAILURE;
          return 0;
      }
      
      // Send username
      snprintf(command, sizeof(command), "USER %s\r\n", user.c_str());
      
      if (Account::sendHost(command) > 0) {
        if (!successful(Account::receiveHost(SINGLE_LINE)))
          return AUTHENTICATION_FAILURE;
      }
      else
        return NO_REPLY_FAILURE;
      
      // Send password
      snprintf(command, sizeof(command), "PASS %s\r\n", pass.c_str());
      
      if (Account::sendHost(command) > 0) { 
        if (!successful(Account::receiveHost(SINGLE_LINE)))
          return AUTHENTICATION_FAILURE;
      }
      else
        return NO_REPLY_FAILURE;
      
      return 0;
      }
      else
      return NO_REPLY_FAILURE;
    }
    catch (...) {
      throw;
    }
  }
  
  
  // Disconnects from the server
  bool PopAccount::logoutHost(void) {
    if (Account::sendHost("QUIT\r\n") > 0)
      return successful(Account::receiveHost(SINGLE_LINE));
    else
      return false;
  }  
  

  // Deletes a message on the POP3 server
  int PopAccount::removeMessage(int mess) {
    char cmd[CMD_LENGTH];
    int error = 0;

    // If we're in test mode, we only simulate the deletion of e-mails
    if (prefs->getTestMode()) {
      report->message(6, (string)PACKAGE_NAME + (string)": Sending simulated DELE command\n");
      return 0;
    }
    else {
      snprintf(cmd, sizeof(cmd), "DELE %d\r\n", mess);
      
      error = Account::sendHost(cmd);
      if (error < 0) {                                   // Server did not accept the delete command. Just aboard the whole operation!
      report->message(6, (string)PACKAGE_NAME + (string)": Failed to send DELE command to the server.\n");
      return -1;
      }
      else if ( (error > 0) && (successful(Account::receiveHost(SINGLE_LINE))) )
      return 0;                                        // Message successfully deleted. Return with status OK!
      else {                                   
      if (Account::sendHost("RSET\r\n") > 0) {         // Server tried to delete, but an error occured. Try to reset account and then aboard operation!
        if (successful(Account::receiveHost(SINGLE_LINE)))
          report->message(6, (string)PACKAGE_NAME + (string)": Reset of mail box was successful.\n");
        else
          report->message(6, (string)PACKAGE_NAME + (string)": Reset of mail box was not successful.\n");
      }
      else
        report->message(6, (string)PACKAGE_NAME + (string)": Failed to send RSET command to the server.\n");
      
      return -1;
      }
    }
  }
  
  
  void PopAccount::getHash(char* hash, char* stamp, char* pass) {
    MD5_CTX mdContext;
    unsigned char digest[16];
    
    MD5Init(&mdContext);
    MD5Update(&mdContext, (unsigned char*)stamp, strlen(stamp));
    MD5Update(&mdContext, (unsigned char*)pass, strlen(pass));
    MD5Final(digest, &mdContext);
   
    for (unsigned int i = 0; i < sizeof(digest); i++)
      sprintf(hash + 2 * i, "%02x", digest[i]);
  }


  // Returns the n-th word in a string,
  // e.g. getWord("My name is Harry", 1) would return "name".
  string PopAccount::getWord(const string& word, int n) {
    int curSpace = 0, nextSpace = 0, wordEnd = 0;

    // Find beginning of word
    for (int i = 0; i < n; i++) {
      nextSpace = word.find(' ', curSpace);
      curSpace = nextSpace + 1;
    }
    
    // Either copy everything up to the next blank character, or up to the end of the line (-1 for the cr)
    if ( (wordEnd = word.find(' ', curSpace)) == -1 )
      wordEnd = word.length() - 1;
    
    // Copy result and unfreeze memory
    strstream myWordStr; myWordStr << word.substr(curSpace, wordEnd) << ends;
    string myWord = myWordStr.str();
    myWordStr.freeze(0);

    return myWord;
  }
  

}

Generated by  Doxygen 1.6.0   Back to index