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