1 /* NetHack 3.6 mail.c $NHDT-Date: 1568508711 2019/09/15 00:51:51 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.40 $ */
2 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3 /*-Copyright (c) Pasi Kallinen, 2018. */
4 /* NetHack may be freely redistributed. See license for details. */
6 /* JNetHack Copyright */
7 /* (c) Issei Numata, Naoki Hamada, Shigehiro Miyashita, 1994-2000 */
8 /* For 3.4-, Copyright (c) SHIRAKATA Kentaro, 2002-2019 */
9 /* JNetHack may be freely redistributed. See license for details. */
17 #endif /* SIMPLE_MAIL */
21 * Notify user when new mail has arrived. Idea by Merlyn Leroy.
23 * The mail daemon can move with less than usual restraint. It can:
24 * - move diagonally from a door
25 * - use secret and closed doors
26 * - run through a monster ("Gangway!", etc.)
27 * - run over pools & traps
29 * Possible extensions:
30 * - Open the file MAIL and do fstat instead of stat for efficiency.
31 * (But sh uses stat, so this cannot be too bad.)
32 * - Examine the mail and produce a scroll of mail named "From somebody".
33 * - Invoke MAILREADER in such a way that only this single mail is read.
34 * - Do something to the text when the scroll is enchanted or cancelled.
35 * - Make the daemon always appear at a stairwell, and have it find a
38 * Note by Olaf Seibert: On the Amiga, we usually don't get mail. So we go
39 * through most of the effects at 'random' moments.
40 * Note by Paul Winner: The MSDOS port also 'fakes' the mail daemon at
44 STATIC_DCL boolean FDECL(md_start, (coord *));
45 STATIC_DCL boolean FDECL(md_stop, (coord *, coord *));
46 STATIC_DCL boolean FDECL(md_rush, (struct monst *, int, int));
47 STATIC_DCL void FDECL(newmail, (struct mail_info *));
49 extern char *viz_rmin, *viz_rmax; /* line-of-sight limits (vision.c) */
51 #if !defined(UNIX) && !defined(VMS)
58 /* DON'T trust all Unices to declare getpwuid() in <pwd.h> */
59 #if !defined(_BULL_SOURCE) && !defined(__sgi) && !defined(_M_UNIX)
60 #if !defined(SUNOS4) && !(defined(ULTRIX) && defined(__GNUC__))
61 /* DO trust all SVR4 to typedef uid_t in <sys/types.h> (probably to a long) */
62 #if defined(POSIX_TYPES) || defined(SVR4) || defined(HPUX)
63 extern struct passwd *FDECL(getpwuid, (uid_t));
65 extern struct passwd *FDECL(getpwuid, (int));
69 static struct stat omstat, nmstat;
70 static char *mailbox = (char *) 0;
71 static long laststattime;
73 #if !defined(MAILPATH) && defined(AMS) /* Just a placeholder for AMS */
74 #define MAILPATH "/dev/null"
76 #if !defined(MAILPATH) && (defined(LINUX) || defined(__osf__))
77 #define MAILPATH "/var/spool/mail/"
79 #if !defined(MAILPATH) && defined(__FreeBSD__)
80 #define MAILPATH "/var/mail/"
82 #if !defined(MAILPATH) && (defined(BSD) || defined(ULTRIX))
83 #define MAILPATH "/usr/spool/mail/"
85 #if !defined(MAILPATH) && (defined(SYSV) || defined(HPUX))
86 #define MAILPATH "/usr/mail/"
93 free((genericptr_t) mailbox), mailbox = (char *) 0;
100 ; /* no need to repeat the setup */
101 } else if ((mailbox = nh_getenv("MAIL")) != 0) {
102 mailbox = dupstr(mailbox);
106 struct passwd ppasswd;
108 (void) memcpy(&ppasswd, getpwuid(getuid()), sizeof (struct passwd));
109 if (ppasswd.pw_dir) {
110 /* note: 'sizeof "LITERAL"' includes +1 for terminating '\0' */
111 mailbox = (char *) alloc((unsigned) (strlen(ppasswd.pw_dir)
112 + sizeof AMS_MAILBOX));
113 Strcpy(mailbox, ppasswd.pw_dir);
114 Strcat(mailbox, AMS_MAILBOX);
117 const char *pw_name = getpwuid(getuid())->pw_name;
119 /* note: 'sizeof "LITERAL"' includes +1 for terminating '\0' */
120 mailbox = (char *) alloc((unsigned) (strlen(pw_name)
122 Strcpy(mailbox, MAILPATH);
123 Strcat(mailbox, pw_name);
125 #endif /* MAILPATH */
128 debugpline3("mailbox=%c%s%c",
129 mailbox ? '\"' : '<',
130 mailbox ? mailbox : "null",
131 mailbox ? '\"' : '>');
133 if (mailbox && stat(mailbox, &omstat)) {
134 #ifdef PERMANENT_MAILBOX
135 pline("Cannot get status of MAIL=\"%s\".", mailbox);
136 free_maildata(); /* set 'mailbox' to Null */
145 * Pick coordinates for a starting position for the mail daemon. Called
146 * from newmail() and newphone().
152 coord testcc; /* scratch coordinates */
153 int row; /* current row we are checking */
154 int lax; /* if TRUE, pick a position in sight. */
155 int dd; /* distance to current point */
156 int max_distance; /* max distance found so far */
159 * If blind and not telepathic, then it doesn't matter what we pick ---
160 * the hero is not going to see it anyway. So pick a nearby position.
162 if (Blind && !Blind_telepat) {
163 if (!enexto(startp, u.ux, u.uy, (struct permonst *) 0))
164 return FALSE; /* no good positions */
169 * Arrive at an up or down stairwell if it is in line of sight from the
172 if (couldsee(upstair.sx, upstair.sy)) {
173 startp->x = upstair.sx;
174 startp->y = upstair.sy;
177 if (couldsee(dnstair.sx, dnstair.sy)) {
178 startp->x = dnstair.sx;
179 startp->y = dnstair.sy;
184 * Try to pick a location out of sight next to the farthest position away
185 * from the hero. If this fails, try again, just picking the farthest
186 * position that could be seen. What we really ought to be doing is
187 * finding a path from a stairwell...
189 * The arrays viz_rmin[] and viz_rmax[] are set even when blind. These
190 * are the LOS limits for each row.
192 lax = 0; /* be picky */
195 for (row = 0; row < ROWNO; row++) {
196 if (viz_rmin[row] < viz_rmax[row]) {
197 /* There are valid positions on this row. */
198 dd = distu(viz_rmin[row], row);
199 if (dd > max_distance) {
203 startp->x = viz_rmin[row];
205 } else if (enexto(&testcc, (xchar) viz_rmin[row], row,
206 (struct permonst *) 0)
207 && !cansee(testcc.x, testcc.y)
208 && couldsee(testcc.x, testcc.y)) {
213 dd = distu(viz_rmax[row], row);
214 if (dd > max_distance) {
218 startp->x = viz_rmax[row];
220 } else if (enexto(&testcc, (xchar) viz_rmax[row], row,
221 (struct permonst *) 0)
222 && !cansee(testcc.x, testcc.y)
223 && couldsee(testcc.x, testcc.y)) {
231 if (max_distance < 0) {
233 lax = 1; /* just find a position */
243 * Try to choose a stopping point as near as possible to the starting
244 * position while still adjacent to the hero. If all else fails, try
245 * enexto(). Use enexto() as a last resort because enexto() chooses
246 * its point randomly, which is not what we want.
249 md_stop(stopp, startp)
250 coord *stopp; /* stopping position (we fill it in) */
251 coord *startp; /* starting position (read only) */
253 int x, y, distance, min_distance = -1;
255 for (x = u.ux - 1; x <= u.ux + 1; x++)
256 for (y = u.uy - 1; y <= u.uy + 1; y++) {
257 if (!isok(x, y) || (x == u.ux && y == u.uy))
260 if (accessible(x, y) && !MON_AT(x, y)) {
261 distance = dist2(x, y, startp->x, startp->y);
262 if (min_distance < 0 || distance < min_distance
263 || (distance == min_distance && rn2(2))) {
266 min_distance = distance;
271 /* If we didn't find a good spot, try enexto(). */
272 if (min_distance < 0 && !enexto(stopp, u.ux, u.uy, &mons[PM_MAIL_DAEMON]))
278 /* Let the mail daemon have a larger vocabulary. */
280 static NEARDATA const char *mail_text[] = { "Gangway!", "Look out!",
283 static NEARDATA const char *mail_text[] = { "
\82Ç
\82¢
\82½
\82Ç
\82¢
\82½
\81I", "
\8bC
\82ð
\82Â
\82¯
\82ë
\81I",
284 "
\82¶
\82á
\82Ü
\82·
\82é
\82æ
\81I" };
286 #define md_exclamations() (mail_text[rn2(3)])
289 * Make the mail daemon run through the dungeon. The daemon will run over
290 * any monsters that are in its path, but will replace them later. Return
291 * FALSE if the md gets stuck in a position where there is a monster. Return
297 register int tx, ty; /* destination of mail daemon */
299 struct monst *mon; /* displaced monster */
300 register int dx, dy; /* direction counters */
301 int fx = md->mx, fy = md->my; /* current location */
302 int nfx = fx, nfy = fy, /* new location */
303 d1, d2; /* shortest distances */
306 * It is possible that the monster at (fx,fy) is not the md when:
307 * the md rushed the hero and failed, and is now starting back.
309 if (m_at(fx, fy) == md) {
310 remove_monster(fx, fy); /* pick up from orig position */
315 * At the beginning and exit of this loop, md is not placed in the
319 /* Find a good location next to (fx,fy) closest to (tx,ty). */
320 d1 = dist2(fx, fy, tx, ty);
321 for (dx = -1; dx <= 1; dx++)
322 for (dy = -1; dy <= 1; dy++)
323 if ((dx || dy) && isok(fx + dx, fy + dy)
324 && !IS_STWALL(levl[fx + dx][fy + dy].typ)) {
325 d2 = dist2(fx + dx, fy + dy, tx, ty);
333 /* Break if the md couldn't find a new position. */
334 if (nfx == fx && nfy == fy)
337 fx = nfx; /* this is our new position */
340 /* Break if the md reaches its destination. */
341 if (fx == tx && fy == ty)
344 if ((mon = m_at(fx, fy)) != 0) /* save monster at this position */
345 verbalize1(md_exclamations());
346 else if (fx == u.ux && fy == u.uy)
348 verbalize("Excuse me.");
350 verbalize("
\82¿
\82å
\82Á
\82Æ
\82µ
\82Â
\82ê
\82¢
\81D");
353 remove_monster(fx, fy);
354 place_monster(md, fx, fy); /* put md down */
355 newsym(fx, fy); /* see it */
356 flush_screen(0); /* make sure md shows up */
357 delay_output(); /* wait a little bit */
359 /* Remove md from the dungeon. Restore original mon, if necessary. */
360 remove_monster(fx, fy);
362 if ((mon->mx != fx) || (mon->my != fy))
363 place_worm_seg(mon, fx, fy);
365 place_monster(mon, fx, fy);
371 * Check for a monster at our stopping position (this is possible, but
372 * very unlikely). If one exists, then have the md leave in disgust.
374 if ((mon = m_at(fx, fy)) != 0) {
375 remove_monster(fx, fy);
376 place_monster(md, fx, fy); /* display md with text below */
379 verbalize("This place's too crowded. I'm outta here.");
381 verbalize("
\82±
\82±
\82Í
\8d¬
\82Ý
\82·
\82¬
\81D
\82±
\82±
\82Å
\91Ò
\82Á
\82Ä
\82é
\82æ
\81D");
382 remove_monster(fx, fy);
384 if ((mon->mx != fx) || (mon->my != fy)) /* put mon back */
385 place_worm_seg(mon, fx, fy);
387 place_monster(mon, fx, fy);
393 place_monster(md, fx, fy); /* place at final spot */
396 delay_output(); /* wait a little bit */
401 /* Deliver a scroll of mail. */
405 struct mail_info *info;
409 boolean message_seen = FALSE;
411 /* Try to find good starting and stopping places. */
412 if (!md_start(&start) || !md_stop(&stop, &start))
415 /* Make the daemon. Have it rush towards the hero. */
416 if (!(md = makemon(&mons[PM_MAIL_DAEMON], start.x, start.y, NO_MM_FLAGS)))
418 if (!md_rush(md, stop.x, stop.y))
423 verbalize("%s, %s! %s.", Hello(md), plname, info->display_txt);
425 verbalize("%s
\81I%s
\81D", Hello(md), info->display_txt);
428 if (info->message_typ) {
429 struct obj *obj = mksobj(SCR_MAIL, FALSE, FALSE);
431 if (info->object_nam)
432 obj = oname(obj, info->object_nam);
433 if (info->response_cmd)
434 new_omailcmd(obj, info->response_cmd);
436 if (distu(md->mx, md->my) > 2)
440 verbalize("
\82Ù
\82ç
\82æ
\81I");
441 display_nhwindow(WIN_MESSAGE, FALSE);
443 obj = hold_another_object(obj, "Oops!", (const char *) 0,
446 obj = hold_another_object(obj, "
\82¨
\82Á
\82Æ
\81I", (const char *) 0,
453 /* zip back to starting location */
454 if (!md_rush(md, start.x, start.y))
455 md->mx = md->my = 0; /* for mongone, md is not on map */
459 /* deliver some classes of messages even if no daemon ever shows up */
460 if (!message_seen && info->message_typ == MSG_OTHER)
462 pline("Hark! \"%s.\"", info->display_txt);
464 pline("
\81u%s
\81D
\81v
\82Æ
\8c¾
\82¤
\82±
\82Æ
\82¾
\81I", info->display_txt);
467 #if !defined(UNIX) && !defined(VMS)
472 if (u.uswallow || !flags.biff)
474 if (mustgetmail < 0) {
475 #if defined(AMIGA) || defined(MSDOS) || defined(TOS)
476 mustgetmail = (moves < 2000) ? (100 + rn2(2000)) : (2000 + rn2(3000));
480 if (--mustgetmail <= 0) {
481 static struct mail_info deliver = {
483 MSG_MAIL, "I have some mail for you", 0, 0
485 MSG_MAIL, "
\83\81\81[
\83\8b\82ð
\8e\9d\82Á
\82Ä
\82«
\82½
\82æ", 0, 0
495 struct obj *otmp UNUSED;
497 static const char *junk[] = {
499 "Report bugs to <%s>.", /*** must be first entry ***/
500 "Please disregard previous letter.",
501 "Welcome to NetHack.",
503 "Report bugs to <%s>.", /*** must be first entry ***/
504 "
\91O
\82Ì
\83\81\81[
\83\8b\82Í
\96Y
\82ê
\82Ä
\82
\82¾
\82³
\82¢
\81D",
505 "JNetHack
\82Ö
\82æ
\82¤
\82±
\82»
\81I",
508 "Only Amiga makes it possible.",
509 "CATS have all the answers.",
512 "This mail complies with the Yendorian Anti-Spam Act (YASA)",
514 "
\82±
\82Ì
\83\81\81[
\83\8b\82Í
\83C
\83F
\83\93\83_
\81[
\83X
\83p
\83\80\91Î
\8dô
\96@(YASA)
\82É
\8f\80\8b\92\82µ
\82Ä
\82¢
\82Ü
\82·
\81D",
516 "Please find enclosed a small token to represent your Owlbear",
518 "
\82 \82È
\82½
\82Ì
\83A
\83E
\83\8b\83x
\83A
\82ð
\95\
\8c»
\82·
\82é
\82½
\82ß
\82É
\93¯
\95\95\82µ
\82½
\8f¬
\82³
\82¢
\83g
\81[
\83N
\83\93\82ð
\92T
\82µ
\82Ä
\82
\82¾
\82³
\82¢",
520 "**FR33 P0T10N 0F FULL H34L1NG**",
522 "**
\8a®
\91S
\89ñ
\95\9c\82Ì
\96ò
\83v
\83\8c\83[
\83\93\83g**",
524 "Please return to sender (Asmodeus)",
526 "
\91\97\90M
\8eÒ(
\83A
\83X
\83\82\83f
\83E
\83X)
\82É
\91\97\82è
\95Ô
\82µ
\82Ä
\82
\82¾
\82³
\82¢",
527 /* when enclosed by "It reads: \"...\"", this is too long
528 for an ordinary 80-column display so wraps to a second line
529 (suboptimal but works correctly);
530 dollar sign and fractional zorkmids are inappropriate within
531 nethack but are suitable for typical dysfunctional spam mail */
533 "Buy a potion of gain level for only $19.99! Guaranteed to be blessed!",
535 "
\83\8c\83x
\83\8b\83A
\83b
\83v
\82Ì
\96ò
\82ª
\82½
\82Á
\82½
\82Ì1980
\89~!
\8fj
\95\9f\95Û
\8fØ!",
536 /* DEVTEAM_URL will be substituted for "%s"; terminating punctuation
537 (formerly "!") has deliberately been omitted so that it can't be
538 mistaken for part of the URL (unfortunately that is still followed
539 by a closing quote--in the pline below, not the data here) */
541 "Invitation: Visit the NetHack web site at %s"
543 "
\8fµ
\91Ò
\8fó: NetHack
\83E
\83F
\83u
\83T
\83C
\83g %s
\82É
\97\88\82Ä
\82Ë!"
546 /* XXX replace with more general substitution code and add local
549 * FIXME: this allocated memory is never freed. However, if the
550 * game is restarted, the junk[] update will be a no-op for second
551 * and subsequent runs and this updated text will still be appropriate.
553 if (index(junk[0], '%')) {
557 for (i = 0; i < SIZE(junk); ++i) {
558 if (index(junk[i], '%')) {
560 /* +2 from '%s' in junk[0] suffices as substitute
561 for usual +1 for terminator */
562 tmp = (char *) alloc(strlen(junk[0])
563 + strlen(DEVTEAM_EMAIL));
564 Sprintf(tmp, junk[0], DEVTEAM_EMAIL);
566 } else if (strstri(junk[i], "web site")) {
567 /* as with junk[0], room for terminator is present */
568 tmp = (char *) alloc(strlen(junk[i])
569 + strlen(DEVTEAM_URL));
570 Sprintf(tmp, junk[i], DEVTEAM_URL);
573 /* could check for "%%" but unless that becomes needed,
574 handling it is more complicated than necessary */
575 impossible("fake mail #%d has undefined substitution", i);
576 junk[i] = "Bad fake mail...";
583 pline("Unfortunately you cannot see what it says.");
585 pline("
\8ec
\94O
\82È
\82ª
\82ç
\89½
\82Æ
\8f\91\82¢
\82Ä
\82 \82é
\82Ì
\82©
\8c©
\82é
\82±
\82Æ
\82ª
\82Å
\82«
\82È
\82¢
\81D");
588 pline("It reads: \"%s\"", junk[rn2(SIZE(junk))]);
590 pline("
\82»
\82ê
\82ð
\93Ç
\82ñ
\82¾
\81F\"%s\"", junk[rn2(SIZE(junk))]);
593 #endif /* !UNIX && !VMS */
600 ck_server_admin_msg();
602 if (!mailbox || u.uswallow || !flags.biff
604 || moves < laststattime + MAILCKFREQ
609 laststattime = moves;
610 if (stat(mailbox, &nmstat)) {
611 #ifdef PERMANENT_MAILBOX
612 pline("Cannot get status of MAIL=\"%s\" anymore.", mailbox);
617 } else if (nmstat.st_mtime > omstat.st_mtime) {
618 if (nmstat.st_size) {
619 static struct mail_info deliver = {
620 #ifndef NO_MAILREADER
622 MSG_MAIL, "I have some mail for you",
624 MSG_MAIL, "
\83\81\83C
\83\8b\82ð
\8e\9d\82Á
\82Ä
\82«
\82½
\82æ",
626 /* suppress creation and delivery of scroll of mail */
628 MSG_OTHER, "You have some mail in the outside world",
630 MSG_OTHER, "
\8aO
\82Ì
\90¢
\8aE
\82©
\82ç
\82Ì
\83\81\81[
\83\8b\82¾",
636 getmailstatus(); /* might be too late ... */
640 #if defined(SIMPLE_MAIL) || defined(SERVER_ADMIN_MSG)
642 read_simplemail(mbox, adminmsg)
646 FILE* mb = fopen(mbox, "r");
647 char curline[128], *msg;
648 boolean seen_one_already = FALSE;
650 struct flock fl = { 0 };
652 const char *msgfrom = adminmsg
654 ? "The voice of %s booms through the caverns:"
655 : "This message is from '%s'.";
657 ? "%s
\82Ì
\90º
\82ª
\93´
\8cA
\82É
\8b¿
\82«
\82í
\82½
\82Á
\82½:"
658 : "
\82±
\82ê
\82Í'%s'
\82©
\82ç
\82Ì
\83\81\83b
\83Z
\81[
\83W
\82¾
\81D";
666 fl.l_whence = SEEK_SET;
672 /* Allow this call to block. */
675 && fcntl (fileno (mb), F_SETLKW, &fl) == -1
680 while (fgets(curline, 128, mb) != NULL) {
684 fcntl (fileno(mb), F_UNLCK, &fl);
687 pline("There is a%s message on this scroll.",
688 seen_one_already ? "nother" : "");
690 pline("
\82±
\82Ì
\8aª
\95¨
\82É
\82Í%s
\83\81\83b
\83Z
\81[
\83W
\82ª
\82 \82é
\81D",
691 seen_one_already ? "
\82Ü
\82¾" : "");
694 msg = strchr(curline, ':');
701 msg[strlen(msg) - 1] = '\0'; /* kill newline */
703 pline(msgfrom, curline);
708 pline("It reads: \"%s\".", msg);
710 pline("
\82»
\82ê
\82ð
\93Ç
\82ñ
\82¾
\81F
\81u%s
\81v", msg);
712 seen_one_already = TRUE;
717 fcntl(fileno(mb), F_SETLKW, &fl);
725 fcntl(fileno(mb), F_UNLCK, &fl);
730 display_nhwindow(WIN_MESSAGE, TRUE);
735 /* bail out _professionally_ */
738 pline("It appears to be all gibberish.");
740 pline("
\82±
\82ê
\82Í
\82Ü
\82Á
\82½
\82
\82¿
\82ñ
\82Õ
\82ñ
\82©
\82ñ
\82Õ
\82ñ
\82¾
\81D");
742 #endif /* SIMPLE_MAIL */
745 ck_server_admin_msg()
747 #ifdef SERVER_ADMIN_MSG
748 static struct stat ost,nst;
749 static long lastchk = 0;
751 if (moves < lastchk + SERVER_ADMIN_MSG_CKFREQ) return;
754 if (!stat(SERVER_ADMIN_MSG, &nst)) {
755 if (nst.st_mtime > ost.st_mtime)
756 read_simplemail(SERVER_ADMIN_MSG, TRUE);
757 ost.st_mtime = nst.st_mtime;
759 #endif /* SERVER_ADMIN_MSG */
765 struct obj *otmp UNUSED;
767 #ifdef DEF_MAILREADER /* This implies that UNIX is defined */
768 register const char *mr = 0;
769 #endif /* DEF_MAILREADER */
771 read_simplemail(mailbox, FALSE);
773 #endif /* SIMPLE_MAIL */
774 #ifdef DEF_MAILREADER /* This implies that UNIX is defined */
775 if (iflags.debug_fuzzer)
777 display_nhwindow(WIN_MESSAGE, FALSE);
778 if (!(mr = nh_getenv("MAILREADER")))
782 (void) execl(mr, mr, (char *) 0);
783 nh_terminate(EXIT_FAILURE);
786 #ifndef AMS /* AMS mailboxes are directories */
787 display_file(mailbox, TRUE);
789 #endif /* DEF_MAILREADER */
791 /* get new stat; not entirely correct: there is a small time
792 window where we do not see new mail */
800 extern NDECL(struct mail_info *parse_next_broadcast);
802 volatile int broadcasts = 0;
807 struct mail_info *brdcst;
809 if (iflags.debug_fuzzer)
811 if (u.uswallow || !flags.biff)
814 while (broadcasts > 0) { /* process all trapped broadcasts [until] */
816 if ((brdcst = parse_next_broadcast()) != 0) {
818 break; /* only handle one real message at a time */
827 #ifdef SHELL /* can't access mail reader without spawning subprocess */
828 const char *txt, *cmd;
829 char *p, buf[BUFSZ] = DUMMY, qbuf[BUFSZ];
832 /* there should be a command in OMAILCMD */
838 if (has_omailcmd(otmp))
839 cmd = OMAILCMD(otmp);
843 Sprintf(qbuf, "System command (%s)", cmd);
845 if (*buf != '\033') {
846 for (p = eos(buf); p > buf; *p = '\0')
848 break; /* strip trailing spaces */
850 cmd = buf; /* use user entered command */
851 if (!strcmpi(cmd, "SPAWN") || !strcmp(cmd, "!"))
852 cmd = (char *) 0; /* interactive escape */
854 vms_doshell(cmd, TRUE);