/variants/slashem/sys/vms/vmsmail.c

https://bitbucket.org/clivecrous/ruhack · C · 486 lines · 311 code · 42 blank · 133 comment · 120 complexity · 06b515e3883f7bedad7ea21649d02e0d MD5 · raw file

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