// nullmailer -- a simple relay-only MTA
// Copyright (C) 2005 Bruce Guenter <bruce@untroubled.org>
//
// 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
//
// You can contact me at <bruce@untroubled.org>. There is also a mailing list
// available to discuss this package. To subscribe, send an email to
// <nullmailer-subscribe@lists.untroubled.org>.
#include "config.h"
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include "ac/systime.h"
#include "configio.h"
#include "defines.h"
#include "errcodes.h"
#include "fdbuf/fdbuf.h"
#include "hostname.h"
#include "itoa.h"
#include "list.h"
#include "setenv.h"
typedef list<mystring> slist;
#define fail(MSG) do { ferr << MSG << endl; return false; } while(0)
#define fail_sys(MSG) do{ ferr << MSG << strerror(errno) << endl; return false; }while(0)
struct remote
{
static const mystring default_proto;
mystring host;
mystring proto;
slist options;
remote(const slist& list);
~remote();
};
const mystring remote::default_proto = "smtp";
remote::remote(const slist& lst)
{
slist::const_iter iter = lst;
host = *iter;
++iter;
if(!iter)
proto = default_proto;
else {
proto = *iter;
for(++iter; iter; ++iter)
options.append(*iter);
}
}
remote::~remote() { }
typedef list<remote> rlist;
unsigned ws_split(const mystring& str, slist& lst)
{
lst.empty();
const char* ptr = str.c_str();
const char* end = ptr + str.length();
unsigned count = 0;
for(;;) {
while(ptr < end && isspace(*ptr))
++ptr;
const char* start = ptr;
while(ptr < end && !isspace(*ptr))
++ptr;
if(ptr == start)
break;
lst.append(mystring(start, ptr-start));
++count;
}
return count;
}
static rlist remotes;
static int pausetime = 60;
bool load_remotes()
{
slist rtmp;
if(!config_readlist("remotes", rtmp) ||
rtmp.count() == 0)
return false;
remotes.empty();
for(slist::const_iter r(rtmp); r; r++) {
if((*r)[0] == '#')
continue;
slist parts;
if(!ws_split(*r, parts))
continue;
remotes.append(remote(parts));
}
return remotes.count() > 0;
}
bool load_config()
{
bool result = true;
if(!load_remotes())
result = false;
if(!config_readint("pausetime", pausetime))
pausetime = 60;
return result;
}
static slist files;
static bool reload_files = false;
void catch_alrm(int)
{
signal(SIGALRM, catch_alrm);
reload_files = true;
}
bool load_files()
{
reload_files = false;
fout << "Rescanning queue." << endl;
DIR* dir = opendir(".");
if(!dir)
fail_sys("Cannot open queue directory: ");
files.empty();
struct dirent* entry;
while((entry = readdir(dir)) != 0) {
const char* name = entry->d_name;
if(name[0] == '.')
continue;
files.append(name);
}
closedir(dir);
return true;
}
void exec_protocol(int fd, remote& remote)
{
if(close(0) == -1 || dup2(fd, 0) == -1 || close(fd) == -1)
return;
mystring program = PROTOCOL_DIR + remote.proto;
const char* args[3+remote.options.count()];
unsigned i = 0;
args[i++] = program.c_str();
for(slist::const_iter opt(remote.options); opt; opt++)
args[i++] = strdup((*opt).c_str());
args[i++] = remote.host.c_str();
args[i++] = 0;
execv(args[0], (char**)args);
}
#undef fail
#define fail(MSG) do { fout << MSG << endl; return false; } while(0)
#define fail2(MSG1,MSG2) do{ fout << MSG1 << MSG2 << endl; return false; }while(0)
bool catchsender(pid_t pid)
{
int status;
if(waitpid(pid, &status, 0) == -1)
fail("Error catching the child process return value.");
else {
if(WIFEXITED(status)) {
status = WEXITSTATUS(status);
if(status)
fail2("Sending failed: ", errorstr[status]);
else {
fout << "Sent file." << endl;
return true;
}
}
else
fail("Sending process crashed or was killed.");
}
}
bool send_one(mystring filename, remote& remote)
{
int fd = open(filename.c_str(), O_RDONLY);
if(fd == -1) {
fout << "Can't open file '" << filename << "'" << endl;
return false;
}
fout << "Starting delivery: protocol: " << remote.proto
<< " host: " << remote.host
<< " file: " << filename << endl;
pid_t pid = fork();
switch(pid) {
case -1:
fail("Fork failed.");
case 0:
exec_protocol(fd, remote);
exit(ERR_EXEC_FAILED);
default:
close(fd);
if(!catchsender(pid))
return false;
if(unlink(filename.c_str()) == -1)
fail("Can't unlink file.");
}
return true;
}
bool send_all()
{
if(!load_config())
fail("Could not load the config");
if(remotes.count() <= 0)
fail("No remote hosts listed for delivery");
if(files.count() == 0)
return true;
fout << "Starting delivery, "
<< itoa(files.count()) << " message(s) in queue." << endl;
for(rlist::iter remote(remotes); remote; remote++) {
slist::iter file(files);
while(file) {
if(send_one(*file, *remote))
files.remove(file);
else
file++;
}
}
fout << "Delivery complete, "
<< itoa(files.count()) << " message(s) remain." << endl;
return true;
}
static int trigger;
#ifdef NAMEDPIPEBUG
static int trigger2;
#endif
bool open_trigger()
{
trigger = open(QUEUE_TRIGGER, O_RDONLY|O_NONBLOCK);
#ifdef NAMEDPIPEBUG
trigger2 = open(QUEUE_TRIGGER, O_WRONLY|O_NONBLOCK);
#endif
if(trigger == -1)
fail("Could not open trigger file.");
return true;
}
bool read_trigger()
{
if(trigger != -1) {
char buf[1024];
read(trigger, buf, sizeof buf);
#ifdef NAMEDPIPEBUG
close(trigger2);
#endif
close(trigger);
}
return open_trigger();
}
bool do_select()
{
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(trigger, &readfds);
struct timeval timeout;
timeout.tv_sec = pausetime;
timeout.tv_usec = 0;
int s = select(trigger+1, &readfds, 0, 0,
(files.count() == 0) ? 0 : &timeout);
if(s == 1) {
fout << "Trigger pulled." << endl;
read_trigger();
reload_files = true;
}
else if(s == -1 && errno != EINTR)
fail("Internal error in select.");
else if(s == 0)
reload_files = true;
if(reload_files)
load_files();
return true;
}
int main(int, char*[])
{
mystring hh;
read_hostnames();
if (!config_read("helohost", hh)) hh = me;
setenv("HELOHOST", hh.c_str(), 1);
if(!open_trigger())
return 1;
if(chdir(QUEUE_MSG_DIR) == -1) {
fout << "Could not chdir to queue message directory." << endl;
return 1;
}
signal(SIGALRM, catch_alrm);
signal(SIGHUP, SIG_IGN);
load_config();
load_files();
for(;;) {
send_all();
if (pausetime == 0) break;
do_select();
}
return 0;
}
syntax highlighted by Code2HTML, v. 0.9.1