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