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