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