2 * This file contains functions which implement parts of the
3 * text-mode user interface.
5 * Copyright (c) 1987-2018 by the citadel.org team
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.
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.
16 #include "textclient.h"
31 " Citadel Help Menu\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"
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"
62 " *** USE .<H>elp ? for additional help *** \n",
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"
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",
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"
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"
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"
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"
115 " This command toggles your floor mode.\n"
117 " <;G>oto FLOORNAME\n"
118 " This command causes the system to take you to the named floor.\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"
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"
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"
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",
136 " New User's Introduction to the site\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"
144 " Full help for the BBS commands can be obtained by typing <.H>elp SUMMARY\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"
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"
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"
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"
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"
197 " There are two special rooms on a Citadel that you should know about.\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"
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"
212 " These people, along with the room admins, keep the site running smoothly.\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"
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"
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"
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"
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"
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"
248 " 'There were messages posted while you were entering.'\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"
257 " '*** You have new mail'\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"
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",
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"
274 " Enter Recipient:\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"
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"
283 " Enter Recipient: Joe Schmoe @ citadrool\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",
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",
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",
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"
303 " <.> <H>elp: Displays help files. Type .H followed by a help file\n"
304 " name. You are now reading <.H>elp SUMMARY\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"
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"
315 " <.> list <Z>apped rooms Shows all rooms you've <Z>apped (forgotten)\n"
318 " Terminate (logoff) commands:\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"
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"
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"
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"
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"
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"
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"
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"
368 " Wholist commands:\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"
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"
389 " Administrative commands: \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"
416 #define IFNEXPERT if ((userflags&US_EXPERT)==0)
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;
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];
433 int next_lazy_cmd = 5;
435 extern int screenwidth, screenheight;
437 extern CtdlIPC *ipc_for_signal_handlers; /* KLUDGE cover your eyes */
438 struct citcmd *cmdlist = NULL;
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 */
449 * If an interesting key has been pressed, return its value, otherwise 0
451 char was_a_key_pressed(void) {
461 retval = select(1, &rfds, NULL, NULL, &tv);
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.
467 if ((retval > 0) && FD_ISSET(0, &rfds)) {
468 set_keepalives(KA_NO);
469 the_character = inkey();
470 set_keepalives(KA_YES);
475 return (the_character);
480 * print_instant() - print instant messages if there are any
482 void print_instant(void) {
490 char *listing = NULL;
491 int r; /* IPC result code */
493 if (instant_msgs == 0) {
501 if (IsEmptyStr(rc_exp_cmd)) {
506 while (instant_msgs != 0) {
507 r = CtdlIPCGetInstantMessage(ipc_for_signal_handlers, &listing, buf);
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);
519 localtime_r(×tamp, &stamp);
521 /* If the page is a Logoff Request, honor it. */
527 if (!IsEmptyStr(rc_exp_cmd)) {
528 outpipe = popen(rc_exp_cmd, "w");
529 if (outpipe != NULL) {
530 /* Header derived from flags */
532 fprintf(outpipe, "Please log off now, as requested ");
534 fprintf(outpipe, "Broadcast message ");
536 fprintf(outpipe, "Chat request ");
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);
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);
551 if (instant_msgs == 0)
556 /* fall back to built-in instant message display */
559 /* Header derived from flags */
561 scr_printf("Please log off now, as requested ");
563 scr_printf("Broadcast message ");
565 scr_printf("Chat request ");
567 scr_printf("Message ");
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);
575 scr_printf("at %d:%02dam", stamp.tm_hour, stamp.tm_min);
578 scr_printf(" from %s", sender);
580 /* Remote node, if any */
581 if (strncmp(ipc_for_signal_handlers->ServInfo.nodename, node, 32))
582 scr_printf(" @%s", node);
585 fmout(screenwidth, NULL, listing, NULL, 0);
589 scr_printf("\n---\n");
596 void set_keepalives(int s) {
597 keepalives_enabled = (char) s;
602 * This loop handles the "keepalive" messages sent to the server when idling.
604 static time_t idlet = 0;
605 static void really_do_keepalive(void) {
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
611 if (!ipc_for_signal_handlers)
614 /* If full keepalives are enabled, send a NOOP to the server and
615 * wait for a response.
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", "");
623 scr_printf("%s%c ", room_name, room_prompt(room_flags));
629 /* If half keepalives are enabled, send a QNOP to the server (if the
630 * server supports it) and then do nothing.
632 if ((keepalives_enabled == KA_HALF)
633 && (ipc_for_signal_handlers->ServInfo.supports_qnop > 0)) {
634 CtdlIPC_chat_send(ipc_for_signal_handlers, "QNOP");
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
643 void do_keepalive(void)
648 if ((now - idlet) < ((long) S_KEEPALIVE)) {
652 /* Do a space-backspace to keep terminal sessions from idling out */
653 scr_printf(" %c", 8);
656 really_do_keepalive();
661 { /* get a character from the keyboard, with */
662 int a; /* the watchdog timer in effect if necessary */
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.
680 tv.tv_sec = S_KEEPALIVE;
683 select(1, &rfds, NULL, NULL, &tv);
684 } while (!FD_ISSET(0, &rfds));
686 /* At this point, there's input, so fetch it.
687 * (There's a hole in the bucket...)
689 a = scr_getc(SCR_BLOCK);
702 { /* Returns 1 for yes, 0 for no */
718 /* Returns 1 for yes, 0 for no, arg is default value */
742 * Function to read a line of text from the terminal.
744 * string Pointer to string buffer
746 * noshow Echo asterisks instead of keystrokes?
747 * bs Allow backspacing out of the prompt? (returns -1 if this happens)
749 * returns: string length
751 int ctdl_getline(char *string, int lim, int noshow, int bs)
753 int pos = strlen(string);
756 if (noshow && !IsEmptyStr(string)) {
757 int num_stars = strlen(string);
758 while (num_stars--) {
762 scr_printf("%s", string);
768 if ((ch == 8) && (pos > 0)) { /* backspace */
775 else if ((ch == 8) && (pos == 0) && (bs)) { /* backspace out of the prompt */
779 else if ((ch == 23) && (pos > 0)) { /* Ctrl-W deletes a word */
780 while ((pos > 0) && !isspace(string[pos])) {
786 while ((pos > 0) && !isspace(string[pos - 1])) {
794 else if (ch == 10) { /* return */
800 else if (isprint(ch)) { /* payload characters */
801 scr_putc((noshow ? '*' : ch));
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()
814 void strprompt(char *prompt, char *str, int len)
818 scr_printf("%s", prompt);
822 ctdl_getline(str, abs(len), (len < 0), 0);
827 * boolprompt() - prompt for a yes/no, print the existing value and
828 * allow the user to press return to keep it...
830 int boolprompt(char *prompt, int prev_val)
835 scr_printf("%s ", prompt);
838 color(BRIGHT_MAGENTA);
839 scr_printf("%s", (prev_val ? "Yes" : "No"));
843 r = (yesno_d(prev_val));
849 * intprompt() - like strprompt(), except for an integer
850 * (note that it RETURNS the new value!)
852 int intprompt(char *prompt, int ival, int imin, int imax)
860 snprintf(buf, sizeof buf, "%d", i);
861 strprompt(prompt, buf, 15);
863 for (p = 0; !IsEmptyStr(&buf[p]); ++p) {
864 if ((!isdigit(buf[p]))
865 && ((buf[p] != '-') || (p != 0)))
869 scr_printf("*** Must be no less than %d.\n", imin);
871 scr_printf("*** Must be no more than %d.\n", imax);
872 } while ((i < imin) || (i > imax));
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()
881 void newprompt(char *prompt, char *str, int len)
884 color(BRIGHT_MAGENTA);
885 scr_printf("%s", prompt);
887 ctdl_getline(str, abs(len), (len < 0), 0);
893 { /* returns a lower case value */
902 * parse the citadel.rc file
904 void load_command_set(void)
909 struct citcmd *lastcmd = NULL;
913 /* first, set up some defaults for non-required variables */
915 strcpy(editor_path, "");
916 strcpy(printcmd, "");
917 strcpy(imagecmd, "");
918 strcpy(rc_username, "");
919 strcpy(rc_password, "");
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;
929 strcpy(rc_url_cmd, "");
930 strcpy(rc_open_cmd, "");
931 strcpy(rc_gotmail_cmd, "");
933 rc_encrypt = RC_DEFAULT;
936 /* now try to open the citadel.rc file */
939 if (getenv("HOME") != NULL) {
940 snprintf(buf, sizeof buf, "%s/.citadelrc", getenv("HOME"));
941 ccfile = fopen(buf, "r");
943 if (ccfile == NULL) {
944 ccfile = fopen(file_citadel_rc, "r");
946 if (ccfile == NULL) {
947 ccfile = fopen("/etc/citadel.rc", "r");
949 if (ccfile == NULL) {
950 ccfile = fopen("./citadel.rc", "r");
952 if (ccfile == NULL) {
953 perror("commands: cannot open citadel.rc");
956 while (fgets(buf, sizeof buf, ccfile) != NULL) {
957 while ((!IsEmptyStr(buf)) ? (isspace(buf[strlen(buf) - 1])) : 0)
958 buf[strlen(buf) - 1] = 0;
960 if (!strncasecmp(buf, "encrypt=", 8)) {
961 if (!strcasecmp(&buf[8], "yes")) {
965 fprintf(stderr, "citadel.rc requires encryption support but citadel is not compiled with OpenSSL");
970 else if (!strcasecmp(&buf[8], "no")) {
972 } else if (!strcasecmp(&buf[8], "default")) {
973 rc_encrypt = RC_DEFAULT;
978 if (!strncasecmp(buf, "editor=", 7)) {
979 strcpy(editor_path, &buf[7]);
982 if (!strncasecmp(buf, "printcmd=", 9))
983 strcpy(printcmd, &buf[9]);
985 if (!strncasecmp(buf, "imagecmd=", 9))
986 strcpy(imagecmd, &buf[9]);
988 if (!strncasecmp(buf, "expcmd=", 7))
989 strcpy(rc_exp_cmd, &buf[7]);
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;
999 if (!strncasecmp(buf, "beep=", 5)) {
1000 rc_exp_beep = atoi(&buf[5]);
1002 if (!strncasecmp(buf, "allow_attachments=", 18)) {
1003 rc_allow_attachments = atoi(&buf[18]);
1005 if (!strncasecmp(buf, "idle_threshold=", 15)) {
1006 rc_idle_threshold = atol(&buf[15]);
1008 if (!strncasecmp(buf, "remember_passwords=", 19)) {
1009 rc_remember_passwords = atoi(&buf[19]);
1011 if (!strncasecmp(buf, "display_message_numbers=", 24)) {
1012 rc_display_message_numbers = atoi(&buf[24]);
1014 if (!strncasecmp(buf, "force_mail_prompts=", 19)) {
1015 rc_force_mail_prompts = atoi(&buf[19]);
1017 if (!strncasecmp(buf, "ansi_color=", 11)) {
1018 if (!strncasecmp(&buf[11], "on", 2))
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 */
1025 if (!strncasecmp(buf, "status_line=", 12)) {
1026 if (!strncasecmp(&buf[12], "on", 2))
1027 enable_status_line = 1;
1029 if (!strncasecmp(buf, "use_background=", 15)) {
1030 if (!strncasecmp(&buf[15], "on", 2))
1031 rc_color_use_bg = 9;
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 */
1039 if (!strncasecmp(buf, "username=", 9))
1040 strcpy(rc_username, &buf[9]);
1042 if (!strncasecmp(buf, "password=", 9))
1043 strcpy(rc_password, &buf[9]);
1045 if (!strncasecmp(buf, "urlcmd=", 7))
1046 strcpy(rc_url_cmd, &buf[7]);
1048 if (!strncasecmp(buf, "opencmd=", 7))
1049 strcpy(rc_open_cmd, &buf[8]);
1051 if (!strncasecmp(buf, "gotmailcmd=", 11))
1052 strcpy(rc_gotmail_cmd, &buf[11]);
1054 if (!strncasecmp(buf, "cmd=", 4)) {
1055 strcpy(buf, &buf[4]);
1057 cptr = (struct citcmd *) malloc(sizeof(struct citcmd));
1059 cptr->c_cmdnum = atoi(buf);
1060 for (d = strlen(buf); d >= 0; --d)
1063 strcpy(buf, &buf[b + 1]);
1065 cptr->c_axlevel = atoi(buf);
1066 for (d = strlen(buf); d >= 0; --d)
1069 strcpy(buf, &buf[b + 1]);
1071 for (a = 0; a < 5; ++a)
1072 cptr->c_keys[a][0] = 0;
1076 buf[strlen(buf) + 1] = 0;
1077 while (!IsEmptyStr(buf)) {
1079 for (d = strlen(buf); d >= 0; --d)
1082 strncpy(cptr->c_keys[a], buf, b);
1083 cptr->c_keys[a][b] = 0;
1085 strcpy(buf, &buf[b + 1]);
1092 if (cmdlist == NULL)
1095 lastcmd->next = cptr;
1105 * return the key associated with a command
1107 char keycmd(char *cmdstr)
1111 for (a = 0; !IsEmptyStr(&cmdstr[a]); ++a)
1112 if (cmdstr[a] == '&')
1113 return (tolower(cmdstr[a + 1]));
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
1122 char *cmd_expand(char *strbuf, int mode)
1125 static char exp[64];
1128 strcpy(exp, strbuf);
1130 for (a = 0; exp[a]; ++a) {
1131 if (strbuf[a] == '&') {
1133 /* dont echo these non mnemonic command keys */
1134 int noecho = strbuf[a + 1] == '<' || strbuf[a + 1] == '>' || strbuf[a + 1] == '+' || strbuf[a + 1] == '-';
1137 strcpy(&exp[a], &exp[a + 1 + noecho]);
1141 strcpy(buf, &exp[a + 2]);
1147 if (!strncmp(&exp[a], "^r", 2)) {
1149 strcpy(&exp[a], room_name);
1150 strcat(exp, &buf[a + 2]);
1152 if (!strncmp(&exp[a], "^c", 2)) {
1154 strcpy(&exp[a + 1], &exp[a + 2]);
1164 * Comparison function to determine if entered commands match a
1165 * command loaded from the config file.
1167 int cmdmatch(char *cmdbuf, struct citcmd *cptr, int ncomp)
1178 for (a = 0; a < ncomp; ++a) {
1179 if ((tolower(cmdbuf[a]) != keycmd(cptr->c_keys[a]))
1180 || (cptr->c_axlevel > cmdax))
1188 * This function returns 1 if a given command requires a string input
1190 int requires_string(struct citcmd *cptr, int ncomp)
1195 strcpy(buf, cptr->c_keys[ncomp - 1]);
1196 for (a = 0; !IsEmptyStr(&buf[a]); ++a) {
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.
1209 int getcmd(CtdlIPC * ipc, char *argbuf)
1218 struct citcmd *cptr;
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.
1227 /* Switch color support on or off if we're in user mode */
1228 if (rc_ansi_color == 3) {
1229 if (userflags & US_COLOR)
1234 /* if we're running in idiot mode, display a cute little menu */
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");
1246 scr_printf(" (Type .Help SUMMARY for extended commands, <X> to hide this menu)\n");
1247 scr_printf("-----------------------------------------------------------------------\n");
1253 for (a = 0; a < 5; ++a)
1255 /* now the room prompt... */
1256 ok_to_interrupt = 1;
1257 color(BRIGHT_WHITE);
1258 scr_printf("\n%s", room_name);
1260 scr_printf("%c ", room_prompt(room_flags));
1264 ok_to_interrupt = 0;
1266 /* Handle the backspace key, but only if there's something
1267 * to backspace over...
1269 if ((ch == 8) && (cmdpos > 0)) {
1270 back(cmdspaces[cmdpos - 1] + 1);
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)
1279 if (this_lazy_cmd == 5)
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));
1287 return (this_lazy_cmd);
1291 return (this_lazy_cmd);
1293 /* Otherwise, process the command */
1294 cmdbuf[cmdpos] = tolower(ch);
1296 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1297 if (cmdmatch(cmdbuf, cptr, cmdpos + 1)) {
1299 scr_printf("%s", cmd_expand(cptr->c_keys[cmdpos], 0));
1300 cmdspaces[cmdpos] = strlen(cmd_expand(cptr->c_keys[cmdpos], 0));
1302 if ((cptr->c_keys[cmdpos + 1]) != 0)
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)) {
1313 ctdl_getline(argbuf, 64, 0, 0);
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"
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))
1330 /* If this command is "read new"
1331 * then the next lazy-command (space bar)
1334 if (cptr->c_cmdnum == 13)
1337 return (cptr->c_cmdnum);
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));
1355 scr_printf("\n%s%c ", room_name, room_prompt(room_flags));
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));
1375 * set tty modes. commands are:
1377 * 01- set to Citadel mode
1378 * 2 - save current settings for later restoral
1379 * 3 - restore saved settings
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;
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;
1398 live.c_cc[VINTR] = 0;
1399 live.c_cc[VQUIT] = 0;
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);
1412 tcgetattr(0, &saved_settings);
1415 tcsetattr(0, TCSADRAIN, &saved_settings);
1421 // this is the old version which uses sgtty.h instead of termios.h
1423 void stty_ctdl(int cmd)
1424 { /* BSD version of stty_ctdl() */
1426 static struct sgttyb saved_settings;
1427 static int last_cmd = 0;
1434 if ((cmd == 0) || (cmd == 1)) {
1436 live.sg_flags |= CBREAK;
1437 live.sg_flags |= CRMOD;
1438 live.sg_flags |= NL1;
1439 live.sg_flags &= ~ECHO;
1441 live.sg_flags |= NOFLSH;
1445 gtty(0, &saved_settings);
1448 stty(0, &saved_settings);
1455 * display_help() - help text viewer
1457 void display_help(CtdlIPC * ipc, char *name)
1460 int num_helps = sizeof(helpnames) / sizeof(char *);
1462 for (i = 0; i < num_helps; ++i) {
1463 if (!strcasecmp(name, helpnames[i])) {
1464 fmout(screenwidth, NULL, helptexts[i], NULL, 0);
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]);
1477 * fmout() - Citadel text formatter and paginator
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 */
1491 /* Space for a single word, which can be at most screenwidth */
1492 word = (char *) calloc(1, width);
1494 scr_printf("Can't alloc memory to print message: %s!\n", strerror(errno));
1498 /* Read the entire message body into memory */
1500 buffer = load_message_from_file(fpin);
1502 scr_printf("Can't print message: %s!\n", strerror(errno));
1510 /* Run the message body */
1512 /* Catch characters that shouldn't be there at all */
1517 /* First, are we looking at a newline? */
1520 if (*e == ' ') { /* Paragraph */
1522 fprintf(fpout, "\n");
1527 } else if (old != ' ') { /* Don't print two spaces */
1529 fprintf(fpout, " ");
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) ) {
1547 /* Or are we looking at a space? */
1550 if (column >= width - 1) {
1551 /* Are we in the rightmost column? */
1553 fprintf(fpout, "\n");
1558 } else if (!(column == 0 && old == ' ')) {
1559 /* Eat only the first space on a line */
1561 fprintf(fpout, " ");
1567 /* ONLY eat the FIRST space on a line */
1573 /* Read a word, slightly messy */
1576 if (!isprint(e[i]) && !isspace(e[i]))
1583 /* We should never see these, but... slightly messy */
1584 if (e[i] == '\t' || e[i] == '\f' || e[i] == '\v')
1587 /* Break up really long words */
1588 /* TODO: auto-hyphenation someday? */
1591 strncpy(word, e, i);
1594 /* Decide where to print the word */
1595 if (column + i >= width) {
1596 /* Wrap to the next line */
1598 fprintf(fpout, "\n");
1605 /* Print the word */
1607 fprintf(fpout, "%s", word);
1609 scr_printf("%s", word);
1612 e += i; /* Start over with the whitepsace! */
1616 if (fpin) /* We allocated this, remember? */
1619 /* Is this necessary? It makes the output kind of spacey. */
1621 fprintf(fpout, "\n");
1631 * support ANSI color if defined
1633 void color(int colornum)
1635 static int hold_color;
1636 static int current_color;
1638 if (colornum == COLOR_PUSH) {
1639 hold_color = current_color;
1643 if (colornum == COLOR_POP) {
1648 current_color = colornum;
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.
1656 if (colornum == ORIGINAL_PAIR)
1657 printf("\033[0;39;49m");
1659 printf("\033[%d;3%d;4%dm", (colornum & 8) ? 1 : 0, (colornum & 7), rc_color_use_bg);
1664 void cls(int colornum)
1667 printf("\033[4%dm\033[2J\033[H\033[0m", colornum ? colornum : rc_color_use_bg);
1673 * Detect whether ANSI color is available (answerback)
1675 void send_ansi_detect(void)
1677 if (rc_ansi_color == 2) {
1684 void look_for_ansi(void)
1692 if (rc_ansi_color == 0) {
1694 } else if (rc_ansi_color == 1) {
1696 } else if (rc_ansi_color == 2) {
1698 /* otherwise, do the auto-detect */
1703 if ((now - AnsiDetect) < 2)
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);
1717 scr_printf("failed to read after select: %s", strerror(errno));
1721 } while (FD_ISSET(0, &rfds));
1723 for (a = 0; !IsEmptyStr(&abuf[a]); ++a) {
1724 if ((abuf[a] == 27) && (abuf[a + 1] == '[')
1725 && (abuf[a + 2] == '?')) {
1734 * Display key options (highlight hotkeys inside angle brackets)
1736 void keyopt(char *buf)
1741 for (i = 0; !IsEmptyStr(&buf[i]); ++i) {
1742 if (buf[i] == '<') {
1743 scr_printf("%c", buf[i]);
1744 color(BRIGHT_MAGENTA);
1746 if (buf[i] == '>' && buf[i + 1] != '>') {
1749 scr_printf("%c", buf[i]);
1758 * Present a key-menu line choice type of thing
1760 char keymenu(char *menuprompt, char *menustring)
1767 int display_prompt = 1;
1769 choices = num_tokens(menustring, '|');
1771 if (menuprompt != NULL)
1773 if ((menuprompt != NULL) && (IsEmptyStr(menuprompt)))
1777 if (display_prompt) {
1779 scr_printf("%s ", menuprompt);
1781 for (i = 0; i < choices; ++i) {
1782 extract_token(buf, menustring, i, '|', sizeof buf);
1792 if ((do_prompt) && (ch == '?')) {
1793 scr_printf("\rOne of... ");
1795 for (i = 0; i < choices; ++i) {
1796 extract_token(buf, menustring, i, '|', sizeof buf);
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))) {