smtp._pike
Go to the documentation of this file.
1 /* Copyright (C) 2000-2006 Thomas Bopp, Thorsten Hampel, Ludger Merkens
2  * Copyright (C) 2002-2004 Christian Schmidt
3  *
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.
8  *
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.
13  *
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
17  *
18  * $Id: smtp.pike,v 1.1 2008/03/31 13:39:57 exodusd Exp $
19  */
20 inherit "/net/coal/login";
21 inherit "/net/base/line";
22 #include <macros.h>
23 #include <config.h>
24 #include <database.h>
25 #include <attributes.h>
26 #include <classes.h>
27 #include <exception.h>
28 #include <access.h>
29 #include <mail.h>
30 class smtp : public login,line{
31 public:
32 
33 
34 
35 /*
36  * implements a smtp-server (see rfc2821 for details)
37  */
38 
39 
40 
41 
42 //#define SMTP_DEBUG
43 
44 #ifdef SMTP_DEBUG
45 #define DEBUG_SMTP(s, args...) werror("net/smtp: "+s+"\n", args)
46 #else
47 #define DEBUG_SMTP
48 #endif
49 
50 
51  int _state = STATE_INITIAL;
52  int _esmtp = 0;
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;
57  object oRcpt;
58  object _Forward = _Server->get_module("forward");
59 
60  string sMessage="";
61  string sSender="";
62  array aoRecipients=({});
63 
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
66 protected:
67  void send_reply(int code, string msg)
68 {
69  array lines = msg / "\n";
70  for(int i=0;i<sizeof(lines); i++) //multiline reply
71  {
72  if(i==sizeof(lines)-1) send_message(""+code+" "+lines[i]+"\r\n");
73  else send_message(""+code+"-"+lines[i]+"\r\n");
74  }
75 }
76 
77 public:
78 
79 //called upon connection, greets the client
80 void create(object f)
81 {
82  ::create(f);
83 
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);
88 }
89 
90 string query_address_name()
91 {
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 )
97  return res[0];
98  return addr;
99 }
100 
101 protected:
102  void ehlo(string client)
103 {
104  if(_state!=STATE_INITIAL)
105  {
106  //reset everything important
107  sMessage="";
108  aoRecipients=({});
109  }
110  _esmtp=1; //client supports ESMTP
111  _state=STATE_IDENTIFIED; //client identified correctly
112 
113  string addr=query_address();
114  sscanf(addr,"%s %*s",addr); //addr now contains ip of connecting host
115 
116  //verify if given name is correct
117  object dns = Protocols.DNS.client();
118  array res = dns->gethostbyaddr(addr);
119  if (res[0]==client)
120  send_reply(250,sServer+" Hello "+client+" ["+addr+"]");
121  else
122  send_reply(250,sServer+" Hello "+client+" ["+addr+"] (Expected \"EHLO "+res[0]+"\")");
123 }
124 
125 public:
126 
127 protected:
128  void helo(string client)
129 {
130  if(_state!=STATE_INITIAL)
131  {
132  //reset everything important
133  sMessage="";
134  aoRecipients=({});
135  }
136  _esmtp=0; //client does not support ESMTP
137  _state=STATE_IDENTIFIED; //client identified correctly
138 
139  string addr=query_address();
140  sscanf(addr,"%s %*s",addr);
141 
142  //verify if given name is correct
143  object dns = Protocols.DNS.client();
144  array res = dns->gethostbyaddr(addr);
145  if (res[0]==client)
146  send_reply(250,sServer+" Hello "+client+" ["+addr+"]");
147  else send_reply(250,sServer+" Hello "+client+" ["+addr+"] (Expected \"HELO "+res[0]+"\")");
148 }
149 
150 public:
151 
152 protected:
153  void help()
154 {
155  send_reply(250,"This is the opensTeam-Mailserver\n"+
156  "Contact: http://www.open-steam.org");
157 }
158 
159 public:
160 
161 protected:
162  string mail(string sender)
163 {
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 )
170  {
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 "+
176  "sTeam user!";
177  }
178  else {
179  oUser = user;
180  }
181  }
182  sSender=sender;
183  _state=STATE_TRANSACTION; //waiting for RCPT command(s) now
184  send_reply(250,"Sender accepted"); //NOTE: sender can't be verified
185  return 0;
186  }
187  return "syntax error, wrong sender format!";
188 }
189 
190 public:
191 
192 protected:
193  array getIPs(string addr)
194 {
195  object dns = Protocols.DNS.client();
196 
197  array result = dns->gethostbyname(lower_case(addr));
198  if ( !arrayp(result) || sizeof(result) < 2 )
199  return ({ });
200 
201  return result[1];
202 }
203 
204 public:
205 
206 int check_rcpt(string user, string domain)
207 {
208  DEBUG_SMTP("Mail to domain=%s - LOCALDOMAIN=%s", domain, sFQDN);
209 
210  if( lower_case(domain) == lower_case(sFQDN) )
211  return 1;
212  else if (_ADMIN->query_attribute("virtual_hosts")[domain])
213  return 1;
214  else {
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) )
220  domains = ({ });
221 
222  domains += ({ _Server->query_config("domain") });
223  if ( search(domains, domain) >= 0 )
224  return 1;
225 
226  array myIPs = getIPs(_Server->get_server_name());
227  array remoteIPs = getIPs(domain);
228 
229  DEBUG_SMTP("Checking IPS: local=%O, remote=%O", myIPs, remoteIPs);
230  if ( sizeof( (myIPs & remoteIPs) ) > 0 )
231  return 1;
232  }
233  return 0;
234 }
235 
236 protected:
237  void rcpt(string recipient)
238 {
239  string address;
240  if ( sscanf(recipient, "%*s<%s>", address) == 0 )
241  address = recipient;
242 
243 
244  if(lower_case(address)=="postmaster")
245  address="postmaster@"+sFQDN; //rcpt to:<postmaster> is always local!
246 
247  string user, domain;
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");
251  return;
252  }
253 
254  int success = check_rcpt(user, domain);
255 
256  if ( success ) //only accept for local domain
257  {
258  string user = lower_case(user);
259  if(user=="postmaster") user="root";//change to other user,if needed
260 
261  int valid = _Forward->is_valid(user); //check if rcpt is ok
262  DEBUG_SMTP("is_valid() returned "+valid+" for target "+user);
263  if(valid > 0)
264  {
265  aoRecipients+=({user}); //"doubled" recipients will be removed later!
266  send_reply(250,"Recipient ok");
267  _state=STATE_RECIPIENT; //waiting for DATA or RCPT
268  }
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);
273  else
274  send_reply(450,"unknown error");
275  }
276  else
277  send_reply(550,"we do not relay for you!");
278 }
279 
280 public:
281 
282 
283 protected:
284  void data()
285 {
286  //"minimize" list of recipients
287  aoRecipients=Array.uniq(aoRecipients);
288 
289  send_reply(354,"send message now, end with single line containing '.'");
290  _state=STATE_DATA;
291  register_data_func(process_data);
292 
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";
297 }
298 
299 public:
300 
301 protected:
302  void process_data(string data)
303 {
304  int i;
305  if ( (i=search(data, "\n.\r\n")) > 0 || (i=search(data, "\n.\n")) > 0 ) {
306  data = data[..i];
307  }
308  else if ( (i=search(sMessage + data, "\n.\r\n")) > 0 ) {
309  sMessage = sMessage[..i];
310  data = "";
311  }
312 
313  if ( i != 0 )
314  sMessage += data;
315 
316  if ( i != -1 )
317  {
318  sMessage+="\r\n";
319 
320  send_reply(250,"Message accepted, size is "+sizeof(sMessage));
321  DEBUG_SMTP("received mail, recipients are:%O",aoRecipients);
322 
323  int res;
324 
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);
331  res = 1;
332  }
333  else
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();
338  sMessage="";
339  aoRecipients=({});
340  DEBUG_SMTP("processing data finished !");
341  }
342 }
343 
344 public:
345 
346 protected:
347  void rset()
348 {
349  if(_state>STATE_IDENTIFIED) _state=STATE_IDENTIFIED;
350  sMessage="";
351  sSender="";
352  aoRecipients=({});
353  send_reply(250,"RSET completed");
354 }
355 
356 public:
357 
358 protected:
359  void noop()
360 {
361  send_reply(250,"NOOP completed");
362 }
363 
364 public:
365 
366 protected:
367  void quit()
368 {
369  send_reply(221,""+sServer+" closing connection");
370  _state=STATE_QUIT;
371  close_connection();
372 }
373 
374 public:
375 
376 protected:
377  void vrfy(string user)
378 {
379  send_reply(252,"Cannot VRFY user, but will accept message and attempt delivery");
380  //verification code may be added here
381 }
382 
383 public:
384 
385 //this function is called for each line the client sends
386 protected:
387  void process_command(string cmd)
388 {
389  if(_state==STATE_DATA)
390  {
391  process_data(cmd);
392  return;
393  }
394 
395  string command,params;
396  if(sscanf(cmd,"%s %s",command,params)!=2)
397  {
398  command=cmd;
399  params="";
400  }
401 
402  switch(upper_case(command))
403  {
404  case "EHLO":
405  if(search(params," ")==-1) ehlo(params);
406  else send_reply(501,"wrong number of arguments");
407  break;
408  case "HELO":
409  if(search(params," ")==-1) helo(params);
410  else send_reply(501,"wrong number of arguments");
411  break;
412  case "HELP":
413  help();
414  break;
415  case "MAIL":
416  if(_state==STATE_IDENTIFIED)
417  {
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);
423  }
424  }
425  else
426  send_reply(501,"syntax error");
427  }
428  else send_reply(503,"bad sequence of commands - EHLO expected");
429  break;
430  case "RCPT":
431  if(_state==STATE_TRANSACTION||_state==STATE_RECIPIENT)
432  {
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");
437  }
438  else send_reply(503,"bad sequence of commands");
439  break;
440  case "DATA":
441  if(_state==STATE_RECIPIENT)
442  {
443  if (params=="") data();
444  else send_reply(501,"wrong number of arguments");
445  }
446  else send_reply(501,"bad sequence of commands");
447  break;
448  case "RSET":
449  if (params=="") rset();
450  else send_reply(501,"wrong number of arguments");
451  break;
452  case "NOOP":
453  noop();
454  break;
455  case "QUIT":
456  if (params=="") quit();
457  else send_reply(501,"wrong number of arguments");
458  break;
459  case "VRFY":
460  vrfy(params);
461  break;
462  default:
463  send_reply(500,"command not recognized");
464  break;
465  }
466 }
467 
468 public:
469 
470 void close_connection()
471 {
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();
475 }
476 
477 
478 
479 
480 
481 
482 };