Main Page   Namespace List   Class Hierarchy   Compound List   File List   Compound Members   File Members  

PopAccount.cc

Go to the documentation of this file.
00001 /* PopAccount.cc - source file for the mailfilter program
00002  * Copyright (c) 2000  Andreas Bauer <baueran@in.tum.de>
00003  *
00004  * This program is free software; you can redistribute it and/or modify
00005  * it under the terms of the GNU General Public License as published by
00006  * the Free Software Foundation; either version 2 of the License, or
00007  * (at your option) any later version.
00008  *
00009  * This program is distributed in the hope that it will be useful,
00010  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012  * GNU General Public License for more details.
00013  *
00014  * You should have received a copy of the GNU General Public License
00015  * along with this program; if not, write to the Free Software
00016  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
00017  * USA.
00018  */
00019 
00020 #include <strstream>
00021 #include <fstream>
00022 #include <string>
00023 #include <vector>
00024 #include <sys/time.h>
00025 #include <stdlib.h>
00026 #include <netinet/in.h>
00027 #include <unistd.h>
00028 #include <arpa/inet.h>
00029 #include <netdb.h>
00030 #include <sys/types.h>
00031 #include <sys/socket.h>
00032 #include <sys/stat.h>
00033 #include <stdio.h>
00034 #include <ctype.h>
00035 #include <regex.h>
00036 #include <fcntl.h>
00037 #include <string.h>
00038 #include "Account.hh"
00039 #include "PopAccount.hh"
00040 #include "Preferences.hh"
00041 #include "Feedback.hh"
00042 #include "mailfilter.hh"
00043 #include "config.h"
00044 
00045 
00046 using namespace std;
00047 using namespace acc;
00048 
00049 namespace pop {
00050 
00051   PopAccount::PopAccount(string server, string user, string password, int port, pref::Preferences* newPrefs) {
00052     this->server = server;
00053     this->user = user;
00054     this->pass = password;
00055     this->port = port;
00056     prefs = newPrefs;
00057     mySocket = 0;
00058     report = new fb::Feedback(prefs->getLogfile(), prefs->getMode());
00059   }
00060   
00061   
00062   PopAccount::~PopAccount() {
00063     // No need to delete prefs, as we only point to the prefs object created in mailfilter.cc
00064     // report, on the other hand, was assigned memory to so we better clean up.
00065     delete(report);      
00066   }
00067   
00068   
00069   int PopAccount::check() {
00070     char cmd[32];
00071     string result;
00072     int messages = 0, size = 0, connectError = 0, loginError = 0;
00073     
00074     // Establish server connection and login
00075     if ( ((connectError = connectSocket()) == 0) && ((loginError = login()) == 0) ) {
00076       
00077       // Store message headers
00078       try {
00079     if (sendCommand("STAT\r\n") <= 0) {
00080       logout();
00081       disconnectSocket();
00082       return(CMD_STAT_FAILED);
00083     }
00084     else {
00085       result = receiveResult();
00086       
00087       if (serverResult(result) == FALSE) {
00088         logout();
00089         disconnectSocket();
00090         return(CMD_STAT_FAILED);
00091       }
00092       else {
00093         messages = atoi(getWord(result, 1).c_str());
00094         char status[MAX_BYTES];
00095 #ifdef HAVE_SNPRINTF
00096         snprintf(status, sizeof(status), "%s: Examining %d message(s).\n", PACKAGE, messages);
00097 #else
00098         sprintf(status, "%s: Examining %d message(s).\n", PACKAGE, messages);
00099 #endif
00100         report->message(status);
00101       
00102         // Store the header for each message in the mail box
00103         for (int i = 0; i < messages; i++) {
00104 
00105           // Get message size
00106 #ifdef HAVE_SNPRINTF
00107           snprintf(cmd, sizeof(cmd), "LIST %d\r\n", i + 1);
00108 #else
00109           sprintf(cmd, "LIST %d\r\n", i + 1);
00110 #endif
00111           if (sendCommand(cmd) <= 0) {
00112         logout();
00113         disconnectSocket();
00114         return(CMD_LIST_FAILED);
00115           }
00116           else {
00117         result = receiveResult();
00118 
00119         if (serverResult(result) == FALSE) {
00120           disconnectSocket();
00121           return(CMD_LIST_FAILED);
00122         }
00123         else
00124           size = atoi(getWord(result, 2).c_str());
00125           }
00126 
00127           // Get header
00128 #ifdef HAVE_SNPRINTF
00129           snprintf(cmd, sizeof(cmd), "TOP %d 0\r\n", i + 1);
00130 #else
00131           sprintf(cmd, "TOP %d 0\r\n", i + 1);
00132 #endif
00133           if (sendCommand(cmd) <= 0) {
00134         logout();
00135         disconnectSocket();
00136         return(CMD_TOP_FAILED);
00137           }
00138           else {
00139         // We now receive the e-mail header by assigning the server command to the function.
00140         // This is necessary cause IMail under Windows NT has the annoying behaviour of
00141         // sending the response to TOP in two seperate streams and somehow I have to inform
00142         // receiveResult that we are waiting for the second stream, even though it could
00143         // all be handled via one stream easily.
00144         result = receiveResult(cmd);
00145         
00146         // Store header content, the number of the message and its size in bytes
00147         if (serverResult(result) == TRUE) {
00148           if (Account::storeMessageHeader(result, i + 1, size) < 0) {
00149             logout();
00150             disconnectSocket();
00151             return(MALFORMED_HEADER_FAILURE);
00152           }
00153         }
00154         else {
00155           logout();
00156           disconnectSocket();
00157           return(CMD_TOP_FAILED);
00158         }
00159           }
00160         } // for
00161       } // else
00162     } // else
00163       } // try
00164       catch(...) {
00165     disconnectSocket();
00166     throw;
00167       }
00168             
00169       // Now filter messages and delete spam and unwanted email
00170       vector<Header>::iterator curMessage = Account::headers.begin();
00171       while(curMessage != Account::headers.end()) {
00172     // Check size (and delete)
00173     if ( (prefs->getMaxsize() > 0) && (curMessage->size > prefs->getMaxsize()) ) {
00174       if (deleteMessage(curMessage->number) < 0)
00175         return(CMD_DELE_FAILED);
00176       else
00177         report->message((string)PACKAGE + (string)": Deleted " + curMessage->sender.c_str() + (string)": " + curMessage->subject.c_str() + (string)", " + curMessage->date.c_str() + (string)".\n");
00178     }
00179     // Check filter rules (and delete)
00180     else {
00181       vector<Line>::iterator curLine = curMessage->lines.begin();
00182       
00183       while(curLine != curMessage->lines.end()) {
00184         regex_t compFilter;
00185         vector<string>::iterator curFilter = prefs->getFilters()->begin();
00186 
00187         // regfree(&compFilter);      // Not so sure if on which platforms regfree breaks and which might need it.
00188                                       // I know that Linux with glibc 2.1.1 doesn't particularly like it...
00189 
00190         while (curFilter != prefs->getFilters()->end()) {
00191           string currentLine = curLine->descr.c_str();
00192           currentLine.append(": ");
00193           currentLine.append(curLine->content.c_str());
00194           
00195           if (prefs->getIcase() == FALSE)
00196         regcomp(&compFilter, curFilter->c_str(), REG_ICASE);
00197           else
00198         regcomp(&compFilter, curFilter->c_str(), 0);
00199 
00200           // Delete spam mail here and write report
00201           if (regexec(&compFilter, currentLine.c_str(), 0, NULL, 0) == 0) {
00202         try {
00203           if (deleteMessage(curMessage->number) >= 0) {
00204             report->message((string)PACKAGE + (string)": Deleted " + curMessage->sender.c_str() + (string)": " + curMessage->subject.c_str() + (string)", " + curMessage->date.c_str() + (string)".\n");
00205             curFilter = prefs->getFilters()->end(); // Jump to the end of the filters! No more checking is needed.
00206           }     
00207           else {
00208             logout();
00209             disconnectSocket();
00210             return(CMD_DELE_FAILED);
00211           }
00212         }
00213         catch(...) {
00214           disconnectSocket();
00215           throw;
00216         }
00217           }
00218           // Mail not yet deleted, check if maybe next filter matches
00219           else
00220         curFilter++;
00221         }
00222 
00223         curMessage->lines.erase(curLine);  // instead of curLine++
00224       }
00225     }
00226     
00227     Account::headers.erase(curMessage);    // instead of curMessage++
00228       }
00229 
00230       // Logout and clean up connections
00231       try {
00232     logout();
00233     disconnectSocket();
00234     return 0;
00235       }
00236       catch(...) {
00237     disconnectSocket();
00238     throw;
00239       }
00240     }
00241     else if (loginError != 0) {
00242       disconnectSocket();
00243       return AUTHENTICATION_FAILURE;
00244     }
00245     else if (connectError != 0) {
00246       disconnectSocket();
00247       return connectError;
00248     }
00249     else {
00250       // Unknown error occured, throw exception
00251       disconnectSocket();
00252       throw UnknownError();
00253     }
00254   }
00255   
00256   
00257   // Creates a sockets and creates connection to the server
00258   // Returns 0 on success, or DNS_LOOKUP_FAILURE and SOCKET_CONNECTION_FAILURE on error
00259   int PopAccount::connectSocket() {
00260     struct sockaddr_in socketAddress;
00261     struct hostent *myHost;
00262     
00263     // Create socket and try to connect
00264     if ( (mySocket = socket(PF_INET, SOCK_STREAM, 0)) < 0)
00265       return(SOCKET_CONNECTION_FAILURE);
00266     else {
00267       myHost = gethostbyname(server.c_str());
00268       
00269       if (!myHost)
00270     return(DNS_LOOKUP_FAILURE);
00271       else {
00272     memset( (char*)&socketAddress, 0, sizeof(struct sockaddr_in) );
00273     socketAddress.sin_family = AF_INET;
00274     memcpy( &(socketAddress.sin_addr.s_addr), myHost->h_addr, myHost->h_length);
00275     socketAddress.sin_port=htons(port);
00276     
00277     if (connect(mySocket, (struct sockaddr*)&socketAddress, sizeof(struct sockaddr)) < 0)
00278       return(SOCKET_CONNECTION_FAILURE);
00279       }
00280     }
00281     
00282     return 0;
00283   }
00284   
00285   
00286   // Login to pop server
00287   // Returns 0 on success, -1 otherwise
00288   int PopAccount::login() {
00289     char command[32];
00290     
00291     try {
00292       // Check whether we received the server's greeting message
00293       if (serverResult(receiveResult()) == TRUE) {
00294     
00295     // Send username
00296 #ifdef HAVE_SNPRINTF
00297     snprintf(command, sizeof(command), "USER %s\r\n", user.c_str());
00298 #else
00299     sprintf(command, "USER %s\r\n", user.c_str());
00300 #endif
00301     if (sendCommand(command) > 0) {
00302       if (serverResult(receiveResult()) == FALSE) {
00303         disconnectSocket();
00304         return -1;
00305       }
00306     }
00307     else
00308       return -1;
00309     
00310     // Send password
00311 #ifdef HAVE_SNPRINTF
00312     snprintf(command, sizeof(command), "PASS %s\r\n", pass.c_str());
00313 #else
00314     sprintf(command, "PASS %s\r\n", pass.c_str());
00315 #endif
00316     if (sendCommand(command) > 0) { 
00317       if (serverResult(receiveResult()) == FALSE) {
00318         disconnectSocket();
00319         return -1;
00320       }
00321     }
00322     else
00323       return -1;
00324     
00325     return 0;
00326       }
00327       else
00328     return -1;
00329     }
00330     catch(...) {
00331       throw;
00332     }
00333   }
00334   
00335   
00336   // Closes the socket
00337   int PopAccount::disconnectSocket() {
00338     return (close(mySocket));
00339   }
00340 
00341 
00342   // Disconnects from the server
00343   // Returns 0 on success, -1 otherwise
00344   int PopAccount::logout() {
00345     try {
00346       if ( (sendCommand("QUIT\r\n") > 0) && (serverResult(receiveResult()) == TRUE) )
00347     return 0;
00348       else
00349     return -1;
00350     }
00351     catch(...) {
00352       throw;
00353     }
00354   }
00355   
00356   
00357   // Sends a command to the socket (server)
00358   // Commands have to be < 32, otherwise -1 is returned
00359   // Returns bytes written on success, -1 otherwise
00360   int PopAccount::sendCommand(string command) {
00361     char cmd[32];
00362     int error = 0;
00363 
00364     if (command.length() <= 32) { 
00365       strcpy(cmd, command.c_str());
00366 
00367       if ( (error = write(mySocket, cmd, strlen(cmd))) == -1 )
00368     return -1;
00369       else
00370     return error;
00371     }
00372     else
00373       return -1;
00374   }
00375 
00376 
00377   // Receives the result from a previous command sent to the server.
00378   // Throws exception on communication errors.
00379   // Returns the received text as string object.
00380   //
00381   // Note: serverCommand is only needed for the TOP n m command and only because
00382   //       IMail on Windows NT sends two streams, one after another, to return
00383   //       the e-mail header of a message. Quite odd.
00384   string PopAccount::receiveResult(string serverCommand = "irrelevant") {
00385     static const int TIME_OUT = 10;
00386     char buffer[MAX_BYTES + 20];   // Leave some space for expansion
00387     int flags = 0, error = 0;
00388     struct timeval tv;
00389     fd_set rfds;
00390     string input;
00391     int counter = 0, bytes = 0, last = 0;
00392     
00393     // Memorize old flags of the open socket
00394     if ( (flags = fcntl(mySocket, F_GETFL, 0)) == -1 )
00395       throw IOException();
00396 
00397     // Change the socket communication stream to nonblocking
00398     if (fcntl(mySocket, F_SETFL, flags | O_NONBLOCK) == -1 )
00399       throw IOException();
00400 
00401     do {
00402       memset(buffer, 0, sizeof(buffer));
00403       last = bytes;
00404       tv.tv_sec = TIME_OUT;
00405       tv.tv_usec = 0;
00406       FD_ZERO(&rfds);
00407       FD_SET(mySocket, &rfds);
00408       
00409       // Only read if the socket is ready and sending data
00410       error = select(mySocket + 1, &rfds, NULL, NULL, &tv);
00411 
00412       if (error < 0)
00413     throw ConnectionTimeOut();
00414       else if (error > 0 && FD_ISSET(mySocket, &rfds)) {
00415     bytes = read(mySocket, buffer, MAX_BYTES);
00416     
00417     if (bytes > 0) {
00418       buffer[bytes] = 0;
00419       counter += bytes;
00420 
00421       if (input.length() > 0)
00422         input.append(buffer, bytes);
00423       else
00424         input = (string)buffer;
00425     }
00426     else if (bytes == 0)
00427       break;
00428     else
00429       throw IOException();
00430       }
00431     } while (   (error > 0)  &&
00432         (last > 0 && bytes > 0 && !isHeaderEnd(input))  ||
00433         ( (last > 0 || bytes == MAX_BYTES) && !isHeaderEnd(input) )  ||
00434         ( (serverCommand.find("TOP") == 0) && !isHeaderEnd(input) )     );   // This line only makes sense with the IMail server
00435                                                                                      // on Windows NT. See check() for more information.
00436 
00437     // Put line end behind the buffer
00438     input[counter] = 0;
00439 
00440     // Reset socket communication to blocking mode
00441     if ( (fcntl(mySocket, F_SETFL, flags)) == -1)
00442       throw IOException();
00443     
00444     // Return the server response
00445     return(input);
00446   }
00447   
00448 
00449   // Deletes a message on the POP3 server
00450   // Returns 0 on success, -1 otherwise
00451   int PopAccount::deleteMessage(int mess) {
00452     char cmd[32];
00453 #ifdef HAVE_SNPRINTF
00454     snprintf(cmd, sizeof(cmd), "DELE %d\r\n", mess);
00455 #else
00456     sprintf(cmd, "DELE %d\r\n", mess);
00457 #endif
00458     try {
00459       if ( (sendCommand(cmd) > 0) && (serverResult(receiveResult()) == TRUE) )
00460     return 0;
00461       else
00462     return -1;
00463     }
00464     catch(...) {
00465       throw;
00466     }
00467   }
00468 
00469   
00470   // Returns the n-th word in a string,
00471   // e.g. getWord("My name is Harry", 1) would return "name".
00472   string PopAccount::getWord(const string word, int n) {
00473     int curSpace = 0, nextSpace = 0, wordEnd = 0;
00474 
00475     // Find beginning of word
00476     for (int i = 0; i < n; i++) {
00477       nextSpace = word.find(' ', curSpace);
00478       curSpace = nextSpace + 1;
00479     }
00480     
00481     // Either copy everything up to the next blank character, or up to the end of the line (-1 for the cr)
00482     if ( (wordEnd = word.find(' ', curSpace)) == -1 )
00483     wordEnd = word.length() - 1;
00484 
00485     return(word.substr(curSpace, wordEnd));
00486   }
00487 
00488 
00489   // Checks whether server message is an error message (-ERR) or a positive status report (+OK)
00490   bool PopAccount::serverResult(const string serverMessage) {
00491     if (serverMessage[0] == '+')
00492       return TRUE;
00493     else
00494       return FALSE;    
00495   }
00496 
00497 
00498   // Returns true if the string buffer contains the end of an e-mail message header
00499   // otherwise the function returns false.
00500   // Note: according to the POP3 specs the end of messages are seperated by <CR><LF><.><CR><LF>
00501   bool PopAccount::isHeaderEnd(const string buffer) {
00502     if (buffer.length() >= 4)
00503       for (unsigned int i = 0; i+4 <= buffer.length(); i++)
00504     if ( (i+4 <= buffer.length()) && (buffer[i] == '\r') && (buffer[i+1] == '\n') && (buffer[i+2] == '.') &&
00505          (buffer[i+3] == '\r') && (buffer[i+4] == '\n') )
00506       return TRUE;
00507 
00508     return FALSE;
00509   }
00510 
00511 }

Generated at Tue Dec 19 19:21:03 2000 for mailfilter by doxygen1.2.3 written by Dimitri van Heesch, © 1997-2000