Filewatcher File Search
FTP Search
  
Directory 
  
Content Search 
   
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;
}


Results 1 - 1
Help - FTP Sites List - Software Dir.
Searching half a billion files worldwide
© 1997-2009 MARUHN Internet Solutions