// ---------------------------------------------------------------------------
// - 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 <Literal*> (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);
}
}
syntax highlighted by Code2HTML, v. 0.9.1