smtp._pike
Go to the documentation of this file.
1 /* Copyright (C) 2000-2006 Thomas Bopp, Thorsten Hampel, Ludger Merkens
2  *
3  * This program is free software; you can redistribute it and/or modify
4  * it under the terms of the GNU General Public License as published by
5  * the Free Software Foundation; either version 2 of the License, or
6  * (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program; if not, write to the Free Software
15  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16  *
17  * $Id: smtp.pike,v 1.2 2009/05/06 19:23:10 astra Exp $
18  */
19 inherit "/kernel/secure_mapping";
20 inherit "/base/serialize";
21 #include <macros.h>
22 #include <access.h>
23 #include <config.h>
24 #include <database.h>
25 #include <attributes.h>
26 #include <classes.h>
27 //! This is the SMTP module for sTeam. It sends mail to some e-mail adress
28 //! by using a local mailserver or doing MX lookup and sending directly
29 //! to the targets mail server.
30 class smtp : public secure_mapping,serialize{
31 public:
32 
33 
34 
35 
36 //#define SMTP_DEBUG
37 
38 #ifdef SMTP_DEBUG
39 #define SMTP_LOG(s, args...) werror("smtp: "+s+"\n", args)
40 #else
41 #define SMTP_LOG(s, args...)
42 #endif
43 
44 #if constant(Protocols.SMTP.client)
45 #define SMTPCLIENT Protocols.SMTP.client
46 #else
47 #define SMTPCLIENT Protocols.SMTP.Client
48 #endif
49 
50 
51  string server;
52  int port;
53 
54 
55  Thread.Queue MsgQueue = Thread.Queue();
56  Thread.Mutex saveMutex = Thread.Mutex();
57 
58  object oSMTP; // cache smtp object (connection)
59  object oDNS; // cache DNS Client
60 
61 private function myfDb;
62 private string mysDbTable;
63  int msgId;
64 
65 private mapping test_objects = ([ ]);
66 
67 private:
68  void init_module()
69 {
70  mixed err = catch( oDNS = Protocols.DNS.client() );
71  if ( err ) {
72  werror( "smtp: warning, could not create DNS client.\n" );
73  }
74  add_data_storage(STORE_SMTP, retrieve_mails, restore_mails);
75 }
76 
77 public:
78 
79 private:
80 mapping retrieve_mails()
81 {
82  return ([ ]);
83 }
84 
85 public:
86 
87 private:
88 void restore_mails(mapping data)
89 {
90 }
91 
92 public:
93 
94 
95 protected:
96  void load_module()
97 {
98  ::load_module();
99  [myfDb , mysDbTable] = _Database->connect_db_mapping();
100  mixed err = catch {
101  if( search(myfDb()->list_tables(), "i_rawmails" ) == -1 ) {
102  myfDb()->big_query("create table i_rawmails (mailid int, rawdata longtext, UNIQUE(mailid))");
103  }
104  };
105  // load all mails, which have not been send previously
106  msgId = 1;
107  err = catch(load_mails());
108  if ( err ) MESSAGE("Error loading mails:\n%O\n%O", err[0], err[1]);
109 }
110 
111 public:
112 
113 protected:
114  void load_mails()
115 {
116  mapping msg;
117 
118  // load mails where ?
119  array mails = index();
120 
121  foreach(mails, string id) {
122  int i = (int)id;
123  msgId = max(i, msgId);
124  msg = get_value(id);
125 
126  // see if something is in raw data table
127  Sql.sql_result res = myfDb()->big_query(sprintf("select rawdata from i_rawmails where mailid='%s'", id));
128  mixed row;
129  while ( row = res->fetch_row() ) {
130 #if constant(steamtools.unserialize)
131  msg->raw = steamtools.unserialize(row[0], find_object);
132 #else
133  msg->raw = unserialize(row[0]);
134 #endif
135  }
136  if ( mappingp(msg) ) {
137  msg->msgid = i;
138  deliver_message(msg);
139  }
140  }
141 }
142 
143 public:
144 
145 protected:
146  void save_mail(mapping msg)
147 {
148  // save mails where ?
149  mapping m;
150 
151  object lock = saveMutex->lock();
152  mixed err = catch {
153  msgId++;
154  msg->msgid = msgId;
155  m = copy_value(msg);
156  m_delete(m, "raw");
157 
158  function serializer;
159  #if constant(steamtools.serialize)
160  serializer = steamtools.serialize;
161  #else
162  serializer = serialize;
163 #endif
164  set_value((string)msgId, m);
165  if ( stringp(msg->raw) && strlen(msg->raw)>0 ) {
166  myfDb()->big_query(sprintf("insert into i_rawmails values ('%d', '%s')",
167  msgId, myfDb()->quote(serializer(msg->raw))));
168  }
169  };
170  if ( err != 0 ) {
171  FATAL("Failed to save mail: %O\n%O", err[0], err[1]);
172  }
173  destruct(lock);
174 }
175 
176 public:
177 
178 protected:
179  void delete_mail(mapping msg)
180 {
181  delete(msg->msgid);
182  myfDb()->big_query(sprintf("delete from i_rawmails where mailid='%d'",
183  msg->msgid));
184 }
185 
186 public:
187 
188 void runtime_install()
189 {
190  SMTP_LOG("Init module SMTP !");
191 
192  // an initial connection needs to be created to load some libraries
193  // otherwise creating connections will fail after the sandbox
194  // is in place (chroot("server/"))
195  server = _Server->query_config(CFG_MAILSERVER);
196  port = _Server->query_config(CFG_MAILPORT);
197  if (objectp(oDNS) && stringp(server) && sizeof(server) > 0 ) {
198  array result = oDNS->gethostbyname(lower_case(server));
199  if (arrayp(result) && sizeof(result)>1 && arrayp(result[1]) && sizeof(result[1])>0) {
200  server = result[1][0];
201  MESSAGE("Using SMTP Server Adress: %O", server);
202  }
203  }
204  if ( !intp(port) || port <= 0 )
205  port = 25;
206 
207  mixed err;
208 
209  if ( stringp(server) && sizeof(server) > 0 && server != "disabled" )
210  err = catch( oSMTP = SMTPCLIENT( server, port ) );
211  if ( err )
212  FATAL("Failed to connect to " + server+" :\n"+sprintf("%O\n", err));
213 
214  start_thread(smtp_thread);
215 }
216 
217 void deliver_message(mapping msg)
218 {
219  object serviceMod = get_module("ServiceManager");
220  if ( objectp(serviceMod) && serviceMod->is_service("smtp") ) {
221  serviceMod->call_service("smtp", ([ "action": "send", "msg": msg, ]));
222  }
223  else {
224  MsgQueue->write(msg);
225  }
226 }
227 
228 
229 void
230 send_mail(array|string email, string subject, string body, void|string from, void|string fromobj, void|string mimetype, void|string fromname, void|string date, void|string message_id, void|string in_reply_to, void|string reply_to, void|string mail_followup_to)
231 {
232  mapping msg = ([ ]);
233  msg->email = email;
234  msg->subject = subject;
235  if ( stringp(subject) )
236  msg->subject = Messaging.get_quoted_string( subject );
237  msg->mimetype = (stringp(mimetype) ? mimetype : "text/plain");
238  if ( lower_case(msg->mimetype) == "text/html" )
239  msg->body = Messaging.fix_html( body );
240  else
241  msg->body = body;
242  msg->date = date || timelib.smtp_time(time());
243  msg->message_id = message_id||("<"+(string)time()+(fromobj||("@"+_Server->get_server_name()))+">");
244  msg->rawmime = 0;
245  if(reply_to)
246  msg->reply_to=reply_to;
247  if(mail_followup_to)
248  msg->mail_followup_to=mail_followup_to;
249  if(in_reply_to)
250  msg->in_reply_to=in_reply_to;
251 
252  get_module("log")->log_debug( "smtp", "send_mail: to=%O", email );
253 
254  if ( stringp(from) && sizeof(from) > 0 )
255  msg->from = from;
256  else
257  msg->from = this_user()->get_identifier() +
258  "@" + _Server->get_server_name();
259  if ( stringp(fromobj) )
260  msg->fromobj = fromobj;
261  if ( stringp(fromname) )
262  msg->fromname = fromname;
263 
264  save_mail(msg);
265  deliver_message(msg);
266 
267  object user = geteuid() || this_user();
268  if ( objectp(user) && user->is_storing_sent_mail() &&
269  objectp(user->get_sent_mail_folder()) ) {
270  object mail_copy = get_factory(CLASS_DOCUMENT)->execute(
271  ([ "name":msg->subject, "mimetype": msg->mimetype ]) );
272  mail_copy->set_attribute( OBJ_DESC, msg->subject );
273  array to_arr;
274  if ( arrayp(email) ) to_arr = email;
275  else to_arr = ({ email });
276  mail_copy->set_attribute( "mailto", to_arr );
277  mail_copy->set_content( msg->body );
278  mail_copy->sanction_object( user, SANCTION_ALL );
279  mail_copy->set_acquire( 0 );
280  object old_euid = geteuid();
281  seteuid( user );
282  get_module( "table:read-documents" )->download_document( 0, mail_copy, UNDEFINED ); // mark as read
283  foreach ( mail_copy->get_annotations(), object ann )
284  get_module( "table:read-documents" )->download_document( 0, ann, UNDEFINED ); // mark as read
285  seteuid( old_euid );
286  user->get_sent_mail_folder()->add_annotation( mail_copy );
287  }
288 }
289 
290 void send_mail_raw(string|array email, string data, string from)
291 {
292 
293  mapping msg = ([ ]);
294  msg->email = email;
295  msg->rawmime = data;
296  if ( stringp(from) && sizeof(from) > 0 )
297  msg->from = from;
298  else
299  msg->from = this_user()->get_identifier()
300  + "@" + _Server->get_server_name();
301 
302  save_mail(msg);
303  deliver_message(msg);
304 }
305 
306 void send_mail_mime(string email, object message)
307 {
308  mapping mimes = message->query_attribute(MAIL_MIMEHEADERS);
309  string from;
310  sscanf(mimes->from, "%*s<%s>", from);
311  if ( !stringp(from) || sizeof(from) < 1 )
312  from = this_user()->get_identifier() + "@" + _Server->get_server_name();
313  send_mail(email, message->get_identifier(), message->get_content(), from);
314 }
315 
316 protected:
317  mixed cb_tag(Parser.HTML p, string tag)
318 {
319  if ( search(tag, "<br") >= 0 || search (tag, "<BR") >= 0 )
320  return ({ "\n" });
321  return ({ "" });
322 }
323 
324 public:
325 
326 void send_message(mapping msg)
327 {
328  object smtp;
329 
330  if ( !intp(port) || port <= 0 ) port = 25;
331 
332  object log = get_module("log");
333 
334  log->log_debug( "smtp", "send_message: server: %O:%O", server, port );
335 
336  if ( !stringp(msg->from) ) {
337  msg->from = "admin@"+_Server->get_server_name();
338  log->log_debug( "smtp", "send_message: invalid 'from', using %s",
339  msg->from );
340  }
341  log->log_debug( "smtp", "send_message: mail from %O to %O (server: %O:%O)\n"
342  + " Subject: %O", msg->from, msg->email, server, port, msg->subject );
343 
344  if ( !arrayp(msg->email) )
345  msg->email = ({ msg->email });
346 
347  foreach ( msg->email, string email ) {
348  string tmp_server = server;
349  int tmp_port = port;
350  mixed smtp_error;
351  if ( stringp(server) && sizeof(server) > 0 ) {
352  smtp_error = catch {
353  smtp = SMTPCLIENT( server, port );
354  };
355  }
356  else {
357  // if no server is configured use the e-mail of the receiver
358  string host;
359  sscanf( email, "%*s@%s", host );
360  if ( !stringp(host) )
361  steam_error("MX Lookup failed, host = 0 in %O", msg->email);
362 
363  if ( !objectp(oDNS) )
364  steam_error("MX Lookup failed, no DNS");
365  tmp_server = oDNS->get_primary_mx(host);
366  if ( !stringp(tmp_server) )
367  return;
368  array dns_data = oDNS->gethostbyname(tmp_server);
369  if ( arrayp(dns_data) && sizeof(dns_data) > 1 &&
370  arrayp(dns_data[1]) && sizeof(dns_data[1]) > 0 )
371  tmp_server = dns_data[1][0];
372  tmp_port = 25;
373  log->log_debug( "smtp", "send_message: MX lookup: %O:%O",
374  tmp_server, tmp_port );
375  smtp_error = catch {
376  smtp = SMTPCLIENT( tmp_server, tmp_port );
377  };
378  }
379 
380  if ( !objectp(smtp) || smtp_error ) {
381  string msg = sprintf( "Invalid mail server %O:%O (from %O to %O)\n",
382  tmp_server, tmp_port, msg->from, email );
383  if ( smtp_error ) msg += sprintf( "%O", smtp_error );
384  log->log_error( "smtp", msg );
385  continue;
386  }
387 
388  if ( stringp(msg->rawmime) ) {
389  // send directly
390  smtp_error = catch {
391  smtp->send_message(msg->from, ({ email }), msg->rawmime);
392  };
393  if ( smtp_error ) {
394  log->log_error( "smtp",
395  "Failed to send mail directly from %O to %O via %O:%O : %O",
396  msg->from, email, tmp_server, tmp_port, smtp_error[0]);
397  }
398  else {
399  log->log_info( "smtp", "Mail sent directly from %O to %O via %O:%O\n",
400  msg->from, email, tmp_server, tmp_port );
401  }
402  continue;
403  }
404 
405  if ( !stringp(msg->mimetype) )
406  msg->mimetype = "text/plain";
407 
408  if ( !stringp(msg->body) )
409  log->log_error( "smtp", "Invalid message body from %O to %O:\n%O",
410  msg->from, email, msg->body );
411 
412  MIME.Message mmsg = MIME.Message(
413  msg->body||"",
414  ([ "Content-Type": (msg->mimetype||"text/plain") + "; charset=utf-8",
415  "Mime-Version": "1.0 (generated by open-sTeam)",
416  "Subject": msg->subject||"",
417  "Date": msg->date || timelib.smtp_time(time()),
418  "From": msg->fromname||msg->from||msg->fromobj||"",
419  "To": (msg->fromobj ? msg->fromobj : email)||"",
420  "Message-Id": msg->message_id||"",
421  ]) );
422 
423  if(msg->mail_followup_to)
424  mmsg->headers["Mail-Followup-To"]=msg->mail_followup_to;
425  if(msg->reply_to)
426  mmsg->headers["Reply-To"]=msg->reply_to;
427  if(msg->in_reply_to)
428  mmsg->headers["In-Reply-To"]=msg->in_reply_to;
429 
430  smtp_error = catch {
431  smtp->send_message(msg->from, ({ email }), (string)mmsg);
432  };
433  if ( smtp_error ) {
434  log->log_error( "smtp", "Failed to send mail from %O to %O via %O:%O"
435  + " : %O\n", msg->from, email, tmp_server, tmp_port, smtp_error[0] );
436  }
437  else {
438  log->log_info( "smtp", "Mail sent from %O to %O via %O:%O\n",
439  msg->from, email, tmp_server, tmp_port );
440  }
441  }
442 }
443 
444 protected:
445  void smtp_thread()
446 {
447  mapping msg;
448 
449  while ( 1 ) {
450  SMTP_LOG("smtp-thread running...");
451  msg = MsgQueue->read();
452  mixed err = catch {
453  send_message(msg);
454  delete_mail(msg);
455  get_module("log")->log_debug( "smtp",
456  "Message from %O to %O sent: '%O'", msg->from, msg->email,
457  (stringp(msg->rawmime)?"mime message": msg->subject) );
458  };
459  if ( err != 0 ) {
460  FATAL("Error while sending message: " + err[0] +
461  sprintf("\n%O\n", err[1]));
462  if ( server == "disabled" ) {
463  sleep(600); // wait 10 minutes (config could change) and continue
464  continue;
465  }
466 
467  FATAL("MAILSERVER="+_Server->query_config(CFG_MAILSERVER));
468  if ( objectp(oSMTP) ) {
469  destruct(oSMTP);
470  oSMTP = 0;
471  }
472  sleep(60); // wait one minute before retrying
473  }
474  }
475 }
476 
477 public:
478 
479 string get_identifier() { return "smtp"; }
480 string get_table_name() { return "smtp"; }
481 
482 
483 
484 void test( void|int try_nr )
485 {
486  object services = get_module("ServiceManager");
487  if ( !objectp(services) || !services->is_service("smtp") && try_nr < 12 ) {
488  Test.add_test_function( test, 10, try_nr+1 );
489  return;
490  }
491  Test.test( "have smtp service",
492  objectp(services) && services->is_service("smtp") );
493  // check the send mail functionality using different ways
494  // due to the behaviour of the message system the mails are sent and
495  // later checked to see whether the have been received within the server
496 
497  // use configured email(s)
498  string email = _Server->query_config("email");
499  if ( !stringp(server) || strlen(server) == 0 ) {
500  Test.skipped( "smtp", "no mailserver set" );
501  return;
502  }
503 
504  object old_euid = geteuid();
505  seteuid( USER("root") );
506 
507  // create temporary test users and group:
508  string tmp_name;
509  int tmp_name_count = 1;
510  object user_sender;
511  do {
512  tmp_name = "mailtest_sender_" + ((string)time()) + "_" +
513  ((string)tmp_name_count++);
514  user_sender = USER( tmp_name );
515  } while ( objectp(user_sender) );
516  user_sender = get_factory(CLASS_USER)->execute( ([ "name": tmp_name,
517  "pw":"test", "email": GROUP("admin")->get_steam_email(), ]) );
518  if ( objectp(user_sender) )
519  test_objects["sender"] = user_sender;
520 
521  object user_receiver;
522  tmp_name_count = 1;
523  do {
524  tmp_name = "mailtest_receiver_" + ((string)time()) + "_" +
525  ((string)tmp_name_count++);
526  user_receiver = USER( tmp_name );
527  } while ( objectp(user_receiver) );
528  user_receiver = get_factory(CLASS_USER)->execute( ([ "name": tmp_name,
529  "pw":"test", ]) );
530  if ( objectp(user_receiver) )
531  test_objects["receiver"] = user_receiver;
532 
533  object group;
534  tmp_name_count = 1;
535  do {
536  tmp_name = "mailtest_group_" + ((string)time()) + "_" +
537  ((string)tmp_name_count++);
538  group = GROUP( tmp_name );
539  } while ( objectp(group) );
540  group = get_factory(CLASS_GROUP)->execute( ([ "name": tmp_name, ]) );
541  if ( objectp(group) )
542  test_objects["group"] = group;
543 
544  // setup temporary users and group:
545  object sent_mail = user_sender->create_sent_mail_folder();
546  user_sender->set_is_storing_sent_mail( true );
547  user_receiver->set_attribute( USER_FORWARD_MSG, 1 );
548  if ( arrayp(_Server->get_cmdline_email_addresses()) ) {
549  if ( sizeof(_Server->get_cmdline_email_addresses()) > 0 )
550  user_receiver->set_attribute( USER_EMAIL,
551  _Server->get_cmdline_email_addresses()[0] );
552  foreach ( _Server->get_cmdline_email_addresses(), string email ) {
553  get_module("forward")->add_forward( user_receiver, email );
554  }
555  }
556  group->add_member( user_receiver );
557 
558  // switch to sender uid and prepare tests:
559  seteuid( user_sender );
560  mapping sent = ([ ]);
561  mapping receive = ([ ]);
562  string testname;
563 
564  // test sending to users:
565  object testmail1 = user_receiver->mail( "test user mail (1)",
566  "test mailsystem 1" );
567  Test.test( "sending direct user mail", objectp(testmail1) );
568  if ( objectp(testmail1) )
569  sent[ "storing direct user mail in sent-mail" ] = "test mailsystem 1";
570  else
571  Test.skipped( "storing direct user mail in sent-mail" );
572 
573  object factory = get_factory(CLASS_DOCUMENT);
574  object plaintext = factory->execute( (["name":"test2.txt" ]) );
575  plaintext->set_content("mail mit object an user (2)");
576  object testmail2 = user_receiver->mail(plaintext, "test mailsystem 2");
577  Test.test( "sending mail document", objectp(testmail2) );
578  if ( objectp(testmail2) )
579  sent[ "storing mail document in sent-mail" ] = "test2.txt";
580  else
581  Test.skipped( "storing mail document in sent-mail" );
582 
583  object html = factory->execute( (["name":"test3.html", ]) );
584  html->set_content("<html><body><h2>Testing mail function with HTML body! (3)</h2></body></html>");
585  object testmail3 = user_receiver->mail(html, "test mailsystem 3");
586  Test.test( "sending mail with html document", objectp(testmail3) );
587  if ( objectp(testmail3) )
588  sent[ "storing mail with html document in sent-mail" ] = "test3.html";
589  else
590  Test.skipped( "storing mail with html document in sent-mail" );
591 
592  object ann = factory->execute( (["name":"test4.html", ]) );
593  plaintext = factory->execute( (["name":"test4.txt" ]) );
594  plaintext->set_content("mail with object and attachement (4)");
595  ann->set_content("<html><body><h2>Testing mail function as HTML annotation!</h2></body></html>");
596  plaintext->add_annotation(ann);
597  object testmail4 = user_receiver->mail(plaintext, "test mailsystem 4");
598  Test.test( "sending mail with annotated document", objectp(testmail4) );
599  if ( objectp(testmail4) )
600  sent[ "storing mail with annotated document in sent-mail" ] = "test4.txt";
601  else
602  Test.skipped( "storing mail with annotated document in sent-mail" );
603 
604  MESSAGE("Testing direct sending with mail system to %O",
605  user_receiver->get_steam_email());
606  testname = "testmail-5 to user address " + ((string)time());
607  send_mail( user_receiver->get_steam_email(), testname,
608  "direct test (5)", "admin@steam.uni-paderborn.de" );
609  receive[ "sending directly to user email address" ] = testname;
610  testname = "testmail-6 to user addresses (array) " + ((string)time());
611  send_mail( ({ user_receiver->get_steam_email(),
612  user_receiver->get_steam_email() }), testname,
613  "test with an array of recipients (6)",
614  "admin@steam.uni-paderborn.de" );
615  receive[ "sending directly to user email addresses (array)" ] = testname;
616 
617  plaintext = factory->execute( (["name":"mailtest-7.txt" ]) );
618  plaintext->set_content("Testing mail with PDF attachement (0 byte) (7)");
619  object pdf = factory->execute( ([ "name": "test.pdf" ]) );
620  pdf->set_content("");
621  plaintext->add_annotation( pdf );
622  object testmail7 = user_receiver->mail( plaintext );
623  Test.test( "sending mail with empty pdf attachment", objectp(testmail7) );
624  if ( objectp(testmail7) )
625  sent[ "storing mail with empty pdf attachment in sent-mail" ] = "mailtest-7.txt";
626  else
627  Test.skipped( "storing mail with empty pdf attachment in sent-mail" );
628 
629  // temporary disable mail host (MX test)
630  string smtphost = _Server->query_config(CFG_MAILSERVER);
631  _Server->set_config(CFG_MAILSERVER, 0, true);
632 
633  MESSAGE("Testing MX Lookup !");
634  testname = "testmail-8 to user address (mx)" + ((string)time());
635  send_mail( user_receiver->get_steam_email(), testname,
636  "Testing mail with MX (8)", "admin@steam.uni-paderborn.de" );
637  receive[ "sending mail to user via mx lookup" ] = testname;
638 
639  _Server->set_config(CFG_MAILSERVER, smtphost, true);
640 
641  // test sending to groups
642  testname = "testmail-9 to group address " + ((string)time());
643  send_mail( group->get_steam_email(), testname,
644  "Testing mail to group", "admin@steam.uni-paderborn.de" );
645  receive[ "sending mail to group via mail address" ] = testname;
646 
647  testname = "testmail-10 to a group " + ((string)time());
648  plaintext = factory->execute( ([ "name":testname ]) );
649  plaintext->set_attribute(DOC_MIME_TYPE, "text/plain");
650  plaintext->set_content("Mail to group (10) ");
651  group->mail( plaintext );
652  receive[ "sending mail as plaintext to group" ] = testname;
653  sent[ "storing mail as plaintext to group in sent-mail" ] = testname;
654 
655  seteuid( old_euid );
656 
657  Test.add_test_function( test_more, 10, sent, receive );
658 }
659 
660 void test_more ( mapping sent, mapping receive, void|int nr_try ) {
661  array received = test_objects["receiver"]->get_annotations();
662  foreach ( indices(receive), string msg ) {
663  object found;
664  string name = receive[msg];
665  foreach ( received, object obj ) {
666  if ( obj->get_identifier() == name ||
667  obj->query_attribute(OBJ_DESC) == name ) {
668  found = obj;
669  break;
670  }
671  }
672  if ( objectp(found) ) {
673  m_delete( receive, msg );
674  Test.succeeded( msg );
675  }
676  }
677 
678  array sent_mails = test_objects["sender"]->get_sent_mail_folder()->get_annotations();
679  foreach ( indices(sent), string msg ) {
680  object found;
681  string name = sent[msg];
682  foreach ( sent_mails, object obj ) {
683  if ( obj->get_identifier() == name ||
684  obj->query_attribute(OBJ_DESC) == name ) {
685  found = obj;
686  break;
687  }
688  }
689  if ( objectp(found) ) {
690  m_delete( sent, msg );
691  Test.succeeded( msg );
692  }
693  }
694 
695  if ( sizeof(receive) > 0 && sizeof(sent) > 0 ) {
696  if ( nr_try < 12 ) Test.add_test_function( test_more, 10, sent, receive, nr_try+1 );
697  else {
698  foreach ( indices(receive), string msg )
699  Test.failed( msg );
700  foreach ( indices(sent), string msg )
701  Test.failed( msg );
702  }
703  }
704 
705  Test.add_test_function( test_wait_for_empty_mail_queue, 10, 0 );
706 }
707 
708 void test_wait_for_empty_mail_queue ( void|int try_nr ) {
709  if ( MsgQueue->size() > 0 && try_nr < 18 ) {
710  Test.add_test_function( test_wait_for_empty_mail_queue, 10, try_nr+1 );
711  return;
712  }
713  // This doesn't seem to work, we might need to wait for the MsgQueue to be
714  // empty for a longer time or something, don't know...
715  //Test.test( "\"send\" mail queue empty", MsgQueue->size() < 1 );
716  Test.skipped( "\"send\" mail queue empty (test currently broken)" );
717 }
718 
719 void test_cleanup () {
720  if ( mappingp(test_objects) ) {
721  object old_euid = geteuid();
722  seteuid( USER("root") );
723  foreach ( values(test_objects), object obj ) {
724  catch( obj->delete() );
725  }
726  seteuid( old_euid );
727  }
728 }
729 
730 
731 };