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