blob: 9901fac698fc843f080cdccd585767fb3e290eca [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
173string expandSystemRecursive(string addr,int maxrec){
174 string *list,*tlist;
175 string ret,tmp;
176 int i,size;
177
178 if(maxrec>8 || !addr){
179 return addr;
180 }
181 maxrec++;
182
183 tlist=({});
184 ret="";
185
186 list=explode(addr,",");
187 list-=({""});
188 size=sizeof(list);
189 for(i=0;i<size;i++){
190 if(tmp=alias[list[i]]){
191 tlist+=explode(tmp,",");
192 }
193 else{
194 tlist+=({list[i]});
195 }
196 }
197 tlist-=({""});
198 ret=implode(tlist,",");
199
200 if((ret!=addr)!=0){
201 ret=expandSystemRecursive(ret,maxrec);
202 }
203 return ret;
204}
205
206// expa: also do alias and forward-expansion? (for inbound external mail)
207// expa == 0 means full expansion, known flags are NO_SYSTEM_ALIASES
208// and NO_USER_ALIASES
209static string *expand( string *addr, int expa )
210{
211 string tmp, *new, *ret;
212 int i;
213 closure qf;
214
215 ret = ({});
216 addr -= ({""});
217 qf = symbol_function( "QueryForward", FWSERV );
218
219 for ( i = sizeof(addr); i--; ){
220 addr[i] = lower_case( addr[i] );
221 // @morgengrauen-namen werden lokal zugestellt.
222 if ( sizeof(new = explode( addr[i], "@" )) == 2 &&
223 member( MG_NAMES, new[1] ) != -1 )
224 addr[i] = new[0];
225
226 if ( !(expa & NO_SYSTEM_ALIASES) && tmp = expandSystemRecursive(addr[i],0) ){
227 ret += explode( tmp, "," );
228 }
229 else
230 ret += ({ addr[i] });
231 }
232
233 for ( i = sizeof(ret); i--; ){
234 if ( ret[i][0] == '\\' )
235 ret[i] = ret[i][1..];
236 else if ( !(expa & NO_USER_ALIASES) )
237 ret = ret - ({ ret[i] }) +
238 explode( funcall( qf, ret[i] ), "," );
239 }
240
241 return ret;
242}
243
244
245static string _filter_addr( string addr )
246{
247 addr = regreplace( addr, "[^<]*<(.*)>[^>]*", "\\1", 0);
248 return regreplace( addr, " *([^ ][^ ]*).*", "\\1", 0);
249}
250
251#ifdef INTERNET_MAIL_ENABLED
252#define FOOTER \
253 "\n*****************************************************************\n" \
254 "* MorgenGrauen MailRelay v1.0 - Processed %s, %s *\n" \
255 "* MorgenGrauen - mg.mud.de 23 - 87.79.24.60 23 *\n" \
256 "*****************************************************************"
257#endif
258
259public string *DeliverMail( mixed msg, int expa )
260{
261 string sender, *recipients, *recok, t, *tmp;
262 mixed *newmsg;
263 int i;
264#ifdef INTERNET_MAIL_ENABLED
265 int ext;
266#endif
267
268 if ( !pointerp(msg) || sizeof(msg) != 9 )
269 return 0;
270
271 DEBUG( sprintf( "DeliverMail: %O %O\n", msg[0..4] +({0})+ msg[6..7], expa ) );
272 t = ctime(time());
273
274 // Ohne Empfaenger wird abgebrochen
275 if (!stringp(msg[MSG_RECIPIENT]))
276 return 0;
277
278 if ( !(expa & MAIL_DELAYED) ){
279 /* determine the real sender */
280 if ( extern_call() && object_name(previous_object())[0..7] != "/secure/" )
281 sender = getuid( this_interactive() || previous_object() );
282 else
283 sender = msg[MSG_SENDER];
284
285 /* make a list of all recipients */
286 recipients = ({ msg[MSG_RECIPIENT] });
287
288 if ( !(expa & NO_CARBON_COPIES) ){
289 if ( pointerp(msg[MSG_CC]) )
290 recipients += msg[MSG_CC];
291
292 if ( pointerp(msg[MSG_BCC]) )
293 recipients += msg[MSG_BCC];
294 }
295
296 // Mail-Aliase ersetzen
297 recipients = expand( recipients, expa );
298
299 // doppelte Adressen loeschen (nebenbei: auf Kleinschreibung wandeln)
300 recipients = unify( recipients );
301
302 // Realnamen und Kommentare entfernen
303 recipients = map( recipients, "_filter_addr", this_object() );
304
305 // auf ungueltige Zeichen checken
306 if ( sizeof(tmp = regexp( recipients, "^[-_.@a-z0-9]*$" ))
307 != sizeof(recipients) ){
308 tmp = recipients - tmp;
309
310 for ( i = sizeof(tmp); i--; )
311 log_file( "MAIL_INVALID", sprintf( "%s: Mail von %O (%O) an "
312 "'%O'.\n", dtime(time()),
313 msg[MSG_FROM],
314 sender, tmp[i] ) );
315
316 recipients -= tmp;
317 }
318
319 // check for valid Subject and Body
320 if (!stringp(msg[MSG_SUBJECT]))
321 msg[MSG_SUBJECT] = "(no subject given)";
322 if (!stringp(msg[MSG_BODY]))
323 msg[MSG_BODY] =
324 "\n\nSorry - This mail was delivered without a mail body\n\n";
325
326 DEBUG( sprintf( "NEED TO DELIVER TO %O\n", recipients ) );
327
328 /* build the new message */
329 newmsg = ({ msg[MSG_FROM], sender, msg[MSG_RECIPIENT],
330 msg[MSG_CC], "", msg[MSG_SUBJECT],
331 dtime(time()), MUDNAME + ":" + time(),
332 msg[MSG_BODY] });
333 }
334
335 /* Send it off ... */
336 recok = ({ });
337
338 // ACHTUNG: durch expand() geaenderte Adressen werden zugestellt,
339 // aber durch /mail/mailer.c zusaetzlich als unzustellbar genannt!
340
341 for ( i = sizeof(recipients); i-- /*&& get_eval_cost() > 500000*/; ){
342 DEBUG( sprintf( "Begin delivering to %s. Evalcosts left: %d.\n",
343 recipients[i], get_eval_cost() ) );
344 if ( member( recipients[i], '@' ) > 0 &&
345 strstr( recipients[i], "daemon" ) < 0 ) {
346 string rec, mud;
347
348 tmp = explode( recipients[i], "@" );
349 mud = tmp[1];
350 rec = tmp[0];
351 sender = regreplace( sender, "@", "%", 1 );
352 // Zustellung via Intermud-Mail an andere Muds.
353 if ( member( mud, '.' ) == -1 ) {
354 "/secure/udp_mail"->deliver_mail( rec, mud, sender,
355 msg[MSG_SUBJECT],
356 msg[MSG_BODY] );
357 recok += ({ recipients[i] });
358 }
359#ifdef INTERNET_MAIL_ENABLED
360 // Zustellung in den Rest des Internets.
361 else {
362 ext = 1;
363 sender = explode( sender, "%" )[0];
364 rec = explode( regreplace( rec, "@", "%", 1 ), "%" )[0];
365 mud = explode( regreplace( mud, "@", "%", 1 ), "%" )[0];
366
367 write_file( sprintf( "/mail/outbound/%s.%d-%d-%d",
368 sender, time(), i, random(123456) ),
369 sprintf( "%s\n%s@%s\n"
370 "Subject: %s\n"
371 "X-MUD-From: %s\n"
372 "X-MUD-To: %s\n"
373 "X-MUD-Cc: %s\n"
374 "X-MU-Subject: %s\n\n",
375 sender, rec, mud,
376 msg[MSG_SUBJECT],
377 sender, recipients[0],
378 pointerp(msg[MSG_CC]) ?
379 implode( msg[MSG_CC], "," ) : "",
380 msg[MSG_SUBJECT] ) + msg[MSG_BODY] +
381 sprintf( FOOTER, t[4..10] + t[20..], t[11..18] )
382 + "\n" );
383 recok += ({ recipients[i] });
384 }
385#endif // INTERNET_MAIL_ENABLED
386
387 }
388 else
389 if ( file_size( SAVEPATH + recipients[i][0..0] + "/" +
390 recipients[i] + ".o" ) >=0 ){
391 save_msg( newmsg, recipients[i] );
392 recok += ({ recipients[i] });
393 }
394 else {
395 string *tmpmsg = copy(newmsg);
396 tmpmsg[MSG_BODY] = "--- Text der Mail geloescht. ---\n";
397 write_file( sprintf( "/mail/outbound/postmaster.%d-%d",
398 time(), random(time()) ),
399 sprintf( "postmaster\n" + BOUNCE_ADDR +
400 "\nSubject: Undeliverable Mail\n%O\n",
401 tmpmsg) );
402 }
403 DEBUG( sprintf( "End delivering to %s. Evalcosts left: %d.\n",
404 recipients[i], get_eval_cost() ) );
405 }
406#ifdef INTERNET_MAIL_ENABLED
407 if ( ext )
408 send_udp( UDPSERV, 4123, "DELIVERMAIL" );
409#endif
410 return recok;
411}
412
413
414public int FingerMail( string user )
415{
416 int newfolder, i;
417
418 //Zugriff beschraenken, Zahl der gelesenen Mails ist Privatsphaere
419 if (!objectp(this_interactive()) || !stringp(user) || !sizeof(user))
420 return(-1);
421 if ((getuid(this_interactive())!=user) &&
422 (process_call() || !ARCH_SECURITY)) return(-1);
423
424 if ( !GetFolders(user) )
425 return 0;
426
427 if ( (newfolder = member(folders[0],"unread")) != -1 )
428 return sizeof(folders[1][newfolder]);
429
430 return 0;
431}
432
433
434static void save_msg( mixed msg, string user )
435{
436 int newfolder;
437 object p;
438
439 GetFolders( user );
440
441 /* if folder 'unread' doesn't exist, create it */
442 newfolder = member( folders[0], "unread");
443
444 if ( newfolder == -1 ){
445 folders[0] += ({ "unread" });
446 folders[1] += ({ ({ }) });
447 newfolder = member( folders[0], "unread");
448 }
449
450 folders[1][newfolder] += ({ msg });
451 save_object( MAILPATH + user[0..0] + "/" + user );
452#ifdef NOTIFY_RECIPIENT
453 if ( p = find_player(user) )
454 tell_object( p, "Ein Postreiter ruft Dir aus einiger Entfernung zu, "
455 "dass Du neue Post hast!\n" );
456#endif
457}
458
459
460/* Remove a message from a folder */
461public int RemoveMsg( int msg, int folder, string user )
462{
463 if ( !SECURITY(previous_object()) )
464 return -2;
465
466 if ( !GetFolders(user) )
467 return -1; /* No such folder */
468
469 if ( !pointerp(folders) || !pointerp(folders[0]) ||
470 folder >= sizeof(folders[0]) )
471 return -1;
472
473 if ( msg < 0 || sizeof(folders[1][folder]) <= msg )
474 return 0; /* No such msg */
475
476 folders[1][folder][msg..msg] = ({});
477
478 save_object( MAILPATH + user[0..0] + "/" + user );
479 return 1; /* Success */
480}
481
482
483/* Move message into another folder */
484public int MoveMsg( int msg, int folder, string newfolder, string user )
485{
486 int target;
487
488 if ( !SECURITY(previous_object()) )
489 return -2;
490
491 if ( !GetFolders(user) )
492 return -1; /* Source folder not found */
493
494 if ( !pointerp(folders) || !pointerp(folders[0]) ||
495 folder >= sizeof(folders[0]) )
496 return -1;
497
498 if ( msg < 0 || sizeof(folders[1][folder]) <= msg )
499 return 0; /* No such msg */
500
501 if ( (target = member(folders[0], newfolder)) == -1 )
502 return -3;
503
504 if ( target == folder )
505 return 1;
506
507 if ( !pointerp(folders[1][target]) )
508 folders[1][target] = ({ folders[1][folder][msg] });
509 else
510 folders[1][target] += ({ folders[1][folder][msg] });
511
512 return RemoveMsg( msg, folder, user );
513}
514
515
516public int DeleteUnreadFolder( string user )
517{
518 int unread, newmail;
519
520 if ( !SECURITY(previous_object()) )
521 return -2;
522
523 if ( !GetFolders(user) )
524 return -1; /* Source folder not found */
525
526 if ( (unread = member( folders[0], "unread")) == -1 )
527 return 0;
528
529 if ( (newmail = member( folders[0], "newmail")) == -1 ){
530 folders[0] += ({ "newmail" });
531 folders[1] += ({({})});
532 newmail = sizeof(folders[1]) - 1;
533 }
534
535 if ( !pointerp(folders[1][newmail]) )
536 folders[1][newmail] = ({});
537
538 if ( pointerp(folders[1][unread]) )
539 folders[1][newmail] += folders[1][unread];
540
541 folders[0][unread..unread] = ({});
542 folders[1][unread..unread] = ({});
543
544 save_object( MAILPATH + user[0..0] + "/" + user );
545 return 0;
546}
547
548
549public int RemoveFolder( string folder, string user )
550{
551 int i;
552
553 if ( !SECURITY(previous_object()) )
554 return -2;
555
556 if ( !GetFolders(user) )
557 return -1; /* No such folder */
558
559 if ( (i = member( folders[0], folder)) == -1 )
560 return -1; /* No such folder */
561
562 if ( sizeof(folders[1][i]) > 0 )
563 return 0; /* Folder not empty */
564
565 folders[0][i..i] = ({});
566 folders[1][i..i] = ({});
567
568 save_object( MAILPATH + user[0..0] + "/" + user );
569 return 1;
570}
571
572
573public int MakeFolder( string folder, string user )
574{
575 if ( !SECURITY(previous_object()) )
576 return -2;
577
578 if ( !folder || !stringp(folder) )
579 return -1; /* Huh ? */
580
581 if ( folder == "unread" )
582 return 0; /* Folder exists virtually :) */
583
584 GetFolders( user );
585
586 if ( member( folders[0], folder) != -1 )
587 return 0; /* Folder exists */
588
589 folders[0] = folders[0] + ({ folder });
590 folders[1] = folders[1] + ({ ({}) });
591
592 save_object( MAILPATH + user[0..0] + "/" + user );
593 return 1;
594}
595
596
597public int query_recipient_ok( string name )
598{
599#if INTERNET_MAIL_ENABLED
600 return (file_size( "secure/save/" + name[0..0] + "/" + name + ".o" ) > 0
601 || member( name, '%' ) > 0 || member( name, '@' ) > 0 );
602#else
603 // es darf zwar ein @ in der Adresse vorkommen, dahinter aber kein . mehr
604 // (dann ist es ne Mail via Intermud-Mail, nicht ins Internet).
605 string *tmp;
606 return (file_size( "secure/save/" + name[0..0] + "/" + name + ".o" ) > 0
607 || member( name, '%' ) > 0
608 || (sizeof(tmp=explode(name,"@")) == 2 && strstr(tmp[1],".") == -1));
609#endif
610}
611
612
613public void deliver_mail(
614 string recipient, /* the local players real name*/
615 string from, /* A string depicting the sender */
616 string subject, /* The mail subject/header */
617 string mail_body /* The actual mail message */ )
618{
619 DEBUG( sprintf("DELIVER %O\n",
620 ({ from, from, recipient, ({}), ({}), subject, time() })) );
621
622 // Geloggt wird, wenn ein aufrufendes Objekt nicht sicher ist.
623 if (object_name(previous_object())[0..7]!="/secure/")
624 write_file("/secure/ARCH/DELIVER_MAIL",
625 sprintf("%s : Aufruf von /secure/mailer->deliver_mail()\n"
626 " Sender: %O Empfaenger: %O\n PO: %O TI: %O TP:%O\n\n",
627 ctime(time()),from, recipient,
628 previous_object(), this_interactive(), this_player()));
629
630 DeliverMail( ({ from, from, recipient, ({}), ({}), subject, time(),
631 "EXTERNAL", mail_body }), 0 );
632}