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.5 2006/05/03 11:16:08 exodusd Exp $
21
constant cvs_version="$Id: smtp.pike,v 1.5 2006/05/03 11:16:08 exodusd Exp $";
24
* implements a smtp-server (see rfc2821 for details)
27
inherit "/net/coal/login";
28
inherit "/net/base/line";
33
#include <attributes.h>
35
#include <exception.h>
43
#define DEBUG_SMTP(s, args...) werror("net/smtp: "+s+"\n", args)
49
static int _state = STATE_INITIAL;
50
static int _esmtp = 0;
51
static string sServer = _Server->query_config("machine");
52
static string sDomain = _Server->query_config("domain");
53
static string sIP = _Server->query_config("ip");
54
static string sFQDN = sServer+"."+sDomain;
56
static object _Forward = _Server->get_module("forward");
58
static string sMessage="";
59
static string sSender="";
60
static array(object) aoRecipients=({});
62
//sends a reply to the client, prefixed by a response code
63
//if msg is more than one line, each is preceded by this code
64
static void send_reply(int code, string msg)
66
array lines = msg / "\n";
67
for(int i=0;i<sizeof(lines); i++) //multiline reply
69
if(i==sizeof(lines)-1) send_message(""+code+" "+lines[i]+"\r\n");
70
else send_message(""+code+"-"+lines[i]+"\r\n");
74
//called upon connection, greets the client
79
string sTime=ctime(time());
80
sTime=sTime-"\n"; //remove trailing LF
81
oUser = MODULE_USERS->lookup("postman");
82
send_reply(220,sFQDN+" sTeaMail SMTP-Server ver1.0 ready, "+sTime);
85
static void ehlo(string client)
87
if(_state!=STATE_INITIAL)
89
//reset everything important
93
_esmtp=1; //client supports ESMTP
94
_state=STATE_IDENTIFIED; //client identified correctly
96
string addr=query_address();
97
sscanf(addr,"%s %*s",addr); //addr now contains ip of connecting host
99
//verify if given name is correct
100
object dns = Protocols.DNS.client();
101
array res = dns->gethostbyaddr(addr);
103
send_reply(250,sServer+" Hello "+client+" ["+addr+"]");
104
else send_reply(250,sServer+" Hello "+client+" ["+addr+"] (Expected \"EHLO "+res[0]+"\")");
107
static void helo(string client)
109
if(_state!=STATE_INITIAL)
111
//reset everything important
115
_esmtp=0; //client does not support ESMTP
116
_state=STATE_IDENTIFIED; //client identified correctly
118
string addr=query_address();
119
sscanf(addr,"%s %*s",addr);
121
//verify if given name is correct
122
object dns = Protocols.DNS.client();
123
array res = dns->gethostbyaddr(addr);
125
send_reply(250,sServer+" Hello "+client+" ["+addr+"]");
126
else send_reply(250,sServer+" Hello "+client+" ["+addr+"] (Expected \"HELO "+res[0]+"\")");
131
send_reply(250,"This is the opensTeam-Mailserver\n"+
132
"Contact: http://www.open-steam.org");
135
static int mail(string sender)
137
string localpart, domain;
138
//sender must look like '<sender@domain>'
139
if ( sender == "" || sender == "<>" ||
140
sscanf(sender,"%*s<%s@%s>", localpart, domain) >= 2 ||
141
sscanf(sender, "%s@%s", localpart, domain) == 2 )
144
_state=STATE_TRANSACTION; //waiting for RCPT command(s) now
145
send_reply(250,"Sender accepted"); //NOTE: sender can't be verified
151
static array(string) getIPs(string addr)
153
object dns = Protocols.DNS.client();
155
array result = dns->gethostbyname(lower_case(addr));
156
if ( !arrayp(result) || sizeof(result) < 2 )
162
int check_rcpt(string user, string domain)
164
DEBUG_SMTP("Mail to domain=%s - LOCALDOMAIN=%s", domain, sFQDN);
167
if( lower_case(domain) == lower_case(sFQDN) )
170
//test if given domain-name matches local ip-adress (->accept it)
171
//workaround for multiple domains on same machine
172
//like "uni-paderborn.de"<->"upb.de"
173
array domains = _Server->query_config("domains");
174
if ( !arrayp(domains) )
177
domains += ({ _Server->query_config("domain") });
178
if ( search(domains, domain) >= 0 )
181
array(string) myIPs = getIPs(_Server->get_server_name());
182
array(string) remoteIPs = getIPs(domain);
184
DEBUG_SMTP("Checking IPS: local=%O, remote=%O", myIPs, remoteIPs);
185
if ( sizeof( (myIPs & remoteIPs) ) > 0 )
191
static void rcpt(string recipient)
194
if ( sscanf(recipient, "%*s<%s>", address) == 0 )
198
if(lower_case(address)=="postmaster")
199
address="postmaster@"+sFQDN; //rcpt to:<postmaster> is always local!
202
if ( sscanf(address, "%s@%s", user, domain) != 2 ) {
203
FATAL("501 upon smtp: rctp(%O)", recipient);
204
send_reply(501, "syntax error, recipient adress has illegal format");
208
int success = check_rcpt(user, domain);
210
if ( success ) //only accept for local domain
212
string user = lower_case(user);
213
if(user=="postmaster") user="root";//change to other user,if needed
215
int valid = _Forward->is_valid(user); //check if rcpt is ok
216
DEBUG_SMTP("is_valid() returned "+valid+" for target "+user);
219
aoRecipients+=({user}); //"doubled" recipients will be removed later!
220
send_reply(250,"Recipient ok");
221
_state=STATE_RECIPIENT; //waiting for DATA or RCPT
223
else if (valid == -1)
224
send_reply(550,"write access failed, set permissions on target first");
225
else if ( valid == 0 )
226
send_reply(550,"unknown recipient");
228
send_reply(450,"unknown error");
231
send_reply(550,"we do not relay for you!");
237
//"minimize" list of recipients
238
aoRecipients=Array.uniq(aoRecipients);
240
send_reply(354,"send message now, end with single line containing '.'");
242
register_data_func(process_data);
244
//add "received"-Header, see rfc for details
245
string addr=query_address();
246
sscanf(addr,"%s %*s",addr);
247
sMessage="Received: from "+addr+" by "+sFQDN+" "+ctime(time())+
248
"X-Envelope_from: "+sSender +"\n";
251
static void process_data(string data)
254
if ( (i=search(data, "\n.\r\n")) > 0 || (i=search(data, "\n.\n")) > 0 ) {
257
else if ( (i=search(sMessage + data, "\n.\r\n")) > 0 ) {
258
sMessage = sMessage[..i];
268
send_reply(250,"Message accepted, size is "+sizeof(sMessage));
269
DEBUG_SMTP("received mail, recipients are:%O",aoRecipients);
273
// make it a task (do not block!)
274
object tmod = get_module("tasks");
275
if ( objectp(tmod) ) {
276
Task.Task rcvTask = Task.Task(_Forward->send_message_raw);
277
rcvTask->params = ({ aoRecipients, sMessage, sSender });
278
tmod->run_task(rcvTask);
282
res=_Forward->send_message_raw(aoRecipients,sMessage,sSender);
283
DEBUG_SMTP("result of _Forward->send_message_raw(...) is "+res);
284
_state=STATE_IDENTIFIED;
285
unregister_data_func();
288
DEBUG_SMTP("processing data finished !");
294
if(_state>STATE_IDENTIFIED) _state=STATE_IDENTIFIED;
298
send_reply(250,"RSET completed");
303
send_reply(250,"NOOP completed");
308
send_reply(221,""+sServer+" closing connection");
313
static void vrfy(string user)
315
send_reply(252,"Cannot VRFY user, but will accept message and attempt delivery");
316
//verification code may be added here
319
//this function is called for each line the client sends
320
static void process_command(string cmd)
322
if(_state==STATE_DATA)
328
string command,params;
329
if(sscanf(cmd,"%s %s",command,params)!=2)
335
switch(upper_case(command))
338
if(search(params," ")==-1) ehlo(params);
339
else send_reply(501,"wrong number of arguments");
342
if(search(params," ")==-1) helo(params);
343
else send_reply(501,"wrong number of arguments");
349
if(_state==STATE_IDENTIFIED)
351
array(string) parts=params/":";
352
if(upper_case(parts[0])=="FROM" && sizeof(parts)==2) {
353
if ( !mail( String.trim_whites(parts[1]) ) ) {
354
FATAL("501 upon mail, sender illegal path format: %O\n",
357
"syntax error, return path has illegal format");
361
send_reply(501,"syntax error");
363
else send_reply(503,"bad sequence of commands - EHLO expected");
366
if(_state==STATE_TRANSACTION||_state==STATE_RECIPIENT)
368
array(string) parts=params/":";
369
if(upper_case(parts[0])=="TO" && sizeof(parts)==2)
370
rcpt( String.trim_whites(parts[1]) );
371
else send_reply(501,"syntax error");
373
else send_reply(503,"bad sequence of commands");
376
if(_state==STATE_RECIPIENT)
378
if (params=="") data();
379
else send_reply(501,"wrong number of arguments");
381
else send_reply(501,"bad sequence of commands");
384
if (params=="") rset();
385
else send_reply(501,"wrong number of arguments");
391
if (params=="") quit();
392
else send_reply(501,"wrong number of arguments");
398
send_reply(500,"command not recognized");
403
void close_connection()
405
if(_state!=STATE_QUIT) //we got called by idle-timeout
406
send_reply(221,""+sServer+" closing connection, idle for too long");
407
::close_connection();