/*
Reply-o-Matic - Automatic message replying system
Copyright (C) 2006 - Rodrigo Barbosa <rodrigob@darkover.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.
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
*/
// #define DEBUG 1
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <time.h>
#include <sys/time.h>
#include <pwd.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdarg.h>
#include <syslog.h>
#include <sys/syslog.h>
#include <libgen.h>
#include <string.h>
#include <sys/file.h>
#include "rom.h"
/* Globals */
char usedflags[256];
int pfd[2], dfd[2], cpfd[2], cdfd[2];
/* Paranoid mode */
paranoid_t paranoid;
/* Rate limiting */
char ratefile[256];
unsigned long int replyrate = 0;
char needrateopen = 0;
FILE *ratefd = NULL;
char ratefn[256];
mode_t oldmask;
/**/
/* Ignores */
char igfile[256];
FILE *igfd = NULL;
char ighomefile[256];
char dochroot = 0, didchroot = 0;
FILE *logfd = NULL;
#define addflag(newflag) strncat(usedflags,newflag,255)
/* Taken from mutt */
#define SKIPWS(x) while(isspace(*x))x++
#define NONULL(x) x?x:""
/**/
/* We need this so we can be sure the children will exit correctly */
void diedie ( unsigned int exsig ) {
if ( cpfd[1] > 2 )
write(cpfd[1],"X",1);
if ( cdfd[1] > 2 )
write(cdfd[1],"X",1);
if ( igfd )
fclose(igfd);
#ifdef DEBUG
if ( logfd )
fclose(logfd);
#endif
umask(oldmask);
closelog();
exit(exsig);
}
void p_log(char *fmt, ...)
{
va_list vaa;
char logmessage[2049];
time_t tdata;
va_start(vaa,fmt);
vsprintf(logmessage,fmt,vaa);
syslog(LOG_INFO,"%s",logmessage);
tdata=time(NULL);
#ifdef DEBUG
if ( logfd )
fprintf(logfd,"%s [%d]: %s\n",ctime(&tdata),getpid(),logmessage);
#endif
va_end(vaa);
}
void p_err(char *fmt, ...)
{
va_list vaa;
char logmessage[2049];
time_t tdata;
va_start(vaa,fmt);
vsprintf(logmessage,fmt,vaa);
syslog(LOG_ERR,"%s",logmessage);
tdata=time(NULL);
#ifdef DEBUG
if ( logfd )
fprintf(logfd,"%s [%d]: %s\n",ctime(&tdata),getpid(),logmessage);
#endif
va_end(vaa);
}
/* Functions taken from mutt, and adapted where (and when) needed */
int ascii_isupper (int c)
{
return (c >= 'A') && (c <= 'Z');
}
int ascii_islower (int c)
{
return (c >= 'a') && (c <= 'z');
}
int ascii_toupper (int c)
{
if (ascii_islower (c))
return c & ~32;
return c;
}
int ascii_tolower (int c)
{
if (ascii_isupper (c))
return c | 32;
return c;
}
int ascii_strcasecmp (const char *a, const char *b)
{
int i;
if (a == b)
return 0;
if (a == NULL && b)
return -1;
if (b == NULL && a)
return 1;
for (; *a || *b; a++, b++)
{
if ((i = ascii_tolower (*a) - ascii_tolower (*b)))
return i;
}
return 0;
}
int mutt_strcasecmp(const char *a, const char *b)
{
return strcasecmp(NONULL(a), NONULL(b));
}
size_t mutt_strlen(const char *a)
{
return a ? strlen (a) : 0;
}
char *
mutt_substrcpy (char *dest, const char *beg, const char *end, size_t destlen)
{
size_t len;
len = end - beg;
if (len > destlen - 1)
len = destlen - 1;
memcpy (dest, beg, len);
dest[len] = 0;
return dest;
}
/**/
/* Mostly copied from mutt */
void getmimetype (const char *path, char *mtype, size_t mtlen)
{
FILE *f;
char *p, *q, *ct;
char buf[2049];
char subtype[256], xtype[256];
int count;
int szf, sze, cur_sze;
int type;
*subtype = '\0';
*xtype = '\0';
type = 0;
cur_sze = 0;
szf = mutt_strlen (path);
for (count = 0 ; count < 3 ; count++)
{
/*
* can't use strtok() because we use it in an inner loop below, so use
* a switch statement here instead.
*/
switch (count)
{
case 0:
snprintf (buf, sizeof (buf), ROMDIR"/mime.types");
break;
case 1:
strncpy (buf, "/etc/mime.types", sizeof(buf));
break;
case 2:
strncpy (buf, "/mime.types", sizeof(buf));
break;
default:
goto bye; /* shouldn't happen */
}
if ((f = fopen (buf, "r")) != NULL)
{
while (fgets (buf, sizeof (buf) - 1, f) != NULL)
{
/* weed out any comments */
if ((p = (char *) strchr (buf, '#')))
*p = 0;
/* remove any leading space. */
ct = buf;
SKIPWS (ct);
/* position on the next field in this line */
if ((p = (char *) strpbrk (ct, " \t")) == NULL)
continue;
*p++ = 0;
SKIPWS (p);
/* cycle through the file extensions */
while ((p = (char *) strtok (p, " \t\n")))
{
sze = mutt_strlen (p);
if ((sze > cur_sze) && (szf >= sze) &&
(mutt_strcasecmp (path + szf - sze, p) == 0 || ascii_strcasecmp (path + szf - sze, p) == 0) &&
(szf == sze || path[szf - sze - 1] == '.'))
{
/* get the content-type */
if ((p = (char *) strchr (ct, '/')) == NULL)
{
/* malformed line, just skip it. */
break;
}
*p++ = 0;
for (q = p; *q && !isspace (*q); q++)
;
mutt_substrcpy (subtype, p, q, sizeof (subtype));
strncpy (xtype, ct, sizeof (xtype));
cur_sze = sze;
}
p = NULL;
}
}
fclose (f);
}
}
bye:
if (*xtype != '\0')
{
snprintf(mtype,mtlen,"%s/%s",xtype,subtype);
}
}
/* Used to create the MIME delimiter */
int getrandom ( int num, char *randout ) {
int rfd;
struct stat rstatbuf;
char rchar;
int rcount = 0;
rfd=open(RANDOM_DEV,O_RDONLY);
if ( !rfd ) {
p_err("Unable to open %s.",RANDOM_DEV);
close(rfd);
return(0);
}
fstat(rfd,&rstatbuf);
if ( ! S_ISCHR(rstatbuf.st_mode) ) {
p_err("%s is no a character device. No random value will be gathered",RANDOM_DEV);
close(rfd);
return(0);
}
randout[num]=0;
while ( rcount < num ) {
if ( read(rfd,&rchar,1) < 1 ) {
randout[rcount+1]=0;
close(rfd);
return(rcount+1);
}
if ( isalnum(rchar) || ( rchar == '+' ) || ( rchar == '_' ) ) {
randout[rcount]=rchar;
rcount++;
}
}
close(rfd);
return(rcount);
}
/* My strcasecmp function */
int rom_strcasecmp ( char * str1, char * str2 ) {
if ( strlen(str1) > strlen(str2) )
return(1);
if ( strlen(str2) > strlen(str1) )
return(-1);
return(strcasecmp(str1,str2));
}
void help ( void ) {
printf( \
"Reply-o-Matic v%s\n" \
"Copyright 2006 - Rodrigo Barbosa <rodrigob@darkover.org\n" \
"Reply-o-Matic cames with ABSOLUTELY NO WARRANTY\n" \
"This is free software, and licensed under the terms of the\n" \
"GNU General Public License v2\n" \
"\n" \
"Usage: rom [ -h[n] ] [ -f <from> ] [ -b <message body file> ]\n" \
" [ -s <message subject> || -S ] [ -r <reply-to> ] [ -v ]\n" \
" [ -c <copy response to> ] [ -d <deliver-to> ]\n" \
" [ -a <file to attach> ] [ -t <attachment mime type > ]\n" \
" [ -C [<chroot dir>] [ -u <uid> ] [ -g <gid> ]\n"\
" [ -U <uid> ] [ -G <gid> ] [ -R [<hours>]\n" \
"\n" \
"Parameters:\n" \
"\n" \
" -h[n] If n is 0, nothing in included on the reply\n" \
" If n is 1, the headers of the original message are included\n" \
" inlined on the reply message (default)\n" \
" If n is 2, the reply message goes as a mime multipart message\n" \
" with the full original message attached\n" \
"\n" \
" -f Set the From: field of the reply message\n" \
" -b A file that contains the text to be included on\n" \
" the reply message (MUST BE A REGULAR FILE!)\n" \
" -s Set the Subject: of the reply message\n" \
" -S Set the Subject line as a reply (RE: ) of the original one\n" \
" (NOTE: -S taked precedence over -s. When both are set, the string in\n" \
" -s is only used if there is not subject line on the original\n" \
" message\n" \
" -r Set the Reply-To: field of the reply message\n" \
" -v Show this page\n" \
" -c Send a copy (blind carbon copy) the reply message\n" \
" to this address too\n" \
" -d Deliver the original message to this e-mail address\n" \
" (useful when you want an autoreply, but still need to\n" \
" get the original message)\n" \
" -a Attach a file on the reply. The file will be send as a \n" \
" MIME attachment, trying to guess the correct mimetype by\n" \
" the file extension. See manpage for more details.\n" \
" -t Specify the Content-Type of the attachment. No effect if\n" \
" used without the -a parameter\n" \
" -C Make ROM run in a chrooted environment. Default chroot dir\n" \
" is /etc/rom/\n" \
" -u -g Make ROM drop it's privileges to the given uid and gid,\n" \
" instead of using it's defaults of 65534\n" \
" -U -G Make ROM run the MTA/MDA with the given uid and gid,\n" \
" instead of not dropping privileges to do it\n"\
" NOTICE: -u, -g, -U and -G will only take effect if the calling user\n" \
" is root (real uid = 0). Otherwhise, ROM will drop it's\n" \
" privileges to the uid and gid of the calling user\n" \
" -R Number of hours to wait before replying to the same\n" \
" originator address. The given value or 1 hour, is -R\n" \
" is used without a value. (Default: don't wait)\n" \
"\n"\
" SEE THE MANPAGE FOR MORE DETAILS\n"\
"\n"\
"This program is distributed in the hope that it will be useful,\n" \
"but WITHOUT ANY WARRANTY; without even the implied warranty of\n" \
"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" \
"GNU General Public License for more details.\n" \
"\n" \
, rom_version);
diedie(-1);
}
char *getbody ( char *fn ) {
FILE *fd;
static char *outbody;
struct stat statbuf;
fd=fopen ( (const char *) fn, "r" );
if ( fd <= 0 ) {
printf("unable to open %s\n",fn);
help();
}
fstat(fileno(fd),&statbuf);
if ( !S_ISREG(statbuf.st_mode) ) {
p_err("Bodyfile must be a regular file\n");
help();
}
outbody=calloc(statbuf.st_size + 1,1);
fread(outbody,1,statbuf.st_size,fd);
fclose(fd);
return (outbody);
}
void raw_to_base64 (unsigned char *, const unsigned char *, size_t, size_t);
/* Read the attachment file, and return it's content */
unsigned char *getattach ( char *fn ) {
int fd;
static unsigned char *outbody;
unsigned char *out64;
struct stat statbuf;
size_t olen;
unsigned filesize;
fd=open ( (const char *) fn, O_RDONLY );
if ( fd <= 0 ) {
p_err("Unable to open attachment\n");
return(NULL);
}
fstat(fd,&statbuf);
if ( !S_ISREG(statbuf.st_mode) ) {
p_err("Attachments must be regular files\n");
return(NULL);
}
filesize=statbuf.st_size;
outbody=calloc(filesize + 1,1);
read(fd,outbody,filesize);
close(fd);
out64=calloc((filesize * 4) + 1,1);
olen=filesize * 4;
raw_to_base64(out64,outbody,filesize,olen);
#ifdef DEBUG
p_log("getattach data: %u %u %u %u\n",strlen(out64),statbuf.st_size,filesize,olen);
#endif
return(out64);
}
/* Header unfolding (RFC2822) */
int unfold ( char *data, char *newdata, unsigned int datalen ) {
unsigned long int i;
unsigned long int ndi = 0;
memset(newdata,0,datalen);
for(i=0;i<datalen;i++) {
if ( i==datalen-1 ) {
newdata[ndi]=data[i];
break;
}
if ( ( data[i]=='\r' ) &&
( data[i+1]=='\n' ) &&
( ( data[i+2]==' ' ) ||
( data[i+2]=='\t' ) ) )
i++;
if ( ( data[i]=='\n' ) &&
( ( data[i+1]==' ') ||
( data[i+1]=='\t' ) ) )
continue;
newdata[ndi++]=data[i];
}
return(ndi);
}
void getparanoid ( void ) {
struct stat statbuf;
memset(¶noid,0,sizeof(paranoid_t));
if ( getuid() == 0 )
return;
if ( stat(ROMDIR"/paranoid",&statbuf) == 0 ) {
if ( getuid() != statbuf.st_uid ) {
paranoid.chroot = statbuf.st_gid & PARANOID_CHROOT ? 1 : 0;
paranoid.lockuid = statbuf.st_gid & PARANOID_LOCKUID ? 1 : 0;
paranoid.lockgid = statbuf.st_gid & PARANOID_LOCKGID ? 1 : 0;
paranoid.noattach = statbuf.st_gid & PARANOID_NOATTACH ? 1 : 0;
return;
}
}
}
int validinclude ( char *filename ) {
char *bname;
char *tname;
tname=strdup(filename);
bname=strdup(basename(tname));
free(tname);
if ( bname[0] == '.' ) {
p_log("Files included (-a or -b) must not start with a dot: \"%s\"",filename);
free(bname);
return(0);
}
free(bname);
return(1);
}
int valid_emailaddress ( char *email ) {
int i;
if ( email == NULL )
return 0;
if ( email[0] == 0 )
return 0;
for (i=0;i<strlen(email);i++) {
if ( !isalnum(email[i]) &&
!strchr("@!#%+=_-",email[i]) )
return 0;
}
return 1;
}
char * extract_addr ( char *xinaddr ) {
static char * outaddr;
char *loc = NULL;
char *inaddr;
int i;
outaddr=calloc(strlen(xinaddr),1);
loc=strchr((const char *) xinaddr,'<');
if (loc) {
inaddr=++loc;
} else
inaddr=xinaddr;
for(i=0;i<strlen(inaddr);i++) {
if ( ( isblank(inaddr[i]) && strlen(outaddr) ) ||
( inaddr[i] == '>' ) )
break;
if ( isblank(inaddr[i]) )
continue;
outaddr[strlen(outaddr)]=inaddr[i];
}
return(outaddr);
}
void split_user_data ( char *uname, char *uaddr, char *data ) {
int i,field=0;
uname[0]=uaddr[0]=0;
for(i=0;i<strlen(data);i++) {
if ( data[i]=='@' )
field++;
if ( !field ) {
uname[strlen(uname)+1]=0;
uname[strlen(uname)]=data[i];
} else {
uaddr[strlen(uaddr)+1]=0;
uaddr[strlen(uaddr)]=data[i];
}
}
}
int check_ignores ( char *userdata ) {
char data[256];
char in_user[256];
char in_addr[256];
char udata[256];
char adata[256];
int field,i;
split_user_data(in_user,in_addr,userdata);
if ( !igfd )
return(0);
while(!feof(igfd)) {
memset(data,0,256);
fgets(data,255,igfd);
if ( strlen(data) == 0 )
continue;
if ( data[strlen(data)-1] == '\n' )
data[strlen(data)-1] = 0;
if ( data[strlen(data)-1] == '\r' )
data[strlen(data)-1] = 0;
field=0;
memset(udata,0,255);
memset(adata,0,255);
for (i=0;i<strlen(data);i++) {
if ( isblank(data[i]))
continue;
if ( data[i]=='@' ) {
field++;
}
if ( field == 0 )
udata[strlen(udata)]=data[i];
if ( field == 1 )
adata[strlen(adata)]=data[i];
}
if ( !field )
continue;
if ( data[0] == '@' ) {
if ( !rom_strcasecmp(adata,in_addr)) {
return 1;
} else {
continue;
}
}
if ( !rom_strcasecmp(data,userdata) ) {
return 1;
}
}
return(0);
}
int check_and_update_rate ( char *userdata ) {
char data[256];
char udata[256];
char tm[256];
int field,i;
char *ratedata;
size_t ratelen = 0;
char found = 1;
struct flock lock;
ratedata=calloc(1,1);
p_log("ratefd = %p, needrateopen = %d\n",ratefd,needrateopen);
if ( !ratefd && !needrateopen ) {
if (didchroot)
snprintf(ratefn,255,"/.rates/%s",ratefile);
else
snprintf(ratefn,255,"%s/.rates/%s",ROMDIR,ratefile);
ratefd=fopen(ratefn,"a+");
rewind(ratefd);
needrateopen=1;
}
p_log("ratefd = %p, needrateopen = %d\n",ratefd,needrateopen);
if ( ratefd && ( needrateopen == 1 )) {
lock.l_type=(F_RDLCK);
fcntl(fileno(ratefd),F_SETLKW,&lock);
while(!feof(ratefd)) {
memset(data,0,256);
fgets(data,255,ratefd);
if ( strlen(data) == 0 )
continue;
field=0;
memset(udata,0,255);
memset(tm,0,255);
for (i=0;i<strlen(data);i++) {
if ( isblank(data[i]) ) {
field++;
continue;
}
if ( field == 0 )
udata[strlen(udata)]=data[i];
if ( field == 1 )
tm[strlen(tm)]=data[i];
}
if ( !rom_strcasecmp(udata,userdata) ) {
if ( time(NULL) < ( atol(tm) + (3600*replyrate) ) ) {
found=0;
} else {
continue;
}
}
ratelen += strlen(data);
ratedata=realloc(ratedata,ratelen+1);
strcat(ratedata,data);
ratedata[ratelen]=0;
}
memset(&lock,0,sizeof(struct flock));
lock.l_type=(F_RDLCK|F_WRLCK);
fcntl(fileno(ratefd),F_SETLKW,&lock);
ftruncate(fileno(ratefd),0L);
fseek(ratefd,0L,SEEK_END);
} else {
if ( needrateopen < 2 )
ratefd=fopen(ratefn,"w");
}
if ( !ratefd ) {
p_log("Unable to open ratefile (dir does not exist?) \"%s\"",ratefn);
return(1);
}
lock.l_type=(F_RDLCK|F_WRLCK);
fcntl(fileno(ratefd),F_SETLKW,&lock);
if ( strlen(ratedata) > 2 ) {
write(fileno(ratefd),ratedata,strlen(ratedata));
if ( ratedata[strlen(ratedata)-1] != '\n' )
write(fileno(ratefd),"\n",1);
}
if ( found )
fprintf(ratefd,"%s %lu",userdata, (unsigned long) time(NULL));
lock.l_type=F_UNLCK;
fcntl(fileno(ratefd),F_SETLKW,&lock);
fclose(ratefd);
return(found);
}
int main ( int argc, char **argv ) {
char instr[2049];
int i,fni,fdi;
char *fname = NULL;
char *fdata = NULL;
int isfield;
char *tmpbuf;
const char *optstr = "f:h::b:s:r:vd:c:Sa:t:C::u:g:U:G:R::";
char c;
char includeorig = 1;
char rom_from[2049] = default_from;
char rom_subject[2049] = default_subject;
char dosubject = 0;
char *rom_body = NULL;
char isbody = 0;
char boundary[256];
char doattach = 0;
char attachtype[256];
char attachpn[2049];
char dotype = 0;
char *attachment = NULL;
char *attachfn = NULL;
int cpid, dpid = 0;
char from[2049],sender[2049],replyto[2049],dest[2049],inreply[2049],replyaddr[2049],forwardto[2049],ccto[2049],subject[2049],precedence[2049];
int romuid = 65534, romgid = 65534;
int mailuid = 0, mailgid = 0;
int rread = 0;
/* Needed for ignorefile copying */
struct passwd *pwent;
struct stat cstatbuf;
char cinfn[256];
char coutfn[256];
/**/
/* Rates */
char etcratefile[256];
/* Needed for chroot */
char chrootdir[256] = ROMDIR;
char dogetbody = 0;
char bodyfile[256];
/**/
/* Incoming message buffer */
char *header = NULL, *uheader = NULL, *body = NULL, *hdr = NULL;
unsigned long int headerlen = 0, uheaderlen = 0, bodylen = 0;
/**/
/* Open connection to syslogd */
openlog("reply-o-matic",LOG_PID,LOG_MAIL);
#ifdef DEBUG
logfd=fopen("/tmp/rom.log","a");
#endif
/* Clearing buffers */
memset(from,0,2049);
memset(sender,0,2049);
memset(replyto,0,2049);
memset(inreply,0,2049);
memset(replyaddr,0,2049);
memset(forwardto,0,2049);
memset(ccto,0,2049);
memset(dest,0,2049);
memset(attachpn,0,2049);
memset(subject,0,2049);
memset(precedence,0,2049);
memset(usedflags,0,256);
memset(boundary,0,256);
memset(attachtype,0,256);
memset(bodyfile,0,256);
memset(ratefile,0,256);
memset(etcratefile,0,256);
memset(igfile,0,256);
memset(cinfn,0,256);
memset(coutfn,0,256);
memset(ratefn,0,256);
/* Getting paranoid */
oldmask=umask(077);
getparanoid();
dochroot=paranoid.chroot ? 1 : 0;
if ( dochroot )
addflag("C");
/* Need to define a boundary for mime multipart */
if ( getrandom(15,boundary) < 15 ) {
snprintf(boundary,255,"=_ReplyOMatic_Default_Boundary_QAPLWSOKEDIJRFUHTYG");
} else {
tmpbuf=calloc(255,1);
snprintf(tmpbuf,255,"=_ReplyOMatic_%s_Boundary",boundary);
strncpy(boundary,tmpbuf,255);
free(tmpbuf);
}
if ( argc > 1 )
while ( (c = getopt(argc,argv,optstr)) ) {
if ( c != -1 )
switch ( c ) {
case 'h' : if ( optarg ) {
if ( strlen(optarg) > 1 )
help();
switch ( optarg[0] ) {
case '0': includeorig = atoi(optarg);
addflag("h0");
break;
case '1': includeorig = atoi(optarg);
addflag("h1");
break;
case '2': includeorig = atoi(optarg);
addflag("h2");
break;
default:
help();
}
} else {
includeorig=1;
addflag("h1");
}
break;
case 'f' : strncpy(rom_from,optarg,2048);
addflag("f");
break;
case 'S' : dosubject |= 2;
addflag("S");
break;
case 's' : strncpy(rom_subject,optarg,2048);
dosubject |= 1;
addflag("s");
break;
case 'b' : dogetbody++;
strncpy(bodyfile,optarg,255);
addflag("b");
break;
case 'r' : strncpy(replyaddr,optarg,2048);
addflag("r");
break;
case 'c' : strncpy(ccto,optarg,2048);
addflag("c");
break;
case 'd' : strncpy(forwardto,optarg,2048);
if (!valid_emailaddress(forwardto)) {
p_err("-d provided with an invalid e-mail address\n");
exit(0);
}
addflag("d");
break;
case 'v' : help();
break;
case 't' : if ( dotype ) {
p_err("Only one mimetype can be specified. Only the first will take effect.");
break;
}
strncpy(attachtype,optarg,255);
addflag("t");
dotype++;
break;
case 'a' : if ( paranoid.noattach )
break;
if ( attachment ) {
p_err("Only one attachment can be specified. Only the first will take effect.");
break;
}
doattach++;
strncpy(attachpn,optarg,2048);
addflag("a");
break;
case 'u' : if ( paranoid.lockuid )
break;
romuid = atoi (optarg);
if ( romuid == 0 )
romuid = 65534;
break;
case 'U' : if ( paranoid.lockuid )
break;
mailuid = atoi (optarg);
break;
case 'g' : if ( paranoid.lockgid )
break;
romgid = atoi (optarg);
if ( romgid == 0 )
romgid = 65534;
break;
case 'G' : if ( paranoid.lockgid )
break;
mailgid = atoi (optarg);
break;
case 'C' : if ( paranoid.chroot )
break;
if ( geteuid() != 0 ) {
p_err("Need to be root to use chroot()\nRunning without chroot()\n");
break;
}
if ( optarg ) {
strncpy(chrootdir,optarg,255);
}
dochroot=1;
addflag("C");
break;
case 'R' : if ( !replyrate ) {
if ( optarg )
replyrate=atol(optarg);
else
replyrate=REPLYRATE;
addflag("R");
}
break;
default:
help();
}
else
break;
}
pipe(pfd);
pipe(cpfd);
switch ( cpid=fork() ) {
case 0: close(0);
close(pfd[1]);
dup2(pfd[0],0);
if ( getuid() == 0 ) {
if ( mailgid )
setgid(mailgid);
if ( mailuid )
setuid(mailuid);
} else {
setgid(getgid());
setuid(getuid());
}
/* Stupid, but effective, why to prevent errors from
* the MTA
* It will only start when we tell it to.
*/
fcntl(cpfd[0],F_SETFL, O_NONBLOCK);
tmpbuf=calloc(2,1);
while ( read(cpfd[0],tmpbuf,1) < 1 ) {
sleep(1);
}
if ( tmpbuf[0] == 'X' ) {
exit(0);
}
free(tmpbuf);
execl("/usr/sbin/sendmail","/usr/sbin/sendmail","-bm","-t",NULL);
printf("Exec error on child: %d\n",errno);
break;
case -1:
diedie(2);
break;
default:
break;
}
if ( forwardto[0] != 0 ) {
pipe(dfd);
pipe(cdfd);
switch ( dpid=fork() ) {
case 0: close(0);
close(dfd[1]);
dup2(dfd[0],0);
if ( getuid() == 0 ) {
if ( mailgid )
setgid(mailgid);
if ( mailuid )
setuid(mailuid);
} else {
setgid(getgid());
setuid(getuid());
}
/* Stupid, but effective, why to prevent errors from
* the MTA
* It will only start when we tell it to.
*/
fcntl(cdfd[0],F_SETFL, O_NONBLOCK);
tmpbuf=calloc(2,1);
while ( (rread = read(cdfd[0],tmpbuf,1)) < 1 ) {
if ( (rread < 0) &&
(errno == EINTR ||
errno == EAGAIN ||
errno == EWOULDBLOCK)
)
sleep(1);
else
exit(0);
}
if ( tmpbuf[0] == 'X' ) {
exit(0);
}
free(tmpbuf);
execl("/usr/sbin/sendmail","/usr/sbin/sendmail","-bm",forwardto,NULL);
printf("Exec error on child: %d\n",errno);
break;
case -1:
diedie(2);
break;
default:
break;
}
}
pwent=getpwuid(getuid());
/* Define ratefile basename */
sprintf(ratefile,".uid-%u",getuid());
/* Try opening it on the users homedir */
snprintf(ratefn,255,"%s/.rom_rates",pwent->pw_dir);
snprintf(etcratefile,255,"%s/.rates/%s",ROMDIR,ratefile);
if (!stat(ratefn,&cstatbuf)) {
/* Only accept if it's a regular file */
if ( S_ISREG(cstatbuf.st_mode) ) {
ratefd=fopen(ratefn,"a+");
rewind(ratefd);
needrateopen=1;
}
}
if ( !ratefd && stat(etcratefile,&cstatbuf) ) {
ratefd=fopen(ratefn,"w");
needrateopen=2;
if ( geteuid() == 0 )
fchown(fileno(ratefd),getuid(),getgid());
}
/* Let's copy the rates file from the user homedir, if it exists */
sprintf(igfile,".uid-%u",getuid());
snprintf(cinfn,255,"%s/.rom_ignores",pwent->pw_dir);
/* Opening the .rom_ignores file on homedir */
if (!stat(cinfn,&cstatbuf)) {
/* Only accept if it's a regular file */
if ( S_ISREG(cstatbuf.st_mode) ) {
igfd=fopen(cinfn,"r");
}
}
/**/
if ( dochroot )
if ( chroot(chrootdir) == 0 ) {
chdir("/");
didchroot=1;
}
/* Dropping privileges is root */
if ( geteuid() == 0 ) {
if ( getuid() == 0 ) {
setgid(romgid);
setuid(romuid);
} else {
setgid(getgid());
setuid(getuid());
}
}
header=calloc(1,1);
body=calloc(1,1);
while (!feof(stdin)) {
memset(instr,0,2049);
fgets(instr,2048,stdin);
if ( feof(stdin) )
break;
if ( strlen(instr) == 1 ) {
isbody=1;
} else {
if ( ( strlen(instr) == 2 ) &&
( instr[0] == '\r' ) &&
( instr[1] == '\n' ) )
isbody=1;
}
if (!isbody) {
header=realloc(header,headerlen+strlen(instr)+1);
headerlen += strlen(instr);
strcat(header,instr);
header[headerlen]=0;
} else {
body=realloc(body,bodylen+strlen(instr)+1);
bodylen += strlen(instr);
strcat(body,instr);
body[bodylen]=0;
}
}
uheader=calloc(headerlen+1,1);
uheaderlen=unfold(header,uheader,headerlen);
hdr=uheader;
while ( (unsigned long int) hdr < ( (unsigned long int) uheader + uheaderlen ) ) {
unsigned int idx = 0;
memset(instr,0,2049);
while ( 1 ) {
if ( (unsigned long int) hdr > ( (unsigned long int) uheader + uheaderlen ) )
break;
instr[idx++]=hdr[0];
if ( hdr[0] == '\n' ) {
hdr++;
break;
}
hdr++;
}
isfield=1;
fname=calloc(strlen(instr) + 2,1);
fdata=calloc(strlen(instr) + 2,1);
fni=fdi=0;
for (i=0;i<strlen(instr)-1;i++) {
if ( instr[i] == 0 ) {
break;
}
if ( ( instr[i] == ' ' ) ||
( instr[i] == ' ' ) ) {
if ( isfield == 1 ) {
isfield=0;
continue;
}
}
if ( isfield )
fname[fni++]=instr[i];
else
fdata[fdi++]=instr[i];
}
if ( !rom_strcasecmp ( fname, "SENDER:" ) )
if ( sender[0] == 0 )
strncpy(sender,fdata,sizeof(sender)-1);
if ( !rom_strcasecmp ( fname, "FROM:" ) )
if ( from[0] == 0 )
strncpy(from,fdata,sizeof(from)-1);
if ( !rom_strcasecmp ( fname, "REPLY-TO:" ) )
if ( replyto[0] == 0 )
strncpy(replyto,fdata,sizeof(replyto)-1);
if ( !rom_strcasecmp (fname, "MESSAGE-ID:" ) )
if ( inreply[0] == 0 )
strncpy(inreply,fdata,sizeof(inreply)-1);
if ( !rom_strcasecmp (fname, "SUBJECT:" ) )
if ( subject[0] == 0 )
strncpy(subject,fdata,sizeof(subject)-1);
if ( !rom_strcasecmp (fname, "PRECEDENCE:" ) )
if ( subject[0] == 0 )
strncpy(precedence,fdata,sizeof(precedence)-1);
}
if ( fname ) {
free(fname);
fname=0;
}
if ( fdata ) {
free(fdata);
fdata=0;
}
if ( ( strlen(sender) + strlen(from) + strlen(replyto) ) == 0 ) {
diedie(1);
}
if ( strlen(replyto) )
strncpy(dest,replyto,2048);
else if ( strlen(from) )
strncpy(dest,from,2048);
else
strncpy(dest,sender,2048);
if ( !rom_strcasecmp(precedence,"list") ||
!rom_strcasecmp(precedence,"bulk") ) {
/* We should not reply to list or bulk messages */
/* But we should still deliver them if asked to */
p_log("Not replying to \"%s\": list or bulk message",dest);
}
if ( replyrate ) {
if ( !check_and_update_rate(extract_addr(dest)) ) {
/* Did not pass the rate test */
diedie(0);
}
}
if (!valid_emailaddress(dest)) {
/* We should not reply to invalid e-mail addresses */
/* But lets deliver it is asked to */
p_log("Not replying to invalid e-mail address \"%s\"\n",dest);
}
/* If we are ignoring, we should not reply, but should deliver
* the original message to -d if asked to
* The same is valid for list and bulk messages
*/
if ( !check_ignores(extract_addr(dest)) &&
rom_strcasecmp(precedence,"list") &&
rom_strcasecmp(precedence,"bulk") &&
valid_emailaddress(dest)
) {
p_log("Auto-reply to \"%s\" (flags: \"%s\")",dest,usedflags);
write(cpfd[1],"1",1);
tmpbuf=calloc(strlen(rom_from)+28,1);
sprintf(tmpbuf,"MIME-Version: 1.0\nFrom: %s\n",rom_from);
write(pfd[1],tmpbuf,strlen(tmpbuf));
free(tmpbuf);
tmpbuf=calloc(strlen(dest)+20,1);
sprintf(tmpbuf,"To: %s\n",dest);
write(pfd[1],tmpbuf,strlen(tmpbuf));
free(tmpbuf);
tmpbuf=calloc(strlen(rom_version)+50,1);
sprintf(tmpbuf,"X-Mailer: Reply-o-Matic v%s\nPrecedence: bulk\n",rom_version);
write(pfd[1],tmpbuf,strlen(tmpbuf));
free(tmpbuf);
if ( inreply[0] != 0 ) {
tmpbuf=calloc((strlen(inreply)*2)+50,1);
sprintf(tmpbuf,"References: %s\nIn-Reply-To: %s\n",inreply,inreply);
write(pfd[1],tmpbuf,strlen(tmpbuf));
free(tmpbuf);
}
if ( replyaddr[0] != 0 ) {
tmpbuf=calloc(strlen(replyaddr)+20,1);
sprintf(tmpbuf,"Reply-To: %s\n",replyaddr);
write(pfd[1],tmpbuf,strlen(tmpbuf));
free(tmpbuf);
}
if ( ccto[0] != 0 ) {
p_log("Sending copy (-c) to \"%s\"",ccto);
tmpbuf=calloc(strlen(ccto)+20,1);
sprintf(tmpbuf,"Bcc: %s\n",ccto);
write(pfd[1],tmpbuf,strlen(tmpbuf));
free(tmpbuf);
}
if ( doattach && validinclude(attachpn) ) {
attachment=getattach(attachpn);
if ( attachment ) {
char *tname;
tname=strdup(attachpn);
attachfn=(char *) strdup(basename(tname));
free(tname);
} else
doattach=0;
}
if ( (includeorig == 2) || doattach) {
tmpbuf=calloc(strlen(boundary)+50,1);
sprintf(tmpbuf,"Content-Type: multipart/mixed; boundary=\"%s\"\n",boundary);
write(pfd[1],tmpbuf,strlen(tmpbuf));
free(tmpbuf);
}
if ( !( dosubject & 2 ) || ( subject[0] == 0 ) ) {
tmpbuf=calloc(strlen(rom_subject)+20,1);
sprintf(tmpbuf,"Subject: %s\n\n",rom_subject);
write(pfd[1],tmpbuf,strlen(tmpbuf));
free(tmpbuf);
} else {
tmpbuf=calloc(strlen(subject)+20,1);
sprintf(tmpbuf,"Subject: RE: %s\n\n",subject);
write(pfd[1],tmpbuf,strlen(tmpbuf));
free(tmpbuf);
}
if ( includeorig == 2 || doattach ) {
tmpbuf=calloc(strlen(boundary)+20,1);
sprintf(tmpbuf,"\n--%s\n",boundary);
write(pfd[1],tmpbuf,strlen(tmpbuf));
write(pfd[1],"\n",1);
free(tmpbuf);
}
if ( dogetbody && validinclude(bodyfile) )
rom_body=getbody(bodyfile);
if ( !rom_body )
write(pfd[1],default_body,strlen(default_body));
else
write(pfd[1],rom_body,strlen(rom_body));
if ( includeorig == 1 ) {
write(pfd[1],data_follows,strlen(data_follows));
write(pfd[1],header,strlen(header));
}
if ( doattach && attachment ) {
tmpbuf=calloc(strlen(boundary)+20,1);
sprintf(tmpbuf,"\n--%s\n",boundary);
write(pfd[1],tmpbuf,strlen(tmpbuf));
free(tmpbuf);
if ( !attachfn )
attachfn=(char *) strdup("NO_NAME");
p_log("Sending attachment \"%s\"",attachfn);
if ( attachtype[0] == 0 )
getmimetype(attachpn,attachtype,2048);
if ( attachtype[0] == 0 )
sprintf(attachtype,"application/octet-stream");
tmpbuf=calloc(strlen(attachtype)+(strlen(attachfn) * 2)+160,1);
sprintf(tmpbuf,"Content-Type: %s; name=\"%s\"\nContent-Disposition: attachment; filename=\"%s\"\nContent-Transfer-Encoding: base64\n\n",attachtype,attachfn,attachfn);
write(pfd[1],tmpbuf,strlen(tmpbuf));
free(tmpbuf);
write(pfd[1],attachment,strlen(attachment));
tmpbuf=calloc(strlen(boundary)+20,1);
sprintf(tmpbuf,"\n\n--%s--\n",boundary);
write(pfd[1],tmpbuf,strlen(tmpbuf));
free(tmpbuf);
free(attachfn);
}
if ( includeorig == 2 ) {
tmpbuf=calloc(strlen(boundary)+20,1);
sprintf(tmpbuf,"\n--%s\n",boundary);
write(pfd[1],tmpbuf,strlen(tmpbuf));
write(pfd[1],"Content-Type: message/rfc822\n\n",30);
write(pfd[1],header,strlen(header));
write(pfd[1],body,strlen(body));
sprintf(tmpbuf,"\n\n--%s--\n",boundary);
write(pfd[1],tmpbuf,strlen(tmpbuf));
free(tmpbuf);
}
write(pfd[1],".\n",2);
waitpid(cpid,NULL,0);
}
if ( forwardto[0] != 0 ) {
p_log("Delivering original message to \"%s\"",forwardto);
/* fork() */
write(cdfd[1],"1",1);
write(dfd[1],header,strlen(header));
#define tmpmsg "X-Note: Delivered by Reply-o-Matic after being replied\n"
write(dfd[1],tmpmsg,strlen(tmpmsg));
tmpbuf=calloc(256,1);
snprintf(tmpbuf,255,"X-Delivery-Agent: Reply-O-Matic v%s\n",rom_version);
write(dfd[1],tmpbuf,strlen(tmpbuf));
write(dfd[1],body,strlen(body));
free(tmpbuf);
write(dfd[1],".\n",2);
waitpid(dpid,NULL,0);
}
diedie(0);
return 0;
}
syntax highlighted by Code2HTML, v. 0.9.1