indent -kr -i8 -l132 on everything in textclient
[citadel] / textclient / commands.c
1 /*
2  * This file contains functions which implement parts of the
3  * text-mode user interface.
4  *
5  * Copyright (c) 1987-2018 by the citadel.org team
6  *
7  * This program is open source software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License version 3.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  */
15
16 #include "textclient.h"
17
18 char *helpnames[] = {
19         "help",
20         "admin",
21         "floors",
22         "intro",
23         "mail",
24         "network",
25         "software"
26 };
27
28 char *helptexts[] = {
29
30         "                          Citadel Help Menu\n"
31             "  \n"
32             " ?         Help. (Typing a '?' will give you a menu almost anywhere)\n"
33             " A         Abandon this room where you stopped reading, goto next room.\n"
34             " C         Chat (multiuser chat, where available)\n"
35             " D         Prints directory, if there is one in the current room.\n"
36             " E         Enter a message.\n"
37             " F         Read all messages in the room, forward.\n"
38             " G         Goto next room which has UNREAD messages.\n"
39             " H         Help. Same as '?'\n"
40             " I         Reads the Information file for this room.\n"
41             " K         List of Known rooms.\n"
42             " L         Reads the last five messages in the room.\n"
43             " N         Reads all new messages in the room.\n"
44             " O         Reads all old messages, backwards.\n"
45             " P         Page another user (send an instant message)\n"
46             " R         Reads all messages in the room, in reverse order.\n"
47             " S         Skips current room without making its messages old.\n"
48             " T         Terminate (logout)\n"
49             " U         Ungoto (returns to the last room you were in)\n"
50             " W         Displays who is currently logged in.\n"
51             " X         Toggle eXpert mode (menus and help blurbs on/off)\n"
52             " Z         Zap (forget) room. (Removes the room from your list)\n"
53             " + -       Goto next, previous room on current floor.\n"
54             " > <       Goto next, previous floor.\n"
55             " *         Enter any locally installed 'doors'.\n"
56             "   \n"
57             " In addition, there are dot commands. You hit the . (dot), then press the\n"
58             "first letter of each word of the command. As you hit the letters, the words\n"
59             "pop onto your screen. Exceptions: after you hit .Help or .Goto, the remainder\n"
60             "of the command is a help file name or room name.\n" "    \n" "      *** 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
437 struct citcmd *cmdlist = NULL;
438
439
440 /* these variables are local to this module */
441 char keepalives_enabled = KA_YES;       /* send NOOPs to server when idle */
442 int ok_to_interrupt = 0;        /* print instant msgs asynchronously */
443 time_t AnsiDetect;              /* when did we send the detect code? */
444 int enable_color = 0;           /* nonzero for ANSI color */
445
446
447
448
449 /*
450  * If an interesting key has been pressed, return its value, otherwise 0
451  */
452 char was_a_key_pressed(void)
453 {
454         fd_set rfds;
455         struct timeval tv;
456         int the_character;
457         int retval;
458
459         FD_ZERO(&rfds);
460         FD_SET(0, &rfds);
461         tv.tv_sec = 0;
462         tv.tv_usec = 0;
463         retval = select(1, &rfds, NULL, NULL, &tv);
464
465         /* Careful!  Disable keepalives during keyboard polling; we're probably
466          * in the middle of a data transfer from the server, in which case
467          * sending a NOOP would throw the client protocol out of sync.
468          */
469         if ((retval > 0) && FD_ISSET(0, &rfds)) {
470                 set_keepalives(KA_NO);
471                 the_character = inkey();
472                 set_keepalives(KA_YES);
473         } else {
474                 the_character = 0;
475         }
476         return (the_character);
477 }
478
479
480
481
482
483 /*
484  * print_instant()  -  print instant messages if there are any
485  */
486 void print_instant(void)
487 {
488         char buf[1024];
489         FILE *outpipe;
490         time_t timestamp;
491         struct tm stamp;
492         int flags = 0;
493         char sender[64];
494         char node[64];
495         char *listing = NULL;
496         int r;                  /* IPC result code */
497
498         if (instant_msgs == 0)
499                 return;
500
501         if (rc_exp_beep) {
502                 ctdl_beep();
503         }
504         if (IsEmptyStr(rc_exp_cmd)) {
505                 color(BRIGHT_RED);
506                 scr_printf("\r---");
507         }
508
509         while (instant_msgs != 0) {
510                 r = CtdlIPCGetInstantMessage(ipc_for_signal_handlers, &listing, buf);
511                 if (r / 100 != 1)
512                         return;
513
514                 instant_msgs = extract_int(buf, 0);
515                 timestamp = extract_long(buf, 1);
516                 flags = extract_int(buf, 2);
517                 extract_token(sender, buf, 3, '|', sizeof sender);
518                 extract_token(node, buf, 4, '|', sizeof node);
519                 strcpy(last_paged, sender);
520
521                 localtime_r(&timestamp, &stamp);
522
523                 /* If the page is a Logoff Request, honor it. */
524                 if (flags & 2) {
525                         termn8 = 1;
526                         return;
527                 }
528
529                 if (!IsEmptyStr(rc_exp_cmd)) {
530                         outpipe = popen(rc_exp_cmd, "w");
531                         if (outpipe != NULL) {
532                                 /* Header derived from flags */
533                                 if (flags & 2)
534                                         fprintf(outpipe, "Please log off now, as requested ");
535                                 else if (flags & 1)
536                                         fprintf(outpipe, "Broadcast message ");
537                                 else if (flags & 4)
538                                         fprintf(outpipe, "Chat request ");
539                                 else
540                                         fprintf(outpipe, "Message ");
541                                 /* Timestamp.  Can this be improved? */
542                                 if (stamp.tm_hour == 0 || stamp.tm_hour == 12)
543                                         fprintf(outpipe, "at 12:%02d%cm", stamp.tm_min, stamp.tm_hour ? 'p' : 'a');
544                                 else if (stamp.tm_hour > 12)    /* pm */
545                                         fprintf(outpipe, "at %d:%02dpm", stamp.tm_hour - 12, stamp.tm_min);
546                                 else    /* am */
547                                         fprintf(outpipe, "at %d:%02dam", stamp.tm_hour, stamp.tm_min);
548                                 fprintf(outpipe, " from %s", sender);
549                                 if (strncmp(ipc_for_signal_handlers->ServInfo.nodename, node, 32))
550                                         fprintf(outpipe, " @%s", node);
551                                 fprintf(outpipe, ":\n%s\n", listing);
552                                 pclose(outpipe);
553                                 if (instant_msgs == 0)
554                                         return;
555                                 continue;
556                         }
557                 }
558                 /* fall back to built-in instant message display */
559                 scr_printf("\n");
560
561                 /* Header derived from flags */
562                 if (flags & 2)
563                         scr_printf("Please log off now, as requested ");
564                 else if (flags & 1)
565                         scr_printf("Broadcast message ");
566                 else if (flags & 4)
567                         scr_printf("Chat request ");
568                 else
569                         scr_printf("Message ");
570
571                 /* Timestamp.  Can this be improved? */
572                 if (stamp.tm_hour == 0 || stamp.tm_hour == 12)  /* 12am/12pm */
573                         scr_printf("at 12:%02d%cm", stamp.tm_min, stamp.tm_hour ? 'p' : 'a');
574                 else if (stamp.tm_hour > 12)    /* pm */
575                         scr_printf("at %d:%02dpm", stamp.tm_hour - 12, stamp.tm_min);
576                 else            /* am */
577                         scr_printf("at %d:%02dam", stamp.tm_hour, stamp.tm_min);
578
579                 /* Sender */
580                 scr_printf(" from %s", sender);
581
582                 /* Remote node, if any */
583                 if (strncmp(ipc_for_signal_handlers->ServInfo.nodename, node, 32))
584                         scr_printf(" @%s", node);
585
586                 scr_printf(":\n");
587                 fmout(screenwidth, NULL, listing, NULL, 0);
588                 free(listing);
589
590         }
591         scr_printf("\n---\n");
592         color(BRIGHT_WHITE);
593
594
595 }
596
597
598 void set_keepalives(int s)
599 {
600         keepalives_enabled = (char) s;
601 }
602
603 /* 
604  * This loop handles the "keepalive" messages sent to the server when idling.
605  */
606
607 static time_t idlet = 0;
608 static void really_do_keepalive(void)
609 {
610
611         time(&idlet);
612
613         /* This may sometimes get called before we are actually connected
614          * to the server.  Don't do anything if we aren't connected. -IO
615          */
616         if (!ipc_for_signal_handlers)
617                 return;
618
619         /* If full keepalives are enabled, send a NOOP to the server and
620          * wait for a response.
621          */
622         if (keepalives_enabled == KA_YES) {
623                 CtdlIPCNoop(ipc_for_signal_handlers);
624                 if (instant_msgs > 0) {
625                         if (ok_to_interrupt == 1) {
626                                 scr_printf("\r%64s\r", "");
627                                 print_instant();
628                                 scr_printf("%s%c ", room_name, room_prompt(room_flags));
629                                 scr_flush();
630                         }
631                 }
632         }
633
634         /* If half keepalives are enabled, send a QNOP to the server (if the
635          * server supports it) and then do nothing.
636          */
637         if ((keepalives_enabled == KA_HALF)
638             && (ipc_for_signal_handlers->ServInfo.supports_qnop > 0)) {
639                 CtdlIPC_chat_send(ipc_for_signal_handlers, "QNOP");
640         }
641 }
642
643 /* I changed this from static to not because I need to call it from
644  * screen.c, either that or make something in screen.c not static.
645  * Fix it how you like. Why all the staticness? stu
646  */
647 void do_keepalive(void)
648 {
649         time_t now;
650
651         time(&now);
652         if ((now - idlet) < ((long) S_KEEPALIVE)) {
653                 return;
654         }
655
656         /* Do a space-backspace to keep terminal sessions from idling out */
657         scr_printf(" %c", 8);
658         scr_flush();
659
660         really_do_keepalive();
661 }
662
663
664
665 int inkey(void)
666 {                               /* get a character from the keyboard, with   */
667         int a;                  /* the watchdog timer in effect if necessary */
668         fd_set rfds;
669         struct timeval tv;
670         time_t start_time;
671
672         scr_flush();
673         time(&start_time);
674
675         do {
676                 /* This loop waits for keyboard input.  If the keepalive
677                  * timer expires, it sends a keepalive to the server if
678                  * necessary and then waits again.
679                  */
680                 do {
681                         do_keepalive();
682
683                         FD_ZERO(&rfds);
684                         FD_SET(0, &rfds);
685                         tv.tv_sec = S_KEEPALIVE;
686                         tv.tv_usec = 0;
687
688                         select(1, &rfds, NULL, NULL, &tv);
689                 } while (!FD_ISSET(0, &rfds));
690
691                 /* At this point, there's input, so fetch it.
692                  * (There's a hole in the bucket...)
693                  */
694                 a = scr_getc(SCR_BLOCK);
695                 if (a == 127) {
696                         a = 8;
697                 }
698                 if (a == 13) {
699                         a = 10;
700                 }
701         } while (a == 0);
702         return (a);
703 }
704
705
706 int yesno(void)
707 {                               /* Returns 1 for yes, 0 for no */
708         int a;
709         while (1) {
710                 a = inkey();
711                 a = tolower(a);
712                 if (a == 'y') {
713                         scr_printf("Yes\n");
714                         return (1);
715                 }
716                 if (a == 'n') {
717                         scr_printf("No\n");
718                         return (0);
719                 }
720         }
721 }
722
723 /* Returns 1 for yes, 0 for no, arg is default value */
724 int yesno_d(int d)
725 {
726         int a;
727         while (1) {
728                 a = inkey();
729                 a = tolower(a);
730                 if (a == 10)
731                         a = (d ? 'y' : 'n');
732                 if (a == 'y') {
733                         scr_printf("Yes\n");
734                         return (1);
735                 }
736                 if (a == 'n') {
737                         scr_printf("No\n");
738                         return (0);
739                 }
740         }
741 }
742
743
744
745
746 /*
747  * Function to read a line of text from the terminal.
748  *
749  * string               Pointer to string buffer
750  * lim                  Maximum length
751  * noshow               Echo asterisks instead of keystrokes?
752  * bs                   Allow backspacing out of the prompt? (returns -1 if this happens)
753  *
754  * returns: string length
755  */
756 int ctdl_getline(char *string, int lim, int noshow, int bs)
757 {
758         int pos = strlen(string);
759         int ch;
760
761         if (noshow && !IsEmptyStr(string)) {
762                 int num_stars = strlen(string);
763                 while (num_stars--) {
764                         scr_putc('*');
765                 }
766         } else {
767                 scr_printf("%s", string);
768         }
769
770         while (1) {
771                 ch = inkey();
772
773                 if ((ch == 8) && (pos > 0)) {   /* backspace */
774                         --pos;
775                         scr_putc(8);
776                         scr_putc(32);
777                         scr_putc(8);
778                 }
779
780                 else if ((ch == 8) && (pos == 0) && (bs)) {     /* backspace out of the prompt */
781                         return (-1);
782                 }
783
784                 else if ((ch == 23) && (pos > 0)) {     /* Ctrl-W deletes a word */
785                         while ((pos > 0) && !isspace(string[pos])) {
786                                 --pos;
787                                 scr_putc(8);
788                                 scr_putc(32);
789                                 scr_putc(8);
790                         }
791                         while ((pos > 0) && !isspace(string[pos - 1])) {
792                                 --pos;
793                                 scr_putc(8);
794                                 scr_putc(32);
795                                 scr_putc(8);
796                         }
797                 }
798
799                 else if (ch == 10) {    /* return */
800                         string[pos] = 0;
801                         scr_printf("\n");
802                         return (pos);
803                 }
804
805                 else if (isprint(ch)) { /* payload characters */
806                         scr_putc((noshow ? '*' : ch));
807                         string[pos] = ch;
808                         ++pos;
809                 }
810         }
811 }
812
813
814 /* 
815  * newprompt()          prompt for a string, print the existing value, and
816  *                      allow the user to press return to keep it...
817  *                      If len is negative, pass the "noshow" flag to ctdl_getline()
818  */
819 void strprompt(char *prompt, char *str, int len)
820 {
821         print_instant();
822         color(DIM_WHITE);
823         scr_printf("%s", prompt);
824         color(DIM_WHITE);
825         scr_printf(": ");
826         color(BRIGHT_CYAN);
827         ctdl_getline(str, abs(len), (len < 0), 0);
828         color(DIM_WHITE);
829 }
830
831 /*
832  * boolprompt()  -  prompt for a yes/no, print the existing value and
833  *                  allow the user to press return to keep it...
834  */
835 int boolprompt(char *prompt, int prev_val)
836 {
837         int r;
838
839         color(DIM_WHITE);
840         scr_printf("%s ", prompt);
841         color(DIM_MAGENTA);
842         scr_printf("[");
843         color(BRIGHT_MAGENTA);
844         scr_printf("%s", (prev_val ? "Yes" : "No"));
845         color(DIM_MAGENTA);
846         scr_printf("]: ");
847         color(BRIGHT_CYAN);
848         r = (yesno_d(prev_val));
849         color(DIM_WHITE);
850         return r;
851 }
852
853 /* 
854  * intprompt()  -  like strprompt(), except for an integer
855  *                 (note that it RETURNS the new value!)
856  */
857 int intprompt(char *prompt, int ival, int imin, int imax)
858 {
859         char buf[16];
860         int i;
861         int p;
862
863         do {
864                 i = ival;
865                 snprintf(buf, sizeof buf, "%d", i);
866                 strprompt(prompt, buf, 15);
867                 i = atoi(buf);
868                 for (p = 0; !IsEmptyStr(&buf[p]); ++p) {
869                         if ((!isdigit(buf[p]))
870                             && ((buf[p] != '-') || (p != 0)))
871                                 i = imin - 1;
872                 }
873                 if (i < imin)
874                         scr_printf("*** Must be no less than %d.\n", imin);
875                 if (i > imax)
876                         scr_printf("*** Must be no more than %d.\n", imax);
877         } while ((i < imin) || (i > imax));
878         return (i);
879 }
880
881 /* 
882  * newprompt()          prompt for a string with no existing value
883  *                      (clears out string buffer first)
884  *                      If len is negative, pass the "noshow" flag to ctdl_getline()
885  */
886 void newprompt(char *prompt, char *str, int len)
887 {
888         str[0] = 0;
889         color(BRIGHT_MAGENTA);
890         scr_printf("%s", prompt);
891         color(DIM_MAGENTA);
892         ctdl_getline(str, abs(len), (len < 0), 0);
893         color(DIM_WHITE);
894 }
895
896
897 int lkey(void)
898 {                               /* returns a lower case value */
899         int a;
900         a = inkey();
901         if (isupper(a))
902                 a = tolower(a);
903         return (a);
904 }
905
906 /*
907  * parse the citadel.rc file
908  */
909 void load_command_set(void)
910 {
911         FILE *ccfile;
912         char buf[1024];
913         struct citcmd *cptr;
914         struct citcmd *lastcmd = NULL;
915         int a, d;
916         int b = 0;
917
918         /* first, set up some defaults for non-required variables */
919
920         strcpy(editor_path, "");
921         strcpy(printcmd, "");
922         strcpy(imagecmd, "");
923         strcpy(rc_username, "");
924         strcpy(rc_password, "");
925         rc_floor_mode = 0;
926         rc_exp_beep = 1;
927         rc_allow_attachments = 0;
928         rc_remember_passwords = 0;
929         strcpy(rc_exp_cmd, "");
930         rc_display_message_numbers = 0;
931         rc_force_mail_prompts = 0;
932         rc_ansi_color = 0;
933         rc_color_use_bg = 0;
934         strcpy(rc_url_cmd, "");
935         strcpy(rc_open_cmd, "");
936         strcpy(rc_gotmail_cmd, "");
937 #ifdef HAVE_OPENSSL
938         rc_encrypt = RC_DEFAULT;
939 #endif
940
941         /* now try to open the citadel.rc file */
942
943         ccfile = NULL;
944         if (getenv("HOME") != NULL) {
945                 snprintf(buf, sizeof buf, "%s/.citadelrc", getenv("HOME"));
946                 ccfile = fopen(buf, "r");
947         }
948         if (ccfile == NULL) {
949                 ccfile = fopen(file_citadel_rc, "r");
950         }
951         if (ccfile == NULL) {
952                 ccfile = fopen("/etc/citadel.rc", "r");
953         }
954         if (ccfile == NULL) {
955                 ccfile = fopen("./citadel.rc", "r");
956         }
957         if (ccfile == NULL) {
958                 perror("commands: cannot open citadel.rc");
959                 logoff(NULL, 3);
960         }
961         while (fgets(buf, sizeof buf, ccfile) != NULL) {
962                 while ((!IsEmptyStr(buf)) ? (isspace(buf[strlen(buf) - 1])) : 0)
963                         buf[strlen(buf) - 1] = 0;
964
965                 if (!strncasecmp(buf, "encrypt=", 8)) {
966                         if (!strcasecmp(&buf[8], "yes")) {
967 #ifdef HAVE_OPENSSL
968                                 rc_encrypt = RC_YES;
969 #else
970                                 fprintf(stderr, "citadel.rc requires encryption support but citadel is not compiled with OpenSSL");
971                                 logoff(NULL, 3);
972 #endif
973                         }
974 #ifdef HAVE_OPENSSL
975                         else if (!strcasecmp(&buf[8], "no")) {
976                                 rc_encrypt = RC_NO;
977                         } else if (!strcasecmp(&buf[8], "default")) {
978                                 rc_encrypt = RC_DEFAULT;
979                         }
980 #endif
981                 }
982
983                 if (!strncasecmp(buf, "editor=", 7)) {
984                         strcpy(editor_path, &buf[7]);
985                 }
986
987                 if (!strncasecmp(buf, "printcmd=", 9))
988                         strcpy(printcmd, &buf[9]);
989
990                 if (!strncasecmp(buf, "imagecmd=", 9))
991                         strcpy(imagecmd, &buf[9]);
992
993                 if (!strncasecmp(buf, "expcmd=", 7))
994                         strcpy(rc_exp_cmd, &buf[7]);
995
996                 if (!strncasecmp(buf, "use_floors=", 11)) {
997                         if (!strcasecmp(&buf[11], "yes"))
998                                 rc_floor_mode = RC_YES;
999                         if (!strcasecmp(&buf[11], "no"))
1000                                 rc_floor_mode = RC_NO;
1001                         if (!strcasecmp(&buf[11], "default"))
1002                                 rc_floor_mode = RC_DEFAULT;
1003                 }
1004                 if (!strncasecmp(buf, "beep=", 5)) {
1005                         rc_exp_beep = atoi(&buf[5]);
1006                 }
1007                 if (!strncasecmp(buf, "allow_attachments=", 18)) {
1008                         rc_allow_attachments = atoi(&buf[18]);
1009                 }
1010                 if (!strncasecmp(buf, "idle_threshold=", 15)) {
1011                         rc_idle_threshold = atol(&buf[15]);
1012                 }
1013                 if (!strncasecmp(buf, "remember_passwords=", 19)) {
1014                         rc_remember_passwords = atoi(&buf[19]);
1015                 }
1016                 if (!strncasecmp(buf, "display_message_numbers=", 24)) {
1017                         rc_display_message_numbers = atoi(&buf[24]);
1018                 }
1019                 if (!strncasecmp(buf, "force_mail_prompts=", 19)) {
1020                         rc_force_mail_prompts = atoi(&buf[19]);
1021                 }
1022                 if (!strncasecmp(buf, "ansi_color=", 11)) {
1023                         if (!strncasecmp(&buf[11], "on", 2))
1024                                 rc_ansi_color = 1;
1025                         if (!strncasecmp(&buf[11], "auto", 4))
1026                                 rc_ansi_color = 2;      /* autodetect */
1027                         if (!strncasecmp(&buf[11], "user", 4))
1028                                 rc_ansi_color = 3;      /* user config */
1029                 }
1030                 if (!strncasecmp(buf, "status_line=", 12)) {
1031                         if (!strncasecmp(&buf[12], "on", 2))
1032                                 enable_status_line = 1;
1033                 }
1034                 if (!strncasecmp(buf, "use_background=", 15)) {
1035                         if (!strncasecmp(&buf[15], "on", 2))
1036                                 rc_color_use_bg = 9;
1037                 }
1038                 if (!strncasecmp(buf, "prompt_control=", 15)) {
1039                         if (!strncasecmp(&buf[15], "on", 2))
1040                                 rc_prompt_control = 1;
1041                         if (!strncasecmp(&buf[15], "user", 4))
1042                                 rc_prompt_control = 3;  /* user config */
1043                 }
1044                 if (!strncasecmp(buf, "username=", 9))
1045                         strcpy(rc_username, &buf[9]);
1046
1047                 if (!strncasecmp(buf, "password=", 9))
1048                         strcpy(rc_password, &buf[9]);
1049
1050                 if (!strncasecmp(buf, "urlcmd=", 7))
1051                         strcpy(rc_url_cmd, &buf[7]);
1052
1053                 if (!strncasecmp(buf, "opencmd=", 7))
1054                         strcpy(rc_open_cmd, &buf[8]);
1055
1056                 if (!strncasecmp(buf, "gotmailcmd=", 11))
1057                         strcpy(rc_gotmail_cmd, &buf[11]);
1058
1059                 if (!strncasecmp(buf, "cmd=", 4)) {
1060                         strcpy(buf, &buf[4]);
1061
1062                         cptr = (struct citcmd *) malloc(sizeof(struct citcmd));
1063
1064                         cptr->c_cmdnum = atoi(buf);
1065                         for (d = strlen(buf); d >= 0; --d)
1066                                 if (buf[d] == ',')
1067                                         b = d;
1068                         strcpy(buf, &buf[b + 1]);
1069
1070                         cptr->c_axlevel = atoi(buf);
1071                         for (d = strlen(buf); d >= 0; --d)
1072                                 if (buf[d] == ',')
1073                                         b = d;
1074                         strcpy(buf, &buf[b + 1]);
1075
1076                         for (a = 0; a < 5; ++a)
1077                                 cptr->c_keys[a][0] = 0;
1078
1079                         a = 0;
1080                         b = 0;
1081                         buf[strlen(buf) + 1] = 0;
1082                         while (!IsEmptyStr(buf)) {
1083                                 b = strlen(buf);
1084                                 for (d = strlen(buf); d >= 0; --d)
1085                                         if (buf[d] == ',')
1086                                                 b = d;
1087                                 strncpy(cptr->c_keys[a], buf, b);
1088                                 cptr->c_keys[a][b] = 0;
1089                                 if (buf[b] == ',')
1090                                         strcpy(buf, &buf[b + 1]);
1091                                 else
1092                                         strcpy(buf, "");
1093                                 ++a;
1094                         }
1095
1096                         cptr->next = NULL;
1097                         if (cmdlist == NULL)
1098                                 cmdlist = cptr;
1099                         else
1100                                 lastcmd->next = cptr;
1101                         lastcmd = cptr;
1102                 }
1103         }
1104         fclose(ccfile);
1105 }
1106
1107
1108
1109 /*
1110  * return the key associated with a command
1111  */
1112 char keycmd(char *cmdstr)
1113 {
1114         int a;
1115
1116         for (a = 0; !IsEmptyStr(&cmdstr[a]); ++a)
1117                 if (cmdstr[a] == '&')
1118                         return (tolower(cmdstr[a + 1]));
1119         return (0);
1120 }
1121
1122
1123 /*
1124  * Output the string from a key command without the ampersand
1125  * "mode" should be set to 0 for normal or 1 for <C>ommand key highlighting
1126  */
1127 char *cmd_expand(char *strbuf, int mode)
1128 {
1129         int a;
1130         static char exp[64];
1131         char buf[1024];
1132
1133         strcpy(exp, strbuf);
1134
1135         for (a = 0; exp[a]; ++a) {
1136                 if (strbuf[a] == '&') {
1137
1138                         /* dont echo these non mnemonic command keys */
1139                         int noecho = strbuf[a + 1] == '<' || strbuf[a + 1] == '>' || strbuf[a + 1] == '+' || strbuf[a + 1] == '-';
1140
1141                         if (mode == 0) {
1142                                 strcpy(&exp[a], &exp[a + 1 + noecho]);
1143                         }
1144                         if (mode == 1) {
1145                                 exp[a] = '<';
1146                                 strcpy(buf, &exp[a + 2]);
1147                                 exp[a + 2] = '>';
1148                                 exp[a + 3] = 0;
1149                                 strcat(exp, buf);
1150                         }
1151                 }
1152                 if (!strncmp(&exp[a], "^r", 2)) {
1153                         strcpy(buf, exp);
1154                         strcpy(&exp[a], room_name);
1155                         strcat(exp, &buf[a + 2]);
1156                 }
1157                 if (!strncmp(&exp[a], "^c", 2)) {
1158                         exp[a] = ',';
1159                         strcpy(&exp[a + 1], &exp[a + 2]);
1160                 }
1161         }
1162
1163         return (exp);
1164 }
1165
1166
1167
1168 /*
1169  * Comparison function to determine if entered commands match a
1170  * command loaded from the config file.
1171  */
1172 int cmdmatch(char *cmdbuf, struct citcmd *cptr, int ncomp)
1173 {
1174         int a;
1175         int cmdax;
1176
1177         cmdax = 0;
1178         if (is_room_aide)
1179                 cmdax = 1;
1180         if (axlevel >= 6)
1181                 cmdax = 2;
1182
1183         for (a = 0; a < ncomp; ++a) {
1184                 if ((tolower(cmdbuf[a]) != keycmd(cptr->c_keys[a]))
1185                     || (cptr->c_axlevel > cmdax))
1186                         return (0);
1187         }
1188         return (1);
1189 }
1190
1191
1192 /*
1193  * This function returns 1 if a given command requires a string input
1194  */
1195 int requires_string(struct citcmd *cptr, int ncomp)
1196 {
1197         int a;
1198         char buf[64];
1199
1200         strcpy(buf, cptr->c_keys[ncomp - 1]);
1201         for (a = 0; !IsEmptyStr(&buf[a]); ++a) {
1202                 if (buf[a] == ':')
1203                         return (1);
1204         }
1205         return (0);
1206 }
1207
1208
1209 /*
1210  * Input a command at the main prompt.
1211  * This function returns an integer command number.  If the command prompts
1212  * for a string then it is placed in the supplied buffer.
1213  */
1214 int getcmd(CtdlIPC * ipc, char *argbuf)
1215 {
1216         char cmdbuf[5];
1217         int cmdspaces[5];
1218         int cmdpos;
1219         int ch;
1220         int a;
1221         int got;
1222         int this_lazy_cmd;
1223         struct citcmd *cptr;
1224
1225         /*
1226          * Starting a new command now, so set sigcaught to 0.  This variable
1227          * is set to nonzero (usually NEXT_KEY or STOP_KEY) if a command has
1228          * been interrupted by a keypress.
1229          */
1230         sigcaught = 0;
1231
1232         /* Switch color support on or off if we're in user mode */
1233         if (rc_ansi_color == 3) {
1234                 if (userflags & US_COLOR)
1235                         enable_color = 1;
1236                 else
1237                         enable_color = 0;
1238         }
1239         /* if we're running in idiot mode, display a cute little menu */
1240
1241         IFNEXPERT {
1242                 scr_printf("-----------------------------------------------------------------------\n");
1243                 scr_printf("Room cmds:    <K>nown rooms, <G>oto next room, <.G>oto a specific room,\n");
1244                 scr_printf("              <S>kip this room, <A>bandon this room, <Z>ap this room,\n");
1245                 scr_printf("              <U>ngoto (move back)\n");
1246                 scr_printf("Message cmds: <N>ew msgs, <F>orward read, <R>everse read, <O>ld msgs,\n");
1247                 scr_printf("              <L>ast five msgs, <E>nter a message\n");
1248                 scr_printf("General cmds: <?> help, <T>erminate, <C>hat, <W>ho is online\n");
1249                 scr_printf("Misc:         <X> toggle eXpert mode, <D>irectory\n");
1250                 scr_printf("\n");
1251                 scr_printf(" (Type .Help SUMMARY for extended commands, <X> to hide this menu)\n");
1252                 scr_printf("-----------------------------------------------------------------------\n");
1253         }
1254
1255         print_instant();
1256         strcpy(argbuf, "");
1257         cmdpos = 0;
1258         for (a = 0; a < 5; ++a)
1259                 cmdbuf[a] = 0;
1260         /* now the room prompt... */
1261         ok_to_interrupt = 1;
1262         color(BRIGHT_WHITE);
1263         scr_printf("\n%s", room_name);
1264         color(DIM_WHITE);
1265         scr_printf("%c ", room_prompt(room_flags));
1266
1267         while (1) {
1268                 ch = inkey();
1269                 ok_to_interrupt = 0;
1270
1271                 /* Handle the backspace key, but only if there's something
1272                  * to backspace over...
1273                  */
1274                 if ((ch == 8) && (cmdpos > 0)) {
1275                         back(cmdspaces[cmdpos - 1] + 1);
1276                         cmdbuf[cmdpos] = 0;
1277                         --cmdpos;
1278                 }
1279                 /* Spacebar invokes "lazy traversal" commands */
1280                 if ((ch == 32) && (cmdpos == 0)) {
1281                         this_lazy_cmd = next_lazy_cmd;
1282                         if (this_lazy_cmd == 13)
1283                                 next_lazy_cmd = 5;
1284                         if (this_lazy_cmd == 5)
1285                                 next_lazy_cmd = 13;
1286                         for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1287                                 if (cptr->c_cmdnum == this_lazy_cmd) {
1288                                         for (a = 0; a < 5; ++a)
1289                                                 if (cptr->c_keys[a][0] != 0)
1290                                                         scr_printf("%s ", cmd_expand(cptr->c_keys[a], 0));
1291                                         scr_printf("\n");
1292                                         return (this_lazy_cmd);
1293                                 }
1294                         }
1295                         scr_printf("\n");
1296                         return (this_lazy_cmd);
1297                 }
1298                 /* Otherwise, process the command */
1299                 cmdbuf[cmdpos] = tolower(ch);
1300
1301                 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1302                         if (cmdmatch(cmdbuf, cptr, cmdpos + 1)) {
1303
1304                                 scr_printf("%s", cmd_expand(cptr->c_keys[cmdpos], 0));
1305                                 cmdspaces[cmdpos] = strlen(cmd_expand(cptr->c_keys[cmdpos], 0));
1306                                 if (cmdpos < 4)
1307                                         if ((cptr->c_keys[cmdpos + 1]) != 0)
1308                                                 scr_putc(' ');
1309                                 ++cmdpos;
1310                         }
1311                 }
1312
1313                 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1314                         if (cmdmatch(cmdbuf, cptr, 5)) {
1315                                 /* We've found our command. */
1316                                 if (requires_string(cptr, cmdpos)) {
1317                                         argbuf[0] = 0;
1318                                         ctdl_getline(argbuf, 64, 0, 0);
1319                                 } else {
1320                                         scr_printf("\n");
1321                                 }
1322
1323                                 /* If this command is one that changes rooms,
1324                                  * then the next lazy-command (space bar)
1325                                  * should be "read new" instead of "goto"
1326                                  */
1327                                 if ((cptr->c_cmdnum == 5)
1328                                     || (cptr->c_cmdnum == 6)
1329                                     || (cptr->c_cmdnum == 47)
1330                                     || (cptr->c_cmdnum == 52)
1331                                     || (cptr->c_cmdnum == 16)
1332                                     || (cptr->c_cmdnum == 20))
1333                                         next_lazy_cmd = 13;
1334
1335                                 /* If this command is "read new"
1336                                  * then the next lazy-command (space bar)
1337                                  * should be "goto"
1338                                  */
1339                                 if (cptr->c_cmdnum == 13)
1340                                         next_lazy_cmd = 5;
1341
1342                                 return (cptr->c_cmdnum);
1343
1344                         }
1345                 }
1346
1347                 if (ch == '?') {
1348                         scr_printf("\rOne of ...                         \n");
1349                         for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1350                                 if (cmdmatch(cmdbuf, cptr, cmdpos)) {
1351                                         for (a = 0; a < 5; ++a) {
1352                                                 keyopt(cmd_expand(cptr->c_keys[a], 1));
1353                                                 scr_printf(" ");
1354                                         }
1355                                         scr_printf("\n");
1356                                 }
1357                         }
1358                         sigcaught = 0;
1359
1360                         scr_printf("\n%s%c ", room_name, room_prompt(room_flags));
1361                         got = 0;
1362                         for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1363                                 if ((got == 0) && (cmdmatch(cmdbuf, cptr, cmdpos))) {
1364                                         for (a = 0; a < cmdpos; ++a) {
1365                                                 scr_printf("%s ", cmd_expand(cptr->c_keys[a], 0));
1366                                         }
1367                                         got = 1;
1368                                 }
1369                         }
1370                 }
1371         }
1372
1373 }
1374
1375
1376
1377
1378
1379 /*
1380  * set tty modes.  commands are:
1381  * 
1382  * 01- set to Citadel mode
1383  * 2 - save current settings for later restoral
1384  * 3 - restore saved settings
1385  */
1386 void stty_ctdl(int cmd)
1387 {                               /* SysV version of stty_ctdl() */
1388         struct termios live;
1389         static struct termios saved_settings;
1390         static int last_cmd = 0;
1391
1392         if (cmd == SB_LAST)
1393                 cmd = last_cmd;
1394         else
1395                 last_cmd = cmd;
1396
1397         if ((cmd == 0) || (cmd == 1)) {
1398                 tcgetattr(0, &live);
1399                 live.c_iflag = ISTRIP | IXON | IXANY;
1400                 live.c_oflag = OPOST | ONLCR;
1401                 live.c_lflag = ISIG | NOFLSH;
1402
1403                 live.c_cc[VINTR] = 0;
1404                 live.c_cc[VQUIT] = 0;
1405
1406                 /* do we even need this stuff anymore? */
1407                 /* live.c_line=0; */
1408                 live.c_cc[VERASE] = 8;
1409                 live.c_cc[VKILL] = 24;
1410                 live.c_cc[VEOF] = 1;
1411                 live.c_cc[VEOL] = 255;
1412                 live.c_cc[VEOL2] = 0;
1413                 live.c_cc[VSTART] = 0;
1414                 tcsetattr(0, TCSADRAIN, &live);
1415         }
1416         if (cmd == 2) {
1417                 tcgetattr(0, &saved_settings);
1418         }
1419         if (cmd == 3) {
1420                 tcsetattr(0, TCSADRAIN, &saved_settings);
1421         }
1422
1423 }
1424
1425
1426 // this is the old version which uses sgtty.h instead of termios.h
1427 #if 0
1428 void stty_ctdl(int cmd)
1429 {                               /* BSD version of stty_ctdl() */
1430         struct sgttyb live;
1431         static struct sgttyb saved_settings;
1432         static int last_cmd = 0;
1433
1434         if (cmd == SB_LAST)
1435                 cmd = last_cmd;
1436         else
1437                 last_cmd = cmd;
1438
1439         if ((cmd == 0) || (cmd == 1)) {
1440                 gtty(0, &live);
1441                 live.sg_flags |= CBREAK;
1442                 live.sg_flags |= CRMOD;
1443                 live.sg_flags |= NL1;
1444                 live.sg_flags &= ~ECHO;
1445                 if (cmd == 1)
1446                         live.sg_flags |= NOFLSH;
1447                 stty(0, &live);
1448         }
1449         if (cmd == 2) {
1450                 gtty(0, &saved_settings);
1451         }
1452         if (cmd == 3) {
1453                 stty(0, &saved_settings);
1454         }
1455 }
1456 #endif
1457
1458
1459 /*
1460  * display_help()  -  help text viewer
1461  */
1462 void display_help(CtdlIPC * ipc, char *name)
1463 {
1464         int i;
1465         int num_helps = sizeof(helpnames) / sizeof(char *);
1466
1467         for (i = 0; i < num_helps; ++i) {
1468                 if (!strcasecmp(name, helpnames[i])) {
1469                         fmout(screenwidth, NULL, helptexts[i], NULL, 0);
1470                         return;
1471                 }
1472         }
1473
1474         scr_printf("'%s' not found.  Enter one of:\n", name);
1475         for (i = 0; i < num_helps; ++i) {
1476                 scr_printf("  %s\n", helpnames[i]);
1477         }
1478 }
1479
1480
1481 /*
1482  * fmout() - Citadel text formatter and paginator
1483  */
1484 int fmout(int width,            /* screen width to use */
1485           FILE * fpin,          /* file to read from, or NULL to format given text */
1486           char *text,           /* text to be formatted (when fpin is NULL */
1487           FILE * fpout,         /* file to write to, or NULL to write to screen */
1488           int subst) {          /* nonzero if we should use hypertext mode */
1489         char *buffer = NULL;    /* The current message */
1490         char *word = NULL;      /* What we are about to actually print */
1491         char *e;                /* Pointer to position in text */
1492         char old = 0;           /* The previous character */
1493         int column = 0;         /* Current column */
1494         size_t i;               /* Generic counter */
1495
1496         /* Space for a single word, which can be at most screenwidth */
1497         word = (char *) calloc(1, width);
1498         if (!word) {
1499                 scr_printf("Can't alloc memory to print message: %s!\n", strerror(errno));
1500                 logoff(NULL, 3);
1501         }
1502
1503         /* Read the entire message body into memory */
1504         if (fpin) {
1505                 buffer = load_message_from_file(fpin);
1506                 if (!buffer) {
1507                         scr_printf("Can't print message: %s!\n", strerror(errno));
1508                         logoff(NULL, 3);
1509                 }
1510         } else {
1511                 buffer = text;
1512         }
1513         e = buffer;
1514
1515         /* Run the message body */
1516         while (*e) {
1517                 /* Catch characters that shouldn't be there at all */
1518                 if (*e == '\r') {
1519                         e++;
1520                         continue;
1521                 }
1522                 /* First, are we looking at a newline? */
1523                 if (*e == '\n') {
1524                         e++;
1525                         if (*e == ' ') {        /* Paragraph */
1526                                 if (fpout) {
1527                                         fprintf(fpout, "\n");
1528                                 } else {
1529                                         scr_printf("\n");
1530                                 }
1531                                 column = 0;
1532                         } else if (old != ' ') {        /* Don't print two spaces */
1533                                 if (fpout) {
1534                                         fprintf(fpout, " ");
1535                                 } else {
1536                                         scr_printf(" ");
1537                                 }
1538                                 column++;
1539                         }
1540                         old = '\n';
1541                         continue;
1542                 }
1543
1544                 /* Are we looking at a nonprintable?
1545                  * (This section is now commented out because we could be displaying
1546                  * a character set like UTF-8 or ISO-8859-1.)
1547                  if ( (*e < 32) || (*e > 126) ) {
1548                  e++;
1549                  continue;
1550                  } */
1551
1552                 /* Or are we looking at a space? */
1553                 if (*e == ' ') {
1554                         e++;
1555                         if (column >= width - 1) {
1556                                 /* Are we in the rightmost column? */
1557                                 if (fpout) {
1558                                         fprintf(fpout, "\n");
1559                                 } else {
1560                                         scr_printf("\n");
1561                                 }
1562                                 column = 0;
1563                         } else if (!(column == 0 && old == ' ')) {
1564                                 /* Eat only the first space on a line */
1565                                 if (fpout) {
1566                                         fprintf(fpout, " ");
1567                                 } else {
1568                                         scr_printf(" ");
1569                                 }
1570                                 column++;
1571                         }
1572                         /* ONLY eat the FIRST space on a line */
1573                         old = ' ';
1574                         continue;
1575                 }
1576                 old = *e;
1577
1578                 /* Read a word, slightly messy */
1579                 i = 0;
1580                 while (e[i]) {
1581                         if (!isprint(e[i]) && !isspace(e[i]))
1582                                 e[i] = ' ';
1583                         if (isspace(e[i]))
1584                                 break;
1585                         i++;
1586                 }
1587
1588                 /* We should never see these, but... slightly messy */
1589                 if (e[i] == '\t' || e[i] == '\f' || e[i] == '\v')
1590                         e[i] = ' ';
1591
1592                 /* Break up really long words */
1593                 /* TODO: auto-hyphenation someday? */
1594                 if (i >= width)
1595                         i = width - 1;
1596                 strncpy(word, e, i);
1597                 word[i] = 0;
1598
1599                 /* Decide where to print the word */
1600                 if (column + i >= width) {
1601                         /* Wrap to the next line */
1602                         if (fpout) {
1603                                 fprintf(fpout, "\n");
1604                         } else {
1605                                 scr_printf("\n");
1606                         }
1607                         column = 0;
1608                 }
1609
1610                 /* Print the word */
1611                 if (fpout) {
1612                         fprintf(fpout, "%s", word);
1613                 } else {
1614                         scr_printf("%s", word);
1615                 }
1616                 column += i;
1617                 e += i;         /* Start over with the whitepsace! */
1618         }
1619
1620         free(word);
1621         if (fpin)               /* We allocated this, remember? */
1622                 free(buffer);
1623
1624         /* Is this necessary?  It makes the output kind of spacey. */
1625         if (fpout) {
1626                 fprintf(fpout, "\n");
1627         } else {
1628                 scr_printf("\n");
1629         }
1630
1631         return sigcaught;
1632 }
1633
1634
1635 /*
1636  * support ANSI color if defined
1637  */
1638 void color(int colornum)
1639 {
1640         static int hold_color;
1641         static int current_color;
1642
1643         if (colornum == COLOR_PUSH) {
1644                 hold_color = current_color;
1645                 return;
1646         }
1647
1648         if (colornum == COLOR_POP) {
1649                 color(hold_color);
1650                 return;
1651         }
1652
1653         current_color = colornum;
1654         if (enable_color) {
1655                 /* When switching to dim white, actually output an 'original
1656                  * pair' sequence -- this looks better on black-on-white
1657                  * terminals. - Changed to ORIGINAL_PAIR as this actually
1658                  * wound up looking horrible on black-on-white terminals, not
1659                  * to mention transparent terminals.
1660                  */
1661                 if (colornum == ORIGINAL_PAIR)
1662                         printf("\033[0;39;49m");
1663                 else
1664                         printf("\033[%d;3%d;4%dm", (colornum & 8) ? 1 : 0, (colornum & 7), rc_color_use_bg);
1665
1666         }
1667 }
1668
1669 void cls(int colornum)
1670 {
1671         if (enable_color) {
1672                 printf("\033[4%dm\033[2J\033[H\033[0m", colornum ? colornum : rc_color_use_bg);
1673         }
1674 }
1675
1676
1677 /*
1678  * Detect whether ANSI color is available (answerback)
1679  */
1680 void send_ansi_detect(void)
1681 {
1682         if (rc_ansi_color == 2) {
1683                 printf("\033[c");
1684                 scr_flush();
1685                 time(&AnsiDetect);
1686         }
1687 }
1688
1689 void look_for_ansi(void)
1690 {
1691         fd_set rfds;
1692         struct timeval tv;
1693         char abuf[512];
1694         time_t now;
1695         int a, rv;
1696
1697         if (rc_ansi_color == 0) {
1698                 enable_color = 0;
1699         } else if (rc_ansi_color == 1) {
1700                 enable_color = 1;
1701         } else if (rc_ansi_color == 2) {
1702
1703                 /* otherwise, do the auto-detect */
1704
1705                 strcpy(abuf, "");
1706
1707                 time(&now);
1708                 if ((now - AnsiDetect) < 2)
1709                         sleep(1);
1710
1711                 do {
1712                         FD_ZERO(&rfds);
1713                         FD_SET(0, &rfds);
1714                         tv.tv_sec = 0;
1715                         tv.tv_usec = 1;
1716
1717                         select(1, &rfds, NULL, NULL, &tv);
1718                         if (FD_ISSET(0, &rfds)) {
1719                                 abuf[strlen(abuf) + 1] = 0;
1720                                 rv = read(0, &abuf[strlen(abuf)], 1);
1721                                 if (rv < 0) {
1722                                         scr_printf("failed to read after select: %s", strerror(errno));
1723                                         break;
1724                                 }
1725                         }
1726                 } while (FD_ISSET(0, &rfds));
1727
1728                 for (a = 0; !IsEmptyStr(&abuf[a]); ++a) {
1729                         if ((abuf[a] == 27) && (abuf[a + 1] == '[')
1730                             && (abuf[a + 2] == '?')) {
1731                                 enable_color = 1;
1732                         }
1733                 }
1734         }
1735 }
1736
1737
1738 /*
1739  * Display key options (highlight hotkeys inside angle brackets)
1740  */
1741 void keyopt(char *buf)
1742 {
1743         int i;
1744
1745         color(DIM_WHITE);
1746         for (i = 0; !IsEmptyStr(&buf[i]); ++i) {
1747                 if (buf[i] == '<') {
1748                         scr_printf("%c", buf[i]);
1749                         color(BRIGHT_MAGENTA);
1750                 } else {
1751                         if (buf[i] == '>' && buf[i + 1] != '>') {
1752                                 color(DIM_WHITE);
1753                         }
1754                         scr_printf("%c", buf[i]);
1755                 }
1756         }
1757         color(DIM_WHITE);
1758 }
1759
1760
1761
1762 /*
1763  * Present a key-menu line choice type of thing
1764  */
1765 char keymenu(char *menuprompt, char *menustring)
1766 {
1767         int i, c, a;
1768         int choices;
1769         int do_prompt = 0;
1770         char buf[1024];
1771         int ch;
1772         int display_prompt = 1;
1773
1774         choices = num_tokens(menustring, '|');
1775
1776         if (menuprompt != NULL)
1777                 do_prompt = 1;
1778         if ((menuprompt != NULL) && (IsEmptyStr(menuprompt)))
1779                 do_prompt = 0;
1780
1781         while (1) {
1782                 if (display_prompt) {
1783                         if (do_prompt) {
1784                                 scr_printf("%s ", menuprompt);
1785                         } else {
1786                                 for (i = 0; i < choices; ++i) {
1787                                         extract_token(buf, menustring, i, '|', sizeof buf);
1788                                         keyopt(buf);
1789                                         scr_printf(" ");
1790                                 }
1791                         }
1792                         scr_printf("-> ");
1793                         display_prompt = 0;
1794                 }
1795                 ch = lkey();
1796
1797                 if ((do_prompt) && (ch == '?')) {
1798                         scr_printf("\rOne of...                               ");
1799                         scr_printf("                                      \n");
1800                         for (i = 0; i < choices; ++i) {
1801                                 extract_token(buf, menustring, i, '|', sizeof buf);
1802                                 scr_printf("   ");
1803                                 keyopt(buf);
1804                                 scr_printf("\n");
1805                         }
1806                         scr_printf("\n");
1807                         display_prompt = 1;
1808                 }
1809
1810                 for (i = 0; i < choices; ++i) {
1811                         extract_token(buf, menustring, i, '|', sizeof buf);
1812                         for (c = 1; !IsEmptyStr(&buf[c]); ++c) {
1813                                 if ((ch == tolower(buf[c]))
1814                                     && (buf[c - 1] == '<')
1815                                     && (buf[c + 1] == '>')) {
1816                                         for (a = 0; !IsEmptyStr(&buf[a]); ++a) {
1817                                                 if ((a != (c - 1)) && (a != (c + 1))) {
1818                                                         scr_putc(buf[a]);
1819                                                 }
1820                                         }
1821                                         scr_printf("\n");
1822                                         return ch;
1823                                 }
1824                         }
1825                 }
1826         }
1827 }