1    | /*	SCCS Id: @(#)mail.c	3.3	1999/08/24	*/
2    | /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3    | /* NetHack may be freely redistributed.  See license for details. */
4    | 
5    | #include "hack.h"
6    | 
7    | #ifdef MAIL
8    | #include "mail.h"
9    | 
10   | /*
11   |  * Notify user when new mail has arrived.  Idea by Merlyn Leroy.
12   |  *
13   |  * The mail daemon can move with less than usual restraint.  It can:
14   |  *	- move diagonally from a door
15   |  *	- use secret and closed doors
16   |  *	- run through a monster ("Gangway!", etc.)
17   |  *	- run over pools & traps
18   |  *
19   |  * Possible extensions:
20   |  *	- Open the file MAIL and do fstat instead of stat for efficiency.
21   |  *	  (But sh uses stat, so this cannot be too bad.)
22   |  *	- Examine the mail and produce a scroll of mail named "From somebody".
23   |  *	- Invoke MAILREADER in such a way that only this single letter is read.
24   |  *	- Do something to the text when the scroll is enchanted or cancelled.
25   |  *	- Make the daemon always appear at a stairwell, and have it find a
26   |  *	  path to the hero.
27   |  *
28   |  * Note by Olaf Seibert: On the Amiga, we usually don't get mail.  So we go
29   |  *			 through most of the effects at 'random' moments.
30   |  * Note by Paul Winner:  The MSDOS port also 'fakes' the mail daemon at
31   |  *			 random intervals.
32   |  */
33   | 
34   | STATIC_DCL boolean FDECL(md_start,(coord *));
35   | STATIC_DCL boolean FDECL(md_stop,(coord *, coord *));
36   | STATIC_DCL boolean FDECL(md_rush,(struct monst *,int,int));
37   | STATIC_DCL void FDECL(newmail, (struct mail_info *));
38   | 
39   | extern char *viz_rmin, *viz_rmax;	/* line-of-sight limits (vision.c) */
40   | 
41   | #ifdef OVL0
42   | 
43   | # if !defined(UNIX) && !defined(VMS) && !defined(LAN_MAIL)
44   | int mustgetmail = -1;
45   | # endif
46   | 
47   | #endif /* OVL0 */
48   | #ifdef OVLB
49   | 
50   | # ifdef UNIX
51   | #include <sys/stat.h>
52   | #include <pwd.h>
53   | /* DON'T trust all Unices to declare getpwuid() in <pwd.h> */
54   | #  if !defined(_BULL_SOURCE) && !defined(__sgi) && !defined(_M_UNIX)
55   | #   if !defined(SUNOS4) && !(defined(ULTRIX) && defined(__GNUC__))
56   | /* DO trust all SVR4 to typedef uid_t in <sys/types.h> (probably to a long) */
57   | #    if defined(POSIX_TYPES) || defined(SVR4) || defined(HPUX)
58   | extern struct passwd *FDECL(getpwuid,(uid_t));
59   | #    else
60   | extern struct passwd *FDECL(getpwuid,(int));
61   | #    endif
62   | #   endif
63   | #  endif
64   | static struct stat omstat,nmstat;
65   | static char *mailbox = (char *)0;
66   | static long laststattime;
67   | 
68   | # if !defined(MAILPATH) && defined(AMS)	/* Just a placeholder for AMS */
69   | #  define MAILPATH "/dev/null"
70   | # endif
71   | # if !defined(MAILPATH) && (defined(LINUX) || defined(__osf__))
72   | #  define MAILPATH "/var/spool/mail/"
73   | # endif
74   | # if !defined(MAILPATH) && defined(__FreeBSD__)
75   | #  define MAILPATH "/var/mail/"
76   | # endif
77   | # if !defined(MAILPATH) && (defined(BSD) || defined(ULTRIX))
78   | #  define MAILPATH "/usr/spool/mail/"
79   | # endif
80   | # if !defined(MAILPATH) && (defined(SYSV) || defined(HPUX))
81   | #  define MAILPATH "/usr/mail/"
82   | # endif
83   | 
84   | void
85   | getmailstatus()
86   | {
87   | 	if(!mailbox && !(mailbox = nh_getenv("MAIL"))) {
88   | #  ifdef MAILPATH
89   | #   ifdef AMS
90   | 	        struct passwd ppasswd;
91   | 
92   | 		(void) memcpy(&ppasswd, getpwuid(getuid()), sizeof(struct passwd));
93   | 		if (ppasswd.pw_dir) {
94   | 		     mailbox = (char *) alloc((unsigned) strlen(ppasswd.pw_dir)+sizeof(AMS_MAILBOX));
95   | 		     Strcpy(mailbox, ppasswd.pw_dir);
96   | 		     Strcat(mailbox, AMS_MAILBOX);
97   | 		} else
98   | 		  return;
99   | #   else
100  | 		const char *pw_name = getpwuid(getuid())->pw_name;
101  | 		mailbox = (char *) alloc(sizeof(MAILPATH)+strlen(pw_name));
102  | 		Strcpy(mailbox, MAILPATH);
103  | 		Strcat(mailbox, pw_name);
104  | #  endif /* AMS */
105  | #  else
106  | 		return;
107  | #  endif
108  | 	}
109  | 	if(stat(mailbox, &omstat)){
110  | #  ifdef PERMANENT_MAILBOX
111  | 		pline("Cannot get status of MAIL=\"%s\".", mailbox);
112  | 		mailbox = 0;
113  | #  else
114  | 		omstat.st_mtime = 0;
115  | #  endif
116  | 	}
117  | }
118  | # endif /* UNIX */
119  | 
120  | #endif /* OVLB */
121  | #ifdef OVL0
122  | 
123  | /*
124  |  * Pick coordinates for a starting position for the mail daemon.  Called
125  |  * from newmail() and newphone().
126  |  */
127  | STATIC_OVL boolean
128  | md_start(startp)
129  |     coord *startp;
130  | {
131  |     coord testcc;	/* scratch coordinates */
132  |     int row;		/* current row we are checking */
133  |     int lax;		/* if TRUE, pick a position in sight. */
134  |     int dd;		/* distance to current point */
135  |     int max_distance;	/* max distance found so far */
136  | 
137  |     /*
138  |      * If blind and not telepathic, then it doesn't matter what we pick ---
139  |      * the hero is not going to see it anyway.  So pick a nearby position.
140  |      */
141  |     if (Blind && !Blind_telepat) {
142  | 	if (!enexto(startp, u.ux, u.uy, (struct permonst *) 0))
143  | 	    return FALSE;	/* no good posiitons */
144  | 	return TRUE;
145  |     }
146  | 
147  |     /*
148  |      * Arrive at an up or down stairwell if it is in line of sight from the
149  |      * hero.
150  |      */
151  |     if (couldsee(upstair.sx, upstair.sy)) {
152  | 	startp->x = upstair.sx;
153  | 	startp->y = upstair.sy;
154  | 	return TRUE;
155  |     }
156  |     if (couldsee(dnstair.sx, dnstair.sy)) {
157  | 	startp->x = dnstair.sx;
158  | 	startp->y = dnstair.sy;
159  | 	return TRUE;
160  |     }
161  | 
162  |     /*
163  |      * Try to pick a location out of sight next to the farthest position away
164  |      * from the hero.  If this fails, try again, just picking the farthest
165  |      * position that could be seen.  What we really ought to be doing is
166  |      * finding a path from a stairwell...
167  |      *
168  |      * The arrays viz_rmin[] and viz_rmax[] are set even when blind.  These
169  |      * are the LOS limits for each row.
170  |      */
171  |     lax = 0;	/* be picky */
172  |     max_distance = -1;
173  | retry:
174  |     for (row = 0; row < ROWNO; row++) {
175  | 	if (viz_rmin[row] < viz_rmax[row]) {
176  | 	    /* There are valid positions on this row. */
177  | 	    dd = distu(viz_rmin[row],row);
178  | 	    if (dd > max_distance) {
179  | 		if (lax) {
180  | 		    max_distance = dd;
181  | 		    startp->y = row;
182  | 		    startp->x = viz_rmin[row];
183  | 		
184  | 		} else if (enexto(&testcc, (xchar)viz_rmin[row], row,
185  | 						(struct permonst *) 0) &&
186  | 			   !cansee(testcc.x, testcc.y) &&
187  | 			   couldsee(testcc.x, testcc.y)) {
188  | 		    max_distance = dd;
189  | 		    *startp = testcc;
190  | 		}
191  | 	    }
192  | 	    dd = distu(viz_rmax[row],row);
193  | 	    if (dd > max_distance) {
194  | 		if (lax) {
195  | 		    max_distance = dd;
196  | 		    startp->y = row;
197  | 		    startp->x = viz_rmax[row];
198  | 		
199  | 		} else if (enexto(&testcc, (xchar)viz_rmax[row], row,
200  | 						(struct permonst *) 0) &&
201  | 			   !cansee(testcc.x,testcc.y) &&
202  | 			   couldsee(testcc.x, testcc.y)) {
203  | 
204  | 		    max_distance = dd;
205  | 		    *startp = testcc;
206  | 		}
207  | 	    }
208  | 	}
209  |     }
210  | 
211  |     if (max_distance < 0) {
212  | 	if (!lax) {
213  | 	    lax = 1;		/* just find a position */
214  | 	    goto retry;
215  | 	}
216  | 	return FALSE;
217  |     }
218  | 
219  |     return TRUE;
220  | }
221  | 
222  | /*
223  |  * Try to choose a stopping point as near as possible to the starting
224  |  * position while still adjacent to the hero.  If all else fails, try
225  |  * enexto().  Use enexto() as a last resort because enexto() chooses
226  |  * its point randomly, which is not what we want.
227  |  */
228  | STATIC_OVL boolean
229  | md_stop(stopp, startp)
230  |     coord *stopp;	/* stopping position (we fill it in) */
231  |     coord *startp;	/* starting positon (read only) */
232  | {
233  |     int x, y, distance, min_distance = -1;
234  | 
235  |     for (x = u.ux-1; x <= u.ux+1; x++)
236  | 	for (y = u.uy-1; y <= u.uy+1; y++) {
237  | 	    if (!isok(x, y) || (x == u.ux && y == u.uy)) continue;
238  | 
239  | 	    if (ACCESSIBLE(levl[x][y].typ) && !MON_AT(x,y)) {
240  | 		distance = dist2(x,y,startp->x,startp->y);
241  | 		if (min_distance < 0 || distance < min_distance ||
242  | 			(distance == min_distance && rn2(2))) {
243  | 		    stopp->x = x;
244  | 		    stopp->y = y;
245  | 		    min_distance = distance;
246  | 		}
247  | 	    }
248  | 	}
249  | 
250  |     /* If we didn't find a good spot, try enexto(). */
251  |     if (min_distance < 0 &&
252  | 		!enexto(stopp, u.ux, u.uy, &mons[PM_MAIL_DAEMON]))
253  | 	return FALSE;
254  | 
255  |     return TRUE;
256  | }
257  | 
258  | /* Let the mail daemon have a larger vocabulary. */
259  | static NEARDATA const char *mail_text[] = {
260  |     "Gangway!",
261  |     "Look out!",
262  |     "Pardon me!"
263  | };
264  | #define md_exclamations()	(mail_text[rn2(3)])
265  | 
266  | /*
267  |  * Make the mail daemon run through the dungeon.  The daemon will run over
268  |  * any monsters that are in its path, but will replace them later.  Return
269  |  * FALSE if the md gets stuck in a position where there is a monster.  Return
270  |  * TRUE otherwise.
271  |  */
272  | STATIC_OVL boolean
273  | md_rush(md,tx,ty)
274  |     struct monst *md;
275  |     register int tx, ty;		/* destination of mail daemon */
276  | {
277  |     struct monst *mon;			/* displaced monster */
278  |     register int dx, dy;		/* direction counters */
279  |     int fx = md->mx, fy = md->my;	/* current location */
280  |     int nfx = fx, nfy = fy,		/* new location */
281  | 	d1, d2;				/* shortest distances */
282  | 
283  |     /*
284  |      * It is possible that the monster at (fx,fy) is not the md when:
285  |      * the md rushed the hero and failed, and is now starting back.
286  |      */
287  |     if (m_at(fx, fy) == md) {
288  | 	remove_monster(fx, fy);		/* pick up from orig position */
289  | 	newsym(fx, fy);
290  |     }
291  | 
292  |     /*
293  |      * At the beginning and exit of this loop, md is not placed in the
294  |      * dungeon.
295  |      */
296  |     while (1) {
297  | 	/* Find a good location next to (fx,fy) closest to (tx,ty). */
298  | 	d1 = dist2(fx,fy,tx,ty);
299  | 	for (dx = -1; dx <= 1; dx++) for(dy = -1; dy <= 1; dy++)
300  | 	    if ((dx || dy) && isok(fx+dx,fy+dy) &&
301  | 				       !IS_STWALL(levl[fx+dx][fy+dy].typ)) {
302  | 		d2 = dist2(fx+dx,fy+dy,tx,ty);
303  | 		if (d2 < d1) {
304  | 		    d1 = d2;
305  | 		    nfx = fx+dx;
306  | 		    nfy = fy+dy;
307  | 		}
308  | 	    }
309  | 
310  | 	/* Break if the md couldn't find a new position. */
311  | 	if (nfx == fx && nfy == fy) break;
312  | 
313  | 	fx = nfx;			/* this is our new position */
314  | 	fy = nfy;
315  | 
316  | 	/* Break if the md reaches its destination. */
317  | 	if (fx == tx && fy == ty) break;
318  | 
319  | 	if ((mon = m_at(fx,fy)) != 0)	/* save monster at this position */
320  | 	    verbalize(md_exclamations());
321  | 	else if (fx == u.ux && fy == u.uy)
322  | 	    verbalize("Excuse me.");
323  | 
324  | 	place_monster(md,fx,fy);	/* put md down */
325  | 	newsym(fx,fy);			/* see it */
326  | 	flush_screen(0);		/* make sure md shows up */
327  | 	delay_output();			/* wait a little bit */
328  | 
329  | 	/* Remove md from the dungeon.  Restore original mon, if necessary. */
330  | 	if (mon) {
331  | 	    if ((mon->mx != fx) || (mon->my != fy))
332  | 		place_worm_seg(mon, fx, fy);
333  | 	    else
334  | 		place_monster(mon, fx, fy);
335  | 	} else
336  | 	    remove_monster(fx, fy);
337  | 	newsym(fx,fy);
338  |     }
339  | 
340  |     /*
341  |      * Check for a monster at our stopping position (this is possible, but
342  |      * very unlikely).  If one exists, then have the md leave in disgust.
343  |      */
344  |     if ((mon = m_at(fx, fy)) != 0) {
345  | 	place_monster(md, fx, fy);	/* display md with text below */
346  | 	newsym(fx, fy);
347  | 	verbalize("This place's too crowded.  I'm outta here.");
348  | 
349  | 	if ((mon->mx != fx) || (mon->my != fy))	/* put mon back */
350  | 	    place_worm_seg(mon, fx, fy);
351  | 	else
352  | 	    place_monster(mon, fx, fy);
353  | 
354  | 	newsym(fx, fy);
355  | 	return FALSE;
356  |     }
357  | 
358  |     place_monster(md, fx, fy);	/* place at final spot */
359  |     newsym(fx, fy);
360  |     flush_screen(0);
361  |     delay_output();			/* wait a little bit */
362  | 
363  |     return TRUE;
364  | }
365  | 
366  | /* Deliver a scroll of mail. */
367  | /*ARGSUSED*/
368  | STATIC_OVL void
369  | newmail(info)
370  | struct mail_info *info;
371  | {
372  |     struct monst *md;
373  |     coord start, stop;
374  |     boolean message_seen = FALSE;
375  | 
376  |     /* Try to find good starting and stopping places. */
377  |     if (!md_start(&start) || !md_stop(&stop,&start)) goto give_up;
378  | 
379  |     /* Make the daemon.  Have it rush towards the hero. */
380  |     if (!(md = makemon(&mons[PM_MAIL_DAEMON], start.x, start.y, NO_MM_FLAGS)))
381  | 	 goto give_up;
382  |     if (!md_rush(md, stop.x, stop.y)) goto go_back;
383  | 
384  |     message_seen = TRUE;
385  |     verbalize("%s, %s!  %s.", Hello(md), plname, info->display_txt);
386  | 
387  |     if (info->message_typ) {
388  | 	struct obj *obj = mksobj(SCR_MAIL, FALSE, FALSE);
389  | 	if (distu(md->mx,md->my) > 2)
390  | 	    verbalize("Catch!");
391  | 	display_nhwindow(WIN_MESSAGE, FALSE);
392  | 	if (info->object_nam) {
393  | 	    obj = oname(obj, info->object_nam);
394  | 	    if (info->response_cmd) {	/*(hide extension of the obj name)*/
395  | 		int namelth = info->response_cmd - info->object_nam - 1;
396  | 		if ( namelth <= 0 || namelth >= (int) obj->onamelth )
397  | 		    impossible("mail delivery screwed up");
398  | 		else
399  | 		    *(ONAME(obj) + namelth) = '\0';
400  | 		/* Note: renaming object will discard the hidden command. */
401  | 	    }
402  | 	}
403  | 	obj = hold_another_object(obj, "Oops!",
404  | 				  (const char *)0, (const char *)0);
405  |     }
406  | 
407  |     /* zip back to starting location */
408  | go_back:
409  |     (void) md_rush(md, start.x, start.y);
410  |     mongone(md);
411  |     /* deliver some classes of messages even if no daemon ever shows up */
412  | give_up:
413  |     if (!message_seen && info->message_typ == MSG_OTHER)
414  | 	pline("Hark!  \"%s.\"", info->display_txt);
415  | }
416  | 
417  | # if !defined(UNIX) && !defined(VMS) && !defined(LAN_MAIL)
418  | 
419  | void
420  | ckmailstatus()
421  | {
422  | 	if (u.uswallow || !flags.biff) return;
423  | 	if (mustgetmail < 0) {
424  | #if defined(AMIGA) || defined(MSDOS) || defined(TOS)
425  | 	    mustgetmail=(moves<2000)?(100+rn2(2000)):(2000+rn2(3000));
426  | #endif
427  | 	    return;
428  | 	}
429  | 	if (--mustgetmail <= 0) {
430  | 		static struct mail_info
431  | 			deliver = {MSG_MAIL,"I have some mail for you",0,0};
432  | 		newmail(&deliver);
433  | 		mustgetmail = -1;
434  | 	}
435  | }
436  | 
437  | /*ARGSUSED*/
438  | void
439  | readmail(otmp)
440  | struct obj *otmp;
441  | {
442  |     static char *junk[] = {
443  |     "Please disregard previous letter.",
444  |     "Welcome to NetHack.",
445  | #ifdef AMIGA
446  |     "Only Amiga makes it possible.",
447  |     "CATS have all the answers.",
448  | #endif
449  |     "Report bugs to <devteam@nethack.org>."
450  |     };
451  | 
452  |     if (Blind) {
453  | 	pline("Unfortunately you cannot see what it says.");
454  |     } else
455  | 	pline("It reads:  \"%s\"", junk[rn2(SIZE(junk))]);
456  | 
457  | }
458  | 
459  | # endif /* !UNIX && !VMS && !LAN_MAIL */
460  | 
461  | # ifdef UNIX
462  | 
463  | void
464  | ckmailstatus()
465  | {
466  | 	if(!mailbox || u.uswallow || !flags.biff
467  | #  ifdef MAILCKFREQ
468  | 		    || moves < laststattime + MAILCKFREQ
469  | #  endif
470  | 							)
471  | 		return;
472  | 
473  | 	laststattime = moves;
474  | 	if(stat(mailbox, &nmstat)){
475  | #  ifdef PERMANENT_MAILBOX
476  | 		pline("Cannot get status of MAIL=\"%s\" anymore.", mailbox);
477  | 		mailbox = 0;
478  | #  else
479  | 		nmstat.st_mtime = 0;
480  | #  endif
481  | 	} else if(nmstat.st_mtime > omstat.st_mtime) {
482  | 		if (nmstat.st_size) {
483  | 		    static struct mail_info deliver = {
484  | #  ifndef NO_MAILREADER
485  | 			MSG_MAIL, "I have some mail for you",
486  | #  else
487  | 			/* suppress creation and delivery of scroll of mail */
488  | 			MSG_OTHER, "You have some mail in the outside world",
489  | #  endif
490  | 			0, 0
491  | 		    };
492  | 		    newmail(&deliver);
493  | 		}
494  | 		getmailstatus();	/* might be too late ... */
495  | 	}
496  | }
497  | 
498  | /*ARGSUSED*/
499  | void
500  | readmail(otmp)
501  | struct obj *otmp;
502  | {
503  | #  ifdef DEF_MAILREADER			/* This implies that UNIX is defined */
504  | 	register const char *mr = 0;
505  | 
506  | 	display_nhwindow(WIN_MESSAGE, FALSE);
507  | 	if(!(mr = nh_getenv("MAILREADER")))
508  | 		mr = DEF_MAILREADER;
509  | 
510  | 	if(child(1)){
511  | 		(void) execl(mr, mr, (char *)0);
512  | 		terminate(EXIT_FAILURE);
513  | 	}
514  | #  else
515  | #   ifndef AMS				/* AMS mailboxes are directories */
516  | 	display_file(mailbox, TRUE);
517  | #   endif /* AMS */
518  | #  endif /* DEF_MAILREADER */
519  | 
520  | 	/* get new stat; not entirely correct: there is a small time
521  | 	   window where we do not see new mail */
522  | 	getmailstatus();
523  | }
524  | 
525  | # endif /* UNIX */
526  | 
527  | # ifdef VMS
528  | 
529  | extern NDECL(struct mail_info *parse_next_broadcast);
530  | 
531  | volatile int broadcasts = 0;
532  | 
533  | void
534  | ckmailstatus()
535  | {
536  |     struct mail_info *brdcst;
537  | 
538  |     if (u.uswallow || !flags.biff) return;
539  | 
540  |     while (broadcasts > 0) {	/* process all trapped broadcasts [until] */
541  | 	broadcasts--;
542  | 	if ((brdcst = parse_next_broadcast()) != 0) {
543  | 	    newmail(brdcst);
544  | 	    break;		/* only handle one real message at a time */
545  | 	}
546  |     }
547  | }
548  | 
549  | void
550  | readmail(otmp)
551  | struct obj *otmp;
552  | {
553  | #  ifdef SHELL	/* can't access mail reader without spawning subprocess */
554  |     const char *txt, *cmd;
555  |     char *p, buf[BUFSZ], qbuf[BUFSZ];
556  |     int len;
557  | 
558  |     /* there should be a command hidden beyond the object name */
559  |     txt = otmp->onamelth ? ONAME(otmp) : "";
560  |     len = strlen(txt);
561  |     cmd = (len + 1 < otmp->onamelth) ? txt + len + 1 : (char *) 0;
562  |     if (!cmd || !*cmd) cmd = "SPAWN";
563  | 
564  |     Sprintf(qbuf, "System command (%s)", cmd);
565  |     getlin(qbuf, buf);
566  |     if (*buf != '\033') {
567  | 	for (p = eos(buf); p > buf; *p = '\0')
568  | 	    if (*--p != ' ') break;	/* strip trailing spaces */
569  | 	if (*buf) cmd = buf;		/* use user entered command */
570  | 	if (!strcmpi(cmd, "SPAWN") || !strcmp(cmd, "!"))
571  | 	    cmd = (char *) 0;		/* interactive escape */
572  | 
573  | 	vms_doshell(cmd, TRUE);
574  | 	(void) sleep(1);
575  |     }
576  | #  endif /* SHELL */
577  | }
578  | 
579  | # endif /* VMS */
580  | 
581  | # ifdef LAN_MAIL
582  | 
583  | void
584  | ckmailstatus()
585  | {
586  | 	static int laststattime = 0;
587  | 	
588  | 	if(u.uswallow || !flags.biff
589  | #  ifdef MAILCKFREQ
590  | 		    || moves < laststattime + MAILCKFREQ
591  | #  endif
592  | 							)
593  | 		return;
594  | 
595  | 	laststattime = moves;
596  | 	if (lan_mail_check()) {
597  | 		    static struct mail_info deliver = {
598  | #  ifndef NO_MAILREADER
599  | 			MSG_MAIL, "I have some mail for you",
600  | #  else
601  | 			/* suppress creation and delivery of scroll of mail */
602  | 			MSG_OTHER, "You have some mail in the outside world",
603  | #  endif
604  | 			0, 0
605  | 		    };
606  | 		    newmail(&deliver);
607  | 	}
608  | }
609  | 
610  | /*ARGSUSED*/
611  | void
612  | readmail(otmp)
613  | struct obj *otmp;
614  | {
615  | 	lan_mail_read(otmp);
616  | }
617  | 
618  | # endif /* LAN_MAIL */
619  | 
620  | #endif /* OVL0 */
621  | 
622  | #endif /* MAIL */
623  | 
624  | /*mail.c*/