aboutsummaryrefslogtreecommitdiff
#include <string.h>
#include <unistd.h>
#include <iostream>
#include <fstream>
#include <set>
#include <regex>

#include <io.h>
#include <direct.h>

#include "vmime/vmime.hpp"
#include "utilities.h"
#include "timeout_handler.hpp"
#include "exceptions.hpp"

extern "C" void validate_config(struct config *config)
{
	if (config->use_SSL == PARAM_NOT_GIVEN)
		config->use_SSL = PARAM_NO;	
	if (config->use_AUTH == PARAM_NOT_GIVEN)
		config->use_AUTH = PARAM_NO;
	if (config->save_body == PARAM_NOT_GIVEN)
		config->save_body = PARAM_YES;
	if (config->save_subj == PARAM_NOT_GIVEN)
		config->save_subj = PARAM_YES;
	if (config->del_msgs == PARAM_NOT_GIVEN)
		config->del_msgs = PARAM_NO;

	if (is_empty_string(config->host))
		FAIL(MSG_BAD_VAR, "SERVER:host");
	if (!is_valid_port_number(config->port_POP3))
		FAIL(MSG_BAD_PORT_VAR, "SERVER:port_pop3");
	if (!config->password)
		FAIL(MSG_BAD_VAR, "SERVER:password");
	if (is_empty_string(config->login))
		FAIL(MSG_BAD_VAR, "SERVER:login");
	if (is_empty_string(config->dest_dir))
		FAIL(MSG_BAD_VAR, "SERVER:outputdir");
	if (config->dest_dir[strlen(config->dest_dir) - 1] != '\\')
		FAIL(MSG_BAD_DEST_DIR_END);
	if (access(config->dest_dir, F_OK) != 0)
		FAIL(MSG_DEST_DIR_NOT_EXIST);
	if (access(config->dest_dir, W_OK) != 0)
		FAIL(MSG_DEST_DIR_NOT_WRITEABLE);
}

static const char mail_dir_base[] = "mail_";
static const int mail_base_length = sizeof(mail_dir_base) - 1;

static long deduct_mail_dir_number(const char *parent_dir_path)
{
	size_t path_length = strlen(parent_dir_path);
	intptr_t search_group;
	_finddata_t find_data;
	long max_number = 0, parsed_number;
	char *glob = (char*) dirtyalloc(path_length + mail_base_length + 2);
	const char *glob_match_start;

	memcpy(glob, parent_dir_path, path_length);
	memcpy(glob + path_length, mail_dir_base, mail_base_length);
	path_length += mail_base_length;
	strcpy(glob + path_length, "*");

	search_group = _findfirst(glob, &find_data);
	if (search_group < 0)
		goto after_searching;
	do {
		glob_match_start = find_data.name + mail_base_length;
		if (*glob_match_start < '1' || *glob_match_start > '9')
			continue;

		parsed_number = atol(glob_match_start);
		if (parsed_number > max_number)
			max_number = parsed_number;
	} while (_findnext(search_group, &find_data) == 0);

	_findclose(search_group);
after_searching:
	free(glob);
	return max_number + 1;
}

static std::string get_next_filename(std::string filename)
{
	std::regex
		numbered_reg("(.[^.]*)[(]([1-9][0-9]{0,8})[)](([.].*)?)"),
		unnumbered_reg("(.[^.]*)(([.].*)?)");
	std::smatch what;
	std::string base_name, number, rest;
	long new_idx;

	if (regex_match(filename, what, numbered_reg)) {
		base_name = what[1];
		number = what[2];
		rest = what[3];
		new_idx = std::stol(number) + 1;
	} else {
		regex_match(filename, what, unnumbered_reg);
		base_name = what[1];
		rest = what[2];
		new_idx = 1;
	}

	return base_name + "(" + std::to_string(new_idx) + ")" + rest;
}

static int save_file_from_handler
(std::string dir_path, std::string file_name,
 vmime::shared_ptr<const vmime::contentHandler> handler,
 std::set<std::string> *filenames_used)
{
	while (filenames_used->count(file_name))
		file_name = get_next_filename(file_name);

	std::ofstream write;
	write.open(dir_path + file_name, std::ios_base::binary);
	vmime::utility::outputStreamAdapter
		write_adapter(write);
	handler->extract(write_adapter);

	filenames_used->insert(file_name);

	if (!write.good()) {
		fprintf(stderr, MSG_FILE_SAVE_FAIL, dir_path + file_name);
		fputs("\r\n", stderr);
		return -1;
	}
	return 0;
}

static void save_message(const struct config *config, long dir_number,
			 vmime::shared_ptr<vmime::net::message> msg,
			 long *saved_files_count)
{
	size_t path_length = strlen(config->dest_dir);
	char *dir_path =
		(char*) dirtyalloc(path_length + mail_base_length + 25);

	memcpy(dir_path, config->dest_dir, path_length);
	memcpy(dir_path + path_length, mail_dir_base, mail_base_length);
	path_length += mail_base_length;
	path_length += sprintf(dir_path + path_length, "%ld\\", dir_number);

	if (_mkdir(dir_path) != 0) {
		PRINT_ERROR(MSG_MKDIR_FAIL, dir_path);
		throw silent_exception();
	}

	std::string dir_path_str = std::string(dir_path);
	std::set<std::string> filenames_used;
	vmime::messageParser mp(msg->getParsedMessage());

	if (config->save_subj) {
		std::ofstream write_subj;
		write_subj.open(dir_path_str + "temat.txt");
		write_subj << mp.getSubject().getWholeBuffer();
		filenames_used.insert("temat.txt");
	}

	if (config->save_body) {
		std::string file_name;
		for (auto text_part : mp.getTextPartList()) {
			if (text_part->getType().getType() == "text/html")
				file_name = "tresc.html";
			else
				file_name = "tresc.txt";

			save_file_from_handler(dir_path_str, file_name,
					       text_part->getText(),
					       &filenames_used);
		}
	}

	for (auto attachment : mp.getAttachmentList()) {
		std::string file_name = attachment->getName().getBuffer();
		if (save_file_from_handler(dir_path_str, file_name,
					   attachment->getData(),
					   &filenames_used) == 0)
			(*saved_files_count)++;
	}

	free(dir_path);
}

using namespace vmime::security::cert;

static void _perform_work(const struct config *config, long *saved_messages,
			  long *saved_files, long *first_mail_dir_number)
{
	vmime::shared_ptr<vmime::net::session> session =
		vmime::net::session::create();
	vmime::shared_ptr<vmime::net::store> store;
	vmime::shared_ptr<vmime::net::folder> folder;
	vmime::shared_ptr <vmime::net::message> msg;
	long left;
	std::string proto = config->use_SSL ? "pop3s" : "pop3";

	  session->getProperties().setProperty("store.protocol", proto);
	session->getProperties().setProperty
	  ("store." + proto + ".server.address", std::string(config->host));
	session->getProperties().setProperty("server.port", config->port_POP3);
	session->getProperties().setProperty
	  ("store." + proto + ".auth.password", std::string(config->password));
	session->getProperties().setProperty
	  ("store." + proto + ".auth.username", std::string(config->login));
	store = session->getStore();
	if (config->use_SSL)
		store->setCertificateVerifier(windows_root_certs_verifier());
	store->setTimeoutHandlerFactory
		(vmime::make_shared<parameterized_timeout_handler_factory>
		 (config->timeout));
	store->connect();
	folder = store->getDefaultFolder();
	folder->open(vmime::net::folder::MODE_READ_WRITE);
	*first_mail_dir_number = deduct_mail_dir_number(config->dest_dir);
	long nmes = folder->getMessageCount();
	for (left = nmes /*folder->getMessageCount()*/; left > 0; left--) {
		msg = folder->getMessage(/*1*/nmes - left + 1);
		save_message(config, *first_mail_dir_number + *saved_messages,
			     msg, saved_files);
		(*saved_messages)++;
		if (config->del_msgs) {
			folder->deleteMessages
				(vmime::net::messageSet::byNumber(nmes - left + 1));
		}
	}
	folder->close(true);
	store->disconnect();
}

extern "C" void perform_work(const struct config *config)
{
	long saved_messages = 0, saved_files = 0, first_mail_dir_number = 0;

#define PRINT_STATS() \
	if (first_mail_dir_number > 0)					\
		(std::cout << "[" << saved_messages << "\\" << saved_files \
		 << "\\" << first_mail_dir_number << "]" << std::endl)

	try {
		_perform_work(config, &saved_messages,
			      &saved_files, &first_mail_dir_number);
		PRINT_STATS();
		SUCCEED();
	} catch (vmime::exception &e) {
		PRINT_STATS();
		handle_vmime_exception(&e);
	} catch (std::exception &e) {
		PRINT_STATS();
		handle_std_exception(&e);
	}
}