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