Skip to content

Commit

Permalink
More class separation.
Browse files Browse the repository at this point in the history
  • Loading branch information
mawis committed Oct 28, 2011
1 parent d5220b3 commit bfa29bc
Show file tree
Hide file tree
Showing 8 changed files with 498 additions and 371 deletions.
4 changes: 4 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
* couriergrey.h: same
* database.cc: added class to access gdbm database
* database.h: same
* mail_procesor.cc: separated class mail_processor to its own file
* mail_processor.h: same
* message_processor.cc: separated class message_processor to its own file
* message_processor.h: same
* whitelist.cc: moved whitelist class to its own file
* whitelist.h: same
* man/couriergrey.8.in: added man file
Expand Down
4 changes: 2 additions & 2 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ SUBDIRS = intl m4 man po

bin_PROGRAMS = couriergrey

noinst_HEADERS = couriergrey.h database.h whitelist.h
noinst_HEADERS = couriergrey.h database.h mail_processor.h message_processor.h whitelist.h

sysconf_DATA = whitelist_ip.dist

couriergrey_SOURCES = couriergrey.cc database.cc whitelist.cc
couriergrey_SOURCES = couriergrey.cc database.cc mail_processor.cc message_processor.cc whitelist.cc

couriergrey_LDFLAGS = @LDFLAGS@

Expand Down
269 changes: 2 additions & 267 deletions couriergrey.cc
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@
#endif

#include "couriergrey.h"
#include <iostream>
#include <cstring>
#include <cerrno>
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
Expand All @@ -35,278 +35,13 @@
#include <cstdio>
#include <poll.h>
#include <glibmm.h>
#include <sstream>
#include <fstream>
#include <list>
#include <ctime>
#include <syslog.h>
#include <netinet/in.h>
#include <stdexcept>
#include <popt.h>

#define SOCKET_BACKLOG_SIZE 10

namespace couriergrey {
void mail_processor::read_mail(const std::string& filename) {
std::ifstream mail(filename.c_str());

std::string header_value;
bool first_received_header = true;
while (std::getline(mail, header_value)) {
// check for end of header
if (header_value.length() == 0) {
// empty line is end of header
break;
}

// check if the header is continued in the next line
while (mail) {
int peeked_character = mail.peek();

if (peeked_character != ' ' and peeked_character != '\t') {
break;
}

std::string continuing_line;
std::getline(mail, continuing_line);
header_value += continuing_line;
}

// check if we got the first received header
if (first_received_header && header_value.substr(0, 9) == "Received:") {
// has the message been received authenticated?
if (header_value.find("(AUTH: ") != std::string::npos) {
authed = true;
}

// the next one isn't the first one anymore
first_received_header = false;
}

// check if it's the SPF header we are looking for
// Note: normally we would have to do case-insensitve matching, but as the header is always
// created by Courier we can just check for the casing that Courier uses.
// Case-sensitve matching is faster ...
if (header_value.substr(0, 13) == "Received-SPF:" && header_value.find("SPF=MAILFROM;") != std::string::npos) {
// okay ... we have to extract the SPF state
std::istringstream header_stream(header_value.substr(13));
std::string spf_state_string;
header_stream >> spf_state_string;

if (spf_state_string == "pass") {
spf_envelope_sender_state = pass;
} else if (spf_state_string == "fail") {
spf_envelope_sender_state = fail;
} else if (spf_state_string == "softfail") {
spf_envelope_sender_state = softfail;
} else if (spf_state_string == "neutral") {
spf_envelope_sender_state = neutral;
} else if (spf_state_string == "none") {
spf_envelope_sender_state = none;
} else if (spf_state_string == "temperror") { // seems not to be created by courier
spf_envelope_sender_state = temperror;
} else if (spf_state_string == "permerror") { // seems not to be created by courier
spf_envelope_sender_state = permerror;
}
}
}
}

message_processor::message_processor(int fd, whitelist const& used_whitelist) : fd(fd), used_whitelist(used_whitelist) {}

void message_processor::do_process() {
std::string data_from_socket;

// read the filenames from the socket
while (data_from_socket.find("\n\n") == std::string::npos) {
char buffer[1024];
ssize_t bytes_read = ::read(fd, buffer, sizeof(buffer));
if (bytes_read <= 0) {
break;
}

data_from_socket += std::string(buffer, bytes_read);
}

// process the message
std::istringstream files(data_from_socket);
int filenum = 0;
std::string one_file;
bool authenticated_sender = false;
std::string sender_address;
std::string sending_mta;
std::list<std::string> recipients;
mail_processor mail;
while (std::getline(files, one_file)) {
// the first line is the filename of the message file
if (!filenum++) {
mail.read_mail(one_file);
if (mail.is_authed()) {
authenticated_sender = true;
}
continue;
}

// skip the empty line at the end
if (one_file == "") {
continue;
}

// if we reach here it's a control file, open it ...
std::ifstream infile(one_file.c_str());

// read the control file line by line
std::string one_line;
while (std::getline(infile, one_line)) {
std::string linetype = one_line.substr(0, 1);

// is this line indicating that the sender has had an authenticated connection?
if (linetype == "i") {
authenticated_sender = true;
continue;
}

// is this line indicating the sender (envelope) address?
if (linetype == "s") {
sender_address = one_line.substr(1);
continue;
}

// is this line the MTA the message has been received from?
if (linetype == "f") {
sending_mta = one_line.substr(1);
continue;
}

// is this line indicating a recipient of the message?
if (linetype == "r") {
recipients.push_back(one_line.substr(1));
continue;
}
}

// we're done with this control file, close it ...
infile.close();
}

// is sender whitelisted?
bool whitelisted = false;
try {
std::string address = sending_mta;
std::string::size_type pos = address.find('[');
if (pos != std::string::npos)
address.erase(0, pos+1);
pos = address.find(']');
if (pos != std::string::npos)
address.erase(pos, std::string::npos);
whitelisted = used_whitelist.is_whitelisted(address);
} catch (std::invalid_argument) {
::syslog(LOG_NOTICE, "Cannot parse sending MTA's address: %s", sending_mta.c_str());
}

// we should no have all data we need to check this message
std::string response = "451 Default Response";

// check if we can accept the message or if we should delay it
if (authenticated_sender) {
// accept authenticated mails always
response = "200 Accepting authenticated mail";
} else if (mail.get_spf_envelope_sender_state() == mail_processor::pass) {
// accept SPF authenticated senders
response = "200 Accepting this mail by SPF";
} else if (sending_mta == "") {
// this should not be possible, if it happens courier's interface might have changed
response = "435 " PACKAGE " could not get the sending MTA's address.";
} else if (whitelisted) {
// the sender has been whitelisted
response = "200 Whitelisted sender";
} else if (recipients.size() < 1) {
// this should not be possible, if it happens courier's interface might have changed
response = "435 " PACKAGE " could not get the envelope recipient.";
} else {
// do our actual magic of greylisting

// extract the IP address from the sending_mta
std::string::size_type pos = sending_mta.rfind("(");
if (pos != std::string::npos) {
sending_mta.erase(0, pos+1);
}
pos = sending_mta.find(")");
if (pos != std::string::npos) {
sending_mta.erase(pos);
}
pos = sending_mta.rfind("[");
if (pos != std::string::npos) {
sending_mta.erase(0, pos+1);
}
pos = sending_mta.find("]");
if (pos != std::string::npos) {
sending_mta.erase(pos);
}
if (sending_mta.substr(0, 7) == "::ffff:") {
sending_mta.erase(0, 7);
}

// calculate identifier for this connection
std::ostringstream mail_identifier;
mail_identifier << sender_address << "/" << sending_mta;
std::list<std::string>::const_iterator p;
for (p=recipients.begin(); p!=recipients.end(); ++p) {
mail_identifier << "/" << *p;
}

// open the database
try {
database db;

std::string mail_identifier_string = mail_identifier.str();

std::string value = db.fetch(mail_identifier_string);

// check when there has been the first delivery attempt for this mail
std::time_t first_delivery = 0;
if (value.empty()) {
// new mail, first attempt is now ...
first_delivery = std::time(NULL);
} else {
// mail relation is already in the database, read first attempt from there
std::istringstream value_stream(value);
value_stream >> first_delivery;
}

// update the content (first attempt + last access for cleanup) in the database
std::ostringstream value_stream;
value_stream << first_delivery << " " << std::time(NULL);
db.store(mail_identifier_string, value_stream.str());

// check if the first attempt for this mail is old enought so that we can accept the mail
std::time_t seconds_to_wait = (first_delivery + 120) - std::time(NULL);
if (seconds_to_wait <= 0) {
response = "200 Thank you, we accept this e-mail.";
} else {
std::ostringstream response_stream;
response_stream << "451 You are greylisted, please try again in " << seconds_to_wait << " s.";
response = response_stream.str();
}
} catch (Glib::ustring msg) {
response = "430 Greylisting DB could not be opened currently. Please try again later: ";
response += msg;
}
}

// append a linefeed to the result
response += "\n";

// write the result
ssize_t written = ::write(fd, response.c_str(), response.length());

// close the socket
::close(fd);

// free our instance again
delete this;
}
}

int main(int argc, char const** argv) {
int do_version = 0;
int dump_whitelist = 0;
Expand Down
Loading

0 comments on commit bfa29bc

Please sign in to comment.