StrBufReplaceToken() set the null terminator correctly
[citadel.git] / textclient / commands.c
1 /*
2  * This file contains functions which implement parts of the
3  * text-mode user interface.
4  *
5  * Copyright (c) 1987-2018 by the citadel.org team
6  *
7  * This program is open source software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License version 3.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  */
15
16 #include "textclient.h"
17
18 char *helpnames[] = {
19         "help",
20         "admin",
21         "floors",
22         "intro",
23         "mail",
24         "network",
25         "software"
26 };
27
28 char *helptexts[] = {
29
30         "                          Citadel Help Menu\n"
31             "  \n"
32             " ?         Help. (Typing a '?' will give you a menu almost anywhere)\n"
33             " A         Abandon this room where you stopped reading, goto next room.\n"
34             " C         Chat (multiuser chat, where available)\n"
35             " D         Prints directory, if there is one in the current room.\n"
36             " E         Enter a message.\n"
37             " F         Read all messages in the room, forward.\n"
38             " G         Goto next room which has UNREAD messages.\n"
39             " H         Help. Same as '?'\n"
40             " I         Reads the Information file for this room.\n"
41             " K         List of Known rooms.\n"
42             " L         Reads the last five messages in the room.\n"
43             " N         Reads all new messages in the room.\n"
44             " O         Reads all old messages, backwards.\n"
45             " P         Page another user (send an instant message)\n"
46             " R         Reads all messages in the room, in reverse order.\n"
47             " S         Skips current room without making its messages old.\n"
48             " T         Terminate (logout)\n"
49             " U         Ungoto (returns to the last room you were in)\n"
50             " W         Displays who is currently logged in.\n"
51             " X         Toggle eXpert mode (menus and help blurbs on/off)\n"
52             " Z         Zap (forget) room. (Removes the room from your list)\n"
53             " + -       Goto next, previous room on current floor.\n"
54             " > <       Goto next, previous floor.\n"
55             "   \n"
56             " In addition, there are dot commands. You hit the . (dot), then press the\n"
57             "first letter of each word of the command. As you hit the letters, the words\n"
58             "pop onto your screen. Exceptions: after you hit .Help or .Goto, the remainder\n"
59             "of the command is a help file name or room name.\n"
60             "    \n"
61             "      *** USE  .<H>elp ?    for additional help *** \n",
62
63         "The following commands are available only to Admins.  A subset of these\n"
64             "commands are available to room aides when they are currently in the room\n"
65             "they are room aide for.\n"
66             "\n"
67             " <.> <A>dmin <K>ill this room   (Delete the current room)\n"
68             " <.> <A>dmin <E>dit this room   (Edit the current room's parameters)\n"
69             " <.> <A>dmin <W>ho knows room   (List users with access to this room)\n"
70             " <.> <A>dmin edit <U>ser        (Change user's access level, password, etc.)\n"
71             " <.> <A>dmin <V>alidate new users   (Process new user registrations)\n"
72             " <.> <A>dmin enter <I>nfo file      (Create/change this room's banner)\n"
73             " <.> <A>dmin <R>oom <I>nvite user   (Grant access to an invitation-only room)\n"
74             " <.> <A>dmin <R>oom <K>ick out user (Revoke access to an invitation-only room)\n"
75             " <.> <A>dmin <F>ile <D>elete        (Delete a file from the room's directory)\n"
76             " <.> <A>dmin <F>ile <S>end over net (Transmit a file to another node)\n"
77             " <.> <A>dmin <F>ile <M>ove          (Move a file to another room's directory)\n"
78             " <.> <A>dmin <M>essage edit:        (Edit system banners)\n"
79             " <.> <A>dmin <P>ost                 (Post a message on behalf of another user)\n"
80             " <.> <A>dmin <S>ystem configuration <G>eneral   (Edit global site config)\n"
81             " <.> <A>dmin <S>ystem configuration <I>nternet  (Edit Internet domains)\n"
82             " <.> <A>dmin <S>ystem configuration check <M>essage base   (Internal checks)\n"
83             " <.> <A>dmin <S>ystem configuration <N>etwork   (Netting with other Citadels)\n"
84             " <.> <A>dmin <S>ystem configuration network <F>ilter list\n"
85             " <.> <A>dmin <T>erminate server <N>ow          (Shut down Citadel server now)\n"
86             " <.> <A>dmin <T>erminate server <S>cheduled    (Shut down Citadel server later)\n"
87             " <.> <A>dmin mailing <L>ist recipients         (For mailing list rooms)\n"
88             " <.> <A>dmin mailing list <D>igest recipients  (For mailing list rooms)\n"
89             " <.> <A>dmin <N>etwork room sharing     (Replication with other Citadels)\n"
90             " \n" " In addition, the <M>ove and <D>elete commands are available at the\n" "message prompt.\n",
91
92         " Floors\n"
93             " ------\n"
94             "   Floors in Citadel are used to group rooms into related subject areas,\n"
95             "just as rooms are used to group messages into manageable groups.\n"
96             " \n"
97             "   You, as a user, do NOT have to use floors.  If you choose not to, you suffer\n"
98             "no penalty; you will not lose access to any rooms.  You may use .EC or ;C (the\n"
99             "latter is easier to use) to decide if you want to use floors.  Feel free to\n"
100             "experiment.\n"
101             " \n"
102             "   Floor options are accessed two ways.  First, if you are in floor mode, the\n"
103             "<G>oto and <S>kip commands take you to the next room with new messages on the\n"
104             "current floor; if there are none left, then the system will automatically\n"
105             "switch floors (and let you know) and put you in the first room with new messages\n"
106             "on that level.  (Notice that your pattern of basic use of Citadel therefore\n"
107             "doesn't really change.)\n"
108             " \n"
109             "   Direct access to floor options is via the use of a ';' command.\n"
110             "The following commands are currently available (more can be\n"
111             "added if needed):\n"
112             " \n"
113             " <;C>onfigure\n"
114             " This command toggles your floor mode.\n"
115             " \n"
116             " <;G>oto FLOORNAME\n"
117             " This command causes the system to take you to the named floor.\n"
118             " \n"
119             " <;K>nown rooms on floors\n"
120             " List all rooms on all floors.  This is a very readable way to get a list of\n"
121             "all rooms on the system.\n"
122             " \n"
123             " <;S>kip FLOORNAME\n"
124             " This command causes the system to mark all rooms on the current floor as\n"
125             "Skipped and takes you to the floor that you specify.\n"
126             " \n"
127             " <;Z>Forget floor\n"
128             "   This command causes you to forget all the rooms currently on the current\n"
129             "floor.  Unfortunately, it doesn't apply to rooms that are subsequently created\n"
130             "or moved to this floor.  (Sorry.)\n"
131             " \n"
132             "   Feel free to experiment, you can't hurt yourself or the system with the\n"
133             "floor stuff unless you ZForget a floor by accident.\n",
134
135         "                  New User's Introduction to the site\n"
136             "  \n"
137             " This is an introduction to the Citadel BBS concept.  It is intended\n"
138             "for new users so that they can more easily become acquainted to using\n"
139             "Citadel when accessing it in the form of a text-based BBS.  Of\n"
140             "course, old users might learn something new each time they read\n"
141             "through it.\n"
142             " \n"
143             " Full help for the BBS commands can be obtained by typing <.H>elp SUMMARY\n"
144             "  \n"
145             " The CITADEL BBS room concept\n"
146             " ----------------------------\n"
147             "   The term BBS stands for 'Bulletin Board System'.  The analogy is\n"
148             "appropriate: one posts messages so that others may read them.  In\n"
149             "order to organize the posts, people can post in different areas of the\n"
150             "BBS, called rooms.\n"
151             "   In order to post in a certain room, you need to be 'in' that room.\n"
152             "Your current prompt is usually the room that you are in, followed the\n"
153             "greater-than-sign, such as:\n"
154             " \n"
155             " Lobby>\n"
156             " \n"
157             " The easiest way to traverse the room structure is with the 'Goto'\n"
158             "command, on the 'G' key.  Pressing 'G' will take you to the next room\n"
159             "in the 'march list' (see below) that has new messages in it.  You can\n"
160             "read these new messages with the 'N' key.\n"
161             " Once you've 'Gotoed' every room in the system (or all of the ones\n"
162             "you choose to read) you return to the 'Lobby,' the first and last room\n"
163             "in the system.  If new messages get posted to rooms you've already\n"
164             "read during your session you will be brought BACK to those rooms so\n"
165             "you can read them.\n"
166             " \n"
167             " March List\n"
168             " ----------\n"
169             "   All the room names are stored in a march list, which is just a\n"
170             "list containing all the room names.  When you <G>oto or <S>kip a\n"
171             "room, you are placed in the next room in your march list THAT HAS NEW\n"
172             "MESSAGES.  If you have no new messages in any of the rooms on your\n"
173             "march list, you will keep going to the Lobby>.  You can choose not to\n"
174             "read certain rooms (that don't interest you) by 'Z'apping them.  When\n"
175             "you <Z>ap a room, you are merely deleting it from your march list (but\n"
176             "not from anybody else's).\n"
177             " \n"
178             "   You can use the <.G>oto (note the period before the G.  You can also use\n"
179             "<J>ump on some systems) to go to any room in the\n"
180             "system.  You don't have to type in the complete name of a room to\n"
181             "'jump' to it; you merely need to type in enough to distinguish it from\n"
182             "the other rooms.  Left-aligned matches carry a heavier weight, so if you\n"
183             "typed (for example) '.Goto TECH', you might be taken to a room called\n"
184             "'Tech Area>' even if it found a room called 'Biotech/Ethics>' first.\n"
185             " \n"
186             "  To return to a room you have previously <Z>apped, use the <.G>oto command\n"
187             "to enter it, and it will be re-inserted into your march list.  In the case\n"
188             "of returning to Zapped rooms, you must type the room name in its entirety.\n"
189             "REMEMBER, rooms with no new messages will not show on your\n"
190             "march list!  You must <.G>oto to a room with no new messages.\n"
191             "Incidentally, you cannot change the order of the rooms on your march list.\n"
192             "It's the same for everybody.\n"
193             " \n"
194             " Special rooms\n"
195             " -------------\n"
196             "   There are two special rooms on a Citadel that you should know about.\n"
197             "  \n"
198             "   The first is the Lobby>.  It's used for system announcements and other\n"
199             "such administrativia.  You cannot <Z>ap the Lobby>.  Each time you first\n"
200             "login, you will be placed in the Lobby>.\n"
201             " \n"
202             "   The second is Mail>.  In Mail>, when you post a messages, you are\n"
203             "prompted to enter the screen name of the person who you want to send the\n"
204             "message to.  Only the person who you send the message to can read the\n"
205             "message.  NO ONE else can read it, not even the admins.  Mail> is the\n"
206             "first room on the march list, and is un-<Z>appable, so you can be sure\n"
207             "that the person will get the message.\n"
208             "   \n"
209             " System admins\n"
210             " -------------\n"
211             "   These people, along with the room admins, keep the site running smoothly.\n"
212             "\n"
213             "   Among the many things that admins do are: create rooms, delete\n"
214             "rooms, set access levels, invite users, check registration, grant\n"
215             "room admin status, and countless other things.  They have access to the\n"
216             "Aide> room, a special room only for admins.\n"
217             " \n"
218             "   If you enter a mail message to 'Sysop' it will be placed in the\n"
219             "Aide> room so that the next admin online will read it and deal with it.\n"
220             "Admins cannot <Z>ap rooms.  All the rooms are always on each admin's\n"
221             "march list.  Admins can read *any* and *every* room, but they *CAN* *NOT*\n"
222             "read other users' Mail!\n"
223             "  \n"
224             " Room admins\n"
225             " -----------\n"
226             "   Room admins are granted special privileges in specific rooms.\n"
227             "They are *NOT* true system admins; their power extends only over the\n"
228             "rooms that they control, and they answer to the system admins.\n"
229             "  \n"
230             "   A room admin's job is to keep the topic of the their room on track,\n"
231             "with nudges in the right direction now and then.  A room admin can also\n"
232             "move an off topic post to another room, or delete a post, if he/she\n"
233             "feels it is necessary. \n"
234             "  \n"
235             "   Currently, very few rooms have room admins.  Most rooms do not need\n"
236             "their own specific room admin.  Being a room admin requires a certain\n"
237             "amount of trust, due to the additional privileges granted.\n"
238             "  \n"
239             " Citadel messages\n"
240             " ----------------\n"
241             "   Most of the time, the BBS code does not print a lot of messages\n"
242             "to your screen.  This is a great benefit once you become familiar\n"
243             "with the system, because you do not have endless menus and screens\n"
244             "to navigate through.  nevertheless, there are some messages which you\n"
245             "might see from time to time.\n"
246             "  \n"
247             "  'There were messages posted while you were entering.'\n"
248             "  \n"
249             "   This is also known as 'simulposting.'  When you start entering a \n"
250             "message, the system knows where you last left off.  When you save\n"
251             "your message, the system checks to see if any messages were entered\n"
252             "while you were typing.  This is so that you know whether you need\n"
253             "to go back and re-read the last few messages.  This message may appear\n"
254             "in any room.\n"
255             "   \n"
256             " '*** You have new mail'\n"
257             "  \n"
258             "   This message is essentially the same as the above message, but can\n"
259             "appear at any time.  It simply means that new mail has arrived for you while\n"
260             "you are logged in.  Simply go to the Mail> room to read it.\n"
261             "  \n"
262             " Who list\n"
263             " --------\n"
264             "   The <W>ho command shows you the names of all users who are currently\n"
265             "online.  It also shows you the name of the room they are currently in.  If\n"
266             "they are in any type of private room, however, the room name will simply\n"
267             "display as '<private room>'.  Along with this information is displayed the\n"
268             "name of the host computer the user is logged in from.\n",
269
270         "To send mail on this system, go to the Mail> room (using the command .G Mail)\n"
271             "and press E to enter a message.  You will be prompted with:\n"
272             " \n"
273             " Enter Recipient:\n"
274             " \n"
275             "   At this point you may enter the name of another user on the system.  Private\n"
276             "mail is only readable by the sender and recipient.  There is no need to delete\n"
277             "mail after it is read; it will scroll out automatically.\n"
278             "  \n"
279             "   To send mail to another user on the Citadel network, simply type the\n"
280             "user's name, followed by @ and then the system name. For example,\n"
281             "  \n"
282             " Enter Recipient: Joe Schmoe @ citadrool\n"
283             "  \n"
284             "  If your account is enabled for Internet mail, you can also send email to\n"
285             "anyone on the Internet here.  Simply enter their address at the prompt:\n"
286             "  \n" " Enter Recipient: ajc@herring.fishnet.com\n",
287
288         "  Welcome to the network. Messages entered in a network room will appear in\n"
289             "that room on all other systems carrying it (The name of the room, however,\n" "may be different on other systems).\n",
290
291         "   Citadel is the premier 'online community' (i.e. Bulletin Board System)\n"
292             "software.  It runs on all POSIX-compliant systems, including Linux.  It is an\n"
293             "advanced client/server application, and is being actively maintained.\n"
294             " \n" "   For more info, visit UNCENSORED! BBS at uncensored.citadel.org\n",
295
296         "Extended commands are available using the period ( . ) key. To use\n"
297             "a dot command, press the . key, and then enter the first letter of\n"
298             "each word in the command. The words will appear as you enter the keys.\n"
299             "You can also backspace over partially entered commands. The following\n"
300             "commands are available:\n"
301             "\n"
302             " <.> <H>elp:    Displays help files.  Type .H followed by a help file\n"
303             "                name.  You are now reading <.H>elp SUMMARY\n"
304             " \n"
305             " <.> <G>oto:    Jumps directly to the room you specify.  You can also\n"
306             "                type a partial room name, just enough to make it unique,\n"
307             "                and it'll find the room you're looking for.  As with the\n"
308             "                regular <G>oto command, messages in the current room will\n"
309             "                be marked as read.\n"
310             " \n"
311             " <.> <S>kip, goto:    This is similar to <.G>oto, except it doesn't mark\n"
312             "                      messages in the current room as read.\n"
313             " \n"
314             " <.> list <Z>apped rooms      Shows all rooms you've <Z>apped (forgotten)\n"
315             "\n"
316             "  \n"
317             " Terminate (logoff) commands:\n"
318             " \n"
319             " <.> <T>erminate and <Q>uit               Log off and disconnect.\n"
320             " <.> <T>erminate and <S>tay online        Log in as a different user.\n"
321             " \n"
322             " \n"
323             " Read commands:\n"
324             "\n"
325             " <.> <R>ead <N>ew messages                Same as <N>ew\n"
326             " <.> <R>ead <O>ld msgs reverse            Same as <O>ld\n"
327             " <.> <R>ead <L>ast five msgs              Same as <L>ast5\n"
328             " <.> read <L>ast:                         Allows you to specify how many\n"
329             "                                          messages you wish to read.\n"
330             "\n"
331             " <.> <R>ead <U>ser listing:               Lists all users on the system if\n"
332             "                                          you just hit enter, otherwise\n"
333             "                                          you can specify a partial match\n"
334             "\n"
335             " <.> <R>ead <T>extfile formatted          File 'download' commands.\n"
336             " <.> <R>ead file using <X>modem   \n"
337             " <.> <R>ead file using <Y>modem   \n"
338             " <.> <R>ead file using <Z>modem   \n"
339             " <.> <R>ead <F>ile unformatted   \n"
340             " <.> <R>ead <D>irectory   \n"
341             "\n"
342             " <.> <R>ead <I>nfo file                   Read the room info file.\n"
343             " <.> <R>ead <B>io                         Read other users' 'bio' files.\n"
344             " <.> <R>ead <C>onfiguration               Display your 'preferences'.\n"
345             " <.> <R>ead <S>ystem info                 Display system statistics.\n"
346             "\n"
347             " \n"
348             " Enter commands:\n"
349             "\n"
350             " <.> <E>nter <M>essage                    Post a message in this room.\n"
351             " <.> <E>nter message with <E>ditor        Post using a full-screen editor.\n"
352             " <.> <E>nter <A>SCII message              Post 'raw' (use this when 'pasting'\n"
353             "                                          a message from your clipboard).\n"
354             "\n"
355             " <.> <E>nter <P>assword                   Change your password.\n"
356             " <.> <E>nter <C>onfiguration              Change your 'preferences'.\n"
357             " <.> <E>nter a new <R>oom                 Create a new room.\n"
358             " <.> <E>nter re<G>istration               Register (name, address, etc.)\n"
359             " <.> <E>nter <B>io                        Enter/change your 'bio' file.\n"
360             "\n"
361             " <.> <E>nter <T>extfile                   File 'upload' commands.\n"
362             " <.> <E>nter file using <X>modem   \n"
363             " <.> <E>nter file using <Y>modem   \n"
364             " <.> <E>nter file using <Z>modem   \n"
365             "  \n"
366             "  \n"
367             "  Wholist commands:\n"
368             " \n"
369             " <.> <W>holist <L>ong             Same as <W>ho is online, but displays\n"
370             "                                  more detailed information.\n"
371             " <.> <W>holist <R>oomname         Masquerade your room name (other users\n"
372             "                                  see the name you enter rather than the\n"
373             "                                  actual name of the room you're in)\n"
374             " <.> <W>holist <H>ostname         Masquerade your host name\n"
375             " <.> <E>nter <U>sername           Masquerade your user name (Admins only)\n"
376             " <.> <W>holist <S>tealth mode     Enter/exit 'stealth mode' (when in stealth\n"
377             "                                  mode you are invisible on the wholist)\n"
378             " \n"
379             " \n"
380             " Floor commands (if using floor mode)\n"
381             " ;<C>onfigure floor mode            - turn floor mode on or off\n"
382             " ;<G>oto floor:                     - jump to a specific floor\n"
383             " ;<K>nown rooms                     - list all rooms on all floors\n"
384             " ;<S>kip to floor:                  - skip current floor, jump to another\n"
385             " ;<Z>ap floor                       - zap (forget) all rooms on this floor\n"
386             " \n"
387             " \n"
388             " Administrative commands: \n"
389             " \n"
390             " <.> <A>dmin <K>ill this room   \n"
391             " <.> <A>dmin <E>dit this room   \n"
392             " <.> <A>dmin <W>ho knows room   \n"
393             " <.> <A>dmin edit <U>ser   \n"
394             " <.> <A>dmin <V>alidate new users   \n"
395             " <.> <A>dmin enter <I>nfo file   \n"
396             " <.> <A>dmin <R>oom <I>nvite user  \n"
397             " <.> <A>dmin <R>oom <K>ick out user  \n"
398             " <.> <A>dmin <F>ile <D>elete  \n"
399             " <.> <A>dmin <F>ile <S>end over net  \n"
400             " <.> <A>dmin <F>ile <M>ove  \n"
401             " <.> <A>dmin <M>essage edit:   \n"
402             " <.> <A>dmin <P>ost   \n"
403             " <.> <A>dmin <S>ystem configuration   \n"
404             " <.> <A>dmin <T>erminate server <N>ow\n" " <.> <A>dmin <T>erminate server <S>cheduled\n"
405 };
406
407
408 struct citcmd {
409         struct citcmd *next;
410         int c_cmdnum;
411         int c_axlevel;
412         char c_keys[5][64];
413 };
414
415 #define IFNEXPERT if ((userflags&US_EXPERT)==0)
416
417
418 int rc_exp_beep;
419 char rc_exp_cmd[1024];
420 int rc_allow_attachments;
421 int rc_display_message_numbers;
422 int rc_force_mail_prompts;
423 int rc_remember_passwords;
424 int rc_ansi_color;
425 int rc_color_use_bg;
426 int rc_prompt_control = 0;
427 time_t rc_idle_threshold = (time_t) 900;
428 char rc_url_cmd[SIZ];
429 char rc_open_cmd[SIZ];
430 char rc_gotmail_cmd[SIZ];
431
432 int next_lazy_cmd = 5;
433
434 extern int screenwidth, screenheight;
435 extern int termn8;
436 extern CtdlIPC *ipc_for_signal_handlers;        /* KLUDGE cover your eyes */
437 struct citcmd *cmdlist = NULL;
438
439
440 /* these variables are local to this module */
441 char keepalives_enabled = KA_YES;       /* send NOOPs to server when idle */
442 int ok_to_interrupt = 0;        /* print instant msgs asynchronously */
443 time_t AnsiDetect;              /* when did we send the detect code? */
444 int enable_color = 0;           /* nonzero for ANSI color */
445
446
447 /*
448  * If an interesting key has been pressed, return its value, otherwise 0
449  */
450 char was_a_key_pressed(void) {
451         fd_set rfds;
452         struct timeval tv;
453         int the_character;
454         int retval;
455
456         FD_ZERO(&rfds);
457         FD_SET(0, &rfds);
458         tv.tv_sec = 0;
459         tv.tv_usec = 0;
460         retval = select(1, &rfds, NULL, NULL, &tv);
461
462         /* Careful!  Disable keepalives during keyboard polling; we're probably
463          * in the middle of a data transfer from the server, in which case
464          * sending a NOOP would throw the client protocol out of sync.
465          */
466         if ((retval > 0) && FD_ISSET(0, &rfds)) {
467                 set_keepalives(KA_NO);
468                 the_character = inkey();
469                 set_keepalives(KA_YES);
470         }
471         else {
472                 the_character = 0;
473         }
474         return (the_character);
475 }
476
477
478 /*
479  * print_instant()  -  print instant messages if there are any
480  */
481 void print_instant(void) {
482         char buf[1024];
483         FILE *outpipe;
484         time_t timestamp;
485         struct tm stamp;
486         int flags = 0;
487         char sender[64];
488         char node[64];
489         char *listing = NULL;
490         int r;                  /* IPC result code */
491
492         if (instant_msgs == 0) {
493                 return;
494         }
495
496         if (rc_exp_beep) {
497                 ctdl_beep();
498         }
499
500         if (IsEmptyStr(rc_exp_cmd)) {
501                 color(BRIGHT_RED);
502                 scr_printf("\r---");
503         }
504
505         while (instant_msgs != 0) {
506                 r = CtdlIPCGetInstantMessage(ipc_for_signal_handlers, &listing, buf);
507                 if (r / 100 != 1) {
508                         return;
509                 }
510
511                 instant_msgs = extract_int(buf, 0);
512                 timestamp = extract_long(buf, 1);
513                 flags = extract_int(buf, 2);
514                 extract_token(sender, buf, 3, '|', sizeof sender);
515                 extract_token(node, buf, 4, '|', sizeof node);
516                 strcpy(last_paged, sender);
517
518                 localtime_r(&timestamp, &stamp);
519
520                 /* If the page is a Logoff Request, honor it. */
521                 if (flags & 2) {
522                         termn8 = 1;
523                         return;
524                 }
525
526                 if (!IsEmptyStr(rc_exp_cmd)) {
527                         outpipe = popen(rc_exp_cmd, "w");
528                         if (outpipe != NULL) {
529                                 /* Header derived from flags */
530                                 if (flags & 2)
531                                         fprintf(outpipe, "Please log off now, as requested ");
532                                 else if (flags & 1)
533                                         fprintf(outpipe, "Broadcast message ");
534                                 else if (flags & 4)
535                                         fprintf(outpipe, "Chat request ");
536                                 else
537                                         fprintf(outpipe, "Message ");
538                                 /* Timestamp.  Can this be improved? */
539                                 if (stamp.tm_hour == 0 || stamp.tm_hour == 12)
540                                         fprintf(outpipe, "at 12:%02d%cm", stamp.tm_min, stamp.tm_hour ? 'p' : 'a');
541                                 else if (stamp.tm_hour > 12)    /* pm */
542                                         fprintf(outpipe, "at %d:%02dpm", stamp.tm_hour - 12, stamp.tm_min);
543                                 else    /* am */
544                                         fprintf(outpipe, "at %d:%02dam", stamp.tm_hour, stamp.tm_min);
545                                 fprintf(outpipe, " from %s", sender);
546                                 if (strncmp(ipc_for_signal_handlers->ServInfo.nodename, node, 32))
547                                         fprintf(outpipe, " @%s", node);
548                                 fprintf(outpipe, ":\n%s\n", listing);
549                                 pclose(outpipe);
550                                 if (instant_msgs == 0)
551                                         return;
552                                 continue;
553                         }
554                 }
555                 /* fall back to built-in instant message display */
556                 scr_printf("\n");
557
558                 /* Header derived from flags */
559                 if (flags & 2)
560                         scr_printf("Please log off now, as requested ");
561                 else if (flags & 1)
562                         scr_printf("Broadcast message ");
563                 else if (flags & 4)
564                         scr_printf("Chat request ");
565                 else
566                         scr_printf("Message ");
567
568                 /* Timestamp.  Can this be improved? */
569                 if (stamp.tm_hour == 0 || stamp.tm_hour == 12)  /* 12am/12pm */
570                         scr_printf("at 12:%02d%cm", stamp.tm_min, stamp.tm_hour ? 'p' : 'a');
571                 else if (stamp.tm_hour > 12)    /* pm */
572                         scr_printf("at %d:%02dpm", stamp.tm_hour - 12, stamp.tm_min);
573                 else            /* am */
574                         scr_printf("at %d:%02dam", stamp.tm_hour, stamp.tm_min);
575
576                 /* Sender */
577                 scr_printf(" from %s", sender);
578
579                 /* Remote node, if any */
580                 if (strncmp(ipc_for_signal_handlers->ServInfo.nodename, node, 32))
581                         scr_printf(" @%s", node);
582
583                 scr_printf(":\n");
584                 fmout(screenwidth, NULL, listing, NULL, 0);
585                 free(listing);
586
587         }
588         scr_printf("\n---\n");
589         color(BRIGHT_WHITE);
590
591
592 }
593
594
595 void set_keepalives(int s) {
596         keepalives_enabled = (char) s;
597 }
598
599
600 /* 
601  * This loop handles the "keepalive" messages sent to the server when idling.
602  */
603 static time_t idlet = 0;
604 static void really_do_keepalive(void) {
605         time(&idlet);
606
607         /* This may sometimes get called before we are actually connected
608          * to the server.  Don't do anything if we aren't connected. -IO
609          */
610         if (!ipc_for_signal_handlers)
611                 return;
612
613         /* If full keepalives are enabled, send a NOOP to the server and
614          * wait for a response.
615          */
616         if (keepalives_enabled == KA_YES) {
617                 CtdlIPCNoop(ipc_for_signal_handlers);
618                 if (instant_msgs > 0) {
619                         if (ok_to_interrupt == 1) {
620                                 scr_printf("\r%64s\r", "");
621                                 print_instant();
622                                 scr_printf("%s%c ", room_name, room_prompt(room_flags));
623                                 scr_flush();
624                         }
625                 }
626         }
627
628         /* If half keepalives are enabled, send a QNOP to the server (if the
629          * server supports it) and then do nothing.
630          */
631         if ((keepalives_enabled == KA_HALF)
632             && (ipc_for_signal_handlers->ServInfo.supports_qnop > 0)) {
633                 CtdlIPC_chat_send(ipc_for_signal_handlers, "QNOP");
634         }
635 }
636
637
638 /* I changed this from static to not because I need to call it from
639  * screen.c, either that or make something in screen.c not static.
640  * Fix it how you like. Why all the staticness? stu
641  */
642 void do_keepalive(void)
643 {
644         time_t now;
645
646         time(&now);
647         if ((now - idlet) < ((long) S_KEEPALIVE)) {
648                 return;
649         }
650
651         /* Do a space-backspace to keep terminal sessions from idling out */
652         scr_printf(" %c", 8);
653         scr_flush();
654
655         really_do_keepalive();
656 }
657
658
659 int inkey(void)
660 {                               /* get a character from the keyboard, with   */
661         int a;                  /* the watchdog timer in effect if necessary */
662         fd_set rfds;
663         struct timeval tv;
664         time_t start_time;
665
666         scr_flush();
667         time(&start_time);
668
669         do {
670                 /* This loop waits for keyboard input.  If the keepalive
671                  * timer expires, it sends a keepalive to the server if
672                  * necessary and then waits again.
673                  */
674                 do {
675                         do_keepalive();
676
677                         FD_ZERO(&rfds);
678                         FD_SET(0, &rfds);
679                         tv.tv_sec = S_KEEPALIVE;
680                         tv.tv_usec = 0;
681
682                         select(1, &rfds, NULL, NULL, &tv);
683                 } while (!FD_ISSET(0, &rfds));
684
685                 /* At this point, there's input, so fetch it.
686                  * (There's a hole in the bucket...)
687                  */
688                 a = scr_getc(SCR_BLOCK);
689                 if (a == 127) {
690                         a = 8;
691                 }
692                 if (a == 13) {
693                         a = 10;
694                 }
695         } while (a == 0);
696         return (a);
697 }
698
699
700 int yesno(void)
701 {                               /* Returns 1 for yes, 0 for no */
702         int a;
703         while (1) {
704                 a = inkey();
705                 a = tolower(a);
706                 if (a == 'y') {
707                         scr_printf("Yes\n");
708                         return (1);
709                 }
710                 if (a == 'n') {
711                         scr_printf("No\n");
712                         return (0);
713                 }
714         }
715 }
716
717 /* Returns 1 for yes, 0 for no, arg is default value */
718 int yesno_d(int d)
719 {
720         int a;
721         while (1) {
722                 a = inkey();
723                 a = tolower(a);
724                 if (a == 10)
725                         a = (d ? 'y' : 'n');
726                 if (a == 'y') {
727                         scr_printf("Yes\n");
728                         return (1);
729                 }
730                 if (a == 'n') {
731                         scr_printf("No\n");
732                         return (0);
733                 }
734         }
735 }
736
737
738
739
740 /*
741  * Function to read a line of text from the terminal.
742  *
743  * string               Pointer to string buffer
744  * lim                  Maximum length
745  * noshow               Echo asterisks instead of keystrokes?
746  * bs                   Allow backspacing out of the prompt? (returns -1 if this happens)
747  *
748  * returns: string length
749  */
750 int ctdl_getline(char *string, int lim, int noshow, int bs)
751 {
752         int pos = strlen(string);
753         int ch;
754
755         if (noshow && !IsEmptyStr(string)) {
756                 int num_stars = strlen(string);
757                 while (num_stars--) {
758                         scr_putc('*');
759                 }
760         } else {
761                 scr_printf("%s", string);
762         }
763
764         while (1) {
765                 ch = inkey();
766
767                 if ((ch == 8) && (pos > 0)) {   /* backspace */
768                         --pos;
769                         scr_putc(8);
770                         scr_putc(32);
771                         scr_putc(8);
772                 }
773
774                 else if ((ch == 8) && (pos == 0) && (bs)) {     /* backspace out of the prompt */
775                         return (-1);
776                 }
777
778                 else if ((ch == 23) && (pos > 0)) {     /* Ctrl-W deletes a word */
779                         while ((pos > 0) && !isspace(string[pos])) {
780                                 --pos;
781                                 scr_putc(8);
782                                 scr_putc(32);
783                                 scr_putc(8);
784                         }
785                         while ((pos > 0) && !isspace(string[pos - 1])) {
786                                 --pos;
787                                 scr_putc(8);
788                                 scr_putc(32);
789                                 scr_putc(8);
790                         }
791                 }
792
793                 else if (ch == 10) {    /* return */
794                         string[pos] = 0;
795                         scr_printf("\n");
796                         return (pos);
797                 }
798
799                 else if (isprint(ch)) { /* payload characters */
800                         scr_putc((noshow ? '*' : ch));
801                         string[pos] = ch;
802                         ++pos;
803                 }
804         }
805 }
806
807
808 /* 
809  * newprompt()          prompt for a string, print the existing value, and
810  *                      allow the user to press return to keep it...
811  *                      If len is negative, pass the "noshow" flag to ctdl_getline()
812  */
813 void strprompt(char *prompt, char *str, int len)
814 {
815         print_instant();
816         color(DIM_WHITE);
817         scr_printf("%s", prompt);
818         color(DIM_WHITE);
819         scr_printf(": ");
820         color(BRIGHT_CYAN);
821         ctdl_getline(str, abs(len), (len < 0), 0);
822         color(DIM_WHITE);
823 }
824
825 /*
826  * boolprompt()  -  prompt for a yes/no, print the existing value and
827  *                  allow the user to press return to keep it...
828  */
829 int boolprompt(char *prompt, int prev_val)
830 {
831         int r;
832
833         color(DIM_WHITE);
834         scr_printf("%s ", prompt);
835         color(DIM_MAGENTA);
836         scr_printf("[");
837         color(BRIGHT_MAGENTA);
838         scr_printf("%s", (prev_val ? "Yes" : "No"));
839         color(DIM_MAGENTA);
840         scr_printf("]: ");
841         color(BRIGHT_CYAN);
842         r = (yesno_d(prev_val));
843         color(DIM_WHITE);
844         return r;
845 }
846
847 /* 
848  * intprompt()  -  like strprompt(), except for an integer
849  *                 (note that it RETURNS the new value!)
850  */
851 int intprompt(char *prompt, int ival, int imin, int imax)
852 {
853         char buf[16];
854         int i;
855         int p;
856
857         do {
858                 i = ival;
859                 snprintf(buf, sizeof buf, "%d", i);
860                 strprompt(prompt, buf, 15);
861                 i = atoi(buf);
862                 for (p = 0; !IsEmptyStr(&buf[p]); ++p) {
863                         if ((!isdigit(buf[p]))
864                             && ((buf[p] != '-') || (p != 0)))
865                                 i = imin - 1;
866                 }
867                 if (i < imin)
868                         scr_printf("*** Must be no less than %d.\n", imin);
869                 if (i > imax)
870                         scr_printf("*** Must be no more than %d.\n", imax);
871         } while ((i < imin) || (i > imax));
872         return (i);
873 }
874
875 /* 
876  * newprompt()          prompt for a string with no existing value
877  *                      (clears out string buffer first)
878  *                      If len is negative, pass the "noshow" flag to ctdl_getline()
879  */
880 void newprompt(char *prompt, char *str, int len)
881 {
882         str[0] = 0;
883         color(BRIGHT_MAGENTA);
884         scr_printf("%s", prompt);
885         color(DIM_MAGENTA);
886         ctdl_getline(str, abs(len), (len < 0), 0);
887         color(DIM_WHITE);
888 }
889
890
891 int lkey(void)
892 {                               /* returns a lower case value */
893         int a;
894         a = inkey();
895         if (isupper(a))
896                 a = tolower(a);
897         return (a);
898 }
899
900 /*
901  * parse the citadel.rc file
902  */
903 void load_command_set(void)
904 {
905         FILE *ccfile;
906         char buf[1024];
907         struct citcmd *cptr;
908         struct citcmd *lastcmd = NULL;
909         int a, d;
910         int b = 0;
911
912         /* first, set up some defaults for non-required variables */
913
914         strcpy(editor_path, "");
915         strcpy(printcmd, "");
916         strcpy(imagecmd, "");
917         strcpy(rc_username, "");
918         strcpy(rc_password, "");
919         rc_floor_mode = 0;
920         rc_exp_beep = 1;
921         rc_allow_attachments = 0;
922         rc_remember_passwords = 0;
923         strcpy(rc_exp_cmd, "");
924         rc_display_message_numbers = 0;
925         rc_force_mail_prompts = 0;
926         rc_ansi_color = 0;
927         rc_color_use_bg = 0;
928         strcpy(rc_url_cmd, "");
929         strcpy(rc_open_cmd, "");
930         strcpy(rc_gotmail_cmd, "");
931 #ifdef HAVE_OPENSSL
932         rc_encrypt = RC_DEFAULT;
933 #endif
934
935         /* now try to open the citadel.rc file */
936
937         ccfile = NULL;
938         if (getenv("HOME") != NULL) {
939                 snprintf(buf, sizeof buf, "%s/.citadelrc", getenv("HOME"));
940                 ccfile = fopen(buf, "r");
941         }
942         if (ccfile == NULL) {
943                 ccfile = fopen(file_citadel_rc, "r");
944         }
945         if (ccfile == NULL) {
946                 ccfile = fopen("/etc/citadel.rc", "r");
947         }
948         if (ccfile == NULL) {
949                 ccfile = fopen("./citadel.rc", "r");
950         }
951         if (ccfile == NULL) {
952                 perror("commands: cannot open citadel.rc");
953                 logoff(NULL, 3);
954         }
955         while (fgets(buf, sizeof buf, ccfile) != NULL) {
956                 while ((!IsEmptyStr(buf)) ? (isspace(buf[strlen(buf) - 1])) : 0)
957                         buf[strlen(buf) - 1] = 0;
958
959                 if (!strncasecmp(buf, "encrypt=", 8)) {
960                         if (!strcasecmp(&buf[8], "yes")) {
961 #ifdef HAVE_OPENSSL
962                                 rc_encrypt = RC_YES;
963 #else
964                                 fprintf(stderr, "citadel.rc requires encryption support but citadel is not compiled with OpenSSL");
965                                 logoff(NULL, 3);
966 #endif
967                         }
968 #ifdef HAVE_OPENSSL
969                         else if (!strcasecmp(&buf[8], "no")) {
970                                 rc_encrypt = RC_NO;
971                         } else if (!strcasecmp(&buf[8], "default")) {
972                                 rc_encrypt = RC_DEFAULT;
973                         }
974 #endif
975                 }
976
977                 if (!strncasecmp(buf, "editor=", 7)) {
978                         strcpy(editor_path, &buf[7]);
979                 }
980
981                 if (!strncasecmp(buf, "printcmd=", 9))
982                         strcpy(printcmd, &buf[9]);
983
984                 if (!strncasecmp(buf, "imagecmd=", 9))
985                         strcpy(imagecmd, &buf[9]);
986
987                 if (!strncasecmp(buf, "expcmd=", 7))
988                         strcpy(rc_exp_cmd, &buf[7]);
989
990                 if (!strncasecmp(buf, "use_floors=", 11)) {
991                         if (!strcasecmp(&buf[11], "yes"))
992                                 rc_floor_mode = RC_YES;
993                         if (!strcasecmp(&buf[11], "no"))
994                                 rc_floor_mode = RC_NO;
995                         if (!strcasecmp(&buf[11], "default"))
996                                 rc_floor_mode = RC_DEFAULT;
997                 }
998                 if (!strncasecmp(buf, "beep=", 5)) {
999                         rc_exp_beep = atoi(&buf[5]);
1000                 }
1001                 if (!strncasecmp(buf, "allow_attachments=", 18)) {
1002                         rc_allow_attachments = atoi(&buf[18]);
1003                 }
1004                 if (!strncasecmp(buf, "idle_threshold=", 15)) {
1005                         rc_idle_threshold = atol(&buf[15]);
1006                 }
1007                 if (!strncasecmp(buf, "remember_passwords=", 19)) {
1008                         rc_remember_passwords = atoi(&buf[19]);
1009                 }
1010                 if (!strncasecmp(buf, "display_message_numbers=", 24)) {
1011                         rc_display_message_numbers = atoi(&buf[24]);
1012                 }
1013                 if (!strncasecmp(buf, "force_mail_prompts=", 19)) {
1014                         rc_force_mail_prompts = atoi(&buf[19]);
1015                 }
1016                 if (!strncasecmp(buf, "ansi_color=", 11)) {
1017                         if (!strncasecmp(&buf[11], "on", 2))
1018                                 rc_ansi_color = 1;
1019                         if (!strncasecmp(&buf[11], "auto", 4))
1020                                 rc_ansi_color = 2;      /* autodetect */
1021                         if (!strncasecmp(&buf[11], "user", 4))
1022                                 rc_ansi_color = 3;      /* user config */
1023                 }
1024                 if (!strncasecmp(buf, "status_line=", 12)) {
1025                         if (!strncasecmp(&buf[12], "on", 2))
1026                                 enable_status_line = 1;
1027                 }
1028                 if (!strncasecmp(buf, "use_background=", 15)) {
1029                         if (!strncasecmp(&buf[15], "on", 2))
1030                                 rc_color_use_bg = 9;
1031                 }
1032                 if (!strncasecmp(buf, "prompt_control=", 15)) {
1033                         if (!strncasecmp(&buf[15], "on", 2))
1034                                 rc_prompt_control = 1;
1035                         if (!strncasecmp(&buf[15], "user", 4))
1036                                 rc_prompt_control = 3;  /* user config */
1037                 }
1038                 if (!strncasecmp(buf, "username=", 9))
1039                         strcpy(rc_username, &buf[9]);
1040
1041                 if (!strncasecmp(buf, "password=", 9))
1042                         strcpy(rc_password, &buf[9]);
1043
1044                 if (!strncasecmp(buf, "urlcmd=", 7))
1045                         strcpy(rc_url_cmd, &buf[7]);
1046
1047                 if (!strncasecmp(buf, "opencmd=", 7))
1048                         strcpy(rc_open_cmd, &buf[8]);
1049
1050                 if (!strncasecmp(buf, "gotmailcmd=", 11))
1051                         strcpy(rc_gotmail_cmd, &buf[11]);
1052
1053                 if (!strncasecmp(buf, "cmd=", 4)) {
1054                         strcpy(buf, &buf[4]);
1055
1056                         cptr = (struct citcmd *) malloc(sizeof(struct citcmd));
1057
1058                         cptr->c_cmdnum = atoi(buf);
1059                         for (d = strlen(buf); d >= 0; --d)
1060                                 if (buf[d] == ',')
1061                                         b = d;
1062                         strcpy(buf, &buf[b + 1]);
1063
1064                         cptr->c_axlevel = atoi(buf);
1065                         for (d = strlen(buf); d >= 0; --d)
1066                                 if (buf[d] == ',')
1067                                         b = d;
1068                         strcpy(buf, &buf[b + 1]);
1069
1070                         for (a = 0; a < 5; ++a)
1071                                 cptr->c_keys[a][0] = 0;
1072
1073                         a = 0;
1074                         b = 0;
1075                         buf[strlen(buf) + 1] = 0;
1076                         while (!IsEmptyStr(buf)) {
1077                                 b = strlen(buf);
1078                                 for (d = strlen(buf); d >= 0; --d)
1079                                         if (buf[d] == ',')
1080                                                 b = d;
1081                                 strncpy(cptr->c_keys[a], buf, b);
1082                                 cptr->c_keys[a][b] = 0;
1083                                 if (buf[b] == ',')
1084                                         strcpy(buf, &buf[b + 1]);
1085                                 else
1086                                         strcpy(buf, "");
1087                                 ++a;
1088                         }
1089
1090                         cptr->next = NULL;
1091                         if (cmdlist == NULL)
1092                                 cmdlist = cptr;
1093                         else
1094                                 lastcmd->next = cptr;
1095                         lastcmd = cptr;
1096                 }
1097         }
1098         fclose(ccfile);
1099 }
1100
1101
1102
1103 /*
1104  * return the key associated with a command
1105  */
1106 char keycmd(char *cmdstr)
1107 {
1108         int a;
1109
1110         for (a = 0; !IsEmptyStr(&cmdstr[a]); ++a)
1111                 if (cmdstr[a] == '&')
1112                         return (tolower(cmdstr[a + 1]));
1113         return (0);
1114 }
1115
1116
1117 /*
1118  * Output the string from a key command without the ampersand
1119  * "mode" should be set to 0 for normal or 1 for <C>ommand key highlighting
1120  */
1121 char *cmd_expand(char *strbuf, int mode)
1122 {
1123         int a;
1124         static char exp[64];
1125         char buf[1024];
1126
1127         strcpy(exp, strbuf);
1128
1129         for (a = 0; exp[a]; ++a) {
1130                 if (strbuf[a] == '&') {
1131
1132                         /* dont echo these non mnemonic command keys */
1133                         int noecho = strbuf[a + 1] == '<' || strbuf[a + 1] == '>' || strbuf[a + 1] == '+' || strbuf[a + 1] == '-';
1134
1135                         if (mode == 0) {
1136                                 strcpy(&exp[a], &exp[a + 1 + noecho]);
1137                         }
1138                         if (mode == 1) {
1139                                 exp[a] = '<';
1140                                 strcpy(buf, &exp[a + 2]);
1141                                 exp[a + 2] = '>';
1142                                 exp[a + 3] = 0;
1143                                 strcat(exp, buf);
1144                         }
1145                 }
1146                 if (!strncmp(&exp[a], "^r", 2)) {
1147                         strcpy(buf, exp);
1148                         strcpy(&exp[a], room_name);
1149                         strcat(exp, &buf[a + 2]);
1150                 }
1151                 if (!strncmp(&exp[a], "^c", 2)) {
1152                         exp[a] = ',';
1153                         strcpy(&exp[a + 1], &exp[a + 2]);
1154                 }
1155         }
1156
1157         return (exp);
1158 }
1159
1160
1161
1162 /*
1163  * Comparison function to determine if entered commands match a
1164  * command loaded from the config file.
1165  */
1166 int cmdmatch(char *cmdbuf, struct citcmd *cptr, int ncomp)
1167 {
1168         int a;
1169         int cmdax;
1170
1171         cmdax = 0;
1172         if (is_room_aide)
1173                 cmdax = 1;
1174         if (axlevel >= 6)
1175                 cmdax = 2;
1176
1177         for (a = 0; a < ncomp; ++a) {
1178                 if ((tolower(cmdbuf[a]) != keycmd(cptr->c_keys[a]))
1179                     || (cptr->c_axlevel > cmdax))
1180                         return (0);
1181         }
1182         return (1);
1183 }
1184
1185
1186 /*
1187  * This function returns 1 if a given command requires a string input
1188  */
1189 int requires_string(struct citcmd *cptr, int ncomp)
1190 {
1191         int a;
1192         char buf[64];
1193
1194         strcpy(buf, cptr->c_keys[ncomp - 1]);
1195         for (a = 0; !IsEmptyStr(&buf[a]); ++a) {
1196                 if (buf[a] == ':')
1197                         return (1);
1198         }
1199         return (0);
1200 }
1201
1202
1203 /*
1204  * Input a command at the main prompt.
1205  * This function returns an integer command number.  If the command prompts
1206  * for a string then it is placed in the supplied buffer.
1207  */
1208 int getcmd(CtdlIPC * ipc, char *argbuf)
1209 {
1210         char cmdbuf[5];
1211         int cmdspaces[5];
1212         int cmdpos;
1213         int ch;
1214         int a;
1215         int got;
1216         int this_lazy_cmd;
1217         struct citcmd *cptr;
1218
1219         /*
1220          * Starting a new command now, so set sigcaught to 0.  This variable
1221          * is set to nonzero (usually NEXT_KEY or STOP_KEY) if a command has
1222          * been interrupted by a keypress.
1223          */
1224         sigcaught = 0;
1225
1226         /* Switch color support on or off if we're in user mode */
1227         if (rc_ansi_color == 3) {
1228                 if (userflags & US_COLOR)
1229                         enable_color = 1;
1230                 else
1231                         enable_color = 0;
1232         }
1233         /* if we're running in idiot mode, display a cute little menu */
1234
1235         IFNEXPERT {
1236                 scr_printf("-----------------------------------------------------------------------\n");
1237                 scr_printf("Room cmds:    <K>nown rooms, <G>oto next room, <.G>oto a specific room,\n");
1238                 scr_printf("              <S>kip this room, <A>bandon this room, <Z>ap this room,\n");
1239                 scr_printf("              <U>ngoto (move back)\n");
1240                 scr_printf("Message cmds: <N>ew msgs, <F>orward read, <R>everse read, <O>ld msgs,\n");
1241                 scr_printf("              <L>ast five msgs, <E>nter a message\n");
1242                 scr_printf("General cmds: <?> help, <T>erminate, <C>hat, <W>ho is online\n");
1243                 scr_printf("Misc:         <X> toggle eXpert mode, <D>irectory\n");
1244                 scr_printf("\n");
1245                 scr_printf(" (Type .Help SUMMARY for extended commands, <X> to hide this menu)\n");
1246                 scr_printf("-----------------------------------------------------------------------\n");
1247         }
1248
1249         print_instant();
1250         strcpy(argbuf, "");
1251         cmdpos = 0;
1252         for (a = 0; a < 5; ++a)
1253                 cmdbuf[a] = 0;
1254         /* now the room prompt... */
1255         ok_to_interrupt = 1;
1256         color(BRIGHT_WHITE);
1257         scr_printf("\n%s", room_name);
1258         color(DIM_WHITE);
1259         scr_printf("%c ", room_prompt(room_flags));
1260
1261         while (1) {
1262                 ch = inkey();
1263                 ok_to_interrupt = 0;
1264
1265                 /* Handle the backspace key, but only if there's something
1266                  * to backspace over...
1267                  */
1268                 if ((ch == 8) && (cmdpos > 0)) {
1269                         back(cmdspaces[cmdpos - 1] + 1);
1270                         cmdbuf[cmdpos] = 0;
1271                         --cmdpos;
1272                 }
1273                 /* Spacebar invokes "lazy traversal" commands */
1274                 if ((ch == 32) && (cmdpos == 0)) {
1275                         this_lazy_cmd = next_lazy_cmd;
1276                         if (this_lazy_cmd == 13)
1277                                 next_lazy_cmd = 5;
1278                         if (this_lazy_cmd == 5)
1279                                 next_lazy_cmd = 13;
1280                         for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1281                                 if (cptr->c_cmdnum == this_lazy_cmd) {
1282                                         for (a = 0; a < 5; ++a)
1283                                                 if (cptr->c_keys[a][0] != 0)
1284                                                         scr_printf("%s ", cmd_expand(cptr->c_keys[a], 0));
1285                                         scr_printf("\n");
1286                                         return (this_lazy_cmd);
1287                                 }
1288                         }
1289                         scr_printf("\n");
1290                         return (this_lazy_cmd);
1291                 }
1292                 /* Otherwise, process the command */
1293                 cmdbuf[cmdpos] = tolower(ch);
1294
1295                 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1296                         if (cmdmatch(cmdbuf, cptr, cmdpos + 1)) {
1297
1298                                 scr_printf("%s", cmd_expand(cptr->c_keys[cmdpos], 0));
1299                                 cmdspaces[cmdpos] = strlen(cmd_expand(cptr->c_keys[cmdpos], 0));
1300                                 if (cmdpos < 4)
1301                                         if ((cptr->c_keys[cmdpos + 1]) != 0)
1302                                                 scr_putc(' ');
1303                                 ++cmdpos;
1304                         }
1305                 }
1306
1307                 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1308                         if (cmdmatch(cmdbuf, cptr, 5)) {
1309                                 /* We've found our command. */
1310                                 if (requires_string(cptr, cmdpos)) {
1311                                         argbuf[0] = 0;
1312                                         ctdl_getline(argbuf, 64, 0, 0);
1313                                 } else {
1314                                         scr_printf("\n");
1315                                 }
1316
1317                                 /* If this command is one that changes rooms,
1318                                  * then the next lazy-command (space bar)
1319                                  * should be "read new" instead of "goto"
1320                                  */
1321                                 if ((cptr->c_cmdnum == 5)
1322                                     || (cptr->c_cmdnum == 6)
1323                                     || (cptr->c_cmdnum == 47)
1324                                     || (cptr->c_cmdnum == 52)
1325                                     || (cptr->c_cmdnum == 16)
1326                                     || (cptr->c_cmdnum == 20))
1327                                         next_lazy_cmd = 13;
1328
1329                                 /* If this command is "read new"
1330                                  * then the next lazy-command (space bar)
1331                                  * should be "goto"
1332                                  */
1333                                 if (cptr->c_cmdnum == 13)
1334                                         next_lazy_cmd = 5;
1335
1336                                 return (cptr->c_cmdnum);
1337
1338                         }
1339                 }
1340
1341                 if (ch == '?') {
1342                         scr_printf("\rOne of ...                         \n");
1343                         for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1344                                 if (cmdmatch(cmdbuf, cptr, cmdpos)) {
1345                                         for (a = 0; a < 5; ++a) {
1346                                                 keyopt(cmd_expand(cptr->c_keys[a], 1));
1347                                                 scr_printf(" ");
1348                                         }
1349                                         scr_printf("\n");
1350                                 }
1351                         }
1352                         sigcaught = 0;
1353
1354                         scr_printf("\n%s%c ", room_name, room_prompt(room_flags));
1355                         got = 0;
1356                         for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1357                                 if ((got == 0) && (cmdmatch(cmdbuf, cptr, cmdpos))) {
1358                                         for (a = 0; a < cmdpos; ++a) {
1359                                                 scr_printf("%s ", cmd_expand(cptr->c_keys[a], 0));
1360                                         }
1361                                         got = 1;
1362                                 }
1363                         }
1364                 }
1365         }
1366
1367 }
1368
1369
1370
1371
1372
1373 /*
1374  * set tty modes.  commands are:
1375  * 
1376  * 01- set to Citadel mode
1377  * 2 - save current settings for later restoral
1378  * 3 - restore saved settings
1379  */
1380 void stty_ctdl(int cmd)
1381 {                               /* SysV version of stty_ctdl() */
1382         struct termios live;
1383         static struct termios saved_settings;
1384         static int last_cmd = 0;
1385
1386         if (cmd == SB_LAST)
1387                 cmd = last_cmd;
1388         else
1389                 last_cmd = cmd;
1390
1391         if ((cmd == 0) || (cmd == 1)) {
1392                 tcgetattr(0, &live);
1393                 live.c_iflag = ISTRIP | IXON | IXANY;
1394                 live.c_oflag = OPOST | ONLCR;
1395                 live.c_lflag = ISIG | NOFLSH;
1396
1397                 live.c_cc[VINTR] = 0;
1398                 live.c_cc[VQUIT] = 0;
1399
1400                 /* do we even need this stuff anymore? */
1401                 /* live.c_line=0; */
1402                 live.c_cc[VERASE] = 8;
1403                 live.c_cc[VKILL] = 24;
1404                 live.c_cc[VEOF] = 1;
1405                 live.c_cc[VEOL] = 255;
1406                 live.c_cc[VEOL2] = 0;
1407                 live.c_cc[VSTART] = 0;
1408                 tcsetattr(0, TCSADRAIN, &live);
1409         }
1410         if (cmd == 2) {
1411                 tcgetattr(0, &saved_settings);
1412         }
1413         if (cmd == 3) {
1414                 tcsetattr(0, TCSADRAIN, &saved_settings);
1415         }
1416
1417 }
1418
1419
1420 // this is the old version which uses sgtty.h instead of termios.h
1421 #if 0
1422 void stty_ctdl(int cmd)
1423 {                               /* BSD version of stty_ctdl() */
1424         struct sgttyb live;
1425         static struct sgttyb saved_settings;
1426         static int last_cmd = 0;
1427
1428         if (cmd == SB_LAST)
1429                 cmd = last_cmd;
1430         else
1431                 last_cmd = cmd;
1432
1433         if ((cmd == 0) || (cmd == 1)) {
1434                 gtty(0, &live);
1435                 live.sg_flags |= CBREAK;
1436                 live.sg_flags |= CRMOD;
1437                 live.sg_flags |= NL1;
1438                 live.sg_flags &= ~ECHO;
1439                 if (cmd == 1)
1440                         live.sg_flags |= NOFLSH;
1441                 stty(0, &live);
1442         }
1443         if (cmd == 2) {
1444                 gtty(0, &saved_settings);
1445         }
1446         if (cmd == 3) {
1447                 stty(0, &saved_settings);
1448         }
1449 }
1450 #endif
1451
1452
1453 /*
1454  * display_help()  -  help text viewer
1455  */
1456 void display_help(CtdlIPC * ipc, char *name)
1457 {
1458         int i;
1459         int num_helps = sizeof(helpnames) / sizeof(char *);
1460
1461         for (i = 0; i < num_helps; ++i) {
1462                 if (!strcasecmp(name, helpnames[i])) {
1463                         fmout(screenwidth, NULL, helptexts[i], NULL, 0);
1464                         return;
1465                 }
1466         }
1467
1468         scr_printf("'%s' not found.  Enter one of:\n", name);
1469         for (i = 0; i < num_helps; ++i) {
1470                 scr_printf("  %s\n", helpnames[i]);
1471         }
1472 }
1473
1474
1475 /*
1476  * fmout() - Citadel text formatter and paginator
1477  */
1478 int fmout(int width,            /* screen width to use */
1479           FILE * fpin,          /* file to read from, or NULL to format given text */
1480           char *text,           /* text to be formatted (when fpin is NULL */
1481           FILE * fpout,         /* file to write to, or NULL to write to screen */
1482           int subst) {          /* nonzero if we should use hypertext mode */
1483         char *buffer = NULL;    /* The current message */
1484         char *word = NULL;      /* What we are about to actually print */
1485         char *e;                /* Pointer to position in text */
1486         char old = 0;           /* The previous character */
1487         int column = 0;         /* Current column */
1488         size_t i;               /* Generic counter */
1489
1490         /* Space for a single word, which can be at most screenwidth */
1491         word = (char *) calloc(1, width);
1492         if (!word) {
1493                 scr_printf("Can't alloc memory to print message: %s!\n", strerror(errno));
1494                 logoff(NULL, 3);
1495         }
1496
1497         /* Read the entire message body into memory */
1498         if (fpin) {
1499                 buffer = load_message_from_file(fpin);
1500                 if (!buffer) {
1501                         scr_printf("Can't print message: %s!\n", strerror(errno));
1502                         logoff(NULL, 3);
1503                 }
1504         } else {
1505                 buffer = text;
1506         }
1507         e = buffer;
1508
1509         /* Run the message body */
1510         while (*e) {
1511                 /* Catch characters that shouldn't be there at all */
1512                 if (*e == '\r') {
1513                         e++;
1514                         continue;
1515                 }
1516                 /* First, are we looking at a newline? */
1517                 if (*e == '\n') {
1518                         e++;
1519                         if (*e == ' ') {        /* Paragraph */
1520                                 if (fpout) {
1521                                         fprintf(fpout, "\n");
1522                                 } else {
1523                                         scr_printf("\n");
1524                                 }
1525                                 column = 0;
1526                         } else if (old != ' ') {        /* Don't print two spaces */
1527                                 if (fpout) {
1528                                         fprintf(fpout, " ");
1529                                 } else {
1530                                         scr_printf(" ");
1531                                 }
1532                                 column++;
1533                         }
1534                         old = '\n';
1535                         continue;
1536                 }
1537
1538                 /* Are we looking at a nonprintable?
1539                  * (This section is now commented out because we could be displaying
1540                  * a character set like UTF-8 or ISO-8859-1.)
1541                  if ( (*e < 32) || (*e > 126) ) {
1542                  e++;
1543                  continue;
1544                  } */
1545
1546                 /* Or are we looking at a space? */
1547                 if (*e == ' ') {
1548                         e++;
1549                         if (column >= width - 1) {
1550                                 /* Are we in the rightmost column? */
1551                                 if (fpout) {
1552                                         fprintf(fpout, "\n");
1553                                 } else {
1554                                         scr_printf("\n");
1555                                 }
1556                                 column = 0;
1557                         } else if (!(column == 0 && old == ' ')) {
1558                                 /* Eat only the first space on a line */
1559                                 if (fpout) {
1560                                         fprintf(fpout, " ");
1561                                 } else {
1562                                         scr_printf(" ");
1563                                 }
1564                                 column++;
1565                         }
1566                         /* ONLY eat the FIRST space on a line */
1567                         old = ' ';
1568                         continue;
1569                 }
1570                 old = *e;
1571
1572                 /* Read a word, slightly messy */
1573                 i = 0;
1574                 while (e[i]) {
1575                         if (!isprint(e[i]) && !isspace(e[i]))
1576                                 e[i] = ' ';
1577                         if (isspace(e[i]))
1578                                 break;
1579                         i++;
1580                 }
1581
1582                 /* We should never see these, but... slightly messy */
1583                 if (e[i] == '\t' || e[i] == '\f' || e[i] == '\v')
1584                         e[i] = ' ';
1585
1586                 /* Break up really long words */
1587                 /* TODO: auto-hyphenation someday? */
1588                 if (i >= width)
1589                         i = width - 1;
1590                 strncpy(word, e, i);
1591                 word[i] = 0;
1592
1593                 /* Decide where to print the word */
1594                 if (column + i >= width) {
1595                         /* Wrap to the next line */
1596                         if (fpout) {
1597                                 fprintf(fpout, "\n");
1598                         } else {
1599                                 scr_printf("\n");
1600                         }
1601                         column = 0;
1602                 }
1603
1604                 /* Print the word */
1605                 if (fpout) {
1606                         fprintf(fpout, "%s", word);
1607                 } else {
1608                         scr_printf("%s", word);
1609                 }
1610                 column += i;
1611                 e += i;         /* Start over with the whitepsace! */
1612         }
1613
1614         free(word);
1615         if (fpin)               /* We allocated this, remember? */
1616                 free(buffer);
1617
1618         /* Is this necessary?  It makes the output kind of spacey. */
1619         if (fpout) {
1620                 fprintf(fpout, "\n");
1621         } else {
1622                 scr_printf("\n");
1623         }
1624
1625         return sigcaught;
1626 }
1627
1628
1629 /*
1630  * support ANSI color if defined
1631  */
1632 void color(int colornum)
1633 {
1634         static int hold_color;
1635         static int current_color;
1636
1637         if (colornum == COLOR_PUSH) {
1638                 hold_color = current_color;
1639                 return;
1640         }
1641
1642         if (colornum == COLOR_POP) {
1643                 color(hold_color);
1644                 return;
1645         }
1646
1647         current_color = colornum;
1648         if (enable_color) {
1649                 /* When switching to dim white, actually output an 'original
1650                  * pair' sequence -- this looks better on black-on-white
1651                  * terminals. - Changed to ORIGINAL_PAIR as this actually
1652                  * wound up looking horrible on black-on-white terminals, not
1653                  * to mention transparent terminals.
1654                  */
1655                 if (colornum == ORIGINAL_PAIR)
1656                         printf("\033[0;39;49m");
1657                 else
1658                         printf("\033[%d;3%d;4%dm", (colornum & 8) ? 1 : 0, (colornum & 7), rc_color_use_bg);
1659
1660         }
1661 }
1662
1663 void cls(int colornum)
1664 {
1665         if (enable_color) {
1666                 printf("\033[4%dm\033[2J\033[H\033[0m", colornum ? colornum : rc_color_use_bg);
1667         }
1668 }
1669
1670
1671 /*
1672  * Detect whether ANSI color is available (answerback)
1673  */
1674 void send_ansi_detect(void)
1675 {
1676         if (rc_ansi_color == 2) {
1677                 printf("\033[c");
1678                 scr_flush();
1679                 time(&AnsiDetect);
1680         }
1681 }
1682
1683 void look_for_ansi(void)
1684 {
1685         fd_set rfds;
1686         struct timeval tv;
1687         char abuf[512];
1688         time_t now;
1689         int a, rv;
1690
1691         if (rc_ansi_color == 0) {
1692                 enable_color = 0;
1693         } else if (rc_ansi_color == 1) {
1694                 enable_color = 1;
1695         } else if (rc_ansi_color == 2) {
1696
1697                 /* otherwise, do the auto-detect */
1698
1699                 strcpy(abuf, "");
1700
1701                 time(&now);
1702                 if ((now - AnsiDetect) < 2)
1703                         sleep(1);
1704
1705                 do {
1706                         FD_ZERO(&rfds);
1707                         FD_SET(0, &rfds);
1708                         tv.tv_sec = 0;
1709                         tv.tv_usec = 1;
1710
1711                         select(1, &rfds, NULL, NULL, &tv);
1712                         if (FD_ISSET(0, &rfds)) {
1713                                 abuf[strlen(abuf) + 1] = 0;
1714                                 rv = read(0, &abuf[strlen(abuf)], 1);
1715                                 if (rv < 0) {
1716                                         scr_printf("failed to read after select: %s", strerror(errno));
1717                                         break;
1718                                 }
1719                         }
1720                 } while (FD_ISSET(0, &rfds));
1721
1722                 for (a = 0; !IsEmptyStr(&abuf[a]); ++a) {
1723                         if ((abuf[a] == 27) && (abuf[a + 1] == '[')
1724                             && (abuf[a + 2] == '?')) {
1725                                 enable_color = 1;
1726                         }
1727                 }
1728         }
1729 }
1730
1731
1732 /*
1733  * Display key options (highlight hotkeys inside angle brackets)
1734  */
1735 void keyopt(char *buf)
1736 {
1737         int i;
1738
1739         color(DIM_WHITE);
1740         for (i = 0; !IsEmptyStr(&buf[i]); ++i) {
1741                 if (buf[i] == '<') {
1742                         scr_printf("%c", buf[i]);
1743                         color(BRIGHT_MAGENTA);
1744                 } else {
1745                         if (buf[i] == '>' && buf[i + 1] != '>') {
1746                                 color(DIM_WHITE);
1747                         }
1748                         scr_printf("%c", buf[i]);
1749                 }
1750         }
1751         color(DIM_WHITE);
1752 }
1753
1754
1755
1756 /*
1757  * Present a key-menu line choice type of thing
1758  */
1759 char keymenu(char *menuprompt, char *menustring)
1760 {
1761         int i, c, a;
1762         int choices;
1763         int do_prompt = 0;
1764         char buf[1024];
1765         int ch;
1766         int display_prompt = 1;
1767
1768         choices = num_tokens(menustring, '|');
1769
1770         if (menuprompt != NULL)
1771                 do_prompt = 1;
1772         if ((menuprompt != NULL) && (IsEmptyStr(menuprompt)))
1773                 do_prompt = 0;
1774
1775         while (1) {
1776                 if (display_prompt) {
1777                         if (do_prompt) {
1778                                 scr_printf("%s ", menuprompt);
1779                         } else {
1780                                 for (i = 0; i < choices; ++i) {
1781                                         extract_token(buf, menustring, i, '|', sizeof buf);
1782                                         keyopt(buf);
1783                                         scr_printf(" ");
1784                                 }
1785                         }
1786                         scr_printf("-> ");
1787                         display_prompt = 0;
1788                 }
1789                 ch = lkey();
1790
1791                 if ((do_prompt) && (ch == '?')) {
1792                         scr_printf("\rOne of...                               ");
1793                         scr_printf("                                      \n");
1794                         for (i = 0; i < choices; ++i) {
1795                                 extract_token(buf, menustring, i, '|', sizeof buf);
1796                                 scr_printf("   ");
1797                                 keyopt(buf);
1798                                 scr_printf("\n");
1799                         }
1800                         scr_printf("\n");
1801                         display_prompt = 1;
1802                 }
1803
1804                 for (i = 0; i < choices; ++i) {
1805                         extract_token(buf, menustring, i, '|', sizeof buf);
1806                         for (c = 1; !IsEmptyStr(&buf[c]); ++c) {
1807                                 if ((ch == tolower(buf[c]))
1808                                     && (buf[c - 1] == '<')
1809                                     && (buf[c + 1] == '>')) {
1810                                         for (a = 0; !IsEmptyStr(&buf[a]); ++a) {
1811                                                 if ((a != (c - 1)) && (a != (c + 1))) {
1812                                                         scr_putc(buf[a]);
1813                                                 }
1814                                         }
1815                                         scr_printf("\n");
1816                                         return ch;
1817                                 }
1818                         }
1819                 }
1820         }
1821 }