OSDN Git Service

Initial Import
[nethackexpress/trunk.git] / sys / vms / vmsmail.c
1 /*      SCCS Id: @(#)vmsmail.c  3.4     1995/06/01      */
2 /* Copyright (c) Robert Patrick Rankin, 1991.                     */
3 /* NetHack may be freely redistributed.  See license for details. */
4
5 #include "config.h"
6 #include "mail.h"
7
8 /* lint supression due to lack of extern.h */
9 unsigned long NDECL(init_broadcast_trapping);
10 unsigned long NDECL(enable_broadcast_trapping);
11 unsigned long NDECL(disable_broadcast_trapping);
12 struct mail_info *NDECL(parse_next_broadcast);
13
14 #ifdef MAIL
15 #include "wintype.h"
16 #include "winprocs.h"
17 #include <ctype.h>
18 #include <descrip.h>
19 #include <errno.h>
20 # ifndef __GNUC__
21 #include <msgdef.h>
22 # else
23 #  define MSG$_TRMHANGUP  6
24 #  define MSG$_TRMBRDCST 83
25 # endif /*__GNUC__*/
26 #include <signal.h>
27 /* #include <string.h> */
28 # define vms_ok(sts) ((sts)&1)
29
30 static struct mail_info *FDECL(parse_brdcst, (char *));
31 static void FDECL(filter_brdcst, (char *));
32 static void NDECL(flush_broadcasts);
33 static void FDECL(broadcast_ast, (int));
34 extern char *FDECL(eos, (char *));
35 extern char *FDECL(strstri, (const char *,const char *));
36 extern int FDECL(strncmpi, (const char *,const char *,int));
37
38 extern size_t FDECL(strspn, (const char *,const char *));
39 #ifndef __DECC
40 extern int VDECL(sscanf, (const char *,const char *,...));
41 #endif
42 extern unsigned long
43         smg$create_pasteboard(),
44         smg$get_broadcast_message(),
45         smg$set_broadcast_trapping(),
46         smg$disable_broadcast_trapping();
47
48 extern volatile int broadcasts;         /* defining declaration in mail.c */
49
50 static long pasteboard_id = 0;          /* SMG's magic cookie */
51
52 /*
53  * Mail (et al) overview:
54  *
55  *      When a broadcast is asynchronously captured, a volatile counter
56  * ('broadcasts') is incremented.  Each player turn, ckmailstatus() polls
57  * the counter and calls parse_next_broadcast() if it's positive; this
58  * returns some display text, object name, and response command, which is
59  * passed to newmail().  Routine newmail() generates a mail-daemon monster
60  * who approaches the character, "speaks" the display text, and delivers
61  * a scroll of mail pre-named to the object name; the response command is
62  * initially appended to the name, so that the object is tagged with both
63  * of them; a NUL is inserted to terminate the ordinary name and hide the
64  * command.  (If the player renames such a scroll, the hidden command will
65  * be lost; who cares?)  Unrecognized broadcasts result in the mail-daemon
66  * arriving and announcing the display text, but no scroll being created.
67  * If SHELL is undefined, then all broadcasts are treated as 'other'; since
68  * no subproceses are allowed, there'd be no way to respond to the scroll.
69  *
70  *      When a scroll of mail is read by the character, readmail() extracts
71  * the hidden command string and uses it for the default when prompting the
72  * player for a system command to spawn.  The player may enter any command
73  * he or she chooses, or just <return> to accept the default or <escape> to
74  * avoid executing any command.  If the command is "SPAWN", a regular shell
75  * escape to DCL is performed; otherwise, the indicated single command is
76  * spawned.  Either way, NetHack resumes play when the subprocess terminates
77  * or explicitly reattaches to its parent.
78  *
79  * Broadcast parsing:
80  *
81  *      The following broadcast messages are [attempted to be] recognized:
82  *    text fragment           name for scroll         default command
83  *      New mail                VMSmail                 MAIL
84  *      New ALL-IN-1 MAIL       A1mail                  A1M
85  *      Software Tools mail     STmail                  MSG [+folder]
86  *      MM mail                 MMmail                  MM
87  *      WPmail: New mail        WPmail                  OFFICE/MAIL
88  *      **M400 mail             M400mail                M400
89  *      " mail", ^"mail "       unknown mail            SPAWN
90  *      " phoning"              Phone call              PHONE ANSWER
91  *      talk-daemon...by...foo  Talk request            TALK[/OLD] foo@bar
92  *      (node)user -            Bitnet noise            XYZZY user@node
93  * Anything else results in just the message text being passed along, no
94  * scroll of mail so consequently no command to execute when scroll read.
95  * The user can set up ``$ XYZZY :== SEND'' prior to invoking NetHack if
96  * vanilla JNET responses to Bitnet messages are prefered.
97  *
98  *      Static return buffers are used because only one broadcast gets
99  * processed at a time, and the essential information in each one is
100  * either displayed and discarded or copied into a scroll-of-mail object.
101  *
102  *      The test driver code below can be used to check out potential new
103  * entries without rebuilding NetHack itself.  CC/DEFINE="TEST_DRIVER"
104  * Link it with hacklib.obj or nethack.olb/incl=hacklib (not nethack/lib).
105  */
106
107 static struct mail_info msg;    /* parse_*()'s return buffer */
108 static char nam_cmd_buf[63],    /* maximum onamelth, size of ONAME(object) */
109             txt_buf[255+1];     /* same size as used for message buf[] */
110
111 /* try to decipher and categorize broadcast message text
112 */
113 static struct mail_info *
114 parse_brdcst(buf)               /* called by parse_next_broadcast() */
115 char *buf;                      /* input: filtered broadcast text */
116 {
117     int typ;
118     char *txt;
119     const char *nam, *cmd;
120 # ifdef SHELL           /* only parse if spawned commands are enabled */
121     register char *p, *q;
122     boolean is_jnet_send;
123     char cmd_buf[127+1], user[127+1], node[127+1], sentinel;
124
125     /* Check these first; otherwise, their arbitrary text would enable
126         easy spoofing of some other message patterns.  Unfortunately,
127         any home-grown broadcast delivery program poses a similar risk. */
128     if (!strncmpi(buf, "reply received", 14)) goto other;
129     is_jnet_send = (sscanf(buf, "(%[^)])%s -%c", node, user, &sentinel) == 3);
130     if (is_jnet_send) goto jnet_send;
131
132     /* scan the text more or less by brute force */
133     if ((q = strstri(buf, " mail")) != 0 ||     /* all known mail broadcasts */
134         !strncmpi(q = buf, "mail ", 5)) {       /* unexpected alternative */
135         typ = MSG_MAIL;
136         p = strstri(q, " from");
137         txt = p ? strcat(strcpy(txt_buf, "Mail for you"), p) : (char *) 0;
138
139         if (!strncmpi(buf, "new mail", 8)) {
140 /*
141 New mail [on node FOO] from [SPAM::]BAR [\"personal_name\"] [\(HH:MM:SS\)]
142 */
143             nam = "VMSmail";            /* assume VMSmail */
144             cmd = "MAIL";
145             if (txt && (p = strrchr(txt, '(')) > txt && /* discard time */
146                 (--p, strspn(p, "0123456789( :.)") == strlen(p))) *p = '\0';
147         } else if (!strncmpi(buf, "new all-in-1", 12)) {
148             int i;
149 /*
150 New ALL-IN-1 MAIL message [on node FOO] from Personal Name \(BAR@SPAM\) [\(DD-MMM-YYYY HH:MM:SS\)]
151 */
152             nam = "A1mail";
153             cmd = "A1M";
154             if (txt && (p = strrchr(txt, '(')) > txt && /* discard date+time */
155                 sscanf(p-1," (%*d-%*[^-]-%*d %*d:%*d:%d) %c",&i,&sentinel) == 1)
156                 *--p = '\0';
157         } else if (!strncmpi(buf, "software tools", 14)) {
158 /*
159 Software Tools mail has arrived on FOO from \'BAR\' [in SPAM]
160 */
161             nam = "STmail";
162             cmd = "MSG";
163             if (txt && (p = strstri(p, " in ")) != 0)   /* specific folder */
164                 cmd = strcat(strcpy(cmd_buf, "MSG +"), p + 4);
165         } else if (q - 2 >= buf && !strncmpi(q - 2, "mm", 2)) {
166 /*
167 {MultiNet\ |PMDF\/}MM mail has arrived on FOO from BAR\n
168 [Subject: subject_text] (PMDF only)
169 */
170             nam = "MMmail";             /* MultiNet's version of MM */
171             cmd = "MM";                 /*{ perhaps "MM READ"? }*/
172         } else if (!strncmpi(buf, "wpmail:", 7)) {
173 /*
174 WPmail: New mail from BAR.  subject_text
175 */
176             nam = "WPmail";             /* WordPerfect [sic] Office */
177             cmd = "OFFICE/MAIL";
178         } else if (!strncmpi(buf, "**m400 mail", 7)) {
179 /*
180 **M400 mail waiting**
181 */
182             nam = "M400mail";           /* Messenger 400 [not seen] */
183             cmd = "M400";
184         } else {
185             /* not recognized, but presumed to be mail */
186             nam = "unknown mail";
187             cmd = "SPAWN";              /* generic escape back to DCL */
188             txt = (char *) 0;           /* don't rely on "from" info here */
189         }
190
191         if (!txt) txt = strcat(strcpy(txt_buf, "Mail for you: "), buf);
192     /*
193      :  end of mail recognition; now check for call-type interruptions...
194      */
195     } else if ((q = strstri(buf, " phoning")) != 0) {
196 /*
197 BAR is phoning you [on FOO] \(HH:MM:SS\)
198 */
199         typ = MSG_CALL;
200         nam = "Phone call";
201         cmd = "PHONE ANSWER";
202         if (!strncmpi(q + 8, " you", 4)) q += (8 + 4), *q = '\0';
203         txt = strcat(strcpy(txt_buf, "Do you hear ringing?  "), buf);
204     } else if ((q = strstri(buf, " talk-daemon")) != 0 ||
205                (q = strstri(buf, " talk_daemon")) != 0) {
206 /*
207 Message from TALK-DAEMON@FOO at HH:MM:SS\n
208 Connection request by BAR@SPAM\n
209 \[Respond with: TALK[/OLD] BAR@SPAM\]
210 */
211         typ = MSG_CALL;
212         nam = "Talk request";           /* MultiNet's TALK and/or TALK/OLD */
213         cmd = "TALK";
214         if ((p = strstri(q, " by ")) != 0) {
215             txt = strcat(strcpy(txt_buf, "Talk request from"), p + 3);
216             if ((p = strstri(p, "respond with")) != 0) {
217                 if (*(p-1) == '[') *(p-1) = '\0'; else *p = '\0'; /* terminate */
218                 p += (sizeof "respond with" - sizeof "");
219                 if (*p == ':') p++;
220                 if (*p == ' ') p++;
221                 cmd = strcpy(cmd_buf, p);       /* "TALK[/OLD] bar@spam" */
222                 p = eos(cmd_buf);
223                 if (*--p == ']') *p = '\0';
224             }
225         } else
226             txt = strcat(strcpy(txt_buf, "Pardon the interruption: "), buf);
227     } else if (is_jnet_send) {  /* sscanf(,"(%[^)])%s -%c",,,)==3 */
228 jnet_send:
229 /*
230 \(SPAM\)BAR - arbitrary_message_text (from BAR@SPAM)
231 */
232         typ = MSG_CALL;
233         nam = "Bitnet noise";           /* RSCS/NJE message received via JNET */
234         Sprintf(cmd_buf, "XYZZY %s@%s", user, node);
235         cmd = cmd_buf;
236         /*{ perhaps just vanilla SEND instead of XYZZY? }*/
237         Sprintf(txt_buf, "Message from %s@%s:%s", user, node,
238                 &buf[1+strlen(node)+1+strlen(user)+2-1]);  /* "(node)user -" */
239         txt = txt_buf;
240     /*
241      :  end of call recognition; anything else is none-of-the-above...
242      */
243     } else {
244 other:
245 # endif /* SHELL */
246 /* arbitrary broadcast: batch job completed, system shutdown imminent, &c */
247         typ = MSG_OTHER;
248         nam = (char *) 0; /*"captured broadcast message"*/
249         cmd = (char *) 0;
250         txt = strcat(strcpy(txt_buf, "Message for you: "), buf);
251 # ifdef SHELL
252     }
253     /* Daemon in newmail() will append period when the text is displayed */
254     if ((p = eos(txt)) > txt && *--p == '.') *p = '\0';
255
256     /* newmail() and readmail() assume that nam and cmd are concatenated */
257     if (nam) {          /* object name to attach to scroll of mail */
258         char *join = strcpy(nam_cmd_buf, nam);
259         if (cmd) {      /* append command to name; readmail() requires it */
260             int len = sizeof nam_cmd_buf - sizeof "" - (strlen(join) + 1);
261             cmd_buf[len] = '\0';        /* possibly truncate */
262             (void) strcat(join, " ");
263             cmd = strcpy(eos(join), cmd);
264         }
265         nam = join;
266     }
267 # endif /* SHELL */
268     /* truncate really long messages to prevent verbalize() from blowing up */
269     if (txt && strlen(txt) > BUFSZ - 50) txt[BUFSZ - 50] = '\0';
270
271     msg.message_typ  = typ;     /* simple index */
272     msg.display_txt  = txt;     /* text for daemon to pline() */
273     msg.object_nam   = nam;     /* 'name' for mail scroll */
274     msg.response_cmd = cmd;     /* command to spawn when scroll read */
275     return &msg;
276 }
277
278 /* filter out non-printable characters and redundant noise
279 */
280 static void
281 filter_brdcst(buf)              /* called by parse_next_broadcast() */
282 register char *buf;             /* in: original text; out: filtered text */
283 {
284     register char c, *p, *buf_p;
285
286     /* filter the text; restrict consecutive spaces or dots to just two */
287     for (p = buf_p = buf; *buf_p; buf_p++) {
288         c = *buf_p & '\177';
289         if (c == ' ' || c == '\t' || c == '\n')
290             if (p == buf ||             /* ignore leading whitespace */
291                 (p >= buf+2 && *(p-1) == ' ' && *(p-2) == ' ')) continue;
292             else c = ' ';
293         else if (c == '.' || c < ' ' || c == '\177')
294             if (p == buf ||             /* skip leading beeps & such */
295                 (p >= buf+2 && *(p-1) == '.' && *(p-2) == '.')) continue;
296             else c = '.';
297         else if (c == '%' &&            /* trim %%% OPCOM verbosity %%% */
298                 p >= buf+2 && *(p-1) == '%' && *(p-2) == '%') continue;
299         *p++ = c;
300     }
301     *p = '\0';                  /* terminate, then strip trailing junk */
302     while (p > buf && (*--p == ' ' || *p == '.')) *p = '\0';
303     return;
304 }
305
306 static char empty_string[] = "";
307
308 /* fetch the text of a captured broadcast, then mangle and decipher it
309 */
310 struct mail_info *
311 parse_next_broadcast()          /* called by ckmailstatus(mail.c) */
312 {
313     short length, msg_type;
314     $DESCRIPTOR(message, empty_string); /* string descriptor for buf[] */
315     struct mail_info *result = 0;
316     /* messages could actually be longer; let long ones be truncated */
317     char buf[255+1];
318
319     message.dsc$a_pointer = buf,  message.dsc$w_length = sizeof buf - 1;
320     msg_type = length = 0;
321     smg$get_broadcast_message(&pasteboard_id, &message, &length, &msg_type);
322     if (msg_type == MSG$_TRMBRDCST) {
323         buf[length] = '\0';
324         filter_brdcst(buf);             /* mask non-printable characters */
325         result = parse_brdcst(buf);     /* do the real work */
326     } else if (msg_type == MSG$_TRMHANGUP) {
327         (void) gsignal(SIGHUP);
328     }
329     return result;
330 }
331
332 /* spit out any pending broadcast messages whenever we leave
333 */
334 static void
335 flush_broadcasts()      /* called from disable_broadcast_trapping() */
336 {
337     if (broadcasts > 0) {
338         short len, typ;
339         $DESCRIPTOR(msg_dsc, empty_string);
340         char buf[512+1];
341
342         msg_dsc.dsc$a_pointer = buf,  msg_dsc.dsc$w_length = sizeof buf - 1;
343         raw_print("");          /* print at least one line for wait_synch() */
344         do {
345             typ = len = 0;
346             smg$get_broadcast_message(&pasteboard_id, &msg_dsc, &len, &typ);
347             if (typ == MSG$_TRMBRDCST) buf[len] = '\0',  raw_print(buf);
348         } while (--broadcasts);
349         wait_synch();           /* prompt with "Hit return to continue: " */
350     }
351 }
352
353 /* AST routine called when the terminal's associated mailbox receives a message
354 */
355 /*ARGSUSED*/
356 static void
357 broadcast_ast(dummy)            /* called asynchronously by terminal driver */
358 int dummy;      /* not used */
359 {
360     broadcasts++;
361 }
362
363 /* initialize the broadcast manipulation code; SMG makes this easy
364 */
365 unsigned long init_broadcast_trapping()   /* called by setftty() [once only] */
366 {
367     unsigned long sts, preserve_screen_flag = 1;
368
369     /* we need a pasteboard to pass to the broadcast setup/teardown routines */
370     sts = smg$create_pasteboard(&pasteboard_id, 0, 0, 0, &preserve_screen_flag);
371     if (!vms_ok(sts)) {
372         errno = EVMSERR,  vaxc$errno = sts;
373         raw_print("");
374         perror("?can't create SMG pasteboard for broadcast trapping");
375         wait_synch();
376         broadcasts = -1;        /* flag that trapping is currently broken */
377     }
378     return sts;
379 }
380
381 /* set up the terminal driver to deliver $brkthru data to a mailbox device
382 */
383 unsigned long enable_broadcast_trapping()       /* called by setftty() */
384 {
385     unsigned long sts = 1;
386
387     if (broadcasts >= 0) {      /* (-1 => no pasteboard, so don't even try) */
388         /* register callback routine to be triggered when broadcasts arrive */
389         /* Note side effect:  also intercepts hangup notification. */
390         /* Another note:  TMPMBX privilege is required. */
391         sts = smg$set_broadcast_trapping(&pasteboard_id, broadcast_ast, 0);
392         if (!vms_ok(sts)) {
393             errno = EVMSERR,  vaxc$errno = sts;
394             raw_print("");
395             perror("?can't enable broadcast trapping");
396             wait_synch();
397         }
398     }
399     return sts;
400 }
401
402 /* return to 'normal'; $brkthru data goes straight to the terminal
403 */
404 unsigned long disable_broadcast_trapping()      /* called by settty() */
405 {
406     unsigned long sts = 1;
407
408     if (broadcasts >= 0) {
409         /* disable trapping; releases associated MBX so that SPAWN can work */
410         sts = smg$disable_broadcast_trapping(&pasteboard_id);
411         if (!vms_ok(sts)) errno = EVMSERR,  vaxc$errno = sts;
412         flush_broadcasts();     /* don't hold on to any buffered ones */
413     }
414     return sts;
415 }
416 #else   /* MAIL */
417         /* simple stubs for non-mail configuration */
418 unsigned long init_broadcast_trapping() { return 1; }
419 unsigned long enable_broadcast_trapping() { return 1; }
420 unsigned long disable_broadcast_trapping() { return 1; }
421 struct mail_info *parse_next_broadcast() { return 0; }
422 #endif  /* MAIL */
423
424 /*----------------------------------------------------------------------*/
425
426 #ifdef TEST_DRIVER
427         /* (Take parse_next_broadcast for a spin. :-) */
428
429 volatile int broadcasts = 0;
430
431 void newmail(foo)
432 struct mail_info *foo;
433 {
434 # define STRING(s) ((s) ? (s) : "<null>")
435     printf("\n\
436   message type = %d\n\
437   display text = \"%s\"\n\
438   object name  = \"%.*s\"\n\
439   response cmd = \"%s\"\n\
440 ",      foo->message_typ, STRING(foo->display_txt),
441         (foo->object_nam && foo->response_cmd) ?
442                 (foo->response_cmd - foo->object_nam - 1) :
443                 strlen(STRING(foo->object_nam)),
444         STRING(foo->object_nam), STRING(foo->response_cmd));
445 # undef STRING
446 }
447
448 void ckmailstatus()
449 {
450     struct mail_info *brdcst, *parse_next_broadcast();
451
452     while (broadcasts > 0) {    /* process all trapped broadcasts [until] */
453         broadcasts--;
454         if ((brdcst = parse_next_broadcast()) != 0) {
455             newmail(brdcst);
456             break;              /* only handle one real message at a time */
457         } else
458             printf("\n--< non-broadcast encountered >--\n");
459     }
460 }
461
462 int main()
463 {
464     char dummy[BUFSIZ];
465
466     init_broadcast_trapping();
467     enable_broadcast_trapping();
468     for (;;) {
469         ckmailstatus();
470         printf("> "), fflush(stdout);   /* issue a prompt */
471         if (!gets(dummy)) break;        /* wait for a response */
472     }
473     disable_broadcast_trapping();
474     return 1;
475 }
476
477 void panic(s) char *s; { raw_print(s); exit(EXIT_FAILURE); }
478
479 void raw_print(s) char *s; { puts(s); fflush(stdout); }
480
481 void wait_synch() { char dummy[BUFSIZ];
482   printf("\nPress <return> to continue: "); fflush(stdout); (void) gets(dummy);
483 }
484 #endif  /* TEST_DRIVER */
485
486 /*vmsmail.c*/