began making the client AppImage-aware
[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-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, then do nothing.
631         if (keepalives_enabled == KA_HALF) {
632                 CtdlIPC_chat_send(ipc_for_signal_handlers, "QNOP");
633         }
634 }
635
636
637 // I changed this from static to not because I need to call it from
638 // screen.c, either that or make something in screen.c not static.
639 // Fix it how you like. Why all the staticness? stu
640 void do_keepalive(void) {
641         time_t now;
642
643         time(&now);
644         if ((now - idlet) < ((long) S_KEEPALIVE)) {
645                 return;
646         }
647
648         // Do a space-backspace to keep terminal sessions from idling out
649         scr_printf(" %c", 8);
650         scr_flush();
651
652         really_do_keepalive();
653 }
654
655
656 // Get a character from the keyboard, with the watchdog timer in effect if necessary.
657 int inkey(void) {
658         int a;
659         fd_set rfds;
660         struct timeval tv;
661         time_t start_time;
662
663         scr_flush();
664         time(&start_time);
665
666         do {
667                 // This loop waits for keyboard input.  If the keepalive
668                 // timer expires, it sends a keepalive to the server if
669                 // necessary and then waits again.
670                 do {
671                         do_keepalive();
672
673                         FD_ZERO(&rfds);
674                         FD_SET(0, &rfds);
675                         tv.tv_sec = S_KEEPALIVE;
676                         tv.tv_usec = 0;
677
678                         select(1, &rfds, NULL, NULL, &tv);
679                 } while (!FD_ISSET(0, &rfds));
680
681                 // At this point, there's input, so fetch it.
682                 // (There's a hole in the bucket...)
683                 a = scr_getc(SCR_BLOCK);
684                 if (a == 127) {
685                         a = 8;
686                 }
687                 if (a == 13) {
688                         a = 10;
689                 }
690         } while (a == 0);
691         return (a);
692 }
693
694
695 // Returns 1 for yes, 0 for no
696 int yesno(void) {
697         int a;
698         while (1) {
699                 a = inkey();
700                 a = tolower(a);
701                 if (a == 'y') {
702                         scr_printf("Yes\n");
703                         return (1);
704                 }
705                 if (a == 'n') {
706                         scr_printf("No\n");
707                         return (0);
708                 }
709         }
710 }
711
712
713 // Returns 1 for yes, 0 for no, arg is default value
714 int yesno_d(int d) {
715         int a;
716         while (1) {
717                 a = inkey();
718                 a = tolower(a);
719                 if (a == 10)
720                         a = (d ? 'y' : 'n');
721                 if (a == 'y') {
722                         scr_printf("Yes\n");
723                         return (1);
724                 }
725                 if (a == 'n') {
726                         scr_printf("No\n");
727                         return (0);
728                 }
729         }
730 }
731
732
733 /*
734  * Function to read a line of text from the terminal.
735  *
736  * string               Pointer to string buffer
737  * lim                  Maximum length
738  * noshow               Echo asterisks instead of keystrokes?
739  * bs                   Allow backspacing out of the prompt? (returns -1 if this happens)
740  *
741  * returns: string length
742  */
743 int ctdl_getline(char *string, int lim, int noshow, int bs)
744 {
745         int pos = strlen(string);
746         int ch;
747
748         if (noshow && !IsEmptyStr(string)) {
749                 int num_stars = strlen(string);
750                 while (num_stars--) {
751                         scr_putc('*');
752                 }
753         } else {
754                 scr_printf("%s", string);
755         }
756
757         while (1) {
758                 ch = inkey();
759
760                 if ((ch == 8) && (pos > 0)) {   /* backspace */
761                         --pos;
762                         scr_putc(8);
763                         scr_putc(32);
764                         scr_putc(8);
765                 }
766
767                 else if ((ch == 8) && (pos == 0) && (bs)) {     /* backspace out of the prompt */
768                         return (-1);
769                 }
770
771                 else if ((ch == 23) && (pos > 0)) {     /* Ctrl-W deletes a word */
772                         while ((pos > 0) && !isspace(string[pos])) {
773                                 --pos;
774                                 scr_putc(8);
775                                 scr_putc(32);
776                                 scr_putc(8);
777                         }
778                         while ((pos > 0) && !isspace(string[pos - 1])) {
779                                 --pos;
780                                 scr_putc(8);
781                                 scr_putc(32);
782                                 scr_putc(8);
783                         }
784                 }
785
786                 else if (ch == 10) {    /* return */
787                         string[pos] = 0;
788                         scr_printf("\n");
789                         return (pos);
790                 }
791
792                 else if (isprint(ch)) { /* payload characters */
793                         scr_putc((noshow ? '*' : ch));
794                         string[pos] = ch;
795                         ++pos;
796                 }
797         }
798 }
799
800
801 /* 
802  * newprompt()          prompt for a string, print the existing value, and
803  *                      allow the user to press return to keep it...
804  *                      If len is negative, pass the "noshow" flag to ctdl_getline()
805  */
806 void strprompt(char *prompt, char *str, int len)
807 {
808         display_instant_messages();
809         color(DIM_WHITE);
810         scr_printf("%s", prompt);
811         color(DIM_WHITE);
812         scr_printf(": ");
813         color(BRIGHT_CYAN);
814         ctdl_getline(str, abs(len), (len < 0), 0);
815         color(DIM_WHITE);
816 }
817
818 /*
819  * boolprompt()  -  prompt for a yes/no, print the existing value and
820  *                  allow the user to press return to keep it...
821  */
822 int boolprompt(char *prompt, int prev_val)
823 {
824         int r;
825
826         color(DIM_WHITE);
827         scr_printf("%s ", prompt);
828         color(DIM_MAGENTA);
829         scr_printf("[");
830         color(BRIGHT_MAGENTA);
831         scr_printf("%s", (prev_val ? "Yes" : "No"));
832         color(DIM_MAGENTA);
833         scr_printf("]: ");
834         color(BRIGHT_CYAN);
835         r = (yesno_d(prev_val));
836         color(DIM_WHITE);
837         return r;
838 }
839
840 /* 
841  * intprompt()  -  like strprompt(), except for an integer
842  *                 (note that it RETURNS the new value!)
843  */
844 int intprompt(char *prompt, int ival, int imin, int imax)
845 {
846         char buf[16];
847         int i;
848         int p;
849
850         do {
851                 i = ival;
852                 snprintf(buf, sizeof buf, "%d", i);
853                 strprompt(prompt, buf, 15);
854                 i = atoi(buf);
855                 for (p = 0; !IsEmptyStr(&buf[p]); ++p) {
856                         if ((!isdigit(buf[p]))
857                             && ((buf[p] != '-') || (p != 0)))
858                                 i = imin - 1;
859                 }
860                 if (i < imin)
861                         scr_printf("*** Must be no less than %d.\n", imin);
862                 if (i > imax)
863                         scr_printf("*** Must be no more than %d.\n", imax);
864         } while ((i < imin) || (i > imax));
865         return (i);
866 }
867
868 /* 
869  * newprompt()          prompt for a string with no existing value
870  *                      (clears out string buffer first)
871  *                      If len is negative, pass the "noshow" flag to ctdl_getline()
872  */
873 void newprompt(char *prompt, char *str, int len)
874 {
875         str[0] = 0;
876         color(BRIGHT_MAGENTA);
877         scr_printf("%s", prompt);
878         color(DIM_MAGENTA);
879         ctdl_getline(str, abs(len), (len < 0), 0);
880         color(DIM_WHITE);
881 }
882
883
884 int lkey(void)
885 {                               /* returns a lower case value */
886         int a;
887         a = inkey();
888         if (isupper(a))
889                 a = tolower(a);
890         return (a);
891 }
892
893 /*
894  * parse the citadel.rc file
895  */
896 void load_command_set(void)
897 {
898         FILE *ccfile;
899         char buf[1024];
900         struct citcmd *cptr;
901         struct citcmd *lastcmd = NULL;
902         int a, d;
903         int b = 0;
904
905         /* first, set up some defaults for non-required variables */
906
907         strcpy(editor_path, "");
908         strcpy(printcmd, "");
909         strcpy(imagecmd, "");
910         strcpy(rc_username, "");
911         strcpy(rc_password, "");
912         rc_floor_mode = 0;
913         rc_exp_beep = 1;
914         rc_allow_attachments = 0;
915         rc_remember_passwords = 0;
916         strcpy(rc_exp_cmd, "");
917         rc_display_message_numbers = 0;
918         rc_force_mail_prompts = 0;
919         rc_ansi_color = 0;
920         rc_color_use_bg = 0;
921         strcpy(rc_url_cmd, "");
922         strcpy(rc_open_cmd, "");
923         strcpy(rc_gotmail_cmd, "");
924 #ifdef HAVE_OPENSSL
925         rc_encrypt = RC_DEFAULT;
926 #endif
927
928         /* now try to open the citadel.rc file */
929
930         ccfile = NULL;
931         if (getenv("HOME") != NULL) {
932                 snprintf(buf, sizeof buf, "%s/.citadelrc", getenv("HOME"));
933                 ccfile = fopen(buf, "r");
934         }
935         if (getenv("APPDIR") != NULL) {
936                 snprintf(buf, sizeof buf, "%s/citadel.rc", getenv("APPDIR"));
937                 ccfile = fopen(buf, "r");
938         }
939         if (ccfile == NULL) {
940                 ccfile = fopen(file_citadel_rc, "r");
941         }
942         if (ccfile == NULL) {
943                 ccfile = fopen("/etc/citadel.rc", "r");
944         }
945         if (ccfile == NULL) {
946                 ccfile = fopen("./citadel.rc", "r");
947         }
948         if (ccfile == NULL) {
949                 perror("commands: cannot open citadel.rc");
950                 logoff(NULL, 3);
951         }
952         while (fgets(buf, sizeof buf, ccfile) != NULL) {
953                 while ((!IsEmptyStr(buf)) ? (isspace(buf[strlen(buf) - 1])) : 0)
954                         buf[strlen(buf) - 1] = 0;
955
956                 if (!strncasecmp(buf, "encrypt=", 8)) {
957                         if (!strcasecmp(&buf[8], "yes")) {
958 #ifdef HAVE_OPENSSL
959                                 rc_encrypt = RC_YES;
960 #else
961                                 fprintf(stderr, "citadel.rc requires encryption support but citadel is not compiled with OpenSSL");
962                                 logoff(NULL, 3);
963 #endif
964                         }
965 #ifdef HAVE_OPENSSL
966                         else if (!strcasecmp(&buf[8], "no")) {
967                                 rc_encrypt = RC_NO;
968                         } else if (!strcasecmp(&buf[8], "default")) {
969                                 rc_encrypt = RC_DEFAULT;
970                         }
971 #endif
972                 }
973
974                 if (!strncasecmp(buf, "editor=", 7)) {
975                         strcpy(editor_path, &buf[7]);
976                 }
977
978                 if (!strncasecmp(buf, "printcmd=", 9))
979                         strcpy(printcmd, &buf[9]);
980
981                 if (!strncasecmp(buf, "imagecmd=", 9))
982                         strcpy(imagecmd, &buf[9]);
983
984                 if (!strncasecmp(buf, "expcmd=", 7))
985                         strcpy(rc_exp_cmd, &buf[7]);
986
987                 if (!strncasecmp(buf, "use_floors=", 11)) {
988                         if (!strcasecmp(&buf[11], "yes"))
989                                 rc_floor_mode = RC_YES;
990                         if (!strcasecmp(&buf[11], "no"))
991                                 rc_floor_mode = RC_NO;
992                         if (!strcasecmp(&buf[11], "default"))
993                                 rc_floor_mode = RC_DEFAULT;
994                 }
995                 if (!strncasecmp(buf, "beep=", 5)) {
996                         rc_exp_beep = atoi(&buf[5]);
997                 }
998                 if (!strncasecmp(buf, "allow_attachments=", 18)) {
999                         rc_allow_attachments = atoi(&buf[18]);
1000                 }
1001                 if (!strncasecmp(buf, "idle_threshold=", 15)) {
1002                         rc_idle_threshold = atol(&buf[15]);
1003                 }
1004                 if (!strncasecmp(buf, "remember_passwords=", 19)) {
1005                         rc_remember_passwords = atoi(&buf[19]);
1006                 }
1007                 if (!strncasecmp(buf, "display_message_numbers=", 24)) {
1008                         rc_display_message_numbers = atoi(&buf[24]);
1009                 }
1010                 if (!strncasecmp(buf, "force_mail_prompts=", 19)) {
1011                         rc_force_mail_prompts = atoi(&buf[19]);
1012                 }
1013                 if (!strncasecmp(buf, "ansi_color=", 11)) {
1014                         if (!strncasecmp(&buf[11], "on", 2))
1015                                 rc_ansi_color = 1;
1016                         if (!strncasecmp(&buf[11], "auto", 4))
1017                                 rc_ansi_color = 2;      /* autodetect */
1018                         if (!strncasecmp(&buf[11], "user", 4))
1019                                 rc_ansi_color = 3;      /* user config */
1020                 }
1021                 if (!strncasecmp(buf, "status_line=", 12)) {
1022                         if (!strncasecmp(&buf[12], "on", 2))
1023                                 enable_status_line = 1;
1024                 }
1025                 if (!strncasecmp(buf, "use_background=", 15)) {
1026                         if (!strncasecmp(&buf[15], "on", 2))
1027                                 rc_color_use_bg = 9;
1028                 }
1029                 if (!strncasecmp(buf, "prompt_control=", 15)) {
1030                         if (!strncasecmp(&buf[15], "on", 2))
1031                                 rc_prompt_control = 1;
1032                         if (!strncasecmp(&buf[15], "user", 4))
1033                                 rc_prompt_control = 3;  /* user config */
1034                 }
1035                 if (!strncasecmp(buf, "username=", 9))
1036                         strcpy(rc_username, &buf[9]);
1037
1038                 if (!strncasecmp(buf, "password=", 9))
1039                         strcpy(rc_password, &buf[9]);
1040
1041                 if (!strncasecmp(buf, "urlcmd=", 7))
1042                         strcpy(rc_url_cmd, &buf[7]);
1043
1044                 if (!strncasecmp(buf, "opencmd=", 7))
1045                         strcpy(rc_open_cmd, &buf[8]);
1046
1047                 if (!strncasecmp(buf, "gotmailcmd=", 11))
1048                         strcpy(rc_gotmail_cmd, &buf[11]);
1049
1050                 if (!strncasecmp(buf, "cmd=", 4)) {
1051                         strcpy(buf, &buf[4]);
1052
1053                         cptr = (struct citcmd *) malloc(sizeof(struct citcmd));
1054
1055                         cptr->c_cmdnum = atoi(buf);
1056                         for (d = strlen(buf); d >= 0; --d)
1057                                 if (buf[d] == ',')
1058                                         b = d;
1059                         strcpy(buf, &buf[b + 1]);
1060
1061                         cptr->c_axlevel = atoi(buf);
1062                         for (d = strlen(buf); d >= 0; --d)
1063                                 if (buf[d] == ',')
1064                                         b = d;
1065                         strcpy(buf, &buf[b + 1]);
1066
1067                         for (a = 0; a < 5; ++a)
1068                                 cptr->c_keys[a][0] = 0;
1069
1070                         a = 0;
1071                         b = 0;
1072                         buf[strlen(buf) + 1] = 0;
1073                         while (!IsEmptyStr(buf)) {
1074                                 b = strlen(buf);
1075                                 for (d = strlen(buf); d >= 0; --d)
1076                                         if (buf[d] == ',')
1077                                                 b = d;
1078                                 strncpy(cptr->c_keys[a], buf, b);
1079                                 cptr->c_keys[a][b] = 0;
1080                                 if (buf[b] == ',')
1081                                         strcpy(buf, &buf[b + 1]);
1082                                 else
1083                                         strcpy(buf, "");
1084                                 ++a;
1085                         }
1086
1087                         cptr->next = NULL;
1088                         if (cmdlist == NULL)
1089                                 cmdlist = cptr;
1090                         else
1091                                 lastcmd->next = cptr;
1092                         lastcmd = cptr;
1093                 }
1094         }
1095         fclose(ccfile);
1096 }
1097
1098
1099
1100 /*
1101  * return the key associated with a command
1102  */
1103 char keycmd(char *cmdstr)
1104 {
1105         int a;
1106
1107         for (a = 0; !IsEmptyStr(&cmdstr[a]); ++a)
1108                 if (cmdstr[a] == '&')
1109                         return (tolower(cmdstr[a + 1]));
1110         return (0);
1111 }
1112
1113
1114 /*
1115  * Output the string from a key command without the ampersand
1116  * "mode" should be set to 0 for normal or 1 for <C>ommand key highlighting
1117  */
1118 char *cmd_expand(char *strbuf, int mode)
1119 {
1120         int a;
1121         static char exp[64];
1122         char buf[1024];
1123
1124         strcpy(exp, strbuf);
1125
1126         for (a = 0; exp[a]; ++a) {
1127                 if (strbuf[a] == '&') {
1128
1129                         /* dont echo these non mnemonic command keys */
1130                         int noecho = strbuf[a + 1] == '<' || strbuf[a + 1] == '>' || strbuf[a + 1] == '+' || strbuf[a + 1] == '-';
1131
1132                         if (mode == 0) {
1133                                 strcpy(&exp[a], &exp[a + 1 + noecho]);
1134                         }
1135                         if (mode == 1) {
1136                                 exp[a] = '<';
1137                                 strcpy(buf, &exp[a + 2]);
1138                                 exp[a + 2] = '>';
1139                                 exp[a + 3] = 0;
1140                                 strcat(exp, buf);
1141                         }
1142                 }
1143                 if (!strncmp(&exp[a], "^r", 2)) {
1144                         strcpy(buf, exp);
1145                         strcpy(&exp[a], room_name);
1146                         strcat(exp, &buf[a + 2]);
1147                 }
1148                 if (!strncmp(&exp[a], "^c", 2)) {
1149                         exp[a] = ',';
1150                         strcpy(&exp[a + 1], &exp[a + 2]);
1151                 }
1152         }
1153
1154         return (exp);
1155 }
1156
1157
1158
1159 /*
1160  * Comparison function to determine if entered commands match a
1161  * command loaded from the config file.
1162  */
1163 int cmdmatch(char *cmdbuf, struct citcmd *cptr, int ncomp)
1164 {
1165         int a;
1166         int cmdax;
1167
1168         cmdax = 0;
1169         if (is_room_aide)
1170                 cmdax = 1;
1171         if (axlevel >= 6)
1172                 cmdax = 2;
1173
1174         for (a = 0; a < ncomp; ++a) {
1175                 if ((tolower(cmdbuf[a]) != keycmd(cptr->c_keys[a]))
1176                     || (cptr->c_axlevel > cmdax))
1177                         return (0);
1178         }
1179         return (1);
1180 }
1181
1182
1183 /*
1184  * This function returns 1 if a given command requires a string input
1185  */
1186 int requires_string(struct citcmd *cptr, int ncomp)
1187 {
1188         int a;
1189         char buf[64];
1190
1191         strcpy(buf, cptr->c_keys[ncomp - 1]);
1192         for (a = 0; !IsEmptyStr(&buf[a]); ++a) {
1193                 if (buf[a] == ':')
1194                         return (1);
1195         }
1196         return (0);
1197 }
1198
1199
1200 /*
1201  * Input a command at the main prompt.
1202  * This function returns an integer command number.  If the command prompts
1203  * for a string then it is placed in the supplied buffer.
1204  */
1205 int getcmd(CtdlIPC * ipc, char *argbuf)
1206 {
1207         char cmdbuf[5];
1208         int cmdspaces[5];
1209         int cmdpos;
1210         int ch;
1211         int a;
1212         int got;
1213         int this_lazy_cmd;
1214         struct citcmd *cptr;
1215
1216         /*
1217          * Starting a new command now, so set sigcaught to 0.  This variable
1218          * is set to nonzero (usually NEXT_KEY or STOP_KEY) if a command has
1219          * been interrupted by a keypress.
1220          */
1221         sigcaught = 0;
1222
1223         /* Switch color support on or off if we're in user mode */
1224         if (rc_ansi_color == 3) {
1225                 if (userflags & US_COLOR)
1226                         enable_color = 1;
1227                 else
1228                         enable_color = 0;
1229         }
1230         /* if we're running in idiot mode, display a cute little menu */
1231
1232         IFNEXPERT {
1233                 scr_printf("-----------------------------------------------------------------------\n");
1234                 scr_printf("Room cmds:    <K>nown rooms, <G>oto next room, <.G>oto a specific room,\n");
1235                 scr_printf("              <S>kip this room, <A>bandon this room, <Z>ap this room,\n");
1236                 scr_printf("              <U>ngoto (move back)\n");
1237                 scr_printf("Message cmds: <N>ew msgs, <F>orward read, <R>everse read, <O>ld msgs,\n");
1238                 scr_printf("              <L>ast five msgs, <E>nter a message\n");
1239                 scr_printf("General cmds: <?> help, <T>erminate, <C>hat, <W>ho is online\n");
1240                 scr_printf("Misc:         <X> toggle eXpert mode, <D>irectory\n");
1241                 scr_printf("\n");
1242                 scr_printf(" (Type .Help SUMMARY for extended commands, <X> to hide this menu)\n");
1243                 scr_printf("-----------------------------------------------------------------------\n");
1244         }
1245
1246         display_instant_messages();
1247         strcpy(argbuf, "");
1248         cmdpos = 0;
1249         for (a = 0; a < 5; ++a)
1250                 cmdbuf[a] = 0;
1251         /* now the room prompt... */
1252         ok_to_interrupt = 1;
1253         color(BRIGHT_WHITE);
1254         scr_printf("\n%s", room_name);
1255         color(DIM_WHITE);
1256         scr_printf("%c ", room_prompt(room_flags));
1257
1258         while (1) {
1259                 ch = inkey();
1260                 ok_to_interrupt = 0;
1261
1262                 /* Handle the backspace key, but only if there's something
1263                  * to backspace over...
1264                  */
1265                 if ((ch == 8) && (cmdpos > 0)) {
1266                         back(cmdspaces[cmdpos - 1] + 1);
1267                         cmdbuf[cmdpos] = 0;
1268                         --cmdpos;
1269                 }
1270                 /* Spacebar invokes "lazy traversal" commands */
1271                 if ((ch == 32) && (cmdpos == 0)) {
1272                         this_lazy_cmd = next_lazy_cmd;
1273                         if (this_lazy_cmd == 13)
1274                                 next_lazy_cmd = 5;
1275                         if (this_lazy_cmd == 5)
1276                                 next_lazy_cmd = 13;
1277                         for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1278                                 if (cptr->c_cmdnum == this_lazy_cmd) {
1279                                         for (a = 0; a < 5; ++a)
1280                                                 if (cptr->c_keys[a][0] != 0)
1281                                                         scr_printf("%s ", cmd_expand(cptr->c_keys[a], 0));
1282                                         scr_printf("\n");
1283                                         return (this_lazy_cmd);
1284                                 }
1285                         }
1286                         scr_printf("\n");
1287                         return (this_lazy_cmd);
1288                 }
1289                 /* Otherwise, process the command */
1290                 cmdbuf[cmdpos] = tolower(ch);
1291
1292                 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1293                         if (cmdmatch(cmdbuf, cptr, cmdpos + 1)) {
1294
1295                                 scr_printf("%s", cmd_expand(cptr->c_keys[cmdpos], 0));
1296                                 cmdspaces[cmdpos] = strlen(cmd_expand(cptr->c_keys[cmdpos], 0));
1297                                 if (cmdpos < 4)
1298                                         if ((cptr->c_keys[cmdpos + 1]) != 0)
1299                                                 scr_putc(' ');
1300                                 ++cmdpos;
1301                         }
1302                 }
1303
1304                 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1305                         if (cmdmatch(cmdbuf, cptr, 5)) {
1306                                 /* We've found our command. */
1307                                 if (requires_string(cptr, cmdpos)) {
1308                                         argbuf[0] = 0;
1309                                         ctdl_getline(argbuf, 64, 0, 0);
1310                                 } else {
1311                                         scr_printf("\n");
1312                                 }
1313
1314                                 /* If this command is one that changes rooms,
1315                                  * then the next lazy-command (space bar)
1316                                  * should be "read new" instead of "goto"
1317                                  */
1318                                 if ((cptr->c_cmdnum == 5)
1319                                     || (cptr->c_cmdnum == 6)
1320                                     || (cptr->c_cmdnum == 47)
1321                                     || (cptr->c_cmdnum == 52)
1322                                     || (cptr->c_cmdnum == 16)
1323                                     || (cptr->c_cmdnum == 20))
1324                                         next_lazy_cmd = 13;
1325
1326                                 /* If this command is "read new"
1327                                  * then the next lazy-command (space bar)
1328                                  * should be "goto"
1329                                  */
1330                                 if (cptr->c_cmdnum == 13)
1331                                         next_lazy_cmd = 5;
1332
1333                                 return (cptr->c_cmdnum);
1334
1335                         }
1336                 }
1337
1338                 if (ch == '?') {
1339                         scr_printf("\rOne of ...                         \n");
1340                         for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1341                                 if (cmdmatch(cmdbuf, cptr, cmdpos)) {
1342                                         for (a = 0; a < 5; ++a) {
1343                                                 keyopt(cmd_expand(cptr->c_keys[a], 1));
1344                                                 scr_printf(" ");
1345                                         }
1346                                         scr_printf("\n");
1347                                 }
1348                         }
1349                         sigcaught = 0;
1350
1351                         scr_printf("\n%s%c ", room_name, room_prompt(room_flags));
1352                         got = 0;
1353                         for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1354                                 if ((got == 0) && (cmdmatch(cmdbuf, cptr, cmdpos))) {
1355                                         for (a = 0; a < cmdpos; ++a) {
1356                                                 scr_printf("%s ", cmd_expand(cptr->c_keys[a], 0));
1357                                         }
1358                                         got = 1;
1359                                 }
1360                         }
1361                 }
1362         }
1363
1364 }
1365
1366
1367 /*
1368  * set tty modes.  commands are:
1369  * 
1370  * 01- set to Citadel mode
1371  * 2 - save current settings for later restoral
1372  * 3 - restore saved settings
1373  */
1374 void stty_ctdl(int cmd) {                               /* SysV version of stty_ctdl() */
1375         struct termios live;
1376         static struct termios saved_settings;
1377         static int last_cmd = 0;
1378
1379         if (cmd == SB_LAST)
1380                 cmd = last_cmd;
1381         else
1382                 last_cmd = cmd;
1383
1384         if ((cmd == 0) || (cmd == 1)) {
1385                 tcgetattr(0, &live);
1386                 live.c_iflag = ISTRIP | IXON | IXANY;
1387                 live.c_oflag = OPOST | ONLCR;
1388                 live.c_lflag = ISIG | NOFLSH;
1389
1390                 live.c_cc[VINTR] = 0;
1391                 live.c_cc[VQUIT] = 0;
1392
1393                 /* do we even need this stuff anymore? */
1394                 /* live.c_line=0; */
1395                 live.c_cc[VERASE] = 8;
1396                 live.c_cc[VKILL] = 24;
1397                 live.c_cc[VEOF] = 1;
1398                 live.c_cc[VEOL] = 255;
1399                 live.c_cc[VEOL2] = 0;
1400                 live.c_cc[VSTART] = 0;
1401                 tcsetattr(0, TCSADRAIN, &live);
1402         }
1403         if (cmd == 2) {
1404                 tcgetattr(0, &saved_settings);
1405         }
1406         if (cmd == 3) {
1407                 tcsetattr(0, TCSADRAIN, &saved_settings);
1408         }
1409
1410 }
1411
1412
1413 /*
1414  * display_help()  -  help text viewer
1415  */
1416 void display_help(CtdlIPC * ipc, char *name) {
1417         int i;
1418         int num_helps = sizeof(helpnames) / sizeof(char *);
1419
1420         for (i = 0; i < num_helps; ++i) {
1421                 if (!strcasecmp(name, helpnames[i])) {
1422                         fmout(screenwidth, NULL, helptexts[i], NULL, 0);
1423                         return;
1424                 }
1425         }
1426
1427         scr_printf("'%s' not found.  Enter one of:\n", name);
1428         for (i = 0; i < num_helps; ++i) {
1429                 scr_printf("  %s\n", helpnames[i]);
1430         }
1431 }
1432
1433
1434 /*
1435  * fmout() - Citadel text formatter and paginator
1436  */
1437 int fmout(int width,            /* screen width to use */
1438         FILE * fpin,            /* file to read from, or NULL to format given text */
1439         char *text,             /* text to be formatted (when fpin is NULL */
1440         FILE * fpout,           /* file to write to, or NULL to write to screen */
1441         int subst) {            /* nonzero if we should use hypertext mode */
1442         char *buffer = NULL;    /* The current message */
1443         char *word = NULL;      /* What we are about to actually print */
1444         char *e;                /* Pointer to position in text */
1445         char old = 0;           /* The previous character */
1446         int column = 0;         /* Current column */
1447         size_t i;               /* Generic counter */
1448
1449         /* Space for a single word, which can be at most screenwidth */
1450         word = (char *) calloc(1, width);
1451         if (!word) {
1452                 scr_printf("Can't alloc memory to print message: %s!\n", strerror(errno));
1453                 logoff(NULL, 3);
1454         }
1455
1456         /* Read the entire message body into memory */
1457         if (fpin) {
1458                 buffer = load_message_from_file(fpin);
1459                 if (!buffer) {
1460                         scr_printf("Can't print message: %s!\n", strerror(errno));
1461                         logoff(NULL, 3);
1462                 }
1463         } else {
1464                 buffer = text;
1465         }
1466         e = buffer;
1467
1468         /* Run the message body */
1469         while (*e) {
1470                 /* Catch characters that shouldn't be there at all */
1471                 if (*e == '\r') {
1472                         e++;
1473                         continue;
1474                 }
1475                 /* First, are we looking at a newline? */
1476                 if (*e == '\n') {
1477                         e++;
1478                         if (*e == ' ') {        /* Paragraph */
1479                                 if (fpout) {
1480                                         fprintf(fpout, "\n");
1481                                 } else {
1482                                         scr_printf("\n");
1483                                 }
1484                                 column = 0;
1485                         } else if (old != ' ') {        /* Don't print two spaces */
1486                                 if (fpout) {
1487                                         fprintf(fpout, " ");
1488                                 } else {
1489                                         scr_printf(" ");
1490                                 }
1491                                 column++;
1492                         }
1493                         old = '\n';
1494                         continue;
1495                 }
1496
1497                 /* Are we looking at a nonprintable?
1498                  * (This section is now commented out because we could be displaying
1499                  * a character set like UTF-8 or ISO-8859-1.)
1500                  if ( (*e < 32) || (*e > 126) ) {
1501                  e++;
1502                  continue;
1503                  } */
1504
1505                 /* Or are we looking at a space? */
1506                 if (*e == ' ') {
1507                         e++;
1508                         if (column >= width - 1) {
1509                                 /* Are we in the rightmost column? */
1510                                 if (fpout) {
1511                                         fprintf(fpout, "\n");
1512                                 } else {
1513                                         scr_printf("\n");
1514                                 }
1515                                 column = 0;
1516                         } else if (!(column == 0 && old == ' ')) {
1517                                 /* Eat only the first space on a line */
1518                                 if (fpout) {
1519                                         fprintf(fpout, " ");
1520                                 } else {
1521                                         scr_printf(" ");
1522                                 }
1523                                 column++;
1524                         }
1525                         /* ONLY eat the FIRST space on a line */
1526                         old = ' ';
1527                         continue;
1528                 }
1529                 old = *e;
1530
1531                 /* Read a word, slightly messy */
1532                 i = 0;
1533                 while (e[i]) {
1534                         if (!isprint(e[i]) && !isspace(e[i]))
1535                                 e[i] = ' ';
1536                         if (isspace(e[i]))
1537                                 break;
1538                         i++;
1539                 }
1540
1541                 /* We should never see these, but... slightly messy */
1542                 if (e[i] == '\t' || e[i] == '\f' || e[i] == '\v')
1543                         e[i] = ' ';
1544
1545                 /* Break up really long words */
1546                 if (i >= width) {
1547                         i = width - 1;
1548                 }
1549                 strncpy(word, e, i);
1550                 word[i] = 0;
1551
1552                 /* Decide where to print the word */
1553                 if (column + i >= width) {
1554                         /* Wrap to the next line */
1555                         if (fpout) {
1556                                 fprintf(fpout, "\n");
1557                         } else {
1558                                 scr_printf("\n");
1559                         }
1560                         column = 0;
1561                 }
1562
1563                 /* Print the word */
1564                 if (fpout) {
1565                         fprintf(fpout, "%s", word);
1566                 } else {
1567                         scr_printf("%s", word);
1568                 }
1569                 column += i;
1570                 e += i;         /* Start over with the whitepsace! */
1571         }
1572
1573         free(word);
1574         if (fpin)               /* We allocated this, remember? */
1575                 free(buffer);
1576
1577         /* Is this necessary?  It makes the output kind of spacey. */
1578         if (fpout) {
1579                 fprintf(fpout, "\n");
1580         } else {
1581                 scr_printf("\n");
1582         }
1583
1584         return sigcaught;
1585 }
1586
1587
1588 /*
1589  * support ANSI color if defined
1590  */
1591 void color(int colornum) {
1592         static int hold_color;
1593         static int current_color;
1594
1595         if (colornum == COLOR_PUSH) {
1596                 hold_color = current_color;
1597                 return;
1598         }
1599
1600         if (colornum == COLOR_POP) {
1601                 color(hold_color);
1602                 return;
1603         }
1604
1605         current_color = colornum;
1606         if (enable_color) {
1607                 /* When switching to dim white, actually output an 'original
1608                  * pair' sequence -- this looks better on black-on-white
1609                  * terminals. - Changed to ORIGINAL_PAIR as this actually
1610                  * wound up looking horrible on black-on-white terminals, not
1611                  * to mention transparent terminals.
1612                  */
1613                 if (colornum == ORIGINAL_PAIR)
1614                         printf("\033[0;39;49m");
1615                 else
1616                         printf("\033[%d;3%d;4%dm", (colornum & 8) ? 1 : 0, (colornum & 7), rc_color_use_bg);
1617
1618         }
1619 }
1620
1621 void cls(int colornum) {
1622         if (enable_color) {
1623                 printf("\033[4%dm\033[2J\033[H\033[0m", colornum ? colornum : rc_color_use_bg);
1624         }
1625 }
1626
1627
1628 /*
1629  * Detect whether ANSI color is available (answerback)
1630  */
1631 void send_ansi_detect(void) {
1632         if (rc_ansi_color == 2) {
1633                 printf("\033[c");
1634                 scr_flush();
1635                 time(&AnsiDetect);
1636         }
1637 }
1638
1639
1640 void look_for_ansi(void) {
1641         fd_set rfds;
1642         struct timeval tv;
1643         char abuf[512];
1644         time_t now;
1645         int a, rv;
1646
1647         if (rc_ansi_color == 0) {
1648                 enable_color = 0;
1649         } else if (rc_ansi_color == 1) {
1650                 enable_color = 1;
1651         } else if (rc_ansi_color == 2) {
1652
1653                 /* otherwise, do the auto-detect */
1654
1655                 strcpy(abuf, "");
1656
1657                 time(&now);
1658                 if ((now - AnsiDetect) < 2)
1659                         sleep(1);
1660
1661                 do {
1662                         FD_ZERO(&rfds);
1663                         FD_SET(0, &rfds);
1664                         tv.tv_sec = 0;
1665                         tv.tv_usec = 1;
1666
1667                         select(1, &rfds, NULL, NULL, &tv);
1668                         if (FD_ISSET(0, &rfds)) {
1669                                 abuf[strlen(abuf) + 1] = 0;
1670                                 rv = read(0, &abuf[strlen(abuf)], 1);
1671                                 if (rv < 0) {
1672                                         scr_printf("failed to read after select: %s", strerror(errno));
1673                                         break;
1674                                 }
1675                         }
1676                 } while (FD_ISSET(0, &rfds));
1677
1678                 for (a = 0; !IsEmptyStr(&abuf[a]); ++a) {
1679                         if ((abuf[a] == 27) && (abuf[a + 1] == '[')
1680                             && (abuf[a + 2] == '?')) {
1681                                 enable_color = 1;
1682                         }
1683                 }
1684         }
1685 }
1686
1687
1688 /*
1689  * Display key options (highlight hotkeys inside angle brackets)
1690  */
1691 void keyopt(char *buf) {
1692         int i;
1693
1694         color(DIM_WHITE);
1695         for (i = 0; !IsEmptyStr(&buf[i]); ++i) {
1696                 if (buf[i] == '<') {
1697                         scr_printf("%c", buf[i]);
1698                         color(BRIGHT_MAGENTA);
1699                 } else {
1700                         if (buf[i] == '>' && buf[i + 1] != '>') {
1701                                 color(DIM_WHITE);
1702                         }
1703                         scr_printf("%c", buf[i]);
1704                 }
1705         }
1706         color(DIM_WHITE);
1707 }
1708
1709
1710 /*
1711  * Present a key-menu line choice type of thing
1712  */
1713 char keymenu(char *menuprompt, char *menustring) {
1714         int i, c, a;
1715         int choices;
1716         int do_prompt = 0;
1717         char buf[1024];
1718         int ch;
1719         int display_prompt = 1;
1720
1721         choices = num_tokens(menustring, '|');
1722
1723         if (menuprompt != NULL)
1724                 do_prompt = 1;
1725         if ((menuprompt != NULL) && (IsEmptyStr(menuprompt)))
1726                 do_prompt = 0;
1727
1728         while (1) {
1729                 if (display_prompt) {
1730                         if (do_prompt) {
1731                                 scr_printf("%s ", menuprompt);
1732                         } else {
1733                                 for (i = 0; i < choices; ++i) {
1734                                         extract_token(buf, menustring, i, '|', sizeof buf);
1735                                         keyopt(buf);
1736                                         scr_printf(" ");
1737                                 }
1738                         }
1739                         scr_printf("-> ");
1740                         display_prompt = 0;
1741                 }
1742                 ch = lkey();
1743
1744                 if ((do_prompt) && (ch == '?')) {
1745                         scr_printf("\rOne of...                               ");
1746                         scr_printf("                                      \n");
1747                         for (i = 0; i < choices; ++i) {
1748                                 extract_token(buf, menustring, i, '|', sizeof buf);
1749                                 scr_printf("   ");
1750                                 keyopt(buf);
1751                                 scr_printf("\n");
1752                         }
1753                         scr_printf("\n");
1754                         display_prompt = 1;
1755                 }
1756
1757                 for (i = 0; i < choices; ++i) {
1758                         extract_token(buf, menustring, i, '|', sizeof buf);
1759                         for (c = 1; !IsEmptyStr(&buf[c]); ++c) {
1760                                 if ((ch == tolower(buf[c]))
1761                                     && (buf[c - 1] == '<')
1762                                     && (buf[c + 1] == '>')) {
1763                                         for (a = 0; !IsEmptyStr(&buf[a]); ++a) {
1764                                                 if ((a != (c - 1)) && (a != (c + 1))) {
1765                                                         scr_putc(buf[a]);
1766                                                 }
1767                                         }
1768                                         scr_printf("\n");
1769                                         return ch;
1770                                 }
1771                         }
1772                 }
1773         }
1774 }