1 /* aNetHack 0.0.1 vmsmail.c $ANH-Date: 1449801741 2015/12/11 02:42:21 $ $ANH-Branch: aNetHack-3.6.0 $:$ANH-Revision: 1.10 $ */
2 /* Copyright (c) Robert Patrick Rankin, 1991. */
3 /* aNetHack may be freely redistributed. See license for details. */
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
);
23 #define MSG$_TRMHANGUP 6
24 #define MSG$_TRMBRDCST 83
27 /* #include <string.h> */
28 #define vms_ok(sts) ((sts) & 1)
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));
38 extern size_t FDECL(strspn
, (const char *, const char *));
40 extern int VDECL(sscanf
, (const char *, const char *, ...));
42 extern unsigned long smg$
create_pasteboard(), smg$
get_broadcast_message(),
43 smg$
set_broadcast_trapping(), smg$
disable_broadcast_trapping();
45 extern volatile int broadcasts
; /* defining declaration in mail.c */
47 static long pasteboard_id
= 0; /* SMG's magic cookie */
50 * Mail (et al) overview:
52 * When a broadcast is asynchronously captured, a volatile counter
53 * ('broadcasts') is incremented. Each player turn, ckmailstatus() polls
54 * the counter and calls parse_next_broadcast() if it's positive; this
55 * returns some display text, object name, and response command, which is
56 * passed to newmail(). Routine newmail() generates a mail-daemon monster
57 * who approaches the character, "speaks" the display text, and delivers
58 * a scroll of mail pre-named to the object name; the response command is
59 * also attached to the scroll's oextra->omailcmd field.
60 * Unrecognized broadcasts result in the mail-daemon
61 * arriving and announcing the display text, but no scroll being created.
62 * If SHELL is undefined, then all broadcasts are treated as 'other'; since
63 * no subproceses are allowed, there'd be no way to respond to the scroll.
65 * When a scroll of mail is read by the character, readmail() extracts
66 * the command string and uses it for the default when prompting the
67 * player for a system command to spawn. The player may enter any command
68 * he or she chooses, or just <return> to accept the default or <escape> to
69 * avoid executing any command. If the command is "SPAWN", a regular shell
70 * escape to DCL is performed; otherwise, the indicated single command is
71 * spawned. Either way, aNetHack resumes play when the subprocess terminates
72 * or explicitly reattaches to its parent.
76 * The following broadcast messages are [attempted to be] recognized:
77 * text fragment name for scroll default command
78 * New mail VMSmail MAIL
79 * New ALL-IN-1 MAIL A1mail A1M
80 * Software Tools mail STmail MSG [+folder]
82 * WPmail: New mail WPmail OFFICE/MAIL
83 * **M400 mail M400mail M400
84 * " mail", ^"mail " unknown mail SPAWN
85 * " phoning" Phone call PHONE ANSWER
86 * talk-daemon...by...foo Talk request TALK[/OLD] foo@bar
87 * (node)user - Bitnet noise XYZZY user@node
88 * Anything else results in just the message text being passed along, no
89 * scroll of mail so consequently no command to execute when scroll read.
90 * The user can set up ``$ XYZZY :== SEND'' prior to invoking aNetHack if
91 * vanilla JNET responses to Bitnet messages are prefered.
93 * Static return buffers are used because only one broadcast gets
94 * processed at a time, and the essential information in each one is
95 * either displayed and discarded or copied into a scroll-of-mail object.
97 * The test driver code below can be used to check out potential new
98 * entries without rebuilding aNetHack itself. CC/DEFINE="TEST_DRIVER"
99 * Link it with hacklib.obj or anethack.olb/incl=hacklib (not anethack/lib).
102 static struct mail_info msg
; /* parse_*()'s return buffer */
103 static char nam_buf
[63], /* maximum onamelth, size of ONAME(object) */
104 cmd_buf
[99], /* arbitrary */
105 txt_buf
[255 + 1]; /* same size as used for message buf[] */
107 /* try to decipher and categorize broadcast message text
109 static struct mail_info
*
110 parse_brdcst(buf
) /* called by parse_next_broadcast() */
111 char *buf
; /* input: filtered broadcast text */
115 const char *nam
, *cmd
;
116 #ifdef SHELL /* only parse if spawned commands are enabled */
117 register char *p
, *q
;
118 boolean is_jnet_send
;
119 char user
[127 + 1], node
[127 + 1], sentinel
;
121 /* Check these first; otherwise, their arbitrary text would enable
122 easy spoofing of some other message patterns. Unfortunately,
123 any home-grown broadcast delivery program poses a similar risk. */
124 if (!strncmpi(buf
, "reply received", 14))
126 is_jnet_send
= (sscanf(buf
, "(%[^)])%s -%c", node
, user
, &sentinel
) == 3);
130 /* scan the text more or less by brute force */
131 if ((q
= strstri(buf
, " mail")) != 0 || /* all known mail broadcasts */
132 !strncmpi(q
= buf
, "mail ", 5)) { /* unexpected alternative */
134 p
= strstri(q
, " from");
135 txt
= p
? strcat(strcpy(txt_buf
, "Mail for you"), p
) : (char *) 0;
137 if (!strncmpi(buf
, "new mail", 8)) {
139 * New mail [on node FOO] from [SPAM::]BAR
140 * [\"personal_name\"] [\(HH:MM:SS\)]
142 nam
= "VMSmail"; /* assume VMSmail */
144 if (txt
&& (p
= strrchr(txt
, '(')) > txt
&& /* discard time */
145 (--p
, strspn(p
, "0123456789( :.)") == strlen(p
)))
147 } else if (!strncmpi(buf
, "new all-in-1", 12)) {
150 * New ALL-IN-1 MAIL message [on node FOO] from Personal Name
151 * \(BAR@SPAM\) [\(DD-MMM-YYYY HH:MM:SS\)]
155 if (txt
&& (p
= strrchr(txt
, '(')) > txt
156 && /* discard date+time */
157 sscanf(p
- 1, " (%*d-%*[^-]-%*d %*d:%*d:%d) %c", &i
,
160 } else if (!strncmpi(buf
, "software tools", 14)) {
162 * Software Tools mail has arrived on FOO from \'BAR\' [in SPAM]
166 if (txt
&& (p
= strstri(p
, " in ")) != 0) /* specific folder */
167 cmd
= strcat(strcpy(cmd_buf
, "MSG +"), p
+ 4);
168 } else if (q
- 2 >= buf
&& !strncmpi(q
- 2, "mm", 2)) {
170 * {MultiNet\ |PMDF\/}MM mail has arrived on FOO from BAR\n
171 * [Subject: subject_text] (PMDF only)
173 nam
= "MMmail"; /* MultiNet's version of MM */
174 cmd
= "MM"; /*{ perhaps "MM READ"? }*/
175 } else if (!strncmpi(buf
, "wpmail:", 7)) {
177 * WPmail: New mail from BAR. subject_text
179 nam
= "WPmail"; /* WordPerfect [sic] Office */
181 } else if (!strncmpi(buf
, "**m400 mail", 7)) {
183 * **M400 mail waiting**
185 nam
= "M400mail"; /* Messenger 400 [not seen] */
188 /* not recognized, but presumed to be mail */
189 nam
= "unknown mail";
190 cmd
= "SPAWN"; /* generic escape back to DCL */
191 txt
= (char *) 0; /* don't rely on "from" info here */
195 txt
= strcat(strcpy(txt_buf
, "Mail for you: "), buf
);
198 * end of mail recognition; now check for call-type interruptions...
200 } else if ((q
= strstri(buf
, " phoning")) != 0) {
202 * BAR is phoning you [on FOO] \(HH:MM:SS\)
206 cmd
= "PHONE ANSWER";
207 if (!strncmpi(q
+ 8, " you", 4))
208 q
+= (8 + 4), *q
= '\0';
209 txt
= strcat(strcpy(txt_buf
, "Do you hear ringing? "), buf
);
210 } else if ((q
= strstri(buf
, " talk-daemon")) != 0
211 || (q
= strstri(buf
, " talk_daemon")) != 0) {
213 * Message from TALK-DAEMON@FOO at HH:MM:SS\n
214 * Connection request by BAR@SPAM\n
215 * \[Respond with: TALK[/OLD] BAR@SPAM\]
218 nam
= "Talk request"; /* MultiNet's TALK and/or TALK/OLD */
220 if ((p
= strstri(q
, " by ")) != 0) {
221 txt
= strcat(strcpy(txt_buf
, "Talk request from"), p
+ 3);
222 if ((p
= strstri(p
, "respond with")) != 0) {
226 *p
= '\0'; /* terminate */
227 p
+= (sizeof "respond with" - sizeof "");
232 cmd
= strcpy(cmd_buf
, p
); /* "TALK[/OLD] bar@spam" */
238 txt
= strcat(strcpy(txt_buf
, "Pardon the interruption: "), buf
);
239 } else if (is_jnet_send
) { /* sscanf(,"(%[^)])%s -%c",,,)==3 */
242 * \(SPAM\)BAR - arbitrary_message_text (from BAR@SPAM)
245 nam
= "Bitnet noise"; /* RSCS/NJE message received via JNET */
246 Sprintf(cmd_buf
, "XYZZY %s@%s", user
, node
);
248 /*{ perhaps just vanilla SEND instead of XYZZY? }*/
249 Sprintf(txt_buf
, "Message from %s@%s:%s", user
, node
,
251 &buf
[1 + strlen(node
) + 1 + strlen(user
) + 2 - 1]);
255 * end of call recognition; anything else is none-of-the-above...
260 /* arbitrary broadcast: batch job completed, system shutdown
264 nam
= (char *) 0; /*"captured broadcast message"*/
266 txt
= strcat(strcpy(txt_buf
, "Message for you: "), buf
);
269 /* Daemon in newmail() will append period when the text is displayed */
270 if ((p
= eos(txt
)) > txt
&& *--p
== '.')
273 /* newmail() and readmail() used to assume that nam and cmd are
274 concatenated but that is no longer the case */
275 if (nam
&& nam
!= nam_buf
) {
276 (void) strncpy(nam_buf
, nam
, sizeof nam_buf
- 1);
277 nam_buf
[sizeof nam_buf
- 1] = '\0';
279 if (cmd
&& cmd
!= cmd_buf
) {
280 (void) strncpy(cmd_buf
, cmd
, sizeof cmd_buf
- 1);
281 cmd_buf
[sizeof cmd_buf
- 1] = '\0';
284 /* truncate really long messages to prevent verbalize() from blowing up */
285 if (txt
&& strlen(txt
) > BUFSZ
- 50)
286 txt
[BUFSZ
- 50] = '\0';
288 msg
.message_typ
= typ
; /* simple index */
289 msg
.display_txt
= txt
; /* text for daemon to pline() */
290 msg
.object_nam
= nam
; /* 'name' for mail scroll */
291 msg
.response_cmd
= cmd
; /* command to spawn when scroll read */
295 /* filter out non-printable characters and redundant noise
297 static void filter_brdcst(buf
) /* called by parse_next_broadcast() */
298 register char *buf
; /* in: original text; out: filtered text */
300 register char c
, *p
, *buf_p
;
302 /* filter the text; restrict consecutive spaces or dots to just two */
303 for (p
= buf_p
= buf
; *buf_p
; buf_p
++) {
305 if (c
== ' ' || c
== '\t' || c
== '\n') {
306 if (p
== buf
|| /* ignore leading whitespace */
307 (p
>= buf
+ 2 && *(p
- 1) == ' ' && *(p
- 2) == ' '))
311 } else if (c
== '.' || c
< ' ' || c
== '\177') {
312 if (p
== buf
|| /* skip leading beeps & such */
313 (p
>= buf
+ 2 && *(p
- 1) == '.' && *(p
- 2) == '.'))
317 } else if (c
== '%' && /* trim %%% OPCOM verbosity %%% */
318 p
>= buf
+ 2 && *(p
- 1) == '%' && *(p
- 2) == '%') {
323 *p
= '\0'; /* terminate, then strip trailing junk */
324 while (p
> buf
&& (*--p
== ' ' || *p
== '.'))
329 static char empty_string
[] = "";
331 /* fetch the text of a captured broadcast, then mangle and decipher it
333 struct mail_info
*parse_next_broadcast() /* called by ckmailstatus(mail.c) */
335 short length
, msg_type
;
336 $
DESCRIPTOR(message
, empty_string
); /* string descriptor for buf[] */
337 struct mail_info
*result
= 0;
338 /* messages could actually be longer; let long ones be truncated */
341 message
.dsc$a_pointer
= buf
, message
.dsc$w_length
= sizeof buf
- 1;
342 msg_type
= length
= 0;
343 smg$
get_broadcast_message(&pasteboard_id
, &message
, &length
, &msg_type
);
344 if (msg_type
== MSG$_TRMBRDCST
) {
346 filter_brdcst(buf
); /* mask non-printable characters */
347 result
= parse_brdcst(buf
); /* do the real work */
348 } else if (msg_type
== MSG$_TRMHANGUP
) {
349 (void) gsignal(SIGHUP
);
354 /* spit out any pending broadcast messages whenever we leave
356 static void flush_broadcasts() /* called from disable_broadcast_trapping() */
358 if (broadcasts
> 0) {
360 $
DESCRIPTOR(msg_dsc
, empty_string
);
363 msg_dsc
.dsc$a_pointer
= buf
, msg_dsc
.dsc$w_length
= sizeof buf
- 1;
364 raw_print(""); /* print at least one line for wait_synch() */
367 smg$
get_broadcast_message(&pasteboard_id
, &msg_dsc
, &len
, &typ
);
368 if (typ
== MSG$_TRMBRDCST
)
369 buf
[len
] = '\0', raw_print(buf
);
370 } while (--broadcasts
);
371 wait_synch(); /* prompt with "Hit return to continue: " */
375 /* AST routine called when terminal's associated mailbox receives a message
379 broadcast_ast(dummy
) /* called asynchronously by terminal driver */
385 /* initialize the broadcast manipulation code; SMG makes this easy
388 init_broadcast_trapping() /* called by setftty() [once only] */
390 unsigned long sts
, preserve_screen_flag
= 1;
392 /* we need a pasteboard to pass to the broadcast setup/teardown routines */
393 sts
= smg$
create_pasteboard(&pasteboard_id
, 0, 0, 0,
394 &preserve_screen_flag
);
396 errno
= EVMSERR
, vaxc$errno
= sts
;
398 perror("?can't create SMG pasteboard for broadcast trapping");
400 broadcasts
= -1; /* flag that trapping is currently broken */
405 /* set up the terminal driver to deliver $brkthru data to a mailbox device
408 enable_broadcast_trapping() /* called by setftty() */
410 unsigned long sts
= 1;
412 if (broadcasts
>= 0) { /* (-1 => no pasteboard, so don't even try) */
413 /* register callback routine to be triggered when broadcasts arrive */
414 /* Note side effect: also intercepts hangup notification. */
415 /* Another note: TMPMBX privilege is required. */
416 sts
= smg$
set_broadcast_trapping(&pasteboard_id
, broadcast_ast
, 0);
418 errno
= EVMSERR
, vaxc$errno
= sts
;
420 perror("?can't enable broadcast trapping");
427 /* return to 'normal'; $brkthru data goes straight to the terminal
430 disable_broadcast_trapping() /* called by settty() */
432 unsigned long sts
= 1;
434 if (broadcasts
>= 0) {
435 /* disable trapping; releases associated MBX so that SPAWN can work */
436 sts
= smg$
disable_broadcast_trapping(&pasteboard_id
);
438 errno
= EVMSERR
, vaxc$errno
= sts
;
439 flush_broadcasts(); /* don't hold on to any buffered ones */
446 /* simple stubs for non-mail configuration */
448 init_broadcast_trapping()
453 enable_broadcast_trapping()
458 disable_broadcast_trapping()
463 parse_next_broadcast()
470 /*----------------------------------------------------------------------*/
473 /* (Take parse_next_broadcast for a spin. :-) */
475 volatile int broadcasts
= 0;
479 struct mail_info
*foo
;
481 #define STRING(s) ((s) ? (s) : "<null>")
484 display text = \"%s\"\n\
485 object name = \"%.*s\"\n\
486 response cmd = \"%s\"\n\
488 foo
->message_typ
, STRING(foo
->display_txt
),
489 (foo
->object_nam
&& foo
->response_cmd
)
490 ? (foo
->response_cmd
- foo
->object_nam
- 1)
491 : strlen(STRING(foo
->object_nam
)),
492 STRING(foo
->object_nam
), STRING(foo
->response_cmd
));
499 struct mail_info
*brdcst
, *parse_next_broadcast();
501 while (broadcasts
> 0) { /* process all trapped broadcasts [until] */
503 if ((brdcst
= parse_next_broadcast()) != 0) {
505 break; /* only handle one real message at a time */
507 printf("\n--< non-broadcast encountered >--\n");
516 init_broadcast_trapping();
517 enable_broadcast_trapping();
520 printf("> "), fflush(stdout
); /* issue a prompt */
522 break; /* wait for a response */
524 disable_broadcast_trapping();
549 printf("\nPress <return> to continue: ");
553 #endif /* TEST_DRIVER */