a580e12000-09-27Fredrik Hübinette (Hubbe) #pike __REAL_VERSION__
a20af62000-09-26Fredrik Hübinette (Hubbe) 
ef10d32003-10-14David Gourdelier //! A mapping(int:string) that maps SMTP return //! codes to english textual messages. constant replycodes = ([ 211:"System status, or system help reply", 214:"Help message", 220:"<host> Service ready", 221:"<host> Service closing transmission channel", 250:"Requested mail action okay, completed", 251:"User not local; will forward to <forward-path>", 354:"Start mail input; end with <CRLF>.<CRLF>", 421:"<host> Service not available, closing transmission channel " "[This may be a reply to any command if the service knows it " "must shut down]", 450:"Requested mail action not taken: mailbox unavailable " "[E.g., mailbox busy]", 451:"Requested action aborted: local error in processing", 452:"Requested action not taken: insufficient system storage", 500:"Syntax error, command unrecognized " "[This may include errors such as command line too long]", 501:"Syntax error in parameters or arguments", 502:"Command not implemented", 503:"Bad sequence of commands", 504:"Command parameter not implemented", 550:"Requested action not taken: mailbox unavailable " "[E.g., mailbox not found, no access]", 551:"User not local; please try <forward-path>", 552:"Requested mail action aborted: exceeded storage allocation", 553:"Requested action not taken: mailbox name not allowed " "[E.g., mailbox syntax incorrect or relaying denied]", 554:"Transaction failed" ]);
d438bf2003-10-15Martin Nilsson  class Protocol
bb02cc1997-09-15Fredrik Hübinette (Hubbe) { // Maybe this should be the other way around?
5946a21998-04-29Fredrik Noring  inherit .NNTP.protocol;
bb02cc1997-09-15Fredrik Hübinette (Hubbe) }
dba8dd2001-11-08Martin Nilsson //!
d438bf2003-10-15Martin Nilsson class Client
bb02cc1997-09-15Fredrik Hübinette (Hubbe) {
d438bf2003-10-15Martin Nilsson  inherit Protocol;
cc1c0e1998-03-07Fredrik Noring  static private int cmd(string c, string|void comment) { int r = command(c); switch(r) { case 200..399: break; default:
484a742002-03-09Martin Nilsson  error( "SMTP: "+c+"\n"+(comment?"SMTP: "+comment+"\n":"")+
ef10d32003-10-14David Gourdelier  "SMTP: "+replycodes[r]+"\n" );
cc1c0e1998-03-07Fredrik Noring  } return r; }
dba8dd2001-11-08Martin Nilsson  //! @decl void create() //! @decl void create(Stdio.File server) //! @decl void create(string server, void|int port) //! Creates an SMTP mail client and connects it to the //! the @[server] provided. The server parameter may //! either be a string witht the hostnam of the mail server, //! or it may be a file object acting as a mail server. //! If @[server] is a string, than an optional port parameter //! may be provided. If no port parameter is provided, port //! 25 is assumed. If no parameters at all is provided //! the client will look up the mail host by searching //! for the DNS MX record. //! //! @throws //! Throws an exception if the client fails to connect to //! the mail server.
799d092001-07-18Mirar (Pontus Hagland)  void create(void|string|Stdio.File server, int|void port)
bb02cc1997-09-15Fredrik Hübinette (Hubbe)  { if(!server) { // Lookup MX record here (Using DNS.pmod) object dns=master()->resolv("Protocols")["DNS"]->client(); server=dns->get_primary_mx(gethostname()); }
799d092001-07-18Mirar (Pontus Hagland)  if (objectp(server)) assign(server); else
bb02cc1997-09-15Fredrik Hübinette (Hubbe)  {
799d092001-07-18Mirar (Pontus Hagland)  if(!port) port = 25; if(!server || !connect(server, port)) {
484a742002-03-09Martin Nilsson  error("Failed to connect to mail server.\n");
799d092001-07-18Mirar (Pontus Hagland)  }
bb02cc1997-09-15Fredrik Hübinette (Hubbe)  } if(readreturncode()/100 != 2)
484a742002-03-09Martin Nilsson  error("Connection refused by SMTP server.\n");
bb02cc1997-09-15Fredrik Hübinette (Hubbe) 
cc1c0e1998-03-07Fredrik Noring  if(catch(cmd("EHLO "+gethostname()))) cmd("HELO "+gethostname(), "greeting failed."); }
dba8dd2001-11-08Martin Nilsson  //! Sends a mail message from @[from] to the mail addresses //! listed in @[to] with the mail body @[body]. The body //! should be a correctly formatted mail DATA block, e.g. //! produced by @[MIME.Message]. //! //! @seealso //! @[simple_mail] //! //! @throws //! If the mail server returns any other return code than //! 200-399 an exception will be thrown.
6b0af52000-02-18Martin Nilsson  void send_message(string from, array(string) to, string body)
cc1c0e1998-03-07Fredrik Noring  {
76cd3d1999-06-29Henrik Grubbström (Grubba)  cmd("MAIL FROM: <" + from + ">"); foreach(to, string t) { cmd("RCPT TO: <" + t + ">"); }
cc1c0e1998-03-07Fredrik Noring  cmd("DATA");
27990a2001-12-06Henrik Grubbström (Grubba)  // Perform quoting according to RFC 2821 4.5.2. if (sizeof(body) && body[0] == '.') { body = "." + body; } body = replace(body, "\r\n.", "\r\n..");
2d77972001-12-06Henrik Grubbström (Grubba)  // RFC 2821 4.1.1.4:
27990a2001-12-06Henrik Grubbström (Grubba)  // An extra <CRLF> MUST NOT be added, as that would cause an empty // line to be added to the message.
d438bf2003-10-15Martin Nilsson  if (has_suffix(body, "\r\n"))
27990a2001-12-06Henrik Grubbström (Grubba)  body += ".";
d438bf2003-10-15Martin Nilsson  else
27990a2001-12-06Henrik Grubbström (Grubba)  body += "\r\n.";
d438bf2003-10-15Martin Nilsson 
27990a2001-12-06Henrik Grubbström (Grubba)  cmd(body);
cc1c0e1998-03-07Fredrik Noring  cmd("QUIT");
bb02cc1997-09-15Fredrik Hübinette (Hubbe)  }
76cd3d1999-06-29Henrik Grubbström (Grubba)  static string parse_addr(string addr) {
8cd5331999-07-07Fredrik Hübinette (Hubbe)  array(string|int) tokens = replace(MIME.tokenize(addr), '@', "@");
76cd3d1999-06-29Henrik Grubbström (Grubba)  int i; tokens = tokens[search(tokens, '<') + 1..]; if ((i = search(tokens, '>')) != -1) { tokens = tokens[..i-1]; } return tokens*""; }
dba8dd2001-11-08Martin Nilsson  //! Sends an e-mail. Wrapper function that uses @[send_message]. //!
5c1d3e2002-09-06Honza Petrous  //! @note //! Some important headers are set to:
cbe8c92003-04-07Martin Nilsson  //! @expr{"Content-Type: text/plain; charset=iso-8859-1"@} and //! @expr{"Content-Transfer-Encoding: 8bit"@}. @expr{"Date:"@} //! header isn't used at all.
5c1d3e2002-09-06Honza Petrous  //!
dba8dd2001-11-08Martin Nilsson  //! @throws //! If the mail server returns any other return code than //! 200-399 an exception will be thrown.
cc1c0e1998-03-07Fredrik Noring  void simple_mail(string to, string subject, string from, string msg)
bb02cc1997-09-15Fredrik Hübinette (Hubbe)  {
7b69642003-08-07Martin Nilsson  if (!has_value(msg, "\r\n"))
fca5372000-10-03Mirar (Pontus Hagland)  msg=replace(msg,"\n","\r\n"); // *simple* mail /Mirar
76cd3d1999-06-29Henrik Grubbström (Grubba)  send_message(parse_addr(from), ({ parse_addr(to) }),
baa6a61998-05-12Marcus Comstedt  (string)MIME.Message(msg, (["mime-version":"1.0", "subject":subject, "from":from, "to":to, "content-type": "text/plain;charset=iso-8859-1", "content-transfer-encoding": "8bit"])));
bb02cc1997-09-15Fredrik Hübinette (Hubbe)  }
1d1ac72000-05-18Mirar (Pontus Hagland) 
dba8dd2001-11-08Martin Nilsson  //! Verifies the mail address @[addr] against the mail server. //! //! @returns //! @array //! @elem int code //! The numerical return code from the VRFY call. //! @elem string message //! The textual answer to the VRFY call. //! @endarray //! //! @note //! Some mail servers does not answer truthfully to //! verfification queries in order to prevent spammers //! and others to gain information about the mail //! addresses present on the mail server. //! //! @throws //! If the mail server returns any other return code than //! 200-399 an exception will be thrown.
1d1ac72000-05-18Mirar (Pontus Hagland)  array(int|string) verify(string addr) { return ({command("VRFY "+addr),rest}); }
bb02cc1997-09-15Fredrik Hübinette (Hubbe) }
ef10d32003-10-14David Gourdelier  //! Class to store configuration variable for the SMTP server
d438bf2003-10-15Martin Nilsson class Configuration {
ef10d32003-10-14David Gourdelier  //! Message max size int maxsize = 100240000; //! Maximum number of recipients int maxrcpt = 1000; //! Verify sender domain for MX int checkdns = 0; //! Lamme check email from validity int checkemail = 1; //! Give raw data and normal MIME data, if set to //! yes your cb_data function should take an extra //! string argument int givedata = 1; }; //! The low-level class for the SMTP server
d438bf2003-10-15Martin Nilsson class Connection {
ef10d32003-10-14David Gourdelier 
d438bf2003-10-15Martin Nilsson  inherit Configuration;
ef10d32003-10-14David Gourdelier  // The commands this module supports
d438bf2003-10-15Martin Nilsson  array(string) commands = ({ "ehlo", "helo", "mail", "rcpt", "data", "rset", "vrfy", "quit", "noop" });
0cd9f22003-10-16David Gourdelier  // do we speak LMTP ? int lmtp_mode = 0;
d438bf2003-10-15Martin Nilsson 
ef10d32003-10-14David Gourdelier  // the fd of the socket
d438bf2003-10-15Martin Nilsson  static object fd = Stdio.File();
ef10d32003-10-14David Gourdelier  // the domains for each i relay
d438bf2003-10-15Martin Nilsson  static array(string) mydomains = ({ });
ef10d32003-10-14David Gourdelier  // the input buffer for read_cb
d438bf2003-10-15Martin Nilsson  static string inputbuffer = "";
ef10d32003-10-14David Gourdelier  // the size of the old data string in read_cb
d438bf2003-10-15Martin Nilsson  static int sizeofpreviousdata = 0;
ef10d32003-10-14David Gourdelier  // the from address
d438bf2003-10-15Martin Nilsson  static string mailfrom = "";
ef10d32003-10-14David Gourdelier  // to the address(es)
d438bf2003-10-15Martin Nilsson  static array(string) mailto = ({ });
ef10d32003-10-14David Gourdelier  // the ident we get from ehlo/helo
d438bf2003-10-15Martin Nilsson  static string ident = "";
ef10d32003-10-14David Gourdelier  // these are obvious
d438bf2003-10-15Martin Nilsson  static string remoteaddr, localaddr; static int localport;
ef10d32003-10-14David Gourdelier  // my name
d438bf2003-10-15Martin Nilsson  static string localhost = gethostname();
ef10d32003-10-14David Gourdelier  // the sequence of commands the client send
d438bf2003-10-15Martin Nilsson  static array(string) sequence = ({ });
0cd9f22003-10-16David Gourdelier  // the message id of the current mail private string|int messageid;
ef10d32003-10-14David Gourdelier  // the callback functions used to guess if user is ok or not
d438bf2003-10-15Martin Nilsson  static function cb_rcptto; static function cb_data; static function cb_mailfrom;
ef10d32003-10-14David Gourdelier  // whether you are in data mode or not... int datamode = 0; array(string) features = ({ "PIPELINING", "8BITMIME", "SIZE " + maxsize });
0cd9f22003-10-16David Gourdelier  static void handle_timeout(string cmd)
ef10d32003-10-14David Gourdelier  {
0cd9f22003-10-16David Gourdelier  string errmsg = "421 Error: timeout exceeded after command " + cmd || "unknown command!" + "\r\n"; catch(fd->write(errmsg)); log(errmsg); close_cb(1);
ef10d32003-10-14David Gourdelier  }
d438bf2003-10-15Martin Nilsson  static void outcode(int code)
ef10d32003-10-14David Gourdelier  {
d438bf2003-10-15Martin Nilsson  fd->write("%d %s\r\n", code, replycodes[code]);
ef10d32003-10-14David Gourdelier #ifdef SMTP_DEBUG
d438bf2003-10-15Martin Nilsson  log("%d %s\r\n", code, replycodes[code]);
ef10d32003-10-14David Gourdelier #endif }
d438bf2003-10-15Martin Nilsson  static void log(string fmt, mixed ... args)
ef10d32003-10-14David Gourdelier  {
0cd9f22003-10-16David Gourdelier  string errmsg = Calendar.now()->format_time() + " Pike SMTP server : "; if(messageid) errmsg += messageid + ": "; errmsg += fmt + "\n"; werror(errmsg, args);
ef10d32003-10-14David Gourdelier  } // make the received header
0cd9f22003-10-16David Gourdelier  static string received()
ef10d32003-10-14David Gourdelier  { string remotehost = Protocols.DNS.client()->gethostbyaddr(remoteaddr)[0] || remoteaddr; string rec;
0cd9f22003-10-16David Gourdelier  string mode = lmtp_mode ? "LMTP": "ESMTP";
ef10d32003-10-14David Gourdelier  rec=sprintf("from %s (%s [%s]) "
0cd9f22003-10-16David Gourdelier  "by %s (Pike %s server) with %s id %d ; %s",
ef10d32003-10-14David Gourdelier  ident, remotehost, remoteaddr,
0cd9f22003-10-16David Gourdelier  gethostname(), mode, mode, messageid,
ef10d32003-10-14David Gourdelier  Calendar.now()->format_smtp()); return rec; } void helo(string argument) { remove_call_out(handle_timeout);
0cd9f22003-10-16David Gourdelier  call_out(handle_timeout, 310, "HELO");
ef10d32003-10-14David Gourdelier  if(sizeof(argument) > 0) {
d438bf2003-10-15Martin Nilsson  fd->write("250 %s\r\n", localhost);
ef10d32003-10-14David Gourdelier  ident = argument; #ifdef SMTP_DEBUG
d438bf2003-10-15Martin Nilsson  log("helo from %s", ident);
ef10d32003-10-14David Gourdelier #endif sequence += ({ "helo" }); } else outcode(501); } void ehlo(string argument) { remove_call_out(handle_timeout);
0cd9f22003-10-16David Gourdelier  call_out(handle_timeout, 310, "EHLO");
ef10d32003-10-14David Gourdelier  if(sizeof(argument) > 0) { string out = "250-" + localhost + "\r\n"; int i = 0; for(; i < sizeof(features) - 1; i++) { out += "250-" + features[i] + "\r\n"; } out += "250 " + features[i] + "\r\n"; fd->write(out); sequence += ({ "ehlo" }); ident = argument; #ifdef SMTP_DEBUG
d438bf2003-10-15Martin Nilsson  log("helo from %s", ident);
ef10d32003-10-14David Gourdelier #endif } else outcode(501); } void lhlo(string argument) { ehlo(argument); } // fetch the email address from the mail from: or rcpt to: commands // content: the input line like mail from:<toto@caudium.net> // what: the action either from or to int|string parse_email(string content, string what) { array parts = content / ":"; if(lower_case(parts[0]) != what) return 500; string validating_mail; parts[1] = String.trim_all_whites(parts[1]); if(!sscanf(parts[1], "<%s>", validating_mail)) sscanf(parts[1], "%s", validating_mail); if(validating_mail == "") validating_mail = "MAILER-DAEMON@" + mydomains[0]; array emailparts = validating_mail / "@"; array(string) temp = lower_case(emailparts[1]) / "."; string domain = temp[sizeof(temp)-2..] * "."; if(checkemail && sizeof(emailparts) != 2) {
d438bf2003-10-15Martin Nilsson  log("invalid mail address '%O', command=%O\n", emailparts, what);
ef10d32003-10-14David Gourdelier  return 553; } if(checkdns) { write("checking dns\n"); if(what == "from" && !Protocols.DNS.client()->get_primary_mx(domain)) {
d438bf2003-10-15Martin Nilsson  log("check dns failed, command=%O, domain=%O\n", what, domain);
ef10d32003-10-14David Gourdelier  return 553; } }
d438bf2003-10-15Martin Nilsson  if(what == "to" && !has_value(mydomains, domain) && !has_value(mydomains, "*") )
ef10d32003-10-14David Gourdelier  {
d438bf2003-10-15Martin Nilsson  log("relaying denied, command=%O, mydomains=%O, domain=%O\n", what, mydomains, domain);
ef10d32003-10-14David Gourdelier  return 553; } return validating_mail; } void mail(string argument) { remove_call_out(handle_timeout);
0cd9f22003-10-16David Gourdelier  call_out(handle_timeout, 310, "MAIL FROM");
ef10d32003-10-14David Gourdelier  int sequence_ok = 0; foreach(({ "ehlo", "helo", "lhlo" }), string needle)
0cd9f22003-10-16David Gourdelier  {
d438bf2003-10-15Martin Nilsson  if(has_value(sequence, needle))
ef10d32003-10-14David Gourdelier  sequence_ok = 1; } if(sequence_ok) { mixed email = parse_email(argument, "from"); if(intp(email)) outcode(email); else { mixed err;
0cd9f22003-10-16David Gourdelier  int check; err = catch(check = cb_mailfrom(email)); if(err || !check) {
ef10d32003-10-14David Gourdelier  outcode(451); log(describe_backtrace(err)); return;
0cd9f22003-10-16David Gourdelier  }
ef10d32003-10-14David Gourdelier  if(check/100 == 2) { mailfrom = email; mailto = ({ }); /* this is used to avoid this problem: 250 Requested mail action okay, completed mail from: vida@caudium.net 250 Requested mail action okay, completed rcpt to: toto@ece.Fr 250 Requested mail action okay, completed mail from: vida@caudium.net 250 Requested mail action okay, completed rcpt to: tux@iteam.org
d438bf2003-10-15Martin Nilsson  553 Requested action not taken: mailbox name not allowed [E.g., mailbox syntax incorrect or relaying denied]
ef10d32003-10-14David Gourdelier  data 354 Start mail input; end with <CRLF>.<CRLF> */
0cd9f22003-10-16David Gourdelier  sequence -= ({ "rcpt to" });
ef10d32003-10-14David Gourdelier  sequence += ({ "mail from" });
0cd9f22003-10-16David Gourdelier  }
ef10d32003-10-14David Gourdelier  outcode(check); } } else outcode(503); } void rcpt(string argument) { mixed err; remove_call_out(handle_timeout);
0cd9f22003-10-16David Gourdelier  call_out(handle_timeout, 310, "RCPT TO");
d438bf2003-10-15Martin Nilsson  if(!has_value(sequence, "mail from"))
ef10d32003-10-14David Gourdelier  { outcode(503); return; } if(sizeof(mailto) >= maxrcpt) { outcode(552); return; } mixed email = parse_email(argument, "to"); if(intp(email)) outcode(email); else { int check; err = catch(check = cb_rcptto(email)); if(err || !check) { outcode(451); log(describe_backtrace(err)); return; } if(check/100 == 2) { mailto += ({ email }); sequence += ({ "rcpt to" }); } outcode(check); } } void data(string argument) { remove_call_out(handle_timeout);
0cd9f22003-10-16David Gourdelier  call_out(handle_timeout, 610, "DATA");
d438bf2003-10-15Martin Nilsson  if(!has_value(sequence, "rcpt to"))
ef10d32003-10-14David Gourdelier  { outcode(503); return; } datamode = 1; outcode(354); } MIME.Message format_headers(MIME.Message message) {
0cd9f22003-10-16David Gourdelier  messageid = hash(message->getdata()[..1000]) || random(100000);
ef10d32003-10-14David Gourdelier  // first add missing headers if(!message->headers->to) message->headers->to = "Undisclosed-recipients"; if(!message->headers->from) message->headers->from = mailfrom; if(!message->headers->subject) message->headers->subject = ""; if(!message->headers->received)
0cd9f22003-10-16David Gourdelier  message->headers->received = received();
ef10d32003-10-14David Gourdelier  else
0cd9f22003-10-16David Gourdelier  message->headers->received = received()
ef10d32003-10-14David Gourdelier  + "\0"+message->headers->received; if(!message->headers["message-id"]) {
d438bf2003-10-15Martin Nilsson  message->headers["message-id"] = sprintf("<%d@%s>", messageid, gethostname());
ef10d32003-10-14David Gourdelier  } return message; } void message(string content) { datamode = 0; if(sizeof(content) > maxsize) { outcode(552); return; } object message; mixed err = catch (message = MIME.Message(content)); if(err) { outcode(554); log(describe_backtrace(err));
d438bf2003-10-15Martin Nilsson  log("content is %O\n", content);
ef10d32003-10-14David Gourdelier  return; } err = catch { message = format_headers(message); }; if(err) { outcode(554); log(describe_backtrace(err)); return; }
0cd9f22003-10-16David Gourdelier  // if we are in LMTP mode we call cb_data for each recipient // and with one recipient. This way we have one mime message per // recipient and one outcode to display to the client per recipient // (that is LMTP specific) if(lmtp_mode) { foreach(mailto, string recipient) { int check; if(givedata) err = catch(check = cb_data(copy_value(message), mailfrom, recipient, content)); else err = catch(check = cb_data(copy_value(message), mailfrom, recipient)); if(err || !check) { outcode(554); log(describe_backtrace(err)); continue; } outcode(check); } } // SMTP mode, cb_data is called one time with an array of recipients // and the same MIME object
ef10d32003-10-14David Gourdelier  else {
0cd9f22003-10-16David Gourdelier  int check; if(givedata) err = catch(check = cb_data(message, mailfrom, mailto, content)); else err = catch(check = cb_data(message, mailfrom, mailto)); if(err || !check) { outcode(554); log(describe_backtrace(err)); return; } outcode(check);
ef10d32003-10-14David Gourdelier  } } void noop() { remove_call_out(handle_timeout);
0cd9f22003-10-16David Gourdelier  call_out(handle_timeout, 310, "NOOP");
ef10d32003-10-14David Gourdelier  outcode(250); } void rset() { remove_call_out(handle_timeout);
0cd9f22003-10-16David Gourdelier  call_out(handle_timeout, 310, "RSET");
ef10d32003-10-14David Gourdelier  inputbuffer = ""; mailfrom = ""; mailto = ({ });
0cd9f22003-10-16David Gourdelier  messageid = 0;
ef10d32003-10-14David Gourdelier  //sequence = ({ }); outcode(250); } void vrfy() { remove_call_out(handle_timeout);
0cd9f22003-10-16David Gourdelier  call_out(handle_timeout, 310, "VRFY");
ef10d32003-10-14David Gourdelier  outcode(252); } void quit() {
d438bf2003-10-15Martin Nilsson  fd->write("221 " + replace(replycodes[221], "<host>", localhost) + "\r\n");
0cd9f22003-10-16David Gourdelier  close_cb(1);
ef10d32003-10-14David Gourdelier  }
d438bf2003-10-15Martin Nilsson  static int launch_functions(string line)
ef10d32003-10-14David Gourdelier  { array(string) command = line / " "; // success if(sizeof(command) > 0) { string _command = lower_case(command[0]); mixed err = 0;
d438bf2003-10-15Martin Nilsson  if(has_value(commands, _command))
ef10d32003-10-14David Gourdelier  { err = catch { #ifdef SMTP_DEBUG
d438bf2003-10-15Martin Nilsson  log("calling %O\n", _command);
ef10d32003-10-14David Gourdelier #endif function fun = this_object()[_command]; fun(command[1..] * " "); }; } else {
d438bf2003-10-15Martin Nilsson  log("command %O not recognized", _command);
ef10d32003-10-14David Gourdelier  outcode(500); } if(err) {
d438bf2003-10-15Martin Nilsson  log("error while executing command %O", _command);
ef10d32003-10-14David Gourdelier  outcode(554); } } }
d438bf2003-10-15Martin Nilsson  static void read_cb(mixed id, string data)
ef10d32003-10-14David Gourdelier  { string pattern; int bufferposition; inputbuffer += replace(data, "\r\n", "\n"); int sizeofdata = sizeof(data); // optimization : don't search all the data, only the last one int searchpos = sizeof(inputbuffer) - sizeofpreviousdata-sizeofdata; sizeofpreviousdata = sizeofdata; if(searchpos < 0) searchpos = 0; datamode ? (pattern = "\n.\n"):(pattern = "\n"); bufferposition = search(inputbuffer, pattern, searchpos); while(bufferposition != -1) { #ifdef SMTP_DEBUG
d438bf2003-10-15Martin Nilsson  write("buffposition=%d, inputbuffer=%O\n", bufferposition, inputbuffer);
ef10d32003-10-14David Gourdelier #endif bufferposition += sizeof(pattern); int end = bufferposition-(1+sizeof(pattern)); if(!datamode) { launch_functions(inputbuffer[..end]);
0cd9f22003-10-16David Gourdelier  if(lower_case(inputbuffer[..end]) == "quit") {
ef10d32003-10-14David Gourdelier  destruct(this_object());
0cd9f22003-10-16David Gourdelier  return; } pattern = "\n";
ef10d32003-10-14David Gourdelier  } if(datamode) { if(pattern=="\n.\n")
0cd9f22003-10-16David Gourdelier  message(inputbuffer[..end+1]); pattern = "\n.\n";
ef10d32003-10-14David Gourdelier  } // end of buffer detection if(bufferposition + sizeof(pattern) >= sizeof(inputbuffer)) { #ifdef SMTP_DEBUG write("breaking\n"); #endif
0cd9f22003-10-16David Gourdelier  inputbuffer = "";
ef10d32003-10-14David Gourdelier  break; } inputbuffer = inputbuffer[bufferposition..]; bufferposition = search(inputbuffer, pattern); } }
d438bf2003-10-15Martin Nilsson  static void write_cb()
ef10d32003-10-14David Gourdelier  {
d438bf2003-10-15Martin Nilsson  fd->write("220 " + replace(replycodes[220], "<host>", localhost) + "\r\n");
ef10d32003-10-14David Gourdelier  fd->set_write_callback(0); }
d438bf2003-10-15Martin Nilsson 
0cd9f22003-10-16David Gourdelier  static void close_cb(int i_close_the_stream)
ef10d32003-10-14David Gourdelier  {
0cd9f22003-10-16David Gourdelier  if(!i_close_the_stream) { string errmsg = "Connexion closed by client "; if(sequence && sizeof(sequence) > 1) errmsg += sequence[-1]; log(errmsg); }
ef10d32003-10-14David Gourdelier  catch (fd->close()); remove_call_out(handle_timeout); }
d438bf2003-10-15Martin Nilsson  void create(object _fd, array(string) _domains, function _cb_mailfrom, function _cb_rcptto, function _cb_data)
ef10d32003-10-14David Gourdelier  { foreach(_domains, string domain) mydomains += ({ lower_case(domain) }); cb_mailfrom = _cb_mailfrom; cb_rcptto = _cb_rcptto; cb_data = _cb_data; fd->assign(_fd); catch(remoteaddr=((fd->query_address()||"")/" ")[0]); catch(localaddr=((fd->query_address(1)||"")/" ")[0]); catch(localport=(int)((fd->query_address(1)||"")/" ")[1]); if(!remoteaddr) {
d438bf2003-10-15Martin Nilsson  fd->write("421 " + replace(replycodes[421], "<host>", localhost) + "\r\n");
0cd9f22003-10-16David Gourdelier  close_cb(1);
ef10d32003-10-14David Gourdelier  return; } if(!localaddr) {
d438bf2003-10-15Martin Nilsson  fd->write("421 " + replace(replycodes[421], "<host>", localhost) + "\r\n");
0cd9f22003-10-16David Gourdelier  close_cb(1);
ef10d32003-10-14David Gourdelier  return; }
d438bf2003-10-15Martin Nilsson  //log("connection from %s to %s:%d", remoteaddr, localaddr, localport);
ef10d32003-10-14David Gourdelier  fd->set_nonblocking(read_cb, write_cb, close_cb);
0cd9f22003-10-16David Gourdelier  call_out(handle_timeout, 300, "'First connexion'");
ef10d32003-10-14David Gourdelier  } }; //! The use of Protocols.SMTP.server is quite easy and allow you //! to design custom functions to process mail. This module does not //! handle mail storage nor relaying to other domains. //! So it is your job to provide mail storage and relay mails to other servers
d438bf2003-10-15Martin Nilsson class Server {
ef10d32003-10-14David Gourdelier 
d438bf2003-10-15Martin Nilsson  static object fdport; static array(string) domains; static function cb_mailfrom; static function cb_rcptto; static function cb_data;
ef10d32003-10-14David Gourdelier 
d438bf2003-10-15Martin Nilsson  static void accept_callback()
ef10d32003-10-14David Gourdelier  { object fd = fdport->accept(); if(!fd) error("Can't accept connections from socket\n");
d438bf2003-10-15Martin Nilsson  Connection(fd, domains, cb_mailfrom, cb_rcptto, cb_data);
ef10d32003-10-14David Gourdelier  destruct(fd); } //! @decl void create(array(string) _domains, void|int port,@ //! void|string ip, function _cb_mailfrom,@ //! function _cb_rcptto, function _cb_data) //! Create a receiving SMTP server. It implements RFC 2821, 2822 and 1854. //! //! @param domain //! Domains name this server relay, you need to provide at least one //! domain (the first one will be used for MAILER-DAEMON address). //! if you want to relay everything you can put a '*' after this //! first domain. //! @param port //! Port this server listen on //! @param listenip //! IP on which server listen //! @param cb_mailfrom //! Mailfrom callback function, this function will be called //! when a client send a mail from command. This function must take a //! string as argument (corresponding to the sender's email) and return //! int corresponding to the SMTP code to output to the client. //! @param cb_rcptto //! Same as cb_mailfrom but called when a client sends a rcpt to. //! @param cb_data //! This function is called each time a client send a data content. //! It must have the following synopsis: //! int cb_data(object mime, string sender, array(string) recipients,@ //! void|string rawdata) //! object mime : the mime data object //! string sender : sender of the mail (from the mailfrom command) //! array(string) recipients : one or more recipients given by the rcpt //! to command //! return : SMTP code to output to the client //! @example //! Here is an example of silly program that does nothing except outputting //! informations to stdout. //! int cb_mailfrom(string mail) //! { //! return 250; //! } //! //! int cb_rcptto(string email) //! { //! // check the user's mailbox here //! return 250; //! } //! //! int cb_data(object mime, string sender, array(string) recipients) //! { //! write(sprintf("smtpd: mailfrom=%s, to=%s, headers=%O\ndata=%s\n", //! sender, recipients * ", ", mime->headers, mime->getdata())); //! // check the data and deliver the mail here //! if(mime->body_parts) //! { //! foreach(mime->body_parts, object mpart)
d438bf2003-10-15Martin Nilsson  //! write("smtpd: mpart data = %O\n", mpart->getdata());
ef10d32003-10-14David Gourdelier  //! } //! return 250; //! } //! //! int main(int argc, array(string) argv) //! {
d438bf2003-10-15Martin Nilsson  //! Protocols.SMTP.Server(({ "ece.fr" }), 2500, "127.0.0.1", @
ef10d32003-10-14David Gourdelier  //! cb_mailfrom, cb_rcptto, cb_data); //! return -1; //! } void create(array(string) _domains, void|int port, void|string ip, function _cb_mailfrom, function _cb_rcptto, function _cb_data) { domains = _domains; cb_mailfrom = _cb_mailfrom; cb_rcptto = _cb_rcptto; cb_data = _cb_data; random_seed(getpid() + time()); if(!port) port = 25; fdport = Stdio.Port(port, accept_callback, ip); if(!fdport) { error("Cannot bind to socket, already bound ?\n"); } } };