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