blob: 1c0d575ed3db7c181916571f7b5a926703e5f7bf [file] [log] [blame]
MG Mud User88f12472016-06-24 23:31:02 +02001// MorgenGrauen MUDlib
2//
3// mailer.c
4//
5// $Id: mailer.c 9547 2016-04-17 19:27:47Z Zesstra $
6
7/*
8 *------------------------------------------------------------
9 * The mail demon. Receives mail from users and delivers it into
10 * the mail directory.
11 *
12 * Deepthought, Nightfall, 25-May-92
13 * Remove-Functions : Jof, 29-June-92
14 * Caching, DeleteUnreadFolder, small changes: Loco, 08-Feb-97
15 * General clean-up and speed-up: Tiamak, 18-Jan-2000
16 * DON'T USE restore_object any more, use GetFolders instead!
17 *------------------------------------------------------------
18 *
19 * Save file format (sort of formal notation):
20 *
21 * mixed *folders = ({
22 * ({ string name1; string name2; ... string nameN; })
23 * ({ mixed *msgs1; mixed *msgs2; ... mixed *msgsN; })
24 * })
25 *
26 * Each msgs field is an array of messages:
27 *
28 * mixed *msgs = ({ mixed *message1; ... mixed *messageM })
29 *
30 * A message is represented as an array with the following fields:
31 *
32 * mixed *message = ({
33 * string from;
34 * string sender;
35 * string recipient;
36 * string *cc;
37 * string *bcc;
38 * string subject;
39 * string date;
40 * string id;
41 * string body;
42 * })
43 *
44 * The mailer demon (/secure/mailer) provides the following functions:
45 *
46 * string *DeliverMail(mixed *message)
47 * Hand a mail message over to the mailer demon. The mailer
48 * demon extracts recipients from the recipient, cc and bcc
49 * fields and removes the bcc information. It then deposits
50 * the message to the mail files of all recipients. A valid
51 * message is shown above. Returns a list of successfully
52 * delivered recipients.
53 *
54 * int FingerMail(string user)
55 * Gives the number of unread messages a user has.
56 *------------------------------------------------------------
57 */
58#pragma strict_types
59#pragma no_clone
60#pragma no_shadow
61#pragma no_inherit
62#pragma verbose_errors
63#pragma pedantic
64#pragma warn_deprecated
65
66#include <config.h>
67#include <mail.h>
68#include <wizlevels.h>
69
70// debugging
71#define DEBUG(msg) if ( find_player("zesstra") ) \
72 tell_object( find_player("zesstra"), "MAILER: "+msg )
73#undef DEBUG
74#define DEBUG(x)
75
76// write out a message to the recipient
77#define NOTIFY_RECIPIENT
78// who gets undeliverable mail?
79#define BOUNCE_ADDR "mud@mg.mud.de"
80#define SECURITY(x) (geteuid(x) == ROOTID || geteuid(x) == MAILID)
81// flag for _DeliverMail
82#define MAIL_DELAYED 4096
83
84// prototypes
85protected void create();
86static int GetFolders( string user );
87static string _unify( string str );
88static string *unify( string *str );
89static string *expand( string *addr, int expa );
90static string _filter_addr( string addr );
91public string *DeliverMail( mixed msg, int expa );
92public int FingerMail( string user );
93static void save_msg( mixed msg, string user );
94public int RemoveMsg( int msg, int folder, string user );
95public int MoveMsg( int msg, int folder, string newfolder, string user );
96public int DeleteUnreadFolder( string user );
97public int RemoveFolder( string folder, string user );
98public int MakeFolder( string folder, string user );
99public int query_recipient_ok( string name );
100public void deliver_mail( string recipient, string from, string subject,
101 string mail_body );
102
103
104mixed *folders; /* used for save and restore of mail files */
105static mapping alias;
106static string cachedname; /* whose folder is still in memory? */
107
108
109protected void create()
110{
111 mixed tmp;
112 int i;
113 string s1, s2;
114
115 seteuid(ROOTID);
116 alias=([]);
117
118 if ( tmp = read_file("/mail/system.mailrc") ){
119 tmp = explode( tmp, "\n" );
120
121 for ( i = sizeof(tmp); i--; )
122 if ( sscanf( tmp[i], "%s %s", s1, s2 ) == 2 )
123 alias[s1] = s2;
124 }
125}
126
127
128// GetFolders laedt einen folder, wenn er nicht im cache ist, und gibt
129// 0 zurueck, wenn der folder nicht vorhanden oder evtl auch leer ist.
130// Sinn: Vor allem bei Listenargumenten im mailer kann es leicht vorkommen,
131// dass dasselbe mailfile einige Male hintereinander gebraucht wird.
132
133static int GetFolders( string user )
134{
135 if ( user == cachedname ){
136 DEBUG( "Using cached folder for " + user + "\n" );
137 return sizeof(folders[1]);
138 }
139
140 cachedname = user;
141
142 if ( !restore_object( MAILPATH + "/" + user[0..0] + "/" + user ) ){
143 DEBUG( "Loading folder: " + user + " (empty)\n" );
144 folders = ({ ({}), ({}) });
145 return 0;
146 }
147
148 DEBUG( "Loading folder:" + user + "\n" );
149 return 1;
150}
151
152
153static string _unify( string str )
154{
155 return str[0] == '\\' ? str[1..] : str;
156}
157
158
159static string *unify( string *str )
160{
161 if ( !pointerp(str) )
162 return ({});
163
164 str = map( filter( str, #'stringp/*'*/ ), #'lower_case/*'*/ );
165 str = map( str, "_unify", this_object() );
166
167 return m_indices( mkmapping(str) );
168}
169
170
171#define MG_NAMES ({ MUDNAME, "mg", "morgengrauen", "mud", "mg.mud.de" })
172
Zesstrae3254852016-10-05 22:00:36 +0200173string expandSystemRecursive(string addr,int maxrec)
174{
175 if(maxrec>8 || !addr)
176 {
MG Mud User88f12472016-06-24 23:31:02 +0200177 return addr;
178 }
Zesstrae3254852016-10-05 22:00:36 +0200179 ++maxrec;
MG Mud User88f12472016-06-24 23:31:02 +0200180
Zesstrae3254852016-10-05 22:00:36 +0200181 string *retlist = ({});
182 int alias_found;
183 foreach(string add : explode(addr,","))
184 {
185 if (add == "")
186 continue;
187 string tmp = alias[add];
188 if (stringp(tmp))
189 {
190 retlist += explode(tmp, ",");
191 ++alias_found;
MG Mud User88f12472016-06-24 23:31:02 +0200192 }
Zesstrae3254852016-10-05 22:00:36 +0200193 else
194 retlist += ({add});
MG Mud User88f12472016-06-24 23:31:02 +0200195 }
Zesstrae3254852016-10-05 22:00:36 +0200196 string ret = implode(retlist-({""}), ",");
197 // Wenn Aliase aufgeloest wurden: noch einmal versuchen...
198 if (alias_found)
199 ret = expandSystemRecursive(ret, maxrec);
MG Mud User88f12472016-06-24 23:31:02 +0200200
MG Mud User88f12472016-06-24 23:31:02 +0200201 return ret;
202}
203
204// expa: also do alias and forward-expansion? (for inbound external mail)
205// expa == 0 means full expansion, known flags are NO_SYSTEM_ALIASES
206// and NO_USER_ALIASES
207static string *expand( string *addr, int expa )
208{
209 string tmp, *new, *ret;
210 int i;
211 closure qf;
212
213 ret = ({});
214 addr -= ({""});
215 qf = symbol_function( "QueryForward", FWSERV );
216
217 for ( i = sizeof(addr); i--; ){
218 addr[i] = lower_case( addr[i] );
219 // @morgengrauen-namen werden lokal zugestellt.
220 if ( sizeof(new = explode( addr[i], "@" )) == 2 &&
221 member( MG_NAMES, new[1] ) != -1 )
222 addr[i] = new[0];
223
224 if ( !(expa & NO_SYSTEM_ALIASES) && tmp = expandSystemRecursive(addr[i],0) ){
225 ret += explode( tmp, "," );
226 }
227 else
228 ret += ({ addr[i] });
229 }
Zesstrae3254852016-10-05 22:00:36 +0200230
MG Mud User88f12472016-06-24 23:31:02 +0200231 for ( i = sizeof(ret); i--; ){
232 if ( ret[i][0] == '\\' )
233 ret[i] = ret[i][1..];
234 else if ( !(expa & NO_USER_ALIASES) )
235 ret = ret - ({ ret[i] }) +
236 explode( funcall( qf, ret[i] ), "," );
237 }
238
239 return ret;
240}
241
242
243static string _filter_addr( string addr )
244{
245 addr = regreplace( addr, "[^<]*<(.*)>[^>]*", "\\1", 0);
246 return regreplace( addr, " *([^ ][^ ]*).*", "\\1", 0);
247}
248
249#ifdef INTERNET_MAIL_ENABLED
250#define FOOTER \
251 "\n*****************************************************************\n" \
252 "* MorgenGrauen MailRelay v1.0 - Processed %s, %s *\n" \
253 "* MorgenGrauen - mg.mud.de 23 - 87.79.24.60 23 *\n" \
254 "*****************************************************************"
255#endif
256
257public string *DeliverMail( mixed msg, int expa )
258{
259 string sender, *recipients, *recok, t, *tmp;
260 mixed *newmsg;
261 int i;
262#ifdef INTERNET_MAIL_ENABLED
263 int ext;
264#endif
265
266 if ( !pointerp(msg) || sizeof(msg) != 9 )
267 return 0;
268
269 DEBUG( sprintf( "DeliverMail: %O %O\n", msg[0..4] +({0})+ msg[6..7], expa ) );
270 t = ctime(time());
271
272 // Ohne Empfaenger wird abgebrochen
273 if (!stringp(msg[MSG_RECIPIENT]))
274 return 0;
275
276 if ( !(expa & MAIL_DELAYED) ){
277 /* determine the real sender */
278 if ( extern_call() && object_name(previous_object())[0..7] != "/secure/" )
279 sender = getuid( this_interactive() || previous_object() );
280 else
281 sender = msg[MSG_SENDER];
282
283 /* make a list of all recipients */
284 recipients = ({ msg[MSG_RECIPIENT] });
285
286 if ( !(expa & NO_CARBON_COPIES) ){
287 if ( pointerp(msg[MSG_CC]) )
288 recipients += msg[MSG_CC];
289
290 if ( pointerp(msg[MSG_BCC]) )
291 recipients += msg[MSG_BCC];
292 }
293
294 // Mail-Aliase ersetzen
295 recipients = expand( recipients, expa );
296
297 // doppelte Adressen loeschen (nebenbei: auf Kleinschreibung wandeln)
298 recipients = unify( recipients );
299
300 // Realnamen und Kommentare entfernen
301 recipients = map( recipients, "_filter_addr", this_object() );
302
303 // auf ungueltige Zeichen checken
304 if ( sizeof(tmp = regexp( recipients, "^[-_.@a-z0-9]*$" ))
305 != sizeof(recipients) ){
306 tmp = recipients - tmp;
307
308 for ( i = sizeof(tmp); i--; )
309 log_file( "MAIL_INVALID", sprintf( "%s: Mail von %O (%O) an "
310 "'%O'.\n", dtime(time()),
311 msg[MSG_FROM],
312 sender, tmp[i] ) );
313
314 recipients -= tmp;
315 }
316
317 // check for valid Subject and Body
318 if (!stringp(msg[MSG_SUBJECT]))
319 msg[MSG_SUBJECT] = "(no subject given)";
320 if (!stringp(msg[MSG_BODY]))
321 msg[MSG_BODY] =
322 "\n\nSorry - This mail was delivered without a mail body\n\n";
323
324 DEBUG( sprintf( "NEED TO DELIVER TO %O\n", recipients ) );
325
326 /* build the new message */
327 newmsg = ({ msg[MSG_FROM], sender, msg[MSG_RECIPIENT],
328 msg[MSG_CC], "", msg[MSG_SUBJECT],
329 dtime(time()), MUDNAME + ":" + time(),
330 msg[MSG_BODY] });
331 }
332
333 /* Send it off ... */
334 recok = ({ });
335
336 // ACHTUNG: durch expand() geaenderte Adressen werden zugestellt,
337 // aber durch /mail/mailer.c zusaetzlich als unzustellbar genannt!
338
339 for ( i = sizeof(recipients); i-- /*&& get_eval_cost() > 500000*/; ){
340 DEBUG( sprintf( "Begin delivering to %s. Evalcosts left: %d.\n",
341 recipients[i], get_eval_cost() ) );
342 if ( member( recipients[i], '@' ) > 0 &&
343 strstr( recipients[i], "daemon" ) < 0 ) {
344 string rec, mud;
345
346 tmp = explode( recipients[i], "@" );
347 mud = tmp[1];
348 rec = tmp[0];
349 sender = regreplace( sender, "@", "%", 1 );
350 // Zustellung via Intermud-Mail an andere Muds.
351 if ( member( mud, '.' ) == -1 ) {
352 "/secure/udp_mail"->deliver_mail( rec, mud, sender,
353 msg[MSG_SUBJECT],
354 msg[MSG_BODY] );
355 recok += ({ recipients[i] });
356 }
357#ifdef INTERNET_MAIL_ENABLED
358 // Zustellung in den Rest des Internets.
359 else {
360 ext = 1;
361 sender = explode( sender, "%" )[0];
362 rec = explode( regreplace( rec, "@", "%", 1 ), "%" )[0];
363 mud = explode( regreplace( mud, "@", "%", 1 ), "%" )[0];
364
365 write_file( sprintf( "/mail/outbound/%s.%d-%d-%d",
366 sender, time(), i, random(123456) ),
367 sprintf( "%s\n%s@%s\n"
368 "Subject: %s\n"
369 "X-MUD-From: %s\n"
370 "X-MUD-To: %s\n"
371 "X-MUD-Cc: %s\n"
372 "X-MU-Subject: %s\n\n",
373 sender, rec, mud,
374 msg[MSG_SUBJECT],
375 sender, recipients[0],
376 pointerp(msg[MSG_CC]) ?
377 implode( msg[MSG_CC], "," ) : "",
378 msg[MSG_SUBJECT] ) + msg[MSG_BODY] +
379 sprintf( FOOTER, t[4..10] + t[20..], t[11..18] )
380 + "\n" );
381 recok += ({ recipients[i] });
382 }
383#endif // INTERNET_MAIL_ENABLED
384
385 }
386 else
387 if ( file_size( SAVEPATH + recipients[i][0..0] + "/" +
388 recipients[i] + ".o" ) >=0 ){
389 save_msg( newmsg, recipients[i] );
390 recok += ({ recipients[i] });
391 }
392 else {
393 string *tmpmsg = copy(newmsg);
394 tmpmsg[MSG_BODY] = "--- Text der Mail geloescht. ---\n";
395 write_file( sprintf( "/mail/outbound/postmaster.%d-%d",
396 time(), random(time()) ),
397 sprintf( "postmaster\n" + BOUNCE_ADDR +
398 "\nSubject: Undeliverable Mail\n%O\n",
399 tmpmsg) );
400 }
401 DEBUG( sprintf( "End delivering to %s. Evalcosts left: %d.\n",
402 recipients[i], get_eval_cost() ) );
403 }
404#ifdef INTERNET_MAIL_ENABLED
405 if ( ext )
406 send_udp( UDPSERV, 4123, "DELIVERMAIL" );
407#endif
408 return recok;
409}
410
411
412public int FingerMail( string user )
413{
414 int newfolder, i;
415
416 //Zugriff beschraenken, Zahl der gelesenen Mails ist Privatsphaere
417 if (!objectp(this_interactive()) || !stringp(user) || !sizeof(user))
418 return(-1);
419 if ((getuid(this_interactive())!=user) &&
420 (process_call() || !ARCH_SECURITY)) return(-1);
421
422 if ( !GetFolders(user) )
423 return 0;
424
425 if ( (newfolder = member(folders[0],"unread")) != -1 )
426 return sizeof(folders[1][newfolder]);
427
428 return 0;
429}
430
431
432static void save_msg( mixed msg, string user )
433{
434 int newfolder;
435 object p;
436
437 GetFolders( user );
438
439 /* if folder 'unread' doesn't exist, create it */
440 newfolder = member( folders[0], "unread");
441
442 if ( newfolder == -1 ){
443 folders[0] += ({ "unread" });
444 folders[1] += ({ ({ }) });
445 newfolder = member( folders[0], "unread");
446 }
447
448 folders[1][newfolder] += ({ msg });
449 save_object( MAILPATH + user[0..0] + "/" + user );
450#ifdef NOTIFY_RECIPIENT
451 if ( p = find_player(user) )
452 tell_object( p, "Ein Postreiter ruft Dir aus einiger Entfernung zu, "
453 "dass Du neue Post hast!\n" );
454#endif
455}
456
457
458/* Remove a message from a folder */
459public int RemoveMsg( int msg, int folder, string user )
460{
461 if ( !SECURITY(previous_object()) )
462 return -2;
463
464 if ( !GetFolders(user) )
465 return -1; /* No such folder */
466
467 if ( !pointerp(folders) || !pointerp(folders[0]) ||
468 folder >= sizeof(folders[0]) )
469 return -1;
470
471 if ( msg < 0 || sizeof(folders[1][folder]) <= msg )
472 return 0; /* No such msg */
473
474 folders[1][folder][msg..msg] = ({});
475
476 save_object( MAILPATH + user[0..0] + "/" + user );
477 return 1; /* Success */
478}
479
480
481/* Move message into another folder */
482public int MoveMsg( int msg, int folder, string newfolder, string user )
483{
484 int target;
485
486 if ( !SECURITY(previous_object()) )
487 return -2;
488
489 if ( !GetFolders(user) )
490 return -1; /* Source folder not found */
491
492 if ( !pointerp(folders) || !pointerp(folders[0]) ||
493 folder >= sizeof(folders[0]) )
494 return -1;
495
496 if ( msg < 0 || sizeof(folders[1][folder]) <= msg )
497 return 0; /* No such msg */
498
499 if ( (target = member(folders[0], newfolder)) == -1 )
500 return -3;
501
502 if ( target == folder )
503 return 1;
504
505 if ( !pointerp(folders[1][target]) )
506 folders[1][target] = ({ folders[1][folder][msg] });
507 else
508 folders[1][target] += ({ folders[1][folder][msg] });
509
510 return RemoveMsg( msg, folder, user );
511}
512
513
514public int DeleteUnreadFolder( string user )
515{
516 int unread, newmail;
517
518 if ( !SECURITY(previous_object()) )
519 return -2;
520
521 if ( !GetFolders(user) )
522 return -1; /* Source folder not found */
523
524 if ( (unread = member( folders[0], "unread")) == -1 )
525 return 0;
526
527 if ( (newmail = member( folders[0], "newmail")) == -1 ){
528 folders[0] += ({ "newmail" });
529 folders[1] += ({({})});
530 newmail = sizeof(folders[1]) - 1;
531 }
532
533 if ( !pointerp(folders[1][newmail]) )
534 folders[1][newmail] = ({});
535
536 if ( pointerp(folders[1][unread]) )
537 folders[1][newmail] += folders[1][unread];
538
539 folders[0][unread..unread] = ({});
540 folders[1][unread..unread] = ({});
541
542 save_object( MAILPATH + user[0..0] + "/" + user );
543 return 0;
544}
545
546
547public int RemoveFolder( string folder, string user )
548{
549 int i;
550
551 if ( !SECURITY(previous_object()) )
552 return -2;
553
554 if ( !GetFolders(user) )
555 return -1; /* No such folder */
556
557 if ( (i = member( folders[0], folder)) == -1 )
558 return -1; /* No such folder */
559
560 if ( sizeof(folders[1][i]) > 0 )
561 return 0; /* Folder not empty */
562
563 folders[0][i..i] = ({});
564 folders[1][i..i] = ({});
565
566 save_object( MAILPATH + user[0..0] + "/" + user );
567 return 1;
568}
569
570
571public int MakeFolder( string folder, string user )
572{
573 if ( !SECURITY(previous_object()) )
574 return -2;
575
576 if ( !folder || !stringp(folder) )
577 return -1; /* Huh ? */
578
579 if ( folder == "unread" )
580 return 0; /* Folder exists virtually :) */
581
582 GetFolders( user );
583
584 if ( member( folders[0], folder) != -1 )
585 return 0; /* Folder exists */
586
587 folders[0] = folders[0] + ({ folder });
588 folders[1] = folders[1] + ({ ({}) });
589
590 save_object( MAILPATH + user[0..0] + "/" + user );
591 return 1;
592}
593
594
595public int query_recipient_ok( string name )
596{
597#if INTERNET_MAIL_ENABLED
598 return (file_size( "secure/save/" + name[0..0] + "/" + name + ".o" ) > 0
599 || member( name, '%' ) > 0 || member( name, '@' ) > 0 );
600#else
601 // es darf zwar ein @ in der Adresse vorkommen, dahinter aber kein . mehr
602 // (dann ist es ne Mail via Intermud-Mail, nicht ins Internet).
603 string *tmp;
604 return (file_size( "secure/save/" + name[0..0] + "/" + name + ".o" ) > 0
605 || member( name, '%' ) > 0
606 || (sizeof(tmp=explode(name,"@")) == 2 && strstr(tmp[1],".") == -1));
607#endif
608}
609
610
611public void deliver_mail(
612 string recipient, /* the local players real name*/
613 string from, /* A string depicting the sender */
614 string subject, /* The mail subject/header */
615 string mail_body /* The actual mail message */ )
616{
617 DEBUG( sprintf("DELIVER %O\n",
618 ({ from, from, recipient, ({}), ({}), subject, time() })) );
619
620 // Geloggt wird, wenn ein aufrufendes Objekt nicht sicher ist.
621 if (object_name(previous_object())[0..7]!="/secure/")
622 write_file("/secure/ARCH/DELIVER_MAIL",
623 sprintf("%s : Aufruf von /secure/mailer->deliver_mail()\n"
624 " Sender: %O Empfaenger: %O\n PO: %O TI: %O TP:%O\n\n",
625 ctime(time()),from, recipient,
626 previous_object(), this_interactive(), this_player()));
627
628 DeliverMail( ({ from, from, recipient, ({}), ({}), subject, time(),
629 "EXTERNAL", mail_body }), 0 );
630}