// --------------------------------------------------------------------------- // - Mail.cpp - // - afnix:net module - mail class implementation - // --------------------------------------------------------------------------- // - This program is free software; you can redistribute it and/or modify - // - it provided that this copyright notice is kept intact. - // - - // - 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. In no event shall - // - the copyright holder be liable for any direct, indirect, incidental or - // - special damages arising in any way out of the use of this software. - // --------------------------------------------------------------------------- // - copyright (c) 1999-2007 amaury darsch - // --------------------------------------------------------------------------- #include "Mail.hpp" #include "Regex.hpp" #include "Vector.hpp" #include "System.hpp" #include "Integer.hpp" #include "QuarkZone.hpp" namespace afnix { // ------------------------------------------------------------------------- // - private section - // ------------------------------------------------------------------------- // default mta constant const String DEF_MTA_ADDR = System::hostname (); const t_word DEF_MTA_PORT = 25; // mta commands const String& MTA_CMD_HELO = "HELO "; const String& MTA_CMD_MAIL = "MAIL FROM: "; const String& MTA_CMD_RCPT = "RCPT TO: "; const String& MTA_CMD_QUIT = "QUIT"; const String& MTA_CMD_SMSG = "DATA"; const String& MTA_CMD_EMSG = "."; // header constants const String& MTA_HDR_FROM = "From: "; const String& MTA_HDR_TORL = "To: "; const String& MTA_HDR_CCRL = "Cc: "; const String& MTA_HDR_SUBJ = "Subject: "; // this procedure returns the host canonical name static String get_canonical_name (void) { String host = System::hostname (); Address addr = host; return addr.getcanon (); } // this procedure send a command to the mta and check the status code // if the status code is not valid, an exception is raised static void mta_send (TcpClient& mta, const String& cmd) { // send the command mta.writeln (cmd); // read the reply String reply = mta.readln (); // extract the status code Regex rgx ("($d$d$d)($N*)"); if (rgx == reply) { long code = rgx.getint (0); if ((code < 200) || (code > 399)) throw Exception ("mta-error", rgx.getstr (1)); } else { throw Exception ("mta-error", "invalid reply message", reply); } } // this procedure adds recipients to the recipients list static void mta_rcpt (TcpClient& mta, const Strvec& rl) { Regex rgx ("$N*($<<$a-+.@>+$>)"); long len = rl.length (); for (long i = 0; i < len; i++) { String addr = rl.get (i); String rcpt = "<"; if (rgx == addr) rcpt = rgx.getstr (0); else rcpt = rcpt + addr + '>'; mta_send (mta, MTA_CMD_RCPT + rcpt); } } // ------------------------------------------------------------------------- // - class section - // ------------------------------------------------------------------------- // create a default mail with no subject Mail::Mail (void) { // initialize the mta address d_addr = DEF_MTA_ADDR; d_port = DEF_MTA_PORT; // get host canonical name d_host = get_canonical_name (); // get the mail from address d_from = System::username () + '@' + d_host; // set no subject d_subj = "no subject"; } // return the class name String Mail::repr (void) const { return "Mail"; } // set the mta address void Mail::setaddr (const String& addr) { wrlock (); d_addr = addr.strip (); unlock (); } // get the mta address String Mail::getaddr (void) const { rdlock (); String addr = d_addr; unlock (); return addr; } // set the mta port void Mail::setport (const t_word port) { wrlock (); d_port = port; unlock (); } // get the mta port t_word Mail::getport (void) const { rdlock (); t_word port = d_port; unlock (); return port; } // add a recipient to the "to" list void Mail::addto (const String& rcpt) { wrlock (); Strvec data = Strvec::split (rcpt, ","); long len = data.length (); for (long i = 0; i < len; i++) { String addr = data.get (i); d_torl.add (addr.strip ()); } unlock (); } // add a recipient to the "cc" list void Mail::addcc (const String& rcpt) { wrlock (); Strvec data = Strvec::split (rcpt, ","); long len = data.length (); for (long i = 0; i < len; i++) { String addr = data.get (i); d_ccrl.add (addr.strip ()); } unlock (); } // add a recipient to the "bcc" list void Mail::addbcc (const String& rcpt) { wrlock (); Strvec data = Strvec::split (rcpt, ","); long len = data.length (); for (long i = 0; i < len; i++) { String addr = data.get (i); d_bcrl.add (addr.strip ()); } unlock (); } // set the mail subject void Mail::setsubj (const String& subj) { wrlock (); d_subj = subj; unlock (); } // add a line to the message buffer void Mail::addmsg (const String& data) { wrlock (); d_mesg.add (data); unlock (); } // send an email by connecting to the mta // this operation is done in two step - first prepare the mail header // second, connect to the mta and send the message. void Mail::send (void) const { rdlock (); Buffer header; try { // get the header gethead (header); // establish a connection with the mta TcpClient mta (d_addr, d_port); // annouce ourself mta_send (mta, MTA_CMD_HELO + d_host); mta_send (mta, MTA_CMD_MAIL + d_from); // loop in the recipient list mta_rcpt (mta, d_torl); mta_rcpt (mta, d_ccrl); mta_rcpt (mta, d_bcrl); // start to send the message mta_send (mta, MTA_CMD_SMSG); // write the header header.write (mta); // write the message d_mesg.write (mta); // end the message mta_send (mta, MTA_CMD_EMSG); // quit mta_send (mta, MTA_CMD_QUIT); // cleanup and return mta.close (); unlock (); } catch (...) { unlock (); throw; } } // get the mail header (private) void Mail::gethead (Buffer& head) const { // from: head.add (MTA_HDR_FROM + System::username ()); head.add (eolc); // add the subject if (d_subj.length () != 0) { head.add (MTA_HDR_SUBJ + d_subj); head.add (eolc); } // prepare the to list String torl; long len = d_torl.length (); for (long i = 0; i < len; i++) { String addr = d_torl.get (i); torl = (i == 0) ? addr : torl + ',' + addr; } if (torl.length () != 0) { head.add (MTA_HDR_TORL + torl); head.add (eolc); } // preapre the cc list String ccrl; len = d_ccrl.length (); for (long i = 0; i < len; i++) { String addr = d_ccrl.get (i); ccrl = (i == 0) ? addr : ccrl + ',' + addr; } if (ccrl.length () != 0) { head.add (MTA_HDR_CCRL + ccrl); head.add (eolc); } // finish the header with a newline head.add (eolc); } // ------------------------------------------------------------------------- // - object section - // ------------------------------------------------------------------------- // the quark zone static const long QUARK_ZONE_LENGTH = 11; static QuarkZone zone (QUARK_ZONE_LENGTH); // the object supported quarks static const long QUARK_TO = zone.intern ("to"); static const long QUARK_CC = zone.intern ("cc"); static const long QUARK_BCC = zone.intern ("bcc"); static const long QUARK_ADD = zone.intern ("add"); static const long QUARK_SEND = zone.intern ("send"); static const long QUARK_ADDLN = zone.intern ("addln"); static const long QUARK_SUBJECT = zone.intern ("subject"); static const long QUARK_GETPORT = zone.intern ("get-mta-port"); static const long QUARK_GETADDR = zone.intern ("get-mta-address"); static const long QUARK_SETPORT = zone.intern ("set-mta-port"); static const long QUARK_SETADDR = zone.intern ("set-mta-address"); // create a new object in a generic way Object* Mail::mknew (Vector* argv) { long argc = (argv == nilp) ? 0 : argv->length (); // check for 0 argument if (argc == 0) return new Mail; // too many arguments throw Exception ("argument-error", "too many argument with mail constructor"); } // return true if the given quark is defined bool Mail::isquark (const long quark, const bool hflg) const { rdlock (); if (zone.exists (quark) == true) { unlock (); return true; } bool result = hflg ? Object::isquark (quark, hflg) : false; unlock (); return result; } // apply thsi object with a set of arguments and a quark Object* Mail::apply (Runnable* robj, Nameset* nset, const long quark, Vector* argv) { // get the number of arguments long argc = (argv == nilp) ? 0 : argv->length (); // dispatch variable length arguments if ((quark == QUARK_ADD) || (quark == QUARK_ADDLN)) { String result; for (long i = 0; i < argc; i++) { Object* obj = argv->get (i); Literal* lobj = dynamic_cast (obj); if (lobj == nilp) throw Exception ("type-error", "invalid object to add", Object::repr (obj)); result = result + lobj->tostring (); } if (quark == QUARK_ADDLN) result = result + eolc; addmsg (result); return nilp; } // check for 0 argument if (argc == 0) { if (quark == QUARK_GETADDR) return new String (getaddr ()); if (quark == QUARK_GETPORT) return new Integer (getport ()); if (quark == QUARK_SEND) { send (); return nilp; } } // check for 1 argument if (argc == 1) { if (quark == QUARK_TO) { String rcpt = argv->getstring (0); addto (rcpt); return nilp; } if (quark == QUARK_CC) { String rcpt = argv->getstring (0); addcc (rcpt); return nilp; } if (quark == QUARK_BCC) { String rcpt = argv->getstring (0); addbcc (rcpt); return nilp; } if (quark == QUARK_SUBJECT) { String subj = argv->getstring (0); setsubj (subj); return nilp; } if (quark == QUARK_SETADDR) { String addr = argv->getstring (0); setaddr (addr); return nilp; } if (quark == QUARK_SETPORT) { t_word port = argv->getint (0); setport (port); return nilp; } } // call the object method return Object::apply (robj, nset, quark, argv); } }