pkg://icqmail-2.0a1.tar.gz:49734/
icqmail-2.0a1/
src/icqmail.cc
downloads
/*
* $Id: icqmail.cc,v 1.27 2000/01/15 02:34:46 lord Exp $
*
* ICQMAIL Simple ICQ->EMail Gateway
* Vadim Zaliva <lord@crocodile.org>
* http://www.crocodile.org/
*
* Copyright (C) 1999 Vadim Zaliva
*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#ifndef DEBUG
// not to generate assert() code.
# define NDEBUG
#endif
#include <stdio.h>
#include <ctype.h>
#include <assert.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <sys/socket.h> /* for AF_INET */
#include <netdb.h> /* for gethostbyaddr */
/* Guile */
#include <libguile.h>
#include <guile/gh.h>
/* Our headers */
#include "cfg.hh"
#include "log.hh"
#include "icqmail.hh"
/* icqlib */
#include <icq.h>
#include "icqbyteorder.h"
#include <iostream>
#include <fstream>
extern "C" const char *icq_ConvertStatus2Str(unsigned int status);
const int IcqMail::CONNECT_DELAY = 3 ;
const int IcqMail::LOGIN_DELAY = 30 ;
const int IcqMail::KEEP_ALIVE_DELAY = 120 ;
const int IcqMail::MAXMACRONAME = 128 ;
const char *IcqMail::DEFAULT_CFG_NAME = "icqmail.cfg" ;
const char *IcqMail::ICQMAILCOPYRIGHT = "IcqMail v2.0a1 by Vadim Zaliva <lord@crocodile.org>" ;
int IcqMail::getICQLogLevel()
{
return icqloglevel;
}
int IcqMail::run(void (*init_hooks)())
{
if(call_scripts)
{
char *tmp=dup(scriptfile.c_str());
gh_eval_file_with_standard_handler(tmp); //TODO: const missing, check if fixed in newer release.
call_script_procedure("icqmail-cfg-file-loaded", cfg);
delete tmp;
}
while(done!=EXIT)
{
if(login_attempt==login_attempts)
{
*log << Log::ERR << "Can't log to the server: " << server << ":" << port << ". (attempted " << login_attempt << " times). Exiting." << Log::SEND;
return 8;
} else if(login_attempt++)
{
*log << Log::DEBUG << "Pausing for " << CONNECT_DELAY << " seconds before next try." << Log::SEND;
sleep(CONNECT_DELAY);
}
if(done==RECONNECT)
{
*log << Log::DEBUG << "Reconnecting to server." << Log::SEND;
}
if(do_connect(server, port)!=-1)
{
(*init_hooks)();
login(status, uin, password);
doICQProtocol();
} else
{
*log << Log::ERR << "Can't connect to the server: " << server << ":" << port << " failed." << Log::SEND;
}
}
return 0;
}
void IcqMail::doLog(time_t time, unsigned char level, const char *str)
{
if(str)
{
*log << Log::DEBUG << "ICQLOG:" << level << ": ";
const char *p=str;
while(*p)
{
if(*p=='\n')
*log << "\\n";
else
if(*p=='\r')
*log << "\\r";
else
*log << *p;
p++;
}
*log << Log::SEND;
}
}
char *IcqMail::loadFile(const DwString &fname) const
{
struct stat st;
unsigned long sz;
char *res ;
if(stat(fname.c_str(), &st))
{
*log << Log::ERR << "Error loading file: " << fname << ". Can't stat." << Log::SEND;
return NULL;
}
sz=st.st_size;
// To avoid attempt to load very big files.
if(sz>64*1024)
{
*log << Log::ERR << "Error loading file: " << fname << " . File too big!" << Log::SEND;
return NULL;
}
try
{
res=new char[sz];
} catch(bad_alloc)
{
*log << Log::ERR << "Error loading file: " << fname << ". Can't allocate memory." << Log::SEND;
return NULL;
}
ifstream f(fname.c_str(),ios::in|ios::binary);
if(!f)
{
*log << Log::ERR << "Error loading file: " << fname << ". Can't open." << Log::SEND;
delete res;
return NULL;
}
f.read(res, sz);
if(!f)
{
*log << Log::ERR << "Error loading file: " << fname << ". Read error." << Log::SEND ;
f.close();
delete res;
return NULL;
}
f.close();
res[sz]='\0';
return res;
}
unsigned int IcqMail::str2uin(const DwString data, uin_t *res)
{
const char *start = data.c_str();
char *end;
if(start == NULL || res==NULL)
return 1; /* bad param */
*res=strtol(start, &end, 10);
if((*res==LONG_MIN || *res==LONG_MAX) && errno==ERANGE)
return 1; /* over/under flow */
return !(*end=='\0' && *start!='\0');
}
void IcqMail::loginOK()
{
MonitorList::const_iterator i;
login_ok = 1;
login_attempt = 0;
*log << Log::DEBUG << "Login OK." << Log::SEND;
icq_ContClear();
for(i=monitorlist.begin();i!=monitorlist.end();++i)
{
uin_t u=(*i).first;
icq_ContAddUser(u);
*log << Log::DEBUG << "Will monitor status changes of UIN " << u << Log::SEND;
};
icq_SendContactList();
}
void IcqMail::login(unsigned long status,
uin_t uin,
const DwString pass)
{
*log << Log::DEBUG << "Logging in as: " << uin << Log::SEND;
icq_Init(uin, pass.c_str());
icq_Login(status);
}
int IcqMail::do_connect(const DwString &server, int port)
{
*log << Log::DEBUG << "Connecting to: " << server << ":" << port << Log::SEND;
icq_UnsetProxy();
return icq_Connect(server.c_str(),port);
}
/**
* Returns text representation of this UIN.
* Uses aliases to resolve it.
*/
DwString IcqMail::uin2name(uin_t uin)
{
char res[2048];
sprintf(res,"%lu",(unsigned long)uin);
try
{
return aliases->find(res,0);
} catch (CfgNotFoundException &ex)
{
sprintf(res,"ICQ UIN %lu",(unsigned long)uin);
return res;
}
}
/**
* Returns option Reply-To overide value address for this UIN.
* or NULL if not found.
* Uses aliases to resolve it.
* returned DwString is allocated with new
*/
DwString IcqMail::uin2replyto(uin_t uin) throw(CfgNotFoundException)
{
char res[32];
sprintf(res,"%ld",(long)uin);
return aliases->find(res,1);
}
void IcqMail::icqIP2str(unsigned long ip,char *res)
{
struct hostent *hp;
unsigned char *bip;
hp=gethostbyaddr((const char *)&ip,sizeof(ip),AF_INET);
if(ip==-1)
{
strcpy(res,"unknown");
return;
}
if(hp && hp->h_name)
{
strcpy(res,hp->h_name);
return;
}
ip=htonl(ip); /* to network order */
bip=(unsigned char *)&ip;
sprintf(res,"%d.%d.%d.%d",
bip[0],
bip[1],
bip[2],
bip[3]
);
}
void IcqMail::emailStatusChange(uin_t uin,
status_change_type type,
unsigned long status,
unsigned long ip,
unsigned long port,
unsigned long real_ip
)
{
DwMessage msg;
DwHeaders &headers = msg.Headers();
Cfg *data = new Cfg();
DwString alias;
DwString replyto;
bool has_replyto;
const char *templ;
alias = uin2name(uin);
try
{
replyto = uin2replyto(uin);
has_replyto = true;
} catch(CfgNotFoundException &ex)
{
has_replyto = false;
}
switch(type)
{
case status_ch_online:
{
char xip[MAXHOSTNAMELEN],xrealip[MAXHOSTNAMELEN];
icqIP2str(ip,xip);
icqIP2str(real_ip,xrealip);
data->add("USER" , alias );
data->add("IP" , xip );
data->add("REALIP" , xrealip);
data->add("PORT", port);
data->add("STATUS" , icq_ConvertStatus2Str(status));
templ=online_template;
break;
}
case status_ch_offline:
data->add("USER" , alias);
templ=offline_template;
break;
case status_ch_changed:
data->add("USER" , alias);
data->add("STATUS" , icq_ConvertStatus2Str(status));
templ=status_change_template;
break;
}
if(call_script_procedure("icqmail-status-changed", data))
{
*log << Log::INFO << "Status Processed by script. No email will be sent." << Log::SEND;
return;
}
char uins[32];
sprintf(uins,"%ld",(long)uin);
DwMailbox *from_mb = new DwMailbox(uins+returnsuffix);
from_mb->Parse();
from_mb->SetFullName(alias);
from_mb->Assemble();
headers.From().Add(from_mb); // headers will delete it
if(has_replyto)
{
DwMailbox *reply_to_mb = new DwMailbox(replyto);
reply_to_mb->Parse();
reply_to_mb->SetFullName(alias);
reply_to_mb->Assemble();
headers.ReplyTo().Add(reply_to_mb); // headers will delete it
}
DwMailbox *to_mb = new DwMailbox(email);
to_mb->Parse();
headers.To().Add(to_mb); // headers will delete it
size_t tmplen = strlen(templ)+alias.length()+128;
char *tmp=new char[tmplen];
substitute(templ, tmp, tmplen, data);
if(sendassubj)
{
headers.Subject().FromString(tmp); //TODO: remove line feeds
} else
{
assert(subject!=NULL);
headers.Subject().FromString(subject);
msg.Body().FromString(tmp);
}
delete tmp;
// --- Actuall sending --
msg.Assemble();
try
{
sendMessage(&msg);
} catch(IcqMailException &ex)
{
*log << Log::ERR << "Error sending status change. Reason: " << ex.reason << Log::SEND;
return;
}
}
void IcqMail::sendEmail(uin_t uin,
const char *name,
const char *xemail,
int isurl,
int is_email_express,
const char *msg0)
{
DwString alias, replyto;
bool has_replyto;
DwMessage msg;
DwHeaders &headers = msg.Headers();
*log << Log::DEBUG << "Sending message: \"" << msg0 << "\"" << Log::SEND;
if(is_email_express)
{
char cmd[2048];
assert(xemail!=NULL);
if(name)
sprintf(cmd,"%s <%s>", name, xemail);
else
sprintf(cmd,"%s", xemail);
if(name==NULL)
alias="UNKNOWN";
else
alias=name;
has_replyto = false;
} else
{
try
{
replyto = uin2replyto(uin);
has_replyto = true;
} catch(CfgNotFoundException &ex)
{
has_replyto = false;
}
alias = uin2name(uin);
}
*log << Log::INFO << "New " << (isurl?"URL":"Message") << " from " << alias << Log::SEND;
// Set FROM, Reply-To
DwMailbox *from_mb;
if(is_email_express)
{
from_mb = new DwMailbox(xemail);
from_mb->Parse();
if(!from_mb->IsValid())
{
*log << Log::WARNING << "Error decoding From address of EmailExpress message. Message will be ignored." << Log::SEND;
delete from_mb;
return;
}
} else
{
char uins[32];
sprintf(uins,"%ld",(long)uin);
from_mb = new DwMailbox(uins+returnsuffix);
from_mb->Parse();
if(has_replyto)
{
DwMailbox *reply_to_mb = new DwMailbox(replyto);
reply_to_mb->Parse();
reply_to_mb->SetFullName(alias);
reply_to_mb->Assemble();
headers.ReplyTo().Add(reply_to_mb); // headers will delete it
}
}
from_mb->SetFullName(alias);
from_mb->Assemble();
headers.From().Add(from_mb); // headers will delete it
DwMailbox *to_mb = new DwMailbox(email);
to_mb->Parse();
headers.To().Add(to_mb); // headers will delete it
if(sendassubj)
{
assert(msg!=NULL);
headers.Subject().FromString(msg0); //TODO: remove line feeds
} else
{
assert(subject!=NULL);
headers.Subject().FromString(subject);
assert(msg0!=NULL);
msg.Body().FromString(msg0);
}
// --- Actuall sending --
msg.Assemble();
try
{
sendMessage(&msg);
} catch(IcqMailException &ex)
{
*log << Log::ERR << "Error sending email message. Reason: " << ex.reason << Log::SEND;
return;
}
if(send_replymsg)
{
if(is_email_express)
{
// Send back confirmation via email.
DwMessage msg;
DwHeaders &headers = msg.Headers();
char uins[32];
sprintf(uins,"%ld",(long)IcqMail::uin);
// From my UIN
from_mb = new DwMailbox(uins+returnsuffix);
from_mb->Parse();
headers.From().Add(from_mb); // headers will delete it
// Reply to my email TODO: maybe to UIN is better?
DwMailbox *reply_to_mb = new DwMailbox(IcqMail::email);
reply_to_mb->Parse();
headers.ReplyTo().Add(reply_to_mb); // headers will delete it
// To sender
DwMailbox *to_mb = new DwMailbox(email);
to_mb->Parse();
to_mb->SetFullName(name);
to_mb->Assemble();
headers.To().Add(to_mb); // headers will delete it
// hardcoded subject. TODO: to cfg
headers.Subject().FromString("Re: Your ICQ message");
// message from cfg.
msg.Body().FromString(replymsg);
msg.Assemble();
try
{
sendMessage(&msg);
} catch(IcqMailException &ex)
{
*log << Log::ERR << "Error sending email return receipt. Reason: " << ex.reason << Log::SEND;
}
} else
{
icq_SendMessage(uin, replymsg.c_str());
}
}
}
void IcqMail::processURL(uin_t uin,
unsigned char hour,
unsigned char minute,
unsigned char day,
unsigned char month,
unsigned short year,
const char *url,
const char *descr)
{
int tmplen = strlen(url)+strlen(descr)+strlen(url_template)+80;
char *tmp = new char[tmplen];
Cfg *data = new Cfg();
data->add("URL", url);
data->add("DESCRIPTION", descr);
data->add("UIN" , uin );
data->add_fmt_ulong("HOUR" , hour , 2);
data->add_fmt_ulong("MINUTE" , minute , 2);
data->add_fmt_ulong("DAY" , day , 2);
data->add_fmt_ulong("MONTH" , month , 2);
data->add_fmt_ulong("YEAR" , year , 4);
if(call_script_procedure("icqmail-new-URL", data))
{
*log << Log::INFO << "URL Processed by script. No email will be sent." << Log::SEND;
} else
{
sendEmail(uin,
NULL, NULL,
1,
0,
substitute(url_template,tmp,tmplen,data)
);
}
delete data;
delete tmp;
}
/**
* This function returns true if messase was sent via
* WWWPager of EmailExpress.
*
* I do not have sure algorithm here, so I use
* some heuristics.
*/
bool IcqMail::isEmailExpress(const char *msg)
{
unsigned char *c=(unsigned char *)strchr(msg,0xC0);
if(c==NULL)
return false;
if(*(c+1)==0xC0)
if(*(c+2)==0xC0)
return true;
return false;
}
/**
* This function returns 1 if messase containing
* list of contacts.
*
* I do not have sure algorihtm here, so I use
* some heuristics.
*/
bool IcqMail::isContacts(const char *msg)
{
unsigned char *c=(unsigned char *)strchr(msg,0xC0);
if(c==NULL)
return false;
else
return !isEmailExpress(msg);
}
void IcqMail::processMessage(uin_t uin,
unsigned char hour,
unsigned char minute,
unsigned char day,
unsigned char month,
unsigned short year,
const char *msg)
{
char *tmp;
size_t tmplen;
Cfg *data = new Cfg();
data->add("UIN" , uin );
data->add_fmt_ulong("HOUR" , hour , 2);
data->add_fmt_ulong("MINUTE" , minute , 2);
data->add_fmt_ulong("DAY" , day , 2);
data->add_fmt_ulong("MONTH" , month , 2);
data->add_fmt_ulong("YEAR" , year , 4);
if(isEmailExpress(msg))
{
char *name, *email, *message;
char *msgCopy;
char delim[2]={ 0300, 0};
tmp = new char[tmplen = (strlen(msg)+strlen(email_template)+4096)];
msgCopy = dup(msg);
name = strtok(msgCopy, delim);
email = strtok(NULL, delim);
(void)strtok(NULL, delim);
message = strtok(NULL, delim);
if(!name || !email || !message)
{
*log << Log::ERR << "Error decoding icoming email express message from " << uin << ". Message discarded." << Log::SEND;
delete msgCopy;
delete data;
delete tmp;
return;
}
data->add("NAME" , name );
data->add("EMAIL", email );
data->add("TEXT" , message);
if(call_script_procedure("icqmail-new-email-express", data))
{
*log << Log::INFO << "EmailExpress message processed by script. No email will be sent." << Log::SEND;
} else
{
sendEmail(uin, name, email,
0, 1,
substitute(email_template,tmp,tmplen,data)
);
}
delete msgCopy;
}
else if(isContacts(msg))
{
/* Contacts message */
char *list, *msgCopy;
int ncontacts;
char delim[2]={ 0300, 0};
msgCopy = dup(msg);
tmp = strtok(msgCopy, delim);
if(tmp == NULL)
{
*log << Log::ERR << "Error decoding icoming contacts message from " << uin << ". Message discarded." << Log::SEND;
return;
}
ncontacts = atoi(tmp);
if(ncontacts)
{
uin_t u;
char *list;
char *p;
int i;
int total=0;
p=list=new char[(2048+strlen(one_contact_template))*ncontacts]; /* guess list size. extra 2K per contact */
*list='\0';
for(i = 1; i <= ncontacts; i++)
{
char *cuin, *cname;
cuin = strtok(NULL, delim);
cname = strtok(NULL, delim);
if(!cuin || !cname)
{
*log << Log::ERR << "Unexpected end of contacts list from " << uin << ". Only first " << (i-1) << " contacts delivered." << Log::SEND;
break;
}
if(!str2uin(cuin, &u))
{
Cfg *element= new Cfg();
element->add("SENDERUIN" , uin );
element->add("NUMBER" , i );
element->add("UIN" , u );
element->add("NAME" , cname);
if(call_script_procedure("icqmail-new-contact", element))
{
*log << Log::INFO << "Contacts entry processed by script. It will not be sent by email." << Log::SEND;
} else
{
tmp = new char[tmplen = (strlen(cname)+strlen(one_contact_template)+80)];
substitute(one_contact_template,tmp,tmplen,element);
p+=sprintf(p, "%s", tmp);
delete tmp;
total++;
}
delete element;
}
else
*log << Log::ERR << "Ignoring invalid contact list entry with UIN: " << cuin << Log::SEND;
}
delete msgCopy;
data->add("TOTAL" , total);
data->add("LIST" , list );
tmp = new char[tmplen = (strlen(list)+strlen(contacts_template)+80)];
delete list;
if(!total)
{
*log << Log::INFO << "All contacts in this message have being processed by script. No email will be sent." << Log::SEND;
} else
{
sendEmail(uin,
NULL, NULL,
0, 0, substitute(contacts_template,tmp,tmplen,data));
}
} else
{
delete msgCopy;
*log << Log::ERR << "Empty contacats message or invalid number of contacts from " << uin << ". Message discarded." << Log::SEND;
}
} else
{
/* normal message */
tmp = new char[tmplen = (strlen(msg)+strlen(message_template)+4096)];
data->add("TEXT" , msg);
if(call_script_procedure("icqmail-new-message", data))
{
*log << Log::INFO << "Regular message processed by script. No email will be sent." << Log::SEND;
} else
{
sendEmail(uin,
NULL, NULL,
0, 0,
substitute(message_template,tmp,tmplen,data));
}
}
delete data;
delete tmp;
}
int IcqMail::doICQProtocol()
{
fd_set rfds;
int sok=icq_GetSok();
struct timeval tv;
int time_to_ping = KEEP_ALIVE_DELAY ;
int time_to_login_timeout = LOGIN_DELAY ;
time_t start;
time_t end ;
done=NOT_YET;
while(!done)
{
FD_ZERO(&rfds);
FD_SET(sok, &rfds);
tv.tv_sec = time_to_ping;
if(!login_ok && tv.tv_sec>time_to_login_timeout)
tv.tv_sec = time_to_login_timeout;
tv.tv_usec = 0;
start = time(NULL);
if(select(sok+1, &rfds, NULL, NULL, &tv)==-1)
{
*log << Log::ERR << "Error in select!" << Log::SEND;
icq_Disconnect();
done=RECONNECT;
break;
}
end = time(NULL);
if(FD_ISSET(sok, &rfds))
icq_HandleServerResponse();
time_to_ping-=(end-start);
if(time_to_ping<=0)
{
// Before sending new ping
// let us check if we are still alive.
if((last_ack != (time_t)0) && ((time(NULL)-last_ack) > (2*KEEP_ALIVE_DELAY)))
{
*log << Log::DEBUG << "Missed KEEP_ALIVE Ack." << Log::SEND;
connectionLost();
} else
{
icq_KeepAlive();
time_to_ping=KEEP_ALIVE_DELAY;
}
}
if(!login_ok)
{
time_to_login_timeout-=(end-start);
if(time_to_login_timeout<=0)
{
*log << Log::ERR << "Login timeout. Disconnecting." << Log::SEND;
icq_Disconnect();
done=RECONNECT;
break;
}
}
}
return 0;
}
bool IcqMail::watching(uin_t uin, int mask)
{
MonitorList::const_iterator i;
i=monitorlist.find(uin);
if(i==monitorlist.end())
return false;
else
return ((*i).second)&mask;
}
void IcqMail::add_to_monitor_list(uin_t uin, int mask)
{
MonitorList::const_iterator i;
i=monitorlist.find(uin);
if(i==monitorlist.end())
monitorlist[uin]=mask;
else
monitorlist[uin]=mask|(((*i).second));
}
int IcqMail::loadTemplates()
{
int i;
const char *tmp;
/* should be same order as destinations */
char *names[]=
{
"MessageTemplate",
"URLTemplate",
"EmailMessageTemplate",
"ContactsTemplate",
"OneContactTemplate",
"OnlineTemplate",
"OfflineTemplate",
"StatusChangeTemplate"
};
/* should be same order as names */
const char ** destinations[]=
{
&message_template,
&url_template,
&email_template,
&contacts_template,
&one_contact_template,
&online_template,
&offline_template,
&status_change_template,
};
for(i=0;i<(sizeof(names)/sizeof(const char *));i++)
{
DwString tmp;
try
{
tmp=cfg->find(names[i], 0);
} catch(CfgNotFoundException &ex)
{
*log << Log::ERR << "Required CFG keyword '" << names[i] << "' is missing" << Log::SEND;
// free already loaded templated
for(int j=0;j<i;j++)
delete *(destinations[j]);
return 1;
}
if(!(*(destinations[i])=loadFile(tmp)))
{
// free already loaded templated
for(int j=0;j<i;j++)
delete *(destinations[j]);
return 1;
}
*log << Log::INFO << "Template file: " << names[i] << " loaded. Size: " << (unsigned long)(tmp.length()) << " bytes" << Log::SEND;
}
return 0;
}
int IcqMail::read_cfg_file(char *name)
{
cfg = new Cfg(name);
try
{
cfg->load();
} catch(CfgException &ex)
{
*log << Log::ERR << "Error reading cfg file: " << name << ". Reason: " << ex.reason << Log::SEND;
return 1;
} catch( ... )
{
*log << Log::ERR << "Error reading cfg file: " << name << Log::SEND;
return 1;
}
try
{
server = cfg->find("Server" , 0);
sendassubj = cfg->findBool("SendAsSubject",0);
if(sendassubj)
subject = cfg->find("Subject" , 0);
password = cfg->find("Password", 0);
//TODO: truncate password to first 8 chars
smtp_server = cfg->find("MailServer",0);
email = cfg->find("ForwardTo", 0);
port = cfg->findInt("Port",0);
login_attempts = cfg->findInt("LoginAttempts" ,0);
DwString tmp = cfg->find("UIN",0);
if(str2uin(tmp, &uin))
{
throw new CfgException("Invalid UIN: "+tmp);
}
tmp=cfg->find("ReturnAddressSuffix",0);
if(tmp[0]=='@')
returnsuffix=tmp;
else
returnsuffix="@"+tmp;
} catch(CfgNotFoundException &ex)
{
*log << Log::ERR << "Required CFG keyword '" << ex.key << "' is missing" << Log::SEND;
return 1;
} catch(CfgException &ex)
{
*log << Log::ERR << "Error getting CFG keyword '" << ex.reason << "': " << Log::SEND;
return 1;
}
/* optional params */
try
{
icqloglevel=cfg->findInt("LogLevel",0);
} catch(CfgException &ex)
{
icqloglevel = 0;
}
try
{
DwString status2=cfg->find("Status",0);
if(DwStrcasecmp(status2,"online")==0)
status = STATUS_ONLINE;
else if(DwStrcasecmp(status2,"invisible")==0)
status = STATUS_INVISIBLE;
else if(DwStrcasecmp(status2,"na")==0)
status = STATUS_NA;
else if(DwStrcasecmp(status2,"free_for_chat")==0)
status = STATUS_FREE_CHAT;
else if(DwStrcasecmp(status2,"occupied")==0)
status = STATUS_OCCUPIED;
else if(DwStrcasecmp(status2,"away")==0)
status = STATUS_AWAY;
else if(DwStrcasecmp(status2,"dnd")==0)
status = STATUS_DND;
else
{
*log << Log::WARNING << "Unknown status in CFG: '" << status2 << "'. Assuming ONLINE." << Log::SEND;
status = STATUS_ONLINE;
}
} catch(CfgNotFoundException &ex)
{
status = STATUS_ONLINE;
}
/* Notification lists management */
{
uin_t muin;
int i=0;
try
{
while(true)
{
DwString tmp=cfg->find("NotifyOnConnect",i++);
if(str2uin(tmp, &muin))
*log << Log::ERR << "Ignoring invalid UIN in NotifyOnConnect in cfg: " << tmp << Log::SEND;
else
add_to_monitor_list(muin,status_ch_online);
}
} catch(CfgNotFoundException &ex)
{
// no more left.
}
i=0;
try
{
while(true)
{
DwString tmp=cfg->find("NotifyOnDisconnect",i++);
if(str2uin(tmp, &muin))
*log << Log::ERR << "Ignoring invalid 'UIN' in NotifyOnDisconnect in cfg: " << tmp << Log::SEND;
else
add_to_monitor_list(muin,status_ch_offline);
}
} catch(CfgNotFoundException &ex)
{
// no more left.
}
i=0;
try
{
while(true)
{
DwString tmp=cfg->find("NotifyOnStatusChange",i++);
if(str2uin(tmp, &muin))
*log << Log::ERR << "Ignoring invalid 'UIN' in NotifyOnStatusChange in cfg: " << tmp << Log::SEND;
else
add_to_monitor_list(muin,status_ch_changed);
}
} catch(CfgNotFoundException &ex)
{
// no more left.
}
}
try
{
replymsg = cfg->find("ReturnReceipt", 0);
send_replymsg = true;
} catch(CfgNotFoundException &e)
{
send_replymsg = false;
}
/* Now let us load message templates */
if(loadTemplates())
return 1;
}
/**
* Even though icqlib have it own connection lost detection mechanism
* we implement our own, based on KEEP-ALIVE scheme.
*/
void IcqMail::serverAck(unsigned short seq)
{
last_ack = time(NULL);
}
void IcqMail::connectionLost()
{
*log << Log::DEBUG << "Connection to server lost." << Log::SEND;
done=RECONNECT;
}
void IcqMail::ctrlc(int st)
{
*log << Log::DEBUG << "Interrupted... Shutting down connection." << Log::SEND;
if(icq_Status!=STATUS_OFFLINE)
{
icq_Logout();
icq_Disconnect();
}
exit(16);
}
void IcqMail::RespondAuthReq (uin_t uin,
unsigned char hour,
unsigned char minute,
unsigned char day,
unsigned char month,
unsigned short year,
const char *nick,
const char *first,
const char *last,
const char *email,
const char *reason)
{
*log << Log::DEBUG << "Authorization request received from " << uin << ". Reason: " << reason << Log::SEND;
icq_SendAuthMsg(uin);
}
int IcqMail::start_daemon()
{
if(fork())
exit(0);
chdir("/");
umask(0);
(void) close(0);
(void) close(1);
(void) close(2);
(void) open("/", O_RDONLY);
(void) dup2(0, 1);
(void) dup2(0, 2);
setsid();
}
void IcqMail::userOnline(uin_t uin, unsigned long status, unsigned long ip, unsigned long port, unsigned long real_ip)
{
if(watching(uin,status_ch_online))
{
*log << Log::INFO << "User " << uin << " online." << Log::SEND;
#if ICQLIBVER <= 013
/*
* Following code is workaround for (reported) bug in icqlib<=0.13
* I an not sure if it is OK to nest macros, so it might look clumsy.
*/
port = htonl (port ); /* back to ICQ order */
port = icqtohl(port ); /* and back to correct host order */
ip = htonl (ip ); /* back to ICQ order */
ip = icqtohl(ip ); /* and back to correct host order */
real_ip = htonl (real_ip); /* back to ICQ order */
real_ip = icqtohl(real_ip); /* and back to correct host order */
#endif
emailStatusChange(uin, status_ch_online, status, ip, port, real_ip);
}
}
void IcqMail::userOffline(uin_t uin)
{
if(watching(uin,status_ch_offline))
{
*log << Log::INFO << "User " << uin << " offline." << Log::SEND;
emailStatusChange(uin, status_ch_offline, 0l, 0l, 0l, 0l);
}
}
void IcqMail::userStatusUpdate(uin_t uin, unsigned long status)
{
if(watching(uin,status_ch_changed))
{
*log << Log::INFO << "User " << uin << " new status " << status << Log::SEND;
emailStatusChange(uin, status_ch_changed, status, 0l, 0l, 0l);
}
}
/*
* returns TRUE if no more processing required
*/
int IcqMail::call_script_procedure(const char *name, Cfg *data)
{
if(!call_scripts)
{
return 0;
} else
{
char *name1;
SCM proc = scm_symbol_value0(name); // for some reason procedure name here is not 'const'
if(!proc)
return 0;
if(!gh_procedure_p(proc))
return 0;
return gh_scm2bool(
gh_call1(proc, data->alist())
);
}
}
IcqMail::IcqMail(int ac, char **av)
{
char *aliasesfile = NULL;
char *cfgfile = NULL;
char *logfile = NULL;
call_scripts = false ;
int param;
extern char* optarg;
extern int optind;
int error = 0;
cerr << ICQMAILCOPYRIGHT << "\n";
dodaemon=false;
while((param = getopt(ac, av, "df:l:a:s:")) != -1)
switch(param)
{
case 'd':
dodaemon = true;
break;
case 'a':
aliasesfile=dup(optarg);
break;
case 'f':
cfgfile=dup(optarg);
break;
case 'l':
logfile=dup(optarg);
break;
case 's':
scriptfile = optarg;
call_scripts = true;
break;
default:
cerr << "Usage: icqmail [-d] [-f cfgfile] [-a aliasesfile] [-l logfile] [-s scriptfile]\n";
}
log=new Log(logfile, "icqmail", dodaemon);
if (!cfgfile)
cfgfile=dup(DEFAULT_CFG_NAME);
if(read_cfg_file(cfgfile))
{
*log << Log::ERR << "Can't read cfg file '" << cfgfile << "' - exiting." << Log::SEND;
exit(2);
}
delete(cfgfile);
if(aliasesfile)
{
aliases=new Cfg(aliasesfile);
try
{
aliases->load();
} catch(CfgException &ex)
{
*log << Log::ERR << "Error reading aliases file: " << aliasesfile << ". Reason: " << ex.reason.c_str() << Log::SEND;
} catch( ... )
{
*log << Log::ERR << "Error reading aliases file: " << aliasesfile << Log::SEND;
}
}
if(dodaemon)
start_daemon();
login_ok = false;
login_attempt = 0;
last_ack = (time_t)0;
}
char *IcqMail::dup(const char *s)
{
if(s==NULL)
return NULL;
char *res=new char[strlen(s)+1];
strcpy(res,s);
return res;
}
void IcqMail::addRecipients(DwAddressList &lst, DwSmtpClient &smtp_client) throw(IcqMailException)
{
DwAddress *r=lst.FirstAddress();
while(r)
{
if(r->IsMailbox())
{
addOneRecipient((DwMailbox*)r, smtp_client);
}
else
{
throw IcqMailException("Non-mailbox addresses are not supported: " + r->AsString()); //TODO: group support
}
r=r->Next();
}
}
/**
* Adds one recepient to message.
*/
void IcqMail::addOneRecipient(DwMailbox *to, DwSmtpClient &smtp_client) throw(IcqMailException)
{
int rc;
//to->Parse();
if(!to->IsValid())
throw IcqMailException("Invalid address: " + to->AsString());
DwString domain = to->Domain();
DwString local = to->LocalPart();
DwString adr = local;
if(!domain.empty())
{
adr.append("@");
adr.append(domain);
}
//*log << Log::DEBUG << "Adding recepient: " << adr << Log::SEND;
rc = smtp_client.Rcpt(adr.c_str());
if(rc != 250 && rc != 251)
throw IcqMailSmtpException("RCPT Failed for: " + adr, rc);
}
/**
* Prepare DwString to be used as SMTP stream.
* This implements transparency procedure
* from section 4.5.2. of RFC-821.
*
* We are more flexible when it is described there
* and uderstand not only "CR LF . CR LF" but also
* "LF . (CR|LF)".
*
* TODO: Rewrite
*/
void IcqMail::quotePeriod(DwString &s)
{
size_t pos=0;
while((pos=s.find("\n.",pos))!=DwString::npos)
{
if(s.length()==(pos+1))
{
// unterminated '.' at last line
s.append(".\r\n");
return;
} else
{
char c=s.at(pos+2);
if(c=='\n' || c=='\n')
s.insert(pos+1,".");
pos+=2;
}
}
}
void IcqMail::sendMessage(DwMessage *msg) throw(IcqMailException)
{
int rc;
if(msg==NULL)
throw IcqMailException("Nothing to send.");
DwHeaders headers = msg->Headers();
// generate message id and date
headers.MessageId().CreateDefault();
headers.Date().FromCalendarTime(time(NULL));
// If X-Mailer field not present add it.
if(headers.FindField("X-Mailer")==NULL)
{
DwField *mailer_field=new DwField();
mailer_field->SetFieldNameStr("X-Mailer");
mailer_field->SetFieldBodyStr(ICQMAILCOPYRIGHT);
mailer_field->Assemble();
headers.AddField(mailer_field); // headers will delete it
}
DwSmtpClient smtp_client;
if(smtp_client.LastError())
throw IcqMailException("Error creating SMTP client.");
// Open connection
smtp_client.Open(smtp_server.c_str());
if(!smtp_client.IsOpen())
throw IcqMailException("Error connecting to SMTP server: "+smtp_server);
if((rc=smtp_client.Helo()) != 250)
throw IcqMailSmtpException("HELO failed.", rc);
// Send data
DwMailbox *from_mb=headers.From().FirstMailbox();
DwString domain = from_mb->Domain ();
DwString local = from_mb->LocalPart ();
DwString ident = local;
if(!domain.empty())
{
ident.append("@");
ident.append(domain);
}
if((rc = smtp_client.Mail(ident.c_str())) != 250)
throw IcqMailSmtpException("MAIL to "+ident+" failed.", rc);
addRecipients(headers.To(), smtp_client);
addRecipients(headers.Cc(), smtp_client);
DwField *bcc_field=headers.FindField("Bcc");
if(bcc_field)
{
addRecipients(headers.Bcc(), smtp_client);
headers.RemoveField(bcc_field); //Hide BCC
}
if((rc = smtp_client.Data()) != 354)
throw IcqMailSmtpException("DATA failed.", rc);
DwString body=msg->AsString();
quotePeriod(body);
rc = smtp_client.SendData(body);
// Restore BCC
if(bcc_field)
headers.AddField(bcc_field);
if(rc != 250 && rc != 251)
throw IcqMailSmtpException("DATA failed.", rc);
// Close connection
if(smtp_client.ReplyCode() != 0) //TODO understand this
if(smtp_client.Quit() != 221)
throw IcqMailException("QUIT failed.");
if(smtp_client.Close() != 0)
throw IcqMailException("Error closing connection.");
return;
}
//TODO: Use DwString class in this function to accumulate macro name.
const char * IcqMail::substitute(const char *from,
char *to,
size_t bufsize,
const Cfg *resolver
)
{
if(!resolver)
return NULL;
char name[MAXMACRONAME+1];
const char *f = from;
char *t = to;
char *name_p;
enum s_states
{
s_out,
s_quote,
s_name
} state = s_out;
do
{
if(state==s_name)
{
if(isalnum(*f) && (name_p-name)<MAXMACRONAME && *f)
{
*name_p++=*f++;
} else
{
*name_p='\0';
try
{
const char *res=resolver->find(name).c_str();
while(*res && (t-to+1)<bufsize)
*t++=*res++;
} catch (CfgNotFoundException &ex)
{
*log << Log::WARNING << "Macro: " << ex.key << " not found while processing template." << Log::SEND;
}
if(*f)
state=s_out;
else
break;
}
} else if(state==s_out)
{
if(*f=='$')
{
f++;
name_p = name;
state = s_name;
} else if(*f=='\\')
{
f++;
state = s_quote;
} else if(*f)
{
*t++=*f++;
} else
{
break;
}
} else if(state==s_quote)
{
if(*f)
{
*t++=*f++;
state = s_out;
} else
{
break;
}
}
} while((t-to+1)<bufsize);
*t='\0';
return to;
}