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