1 /* Copyright (C) 2000-2006 Thomas Bopp, Thorsten Hampel, Ludger Merkens
2 * Copyright (C) 2002-2004 Christian Schmidt
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 * $Id: smtp.pike,v 1.1 2008/03/31 13:39:57 exodusd Exp $
20 inherit "/net/coal/login";
21 inherit "/net/base/line";
25 #include <attributes.h>
27 #include <exception.h>
30 class smtp : public login,line{
36 * implements a smtp-server (see rfc2821 for details)
45 #define DEBUG_SMTP(s, args...) werror("net/smtp: "+s+"\n", args)
51 int _state = STATE_INITIAL;
53 string sServer = _Server->query_config("machine");
54 string sDomain = _Server->query_config("domain");
55 string sIP = _Server->query_config("ip");
56 string sFQDN = sServer+"."+sDomain;
58 object _Forward = _Server->get_module("forward");
62 array aoRecipients=({});
64 //sends a reply to the client, prefixed by a response code
65 //if msg is more than one line, each is preceded by this code
67 void send_reply(int code, string msg)
69 array lines = msg / "\n";
70 for(int i=0;i<sizeof(lines); i++) //multiline reply
72 if(i==sizeof(lines)-1) send_message(""+code+" "+lines[i]+"\r\n");
73 else send_message(""+code+"-"+lines[i]+"\r\n");
79 //called upon connection, greets the client
84 string sTime=ctime(time());
85 sTime=sTime-"\n"; //remove trailing LF
86 oUser = MODULE_USERS->lookup("postman");
87 send_reply(220,sFQDN+" sTeaMail SMTP-Server ver1.0 ready, "+sTime);
90 string query_address_name()
92 string addr = query_address();
93 sscanf(addr,"%s %*s",addr);
94 object dns = Protocols.DNS.client();
95 array res = dns->gethostbyaddr(addr);
96 if (arrayp(res) && sizeof(res) > 1 )
102 void ehlo(string client)
104 if(_state!=STATE_INITIAL)
106 //reset everything important
110 _esmtp=1; //client supports ESMTP
111 _state=STATE_IDENTIFIED; //client identified correctly
113 string addr=query_address();
114 sscanf(addr,"%s %*s",addr); //addr now contains ip of connecting host
116 //verify if given name is correct
117 object dns = Protocols.DNS.client();
118 array res = dns->gethostbyaddr(addr);
120 send_reply(250,sServer+" Hello "+client+" ["+addr+"]");
122 send_reply(250,sServer+" Hello "+client+" ["+addr+"] (Expected \"EHLO "+res[0]+"\")");
128 void helo(string client)
130 if(_state!=STATE_INITIAL)
132 //reset everything important
136 _esmtp=0; //client does not support ESMTP
137 _state=STATE_IDENTIFIED; //client identified correctly
139 string addr=query_address();
140 sscanf(addr,"%s %*s",addr);
142 //verify if given name is correct
143 object dns = Protocols.DNS.client();
144 array res = dns->gethostbyaddr(addr);
146 send_reply(250,sServer+" Hello "+client+" ["+addr+"]");
147 else send_reply(250,sServer+" Hello "+client+" ["+addr+"] (Expected \"HELO "+res[0]+"\")");
155 send_reply(250,"This is the opensTeam-Mailserver\n"+
156 "Contact: http://www.open-steam.org");
162 string mail(string sender)
164 string localpart, domain;
165 //sender must look like '<sender@domain>'
166 oUser = USER("postman");
167 if ( sender == "" || sender == "<>" ||
168 sscanf(sender,"%*s<%s@%s>", localpart, domain) >= 2 ||
169 sscanf(sender, "%s@%s", localpart, domain) == 2 )
171 object user = _Forward->lookup_sender(sender);
172 if (_Server->get_config("mail_mailsystem")=="closed") {
173 if (!objectp(user)) {
174 MESSAGE("MAIL: Rejecting from " + sender);
175 return "Mailing is restricted: members only - " + sender + " not a "+
183 _state=STATE_TRANSACTION; //waiting for RCPT command(s) now
184 send_reply(250,"Sender accepted"); //NOTE: sender can't be verified
187 return "syntax error, wrong sender format!";
193 array getIPs(string addr)
195 object dns = Protocols.DNS.client();
197 array result = dns->gethostbyname(lower_case(addr));
198 if ( !arrayp(result) || sizeof(result) < 2 )
206 int check_rcpt(string user, string domain)
208 DEBUG_SMTP("Mail to domain=%s - LOCALDOMAIN=%s", domain, sFQDN);
210 if( lower_case(domain) == lower_case(sFQDN) )
212 else if (_ADMIN->query_attribute("virtual_hosts")[domain])
215 //test if given domain-name matches local ip-adress (->accept it)
216 //workaround for multiple domains on same machine
217 //like "uni-paderborn.de"<->"upb.de"
218 array domains = _Server->query_config("domains");
219 if ( !arrayp(domains) )
222 domains += ({ _Server->query_config("domain") });
223 if ( search(domains, domain) >= 0 )
226 array myIPs = getIPs(_Server->get_server_name());
227 array remoteIPs = getIPs(domain);
229 DEBUG_SMTP("Checking IPS: local=%O, remote=%O", myIPs, remoteIPs);
230 if ( sizeof( (myIPs & remoteIPs) ) > 0 )
237 void rcpt(string recipient)
240 if ( sscanf(recipient, "%*s<%s>", address) == 0 )
244 if(lower_case(address)=="postmaster")
245 address="postmaster@"+sFQDN; //rcpt to:<postmaster> is always local!
248 if ( sscanf(address, "%s@%s", user, domain) != 2 ) {
249 FATAL("501 upon smtp: rctp(%O)", recipient);
250 send_reply(501, "syntax error, recipient adress has illegal format");
254 int success = check_rcpt(user, domain);
256 if ( success ) //only accept for local domain
258 string user = lower_case(user);
259 if(user=="postmaster") user="root";//change to other user,if needed
261 int valid = _Forward->is_valid(user); //check if rcpt is ok
262 DEBUG_SMTP("is_valid() returned "+valid+" for target "+user);
265 aoRecipients+=({user}); //"doubled" recipients will be removed later!
266 send_reply(250,"Recipient ok");
267 _state=STATE_RECIPIENT; //waiting for DATA or RCPT
269 else if (valid == -1)
270 send_reply(550,"write access failed, set permissions on target first");
271 else if ( valid == 0 )
272 send_reply(550,"unknown recipient "+user);
274 send_reply(450,"unknown error");
277 send_reply(550,"we do not relay for you!");
286 //"minimize" list of recipients
287 aoRecipients=Array.uniq(aoRecipients);
289 send_reply(354,"send message now, end with single line containing '.'");
291 register_data_func(process_data);
293 //add "received"-Header, see rfc for details
294 string addr=query_address_name();;
295 sMessage="Received: from "+addr+" by "+sFQDN+" "+ctime(time())+
296 "X-Envelope_from: "+sSender +"\n";
302 void process_data(string data)
305 if ( (i=search(data, "\n.\r\n")) > 0 || (i=search(data, "\n.\n")) > 0 ) {
308 else if ( (i=search(sMessage + data, "\n.\r\n")) > 0 ) {
309 sMessage = sMessage[..i];
320 send_reply(250,"Message accepted, size is "+sizeof(sMessage));
321 DEBUG_SMTP("received mail, recipients are:%O",aoRecipients);
325 // make it a task (do not block!)
326 object tmod = get_module("tasks");
327 if ( objectp(tmod) ) {
328 Task.Task rcvTask = Task.Task(_Forward->send_message_raw);
329 rcvTask->params = ({ aoRecipients, sMessage, sSender });
330 tmod->run_task(rcvTask);
334 res=_Forward->send_message_raw(aoRecipients,sMessage,sSender);
335 DEBUG_SMTP("result of _Forward->send_message_raw(...) is "+res);
336 _state=STATE_IDENTIFIED;
337 unregister_data_func();
340 DEBUG_SMTP("processing data finished !");
349 if(_state>STATE_IDENTIFIED) _state=STATE_IDENTIFIED;
353 send_reply(250,"RSET completed");
361 send_reply(250,"NOOP completed");
369 send_reply(221,""+sServer+" closing connection");
377 void vrfy(string user)
379 send_reply(252,"Cannot VRFY user, but will accept message and attempt delivery");
380 //verification code may be added here
385 //this function is called for each line the client sends
387 void process_command(string cmd)
389 if(_state==STATE_DATA)
395 string command,params;
396 if(sscanf(cmd,"%s %s",command,params)!=2)
402 switch(upper_case(command))
405 if(search(params," ")==-1) ehlo(params);
406 else send_reply(501,"wrong number of arguments");
409 if(search(params," ")==-1) helo(params);
410 else send_reply(501,"wrong number of arguments");
416 if(_state==STATE_IDENTIFIED)
418 array parts=params/":";
419 if(upper_case(parts[0])=="FROM" && sizeof(parts)==2) {
420 string res = mail( String.trim_whites(parts[1]));
421 if ( stringp(res) ) {
422 send_reply(501, res);
426 send_reply(501,"syntax error");
428 else send_reply(503,"bad sequence of commands - EHLO expected");
431 if(_state==STATE_TRANSACTION||_state==STATE_RECIPIENT)
433 array parts=params/":";
434 if(upper_case(parts[0])=="TO" && sizeof(parts)==2)
435 rcpt( String.trim_whites(parts[1]) );
436 else send_reply(501,"syntax error");
438 else send_reply(503,"bad sequence of commands");
441 if(_state==STATE_RECIPIENT)
443 if (params=="") data();
444 else send_reply(501,"wrong number of arguments");
446 else send_reply(501,"bad sequence of commands");
449 if (params=="") rset();
450 else send_reply(501,"wrong number of arguments");
456 if (params=="") quit();
457 else send_reply(501,"wrong number of arguments");
463 send_reply(500,"command not recognized");
470 void close_connection()
472 if(_state!=STATE_QUIT) //we got called by idle-timeout
473 send_reply(221,""+sServer+" closing connection, idle for too long");
474 ::close_connection();