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