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"
30 " Citadel Help Menu\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"
57 " In addition, there are dot commands. You hit the . (dot), then press the\n"
58 "first letter of each word of the command. As you hit the letters, the words\n"
59 "pop onto your screen. Exceptions: after you hit .Help or .Goto, the remainder\n"
60 "of the command is a help file name or room name.\n" " \n" " *** USE .<H>elp ? for additional help *** \n",
62 "The following commands are available only to Admins. A subset of these\n"
63 "commands are available to room aides when they are currently in the room\n"
64 "they are room aide for.\n"
66 " <.> <A>dmin <K>ill this room (Delete the current room)\n"
67 " <.> <A>dmin <E>dit this room (Edit the current room's parameters)\n"
68 " <.> <A>dmin <W>ho knows room (List users with access to this room)\n"
69 " <.> <A>dmin edit <U>ser (Change user's access level, password, etc.)\n"
70 " <.> <A>dmin <V>alidate new users (Process new user registrations)\n"
71 " <.> <A>dmin enter <I>nfo file (Create/change this room's banner)\n"
72 " <.> <A>dmin <R>oom <I>nvite user (Grant access to an invitation-only room)\n"
73 " <.> <A>dmin <R>oom <K>ick out user (Revoke access to an invitation-only room)\n"
74 " <.> <A>dmin <F>ile <D>elete (Delete a file from the room's directory)\n"
75 " <.> <A>dmin <F>ile <S>end over net (Transmit a file to another node)\n"
76 " <.> <A>dmin <F>ile <M>ove (Move a file to another room's directory)\n"
77 " <.> <A>dmin <M>essage edit: (Edit system banners)\n"
78 " <.> <A>dmin <P>ost (Post a message on behalf of another user)\n"
79 " <.> <A>dmin <S>ystem configuration <G>eneral (Edit global site config)\n"
80 " <.> <A>dmin <S>ystem configuration <I>nternet (Edit Internet domains)\n"
81 " <.> <A>dmin <S>ystem configuration check <M>essage base (Internal checks)\n"
82 " <.> <A>dmin <S>ystem configuration <N>etwork (Netting with other Citadels)\n"
83 " <.> <A>dmin <S>ystem configuration network <F>ilter list\n"
84 " <.> <A>dmin <T>erminate server <N>ow (Shut down Citadel server now)\n"
85 " <.> <A>dmin <T>erminate server <S>cheduled (Shut down Citadel server later)\n"
86 " <.> <A>dmin mailing <L>ist recipients (For mailing list rooms)\n"
87 " <.> <A>dmin mailing list <D>igest recipients (For mailing list rooms)\n"
88 " <.> <A>dmin <N>etwork room sharing (Replication with other Citadels)\n"
89 " \n" " In addition, the <M>ove and <D>elete commands are available at the\n" "message prompt.\n",
93 " Floors in Citadel are used to group rooms into related subject areas,\n"
94 "just as rooms are used to group messages into manageable groups.\n"
96 " You, as a user, do NOT have to use floors. If you choose not to, you suffer\n"
97 "no penalty; you will not lose access to any rooms. You may use .EC or ;C (the\n"
98 "latter is easier to use) to decide if you want to use floors. Feel free to\n"
101 " Floor options are accessed two ways. First, if you are in floor mode, the\n"
102 "<G>oto and <S>kip commands take you to the next room with new messages on the\n"
103 "current floor; if there are none left, then the system will automatically\n"
104 "switch floors (and let you know) and put you in the first room with new messages\n"
105 "on that level. (Notice that your pattern of basic use of Citadel therefore\n"
106 "doesn't really change.)\n"
108 " Direct access to floor options is via the use of a ';' command.\n"
109 "The following commands are currently available (more can be\n"
110 "added if needed):\n"
113 " This command toggles your floor mode.\n"
115 " <;G>oto FLOORNAME\n"
116 " This command causes the system to take you to the named floor.\n"
118 " <;K>nown rooms on floors\n"
119 " List all rooms on all floors. This is a very readable way to get a list of\n"
120 "all rooms on the system.\n"
122 " <;S>kip FLOORNAME\n"
123 " This command causes the system to mark all rooms on the current floor as\n"
124 "Skipped and takes you to the floor that you specify.\n"
126 " <;Z>Forget floor\n"
127 " This command causes you to forget all the rooms currently on the current\n"
128 "floor. Unfortunately, it doesn't apply to rooms that are subsequently created\n"
129 "or moved to this floor. (Sorry.)\n"
131 " Feel free to experiment, you can't hurt yourself or the system with the\n"
132 "floor stuff unless you ZForget a floor by accident.\n",
134 " New User's Introduction to the site\n"
136 " This is an introduction to the Citadel BBS concept. It is intended\n"
137 "for new users so that they can more easily become acquainted to using\n"
138 "Citadel when accessing it in the form of a text-based BBS. Of\n"
139 "course, old users might learn something new each time they read\n"
142 " Full help for the BBS commands can be obtained by typing <.H>elp SUMMARY\n"
144 " The CITADEL BBS room concept\n"
145 " ----------------------------\n"
146 " The term BBS stands for 'Bulletin Board System'. The analogy is\n"
147 "appropriate: one posts messages so that others may read them. In\n"
148 "order to organize the posts, people can post in different areas of the\n"
149 "BBS, called rooms.\n"
150 " In order to post in a certain room, you need to be 'in' that room.\n"
151 "Your current prompt is usually the room that you are in, followed the\n"
152 "greater-than-sign, such as:\n"
156 " The easiest way to traverse the room structure is with the 'Goto'\n"
157 "command, on the 'G' key. Pressing 'G' will take you to the next room\n"
158 "in the 'march list' (see below) that has new messages in it. You can\n"
159 "read these new messages with the 'N' key.\n"
160 " Once you've 'Gotoed' every room in the system (or all of the ones\n"
161 "you choose to read) you return to the 'Lobby,' the first and last room\n"
162 "in the system. If new messages get posted to rooms you've already\n"
163 "read during your session you will be brought BACK to those rooms so\n"
164 "you can read them.\n"
168 " All the room names are stored in a march list, which is just a\n"
169 "list containing all the room names. When you <G>oto or <S>kip a\n"
170 "room, you are placed in the next room in your march list THAT HAS NEW\n"
171 "MESSAGES. If you have no new messages in any of the rooms on your\n"
172 "march list, you will keep going to the Lobby>. You can choose not to\n"
173 "read certain rooms (that don't interest you) by 'Z'apping them. When\n"
174 "you <Z>ap a room, you are merely deleting it from your march list (but\n"
175 "not from anybody else's).\n"
177 " You can use the <.G>oto (note the period before the G. You can also use\n"
178 "<J>ump on some systems) to go to any room in the\n"
179 "system. You don't have to type in the complete name of a room to\n"
180 "'jump' to it; you merely need to type in enough to distinguish it from\n"
181 "the other rooms. Left-aligned matches carry a heavier weight, so if you\n"
182 "typed (for example) '.Goto TECH', you might be taken to a room called\n"
183 "'Tech Area>' even if it found a room called 'Biotech/Ethics>' first.\n"
185 " To return to a room you have previously <Z>apped, use the <.G>oto command\n"
186 "to enter it, and it will be re-inserted into your march list. In the case\n"
187 "of returning to Zapped rooms, you must type the room name in its entirety.\n"
188 "REMEMBER, rooms with no new messages will not show on your\n"
189 "march list! You must <.G>oto to a room with no new messages.\n"
190 "Incidentally, you cannot change the order of the rooms on your march list.\n"
191 "It's the same for everybody.\n"
195 " There are two special rooms on a Citadel that you should know about.\n"
197 " The first is the Lobby>. It's used for system announcements and other\n"
198 "such administrativia. You cannot <Z>ap the Lobby>. Each time you first\n"
199 "login, you will be placed in the Lobby>.\n"
201 " The second is Mail>. In Mail>, when you post a messages, you are\n"
202 "prompted to enter the screen name of the person who you want to send the\n"
203 "message to. Only the person who you send the message to can read the\n"
204 "message. NO ONE else can read it, not even the admins. Mail> is the\n"
205 "first room on the march list, and is un-<Z>appable, so you can be sure\n"
206 "that the person will get the message.\n"
210 " These people, along with the room admins, keep the site running smoothly.\n"
212 " Among the many things that admins do are: create rooms, delete\n"
213 "rooms, set access levels, invite users, check registration, grant\n"
214 "room admin status, and countless other things. They have access to the\n"
215 "Aide> room, a special room only for admins.\n"
217 " If you enter a mail message to 'Sysop' it will be placed in the\n"
218 "Aide> room so that the next admin online will read it and deal with it.\n"
219 "Admins cannot <Z>ap rooms. All the rooms are always on each admin's\n"
220 "march list. Admins can read *any* and *every* room, but they *CAN* *NOT*\n"
221 "read other users' Mail!\n"
225 " Room admins are granted special privileges in specific rooms.\n"
226 "They are *NOT* true system admins; their power extends only over the\n"
227 "rooms that they control, and they answer to the system admins.\n"
229 " A room admin's job is to keep the topic of the their room on track,\n"
230 "with nudges in the right direction now and then. A room admin can also\n"
231 "move an off topic post to another room, or delete a post, if he/she\n"
232 "feels it is necessary. \n"
234 " Currently, very few rooms have room admins. Most rooms do not need\n"
235 "their own specific room admin. Being a room admin requires a certain\n"
236 "amount of trust, due to the additional privileges granted.\n"
238 " Citadel messages\n"
239 " ----------------\n"
240 " Most of the time, the BBS code does not print a lot of messages\n"
241 "to your screen. This is a great benefit once you become familiar\n"
242 "with the system, because you do not have endless menus and screens\n"
243 "to navigate through. nevertheless, there are some messages which you\n"
244 "might see from time to time.\n"
246 " 'There were messages posted while you were entering.'\n"
248 " This is also known as 'simulposting.' When you start entering a \n"
249 "message, the system knows where you last left off. When you save\n"
250 "your message, the system checks to see if any messages were entered\n"
251 "while you were typing. This is so that you know whether you need\n"
252 "to go back and re-read the last few messages. This message may appear\n"
255 " '*** You have new mail'\n"
257 " This message is essentially the same as the above message, but can\n"
258 "appear at any time. It simply means that new mail has arrived for you while\n"
259 "you are logged in. Simply go to the Mail> room to read it.\n"
263 " The <W>ho command shows you the names of all users who are currently\n"
264 "online. It also shows you the name of the room they are currently in. If\n"
265 "they are in any type of private room, however, the room name will simply\n"
266 "display as '<private room>'. Along with this information is displayed the\n"
267 "name of the host computer the user is logged in from.\n",
269 "To send mail on this system, go to the Mail> room (using the command .G Mail)\n"
270 "and press E to enter a message. You will be prompted with:\n"
272 " Enter Recipient:\n"
274 " At this point you may enter the name of another user on the system. Private\n"
275 "mail is only readable by the sender and recipient. There is no need to delete\n"
276 "mail after it is read; it will scroll out automatically.\n"
278 " To send mail to another user on the Citadel network, simply type the\n"
279 "user's name, followed by @ and then the system name. For example,\n"
281 " Enter Recipient: Joe Schmoe @ citadrool\n"
283 " If your account is enabled for Internet mail, you can also send email to\n"
284 "anyone on the Internet here. Simply enter their address at the prompt:\n"
285 " \n" " Enter Recipient: ajc@herring.fishnet.com\n",
287 " Welcome to the network. Messages entered in a network room will appear in\n"
288 "that room on all other systems carrying it (The name of the room, however,\n" "may be different on other systems).\n",
290 " Citadel is the premier 'online community' (i.e. Bulletin Board System)\n"
291 "software. It runs on all POSIX-compliant systems, including Linux. It is an\n"
292 "advanced client/server application, and is being actively maintained.\n"
293 " \n" " For more info, visit UNCENSORED! BBS at uncensored.citadel.org\n",
295 "Extended commands are available using the period ( . ) key. To use\n"
296 "a dot command, press the . key, and then enter the first letter of\n"
297 "each word in the command. The words will appear as you enter the keys.\n"
298 "You can also backspace over partially entered commands. The following\n"
299 "commands are available:\n"
301 " <.> <H>elp: Displays help files. Type .H followed by a help file\n"
302 " name. You are now reading <.H>elp SUMMARY\n"
304 " <.> <G>oto: Jumps directly to the room you specify. You can also\n"
305 " type a partial room name, just enough to make it unique,\n"
306 " and it'll find the room you're looking for. As with the\n"
307 " regular <G>oto command, messages in the current room will\n"
308 " be marked as read.\n"
310 " <.> <S>kip, goto: This is similar to <.G>oto, except it doesn't mark\n"
311 " messages in the current room as read.\n"
313 " <.> list <Z>apped rooms Shows all rooms you've <Z>apped (forgotten)\n"
316 " Terminate (logoff) commands:\n"
318 " <.> <T>erminate and <Q>uit Log off and disconnect.\n"
319 " <.> <T>erminate and <S>tay online Log in as a different user.\n"
324 " <.> <R>ead <N>ew messages Same as <N>ew\n"
325 " <.> <R>ead <O>ld msgs reverse Same as <O>ld\n"
326 " <.> <R>ead <L>ast five msgs Same as <L>ast5\n"
327 " <.> read <L>ast: Allows you to specify how many\n"
328 " messages you wish to read.\n"
330 " <.> <R>ead <U>ser listing: Lists all users on the system if\n"
331 " you just hit enter, otherwise\n"
332 " you can specify a partial match\n"
334 " <.> <R>ead <T>extfile formatted File 'download' commands.\n"
335 " <.> <R>ead file using <X>modem \n"
336 " <.> <R>ead file using <Y>modem \n"
337 " <.> <R>ead file using <Z>modem \n"
338 " <.> <R>ead <F>ile unformatted \n"
339 " <.> <R>ead <D>irectory \n"
341 " <.> <R>ead <I>nfo file Read the room info file.\n"
342 " <.> <R>ead <B>io Read other users' 'bio' files.\n"
343 " <.> <R>ead <C>onfiguration Display your 'preferences'.\n"
344 " <.> <R>ead <S>ystem info Display system statistics.\n"
349 " <.> <E>nter <M>essage Post a message in this room.\n"
350 " <.> <E>nter message with <E>ditor Post using a full-screen editor.\n"
351 " <.> <E>nter <A>SCII message Post 'raw' (use this when 'pasting'\n"
352 " a message from your clipboard).\n"
354 " <.> <E>nter <P>assword Change your password.\n"
355 " <.> <E>nter <C>onfiguration Change your 'preferences'.\n"
356 " <.> <E>nter a new <R>oom Create a new room.\n"
357 " <.> <E>nter re<G>istration Register (name, address, etc.)\n"
358 " <.> <E>nter <B>io Enter/change your 'bio' file.\n"
360 " <.> <E>nter <T>extfile File 'upload' commands.\n"
361 " <.> <E>nter file using <X>modem \n"
362 " <.> <E>nter file using <Y>modem \n"
363 " <.> <E>nter file using <Z>modem \n"
366 " Wholist commands:\n"
368 " <.> <W>holist <L>ong Same as <W>ho is online, but displays\n"
369 " more detailed information.\n"
370 " <.> <W>holist <R>oomname Masquerade your room name (other users\n"
371 " see the name you enter rather than the\n"
372 " actual name of the room you're in)\n"
373 " <.> <W>holist <H>ostname Masquerade your host name\n"
374 " <.> <E>nter <U>sername Masquerade your user name (Admins only)\n"
375 " <.> <W>holist <S>tealth mode Enter/exit 'stealth mode' (when in stealth\n"
376 " mode you are invisible on the wholist)\n"
379 " Floor commands (if using floor mode)\n"
380 " ;<C>onfigure floor mode - turn floor mode on or off\n"
381 " ;<G>oto floor: - jump to a specific floor\n"
382 " ;<K>nown rooms - list all rooms on all floors\n"
383 " ;<S>kip to floor: - skip current floor, jump to another\n"
384 " ;<Z>ap floor - zap (forget) all rooms on this floor\n"
387 " Administrative commands: \n"
389 " <.> <A>dmin <K>ill this room \n"
390 " <.> <A>dmin <E>dit this room \n"
391 " <.> <A>dmin <W>ho knows room \n"
392 " <.> <A>dmin edit <U>ser \n"
393 " <.> <A>dmin <V>alidate new users \n"
394 " <.> <A>dmin enter <I>nfo file \n"
395 " <.> <A>dmin <R>oom <I>nvite user \n"
396 " <.> <A>dmin <R>oom <K>ick out user \n"
397 " <.> <A>dmin <F>ile <D>elete \n"
398 " <.> <A>dmin <F>ile <S>end over net \n"
399 " <.> <A>dmin <F>ile <M>ove \n"
400 " <.> <A>dmin <M>essage edit: \n"
401 " <.> <A>dmin <P>ost \n"
402 " <.> <A>dmin <S>ystem configuration \n"
403 " <.> <A>dmin <T>erminate server <N>ow\n" " <.> <A>dmin <T>erminate server <S>cheduled\n"
414 #define IFNEXPERT if ((userflags&US_EXPERT)==0)
418 char rc_exp_cmd[1024];
419 int rc_allow_attachments;
420 int rc_display_message_numbers;
421 int rc_force_mail_prompts;
422 int rc_remember_passwords;
425 int rc_prompt_control = 0;
426 time_t rc_idle_threshold = (time_t) 900;
427 char rc_url_cmd[SIZ];
428 char rc_open_cmd[SIZ];
429 char rc_gotmail_cmd[SIZ];
431 int next_lazy_cmd = 5;
433 extern int screenwidth, screenheight;
435 extern CtdlIPC *ipc_for_signal_handlers; /* KLUDGE cover your eyes */
437 struct citcmd *cmdlist = NULL;
440 /* these variables are local to this module */
441 char keepalives_enabled = KA_YES; /* send NOOPs to server when idle */
442 int ok_to_interrupt = 0; /* print instant msgs asynchronously */
443 time_t AnsiDetect; /* when did we send the detect code? */
444 int enable_color = 0; /* nonzero for ANSI color */
450 * If an interesting key has been pressed, return its value, otherwise 0
452 char was_a_key_pressed(void)
463 retval = select(1, &rfds, NULL, NULL, &tv);
465 /* Careful! Disable keepalives during keyboard polling; we're probably
466 * in the middle of a data transfer from the server, in which case
467 * sending a NOOP would throw the client protocol out of sync.
469 if ((retval > 0) && FD_ISSET(0, &rfds)) {
470 set_keepalives(KA_NO);
471 the_character = inkey();
472 set_keepalives(KA_YES);
476 return (the_character);
484 * print_instant() - print instant messages if there are any
486 void print_instant(void)
495 char *listing = NULL;
496 int r; /* IPC result code */
498 if (instant_msgs == 0)
504 if (IsEmptyStr(rc_exp_cmd)) {
509 while (instant_msgs != 0) {
510 r = CtdlIPCGetInstantMessage(ipc_for_signal_handlers, &listing, buf);
514 instant_msgs = extract_int(buf, 0);
515 timestamp = extract_long(buf, 1);
516 flags = extract_int(buf, 2);
517 extract_token(sender, buf, 3, '|', sizeof sender);
518 extract_token(node, buf, 4, '|', sizeof node);
519 strcpy(last_paged, sender);
521 localtime_r(×tamp, &stamp);
523 /* If the page is a Logoff Request, honor it. */
529 if (!IsEmptyStr(rc_exp_cmd)) {
530 outpipe = popen(rc_exp_cmd, "w");
531 if (outpipe != NULL) {
532 /* Header derived from flags */
534 fprintf(outpipe, "Please log off now, as requested ");
536 fprintf(outpipe, "Broadcast message ");
538 fprintf(outpipe, "Chat request ");
540 fprintf(outpipe, "Message ");
541 /* Timestamp. Can this be improved? */
542 if (stamp.tm_hour == 0 || stamp.tm_hour == 12)
543 fprintf(outpipe, "at 12:%02d%cm", stamp.tm_min, stamp.tm_hour ? 'p' : 'a');
544 else if (stamp.tm_hour > 12) /* pm */
545 fprintf(outpipe, "at %d:%02dpm", stamp.tm_hour - 12, stamp.tm_min);
547 fprintf(outpipe, "at %d:%02dam", stamp.tm_hour, stamp.tm_min);
548 fprintf(outpipe, " from %s", sender);
549 if (strncmp(ipc_for_signal_handlers->ServInfo.nodename, node, 32))
550 fprintf(outpipe, " @%s", node);
551 fprintf(outpipe, ":\n%s\n", listing);
553 if (instant_msgs == 0)
558 /* fall back to built-in instant message display */
561 /* Header derived from flags */
563 scr_printf("Please log off now, as requested ");
565 scr_printf("Broadcast message ");
567 scr_printf("Chat request ");
569 scr_printf("Message ");
571 /* Timestamp. Can this be improved? */
572 if (stamp.tm_hour == 0 || stamp.tm_hour == 12) /* 12am/12pm */
573 scr_printf("at 12:%02d%cm", stamp.tm_min, stamp.tm_hour ? 'p' : 'a');
574 else if (stamp.tm_hour > 12) /* pm */
575 scr_printf("at %d:%02dpm", stamp.tm_hour - 12, stamp.tm_min);
577 scr_printf("at %d:%02dam", stamp.tm_hour, stamp.tm_min);
580 scr_printf(" from %s", sender);
582 /* Remote node, if any */
583 if (strncmp(ipc_for_signal_handlers->ServInfo.nodename, node, 32))
584 scr_printf(" @%s", node);
587 fmout(screenwidth, NULL, listing, NULL, 0);
591 scr_printf("\n---\n");
598 void set_keepalives(int s)
600 keepalives_enabled = (char) s;
604 * This loop handles the "keepalive" messages sent to the server when idling.
607 static time_t idlet = 0;
608 static void really_do_keepalive(void)
613 /* This may sometimes get called before we are actually connected
614 * to the server. Don't do anything if we aren't connected. -IO
616 if (!ipc_for_signal_handlers)
619 /* If full keepalives are enabled, send a NOOP to the server and
620 * wait for a response.
622 if (keepalives_enabled == KA_YES) {
623 CtdlIPCNoop(ipc_for_signal_handlers);
624 if (instant_msgs > 0) {
625 if (ok_to_interrupt == 1) {
626 scr_printf("\r%64s\r", "");
628 scr_printf("%s%c ", room_name, room_prompt(room_flags));
634 /* If half keepalives are enabled, send a QNOP to the server (if the
635 * server supports it) and then do nothing.
637 if ((keepalives_enabled == KA_HALF)
638 && (ipc_for_signal_handlers->ServInfo.supports_qnop > 0)) {
639 CtdlIPC_chat_send(ipc_for_signal_handlers, "QNOP");
643 /* I changed this from static to not because I need to call it from
644 * screen.c, either that or make something in screen.c not static.
645 * Fix it how you like. Why all the staticness? stu
647 void do_keepalive(void)
652 if ((now - idlet) < ((long) S_KEEPALIVE)) {
656 /* Do a space-backspace to keep terminal sessions from idling out */
657 scr_printf(" %c", 8);
660 really_do_keepalive();
666 { /* get a character from the keyboard, with */
667 int a; /* the watchdog timer in effect if necessary */
676 /* This loop waits for keyboard input. If the keepalive
677 * timer expires, it sends a keepalive to the server if
678 * necessary and then waits again.
685 tv.tv_sec = S_KEEPALIVE;
688 select(1, &rfds, NULL, NULL, &tv);
689 } while (!FD_ISSET(0, &rfds));
691 /* At this point, there's input, so fetch it.
692 * (There's a hole in the bucket...)
694 a = scr_getc(SCR_BLOCK);
707 { /* Returns 1 for yes, 0 for no */
723 /* Returns 1 for yes, 0 for no, arg is default value */
747 * Function to read a line of text from the terminal.
749 * string Pointer to string buffer
751 * noshow Echo asterisks instead of keystrokes?
752 * bs Allow backspacing out of the prompt? (returns -1 if this happens)
754 * returns: string length
756 int ctdl_getline(char *string, int lim, int noshow, int bs)
758 int pos = strlen(string);
761 if (noshow && !IsEmptyStr(string)) {
762 int num_stars = strlen(string);
763 while (num_stars--) {
767 scr_printf("%s", string);
773 if ((ch == 8) && (pos > 0)) { /* backspace */
780 else if ((ch == 8) && (pos == 0) && (bs)) { /* backspace out of the prompt */
784 else if ((ch == 23) && (pos > 0)) { /* Ctrl-W deletes a word */
785 while ((pos > 0) && !isspace(string[pos])) {
791 while ((pos > 0) && !isspace(string[pos - 1])) {
799 else if (ch == 10) { /* return */
805 else if (isprint(ch)) { /* payload characters */
806 scr_putc((noshow ? '*' : ch));
815 * newprompt() prompt for a string, print the existing value, and
816 * allow the user to press return to keep it...
817 * If len is negative, pass the "noshow" flag to ctdl_getline()
819 void strprompt(char *prompt, char *str, int len)
823 scr_printf("%s", prompt);
827 ctdl_getline(str, abs(len), (len < 0), 0);
832 * boolprompt() - prompt for a yes/no, print the existing value and
833 * allow the user to press return to keep it...
835 int boolprompt(char *prompt, int prev_val)
840 scr_printf("%s ", prompt);
843 color(BRIGHT_MAGENTA);
844 scr_printf("%s", (prev_val ? "Yes" : "No"));
848 r = (yesno_d(prev_val));
854 * intprompt() - like strprompt(), except for an integer
855 * (note that it RETURNS the new value!)
857 int intprompt(char *prompt, int ival, int imin, int imax)
865 snprintf(buf, sizeof buf, "%d", i);
866 strprompt(prompt, buf, 15);
868 for (p = 0; !IsEmptyStr(&buf[p]); ++p) {
869 if ((!isdigit(buf[p]))
870 && ((buf[p] != '-') || (p != 0)))
874 scr_printf("*** Must be no less than %d.\n", imin);
876 scr_printf("*** Must be no more than %d.\n", imax);
877 } while ((i < imin) || (i > imax));
882 * newprompt() prompt for a string with no existing value
883 * (clears out string buffer first)
884 * If len is negative, pass the "noshow" flag to ctdl_getline()
886 void newprompt(char *prompt, char *str, int len)
889 color(BRIGHT_MAGENTA);
890 scr_printf("%s", prompt);
892 ctdl_getline(str, abs(len), (len < 0), 0);
898 { /* returns a lower case value */
907 * parse the citadel.rc file
909 void load_command_set(void)
914 struct citcmd *lastcmd = NULL;
918 /* first, set up some defaults for non-required variables */
920 strcpy(editor_path, "");
921 strcpy(printcmd, "");
922 strcpy(imagecmd, "");
923 strcpy(rc_username, "");
924 strcpy(rc_password, "");
927 rc_allow_attachments = 0;
928 rc_remember_passwords = 0;
929 strcpy(rc_exp_cmd, "");
930 rc_display_message_numbers = 0;
931 rc_force_mail_prompts = 0;
934 strcpy(rc_url_cmd, "");
935 strcpy(rc_open_cmd, "");
936 strcpy(rc_gotmail_cmd, "");
938 rc_encrypt = RC_DEFAULT;
941 /* now try to open the citadel.rc file */
944 if (getenv("HOME") != NULL) {
945 snprintf(buf, sizeof buf, "%s/.citadelrc", getenv("HOME"));
946 ccfile = fopen(buf, "r");
948 if (ccfile == NULL) {
949 ccfile = fopen(file_citadel_rc, "r");
951 if (ccfile == NULL) {
952 ccfile = fopen("/etc/citadel.rc", "r");
954 if (ccfile == NULL) {
955 ccfile = fopen("./citadel.rc", "r");
957 if (ccfile == NULL) {
958 perror("commands: cannot open citadel.rc");
961 while (fgets(buf, sizeof buf, ccfile) != NULL) {
962 while ((!IsEmptyStr(buf)) ? (isspace(buf[strlen(buf) - 1])) : 0)
963 buf[strlen(buf) - 1] = 0;
965 if (!strncasecmp(buf, "encrypt=", 8)) {
966 if (!strcasecmp(&buf[8], "yes")) {
970 fprintf(stderr, "citadel.rc requires encryption support but citadel is not compiled with OpenSSL");
975 else if (!strcasecmp(&buf[8], "no")) {
977 } else if (!strcasecmp(&buf[8], "default")) {
978 rc_encrypt = RC_DEFAULT;
983 if (!strncasecmp(buf, "editor=", 7)) {
984 strcpy(editor_path, &buf[7]);
987 if (!strncasecmp(buf, "printcmd=", 9))
988 strcpy(printcmd, &buf[9]);
990 if (!strncasecmp(buf, "imagecmd=", 9))
991 strcpy(imagecmd, &buf[9]);
993 if (!strncasecmp(buf, "expcmd=", 7))
994 strcpy(rc_exp_cmd, &buf[7]);
996 if (!strncasecmp(buf, "use_floors=", 11)) {
997 if (!strcasecmp(&buf[11], "yes"))
998 rc_floor_mode = RC_YES;
999 if (!strcasecmp(&buf[11], "no"))
1000 rc_floor_mode = RC_NO;
1001 if (!strcasecmp(&buf[11], "default"))
1002 rc_floor_mode = RC_DEFAULT;
1004 if (!strncasecmp(buf, "beep=", 5)) {
1005 rc_exp_beep = atoi(&buf[5]);
1007 if (!strncasecmp(buf, "allow_attachments=", 18)) {
1008 rc_allow_attachments = atoi(&buf[18]);
1010 if (!strncasecmp(buf, "idle_threshold=", 15)) {
1011 rc_idle_threshold = atol(&buf[15]);
1013 if (!strncasecmp(buf, "remember_passwords=", 19)) {
1014 rc_remember_passwords = atoi(&buf[19]);
1016 if (!strncasecmp(buf, "display_message_numbers=", 24)) {
1017 rc_display_message_numbers = atoi(&buf[24]);
1019 if (!strncasecmp(buf, "force_mail_prompts=", 19)) {
1020 rc_force_mail_prompts = atoi(&buf[19]);
1022 if (!strncasecmp(buf, "ansi_color=", 11)) {
1023 if (!strncasecmp(&buf[11], "on", 2))
1025 if (!strncasecmp(&buf[11], "auto", 4))
1026 rc_ansi_color = 2; /* autodetect */
1027 if (!strncasecmp(&buf[11], "user", 4))
1028 rc_ansi_color = 3; /* user config */
1030 if (!strncasecmp(buf, "status_line=", 12)) {
1031 if (!strncasecmp(&buf[12], "on", 2))
1032 enable_status_line = 1;
1034 if (!strncasecmp(buf, "use_background=", 15)) {
1035 if (!strncasecmp(&buf[15], "on", 2))
1036 rc_color_use_bg = 9;
1038 if (!strncasecmp(buf, "prompt_control=", 15)) {
1039 if (!strncasecmp(&buf[15], "on", 2))
1040 rc_prompt_control = 1;
1041 if (!strncasecmp(&buf[15], "user", 4))
1042 rc_prompt_control = 3; /* user config */
1044 if (!strncasecmp(buf, "username=", 9))
1045 strcpy(rc_username, &buf[9]);
1047 if (!strncasecmp(buf, "password=", 9))
1048 strcpy(rc_password, &buf[9]);
1050 if (!strncasecmp(buf, "urlcmd=", 7))
1051 strcpy(rc_url_cmd, &buf[7]);
1053 if (!strncasecmp(buf, "opencmd=", 7))
1054 strcpy(rc_open_cmd, &buf[8]);
1056 if (!strncasecmp(buf, "gotmailcmd=", 11))
1057 strcpy(rc_gotmail_cmd, &buf[11]);
1059 if (!strncasecmp(buf, "cmd=", 4)) {
1060 strcpy(buf, &buf[4]);
1062 cptr = (struct citcmd *) malloc(sizeof(struct citcmd));
1064 cptr->c_cmdnum = atoi(buf);
1065 for (d = strlen(buf); d >= 0; --d)
1068 strcpy(buf, &buf[b + 1]);
1070 cptr->c_axlevel = atoi(buf);
1071 for (d = strlen(buf); d >= 0; --d)
1074 strcpy(buf, &buf[b + 1]);
1076 for (a = 0; a < 5; ++a)
1077 cptr->c_keys[a][0] = 0;
1081 buf[strlen(buf) + 1] = 0;
1082 while (!IsEmptyStr(buf)) {
1084 for (d = strlen(buf); d >= 0; --d)
1087 strncpy(cptr->c_keys[a], buf, b);
1088 cptr->c_keys[a][b] = 0;
1090 strcpy(buf, &buf[b + 1]);
1097 if (cmdlist == NULL)
1100 lastcmd->next = cptr;
1110 * return the key associated with a command
1112 char keycmd(char *cmdstr)
1116 for (a = 0; !IsEmptyStr(&cmdstr[a]); ++a)
1117 if (cmdstr[a] == '&')
1118 return (tolower(cmdstr[a + 1]));
1124 * Output the string from a key command without the ampersand
1125 * "mode" should be set to 0 for normal or 1 for <C>ommand key highlighting
1127 char *cmd_expand(char *strbuf, int mode)
1130 static char exp[64];
1133 strcpy(exp, strbuf);
1135 for (a = 0; exp[a]; ++a) {
1136 if (strbuf[a] == '&') {
1138 /* dont echo these non mnemonic command keys */
1139 int noecho = strbuf[a + 1] == '<' || strbuf[a + 1] == '>' || strbuf[a + 1] == '+' || strbuf[a + 1] == '-';
1142 strcpy(&exp[a], &exp[a + 1 + noecho]);
1146 strcpy(buf, &exp[a + 2]);
1152 if (!strncmp(&exp[a], "^r", 2)) {
1154 strcpy(&exp[a], room_name);
1155 strcat(exp, &buf[a + 2]);
1157 if (!strncmp(&exp[a], "^c", 2)) {
1159 strcpy(&exp[a + 1], &exp[a + 2]);
1169 * Comparison function to determine if entered commands match a
1170 * command loaded from the config file.
1172 int cmdmatch(char *cmdbuf, struct citcmd *cptr, int ncomp)
1183 for (a = 0; a < ncomp; ++a) {
1184 if ((tolower(cmdbuf[a]) != keycmd(cptr->c_keys[a]))
1185 || (cptr->c_axlevel > cmdax))
1193 * This function returns 1 if a given command requires a string input
1195 int requires_string(struct citcmd *cptr, int ncomp)
1200 strcpy(buf, cptr->c_keys[ncomp - 1]);
1201 for (a = 0; !IsEmptyStr(&buf[a]); ++a) {
1210 * Input a command at the main prompt.
1211 * This function returns an integer command number. If the command prompts
1212 * for a string then it is placed in the supplied buffer.
1214 int getcmd(CtdlIPC * ipc, char *argbuf)
1223 struct citcmd *cptr;
1226 * Starting a new command now, so set sigcaught to 0. This variable
1227 * is set to nonzero (usually NEXT_KEY or STOP_KEY) if a command has
1228 * been interrupted by a keypress.
1232 /* Switch color support on or off if we're in user mode */
1233 if (rc_ansi_color == 3) {
1234 if (userflags & US_COLOR)
1239 /* if we're running in idiot mode, display a cute little menu */
1242 scr_printf("-----------------------------------------------------------------------\n");
1243 scr_printf("Room cmds: <K>nown rooms, <G>oto next room, <.G>oto a specific room,\n");
1244 scr_printf(" <S>kip this room, <A>bandon this room, <Z>ap this room,\n");
1245 scr_printf(" <U>ngoto (move back)\n");
1246 scr_printf("Message cmds: <N>ew msgs, <F>orward read, <R>everse read, <O>ld msgs,\n");
1247 scr_printf(" <L>ast five msgs, <E>nter a message\n");
1248 scr_printf("General cmds: <?> help, <T>erminate, <C>hat, <W>ho is online\n");
1249 scr_printf("Misc: <X> toggle eXpert mode, <D>irectory\n");
1251 scr_printf(" (Type .Help SUMMARY for extended commands, <X> to hide this menu)\n");
1252 scr_printf("-----------------------------------------------------------------------\n");
1258 for (a = 0; a < 5; ++a)
1260 /* now the room prompt... */
1261 ok_to_interrupt = 1;
1262 color(BRIGHT_WHITE);
1263 scr_printf("\n%s", room_name);
1265 scr_printf("%c ", room_prompt(room_flags));
1269 ok_to_interrupt = 0;
1271 /* Handle the backspace key, but only if there's something
1272 * to backspace over...
1274 if ((ch == 8) && (cmdpos > 0)) {
1275 back(cmdspaces[cmdpos - 1] + 1);
1279 /* Spacebar invokes "lazy traversal" commands */
1280 if ((ch == 32) && (cmdpos == 0)) {
1281 this_lazy_cmd = next_lazy_cmd;
1282 if (this_lazy_cmd == 13)
1284 if (this_lazy_cmd == 5)
1286 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1287 if (cptr->c_cmdnum == this_lazy_cmd) {
1288 for (a = 0; a < 5; ++a)
1289 if (cptr->c_keys[a][0] != 0)
1290 scr_printf("%s ", cmd_expand(cptr->c_keys[a], 0));
1292 return (this_lazy_cmd);
1296 return (this_lazy_cmd);
1298 /* Otherwise, process the command */
1299 cmdbuf[cmdpos] = tolower(ch);
1301 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1302 if (cmdmatch(cmdbuf, cptr, cmdpos + 1)) {
1304 scr_printf("%s", cmd_expand(cptr->c_keys[cmdpos], 0));
1305 cmdspaces[cmdpos] = strlen(cmd_expand(cptr->c_keys[cmdpos], 0));
1307 if ((cptr->c_keys[cmdpos + 1]) != 0)
1313 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1314 if (cmdmatch(cmdbuf, cptr, 5)) {
1315 /* We've found our command. */
1316 if (requires_string(cptr, cmdpos)) {
1318 ctdl_getline(argbuf, 64, 0, 0);
1323 /* If this command is one that changes rooms,
1324 * then the next lazy-command (space bar)
1325 * should be "read new" instead of "goto"
1327 if ((cptr->c_cmdnum == 5)
1328 || (cptr->c_cmdnum == 6)
1329 || (cptr->c_cmdnum == 47)
1330 || (cptr->c_cmdnum == 52)
1331 || (cptr->c_cmdnum == 16)
1332 || (cptr->c_cmdnum == 20))
1335 /* If this command is "read new"
1336 * then the next lazy-command (space bar)
1339 if (cptr->c_cmdnum == 13)
1342 return (cptr->c_cmdnum);
1348 scr_printf("\rOne of ... \n");
1349 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1350 if (cmdmatch(cmdbuf, cptr, cmdpos)) {
1351 for (a = 0; a < 5; ++a) {
1352 keyopt(cmd_expand(cptr->c_keys[a], 1));
1360 scr_printf("\n%s%c ", room_name, room_prompt(room_flags));
1362 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1363 if ((got == 0) && (cmdmatch(cmdbuf, cptr, cmdpos))) {
1364 for (a = 0; a < cmdpos; ++a) {
1365 scr_printf("%s ", cmd_expand(cptr->c_keys[a], 0));
1380 * set tty modes. commands are:
1382 * 01- set to Citadel mode
1383 * 2 - save current settings for later restoral
1384 * 3 - restore saved settings
1386 void stty_ctdl(int cmd)
1387 { /* SysV version of stty_ctdl() */
1388 struct termios live;
1389 static struct termios saved_settings;
1390 static int last_cmd = 0;
1397 if ((cmd == 0) || (cmd == 1)) {
1398 tcgetattr(0, &live);
1399 live.c_iflag = ISTRIP | IXON | IXANY;
1400 live.c_oflag = OPOST | ONLCR;
1401 live.c_lflag = ISIG | NOFLSH;
1403 live.c_cc[VINTR] = 0;
1404 live.c_cc[VQUIT] = 0;
1406 /* do we even need this stuff anymore? */
1407 /* live.c_line=0; */
1408 live.c_cc[VERASE] = 8;
1409 live.c_cc[VKILL] = 24;
1410 live.c_cc[VEOF] = 1;
1411 live.c_cc[VEOL] = 255;
1412 live.c_cc[VEOL2] = 0;
1413 live.c_cc[VSTART] = 0;
1414 tcsetattr(0, TCSADRAIN, &live);
1417 tcgetattr(0, &saved_settings);
1420 tcsetattr(0, TCSADRAIN, &saved_settings);
1426 // this is the old version which uses sgtty.h instead of termios.h
1428 void stty_ctdl(int cmd)
1429 { /* BSD version of stty_ctdl() */
1431 static struct sgttyb saved_settings;
1432 static int last_cmd = 0;
1439 if ((cmd == 0) || (cmd == 1)) {
1441 live.sg_flags |= CBREAK;
1442 live.sg_flags |= CRMOD;
1443 live.sg_flags |= NL1;
1444 live.sg_flags &= ~ECHO;
1446 live.sg_flags |= NOFLSH;
1450 gtty(0, &saved_settings);
1453 stty(0, &saved_settings);
1460 * display_help() - help text viewer
1462 void display_help(CtdlIPC * ipc, char *name)
1465 int num_helps = sizeof(helpnames) / sizeof(char *);
1467 for (i = 0; i < num_helps; ++i) {
1468 if (!strcasecmp(name, helpnames[i])) {
1469 fmout(screenwidth, NULL, helptexts[i], NULL, 0);
1474 scr_printf("'%s' not found. Enter one of:\n", name);
1475 for (i = 0; i < num_helps; ++i) {
1476 scr_printf(" %s\n", helpnames[i]);
1482 * fmout() - Citadel text formatter and paginator
1484 int fmout(int width, /* screen width to use */
1485 FILE * fpin, /* file to read from, or NULL to format given text */
1486 char *text, /* text to be formatted (when fpin is NULL */
1487 FILE * fpout, /* file to write to, or NULL to write to screen */
1488 int subst) { /* nonzero if we should use hypertext mode */
1489 char *buffer = NULL; /* The current message */
1490 char *word = NULL; /* What we are about to actually print */
1491 char *e; /* Pointer to position in text */
1492 char old = 0; /* The previous character */
1493 int column = 0; /* Current column */
1494 size_t i; /* Generic counter */
1496 /* Space for a single word, which can be at most screenwidth */
1497 word = (char *) calloc(1, width);
1499 scr_printf("Can't alloc memory to print message: %s!\n", strerror(errno));
1503 /* Read the entire message body into memory */
1505 buffer = load_message_from_file(fpin);
1507 scr_printf("Can't print message: %s!\n", strerror(errno));
1515 /* Run the message body */
1517 /* Catch characters that shouldn't be there at all */
1522 /* First, are we looking at a newline? */
1525 if (*e == ' ') { /* Paragraph */
1527 fprintf(fpout, "\n");
1532 } else if (old != ' ') { /* Don't print two spaces */
1534 fprintf(fpout, " ");
1544 /* Are we looking at a nonprintable?
1545 * (This section is now commented out because we could be displaying
1546 * a character set like UTF-8 or ISO-8859-1.)
1547 if ( (*e < 32) || (*e > 126) ) {
1552 /* Or are we looking at a space? */
1555 if (column >= width - 1) {
1556 /* Are we in the rightmost column? */
1558 fprintf(fpout, "\n");
1563 } else if (!(column == 0 && old == ' ')) {
1564 /* Eat only the first space on a line */
1566 fprintf(fpout, " ");
1572 /* ONLY eat the FIRST space on a line */
1578 /* Read a word, slightly messy */
1581 if (!isprint(e[i]) && !isspace(e[i]))
1588 /* We should never see these, but... slightly messy */
1589 if (e[i] == '\t' || e[i] == '\f' || e[i] == '\v')
1592 /* Break up really long words */
1593 /* TODO: auto-hyphenation someday? */
1596 strncpy(word, e, i);
1599 /* Decide where to print the word */
1600 if (column + i >= width) {
1601 /* Wrap to the next line */
1603 fprintf(fpout, "\n");
1610 /* Print the word */
1612 fprintf(fpout, "%s", word);
1614 scr_printf("%s", word);
1617 e += i; /* Start over with the whitepsace! */
1621 if (fpin) /* We allocated this, remember? */
1624 /* Is this necessary? It makes the output kind of spacey. */
1626 fprintf(fpout, "\n");
1636 * support ANSI color if defined
1638 void color(int colornum)
1640 static int hold_color;
1641 static int current_color;
1643 if (colornum == COLOR_PUSH) {
1644 hold_color = current_color;
1648 if (colornum == COLOR_POP) {
1653 current_color = colornum;
1655 /* When switching to dim white, actually output an 'original
1656 * pair' sequence -- this looks better on black-on-white
1657 * terminals. - Changed to ORIGINAL_PAIR as this actually
1658 * wound up looking horrible on black-on-white terminals, not
1659 * to mention transparent terminals.
1661 if (colornum == ORIGINAL_PAIR)
1662 printf("\033[0;39;49m");
1664 printf("\033[%d;3%d;4%dm", (colornum & 8) ? 1 : 0, (colornum & 7), rc_color_use_bg);
1669 void cls(int colornum)
1672 printf("\033[4%dm\033[2J\033[H\033[0m", colornum ? colornum : rc_color_use_bg);
1678 * Detect whether ANSI color is available (answerback)
1680 void send_ansi_detect(void)
1682 if (rc_ansi_color == 2) {
1689 void look_for_ansi(void)
1697 if (rc_ansi_color == 0) {
1699 } else if (rc_ansi_color == 1) {
1701 } else if (rc_ansi_color == 2) {
1703 /* otherwise, do the auto-detect */
1708 if ((now - AnsiDetect) < 2)
1717 select(1, &rfds, NULL, NULL, &tv);
1718 if (FD_ISSET(0, &rfds)) {
1719 abuf[strlen(abuf) + 1] = 0;
1720 rv = read(0, &abuf[strlen(abuf)], 1);
1722 scr_printf("failed to read after select: %s", strerror(errno));
1726 } while (FD_ISSET(0, &rfds));
1728 for (a = 0; !IsEmptyStr(&abuf[a]); ++a) {
1729 if ((abuf[a] == 27) && (abuf[a + 1] == '[')
1730 && (abuf[a + 2] == '?')) {
1739 * Display key options (highlight hotkeys inside angle brackets)
1741 void keyopt(char *buf)
1746 for (i = 0; !IsEmptyStr(&buf[i]); ++i) {
1747 if (buf[i] == '<') {
1748 scr_printf("%c", buf[i]);
1749 color(BRIGHT_MAGENTA);
1751 if (buf[i] == '>' && buf[i + 1] != '>') {
1754 scr_printf("%c", buf[i]);
1763 * Present a key-menu line choice type of thing
1765 char keymenu(char *menuprompt, char *menustring)
1772 int display_prompt = 1;
1774 choices = num_tokens(menustring, '|');
1776 if (menuprompt != NULL)
1778 if ((menuprompt != NULL) && (IsEmptyStr(menuprompt)))
1782 if (display_prompt) {
1784 scr_printf("%s ", menuprompt);
1786 for (i = 0; i < choices; ++i) {
1787 extract_token(buf, menustring, i, '|', sizeof buf);
1797 if ((do_prompt) && (ch == '?')) {
1798 scr_printf("\rOne of... ");
1800 for (i = 0; i < choices; ++i) {
1801 extract_token(buf, menustring, i, '|', sizeof buf);
1810 for (i = 0; i < choices; ++i) {
1811 extract_token(buf, menustring, i, '|', sizeof buf);
1812 for (c = 1; !IsEmptyStr(&buf[c]); ++c) {
1813 if ((ch == tolower(buf[c]))
1814 && (buf[c - 1] == '<')
1815 && (buf[c + 1] == '>')) {
1816 for (a = 0; !IsEmptyStr(&buf[a]); ++a) {
1817 if ((a != (c - 1)) && (a != (c + 1))) {