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"
62 " *** USE .<H>elp ? for additional help *** \n"
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"
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"
94 " In addition, the <M>ove and <D>elete commands are available at the\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"
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"
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"
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"
121 " This command toggles your floor mode.\n"
123 " <;G>oto FLOORNAME\n"
124 " This command causes the system to take you to the named floor.\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"
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"
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"
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"
144 " New User's Introduction to the site\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"
152 " Full help for the BBS commands can be obtained by typing <.H>elp SUMMARY\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"
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"
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"
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"
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"
205 " There are two special rooms on a Citadel that you should know about.\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"
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"
220 " These people, along with the room admins, keep the site running smoothly.\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"
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"
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"
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"
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"
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"
256 " 'There were messages posted while you were entering.'\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"
265 " '*** You have new mail'\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"
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"
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"
284 " Enter Recipient:\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"
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"
293 " Enter Recipient: Joe Schmoe @ citadrool\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"
298 " Enter Recipient: ajc@herring.fishnet.com\n"
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"
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"
312 " For more info, visit UNCENSORED! BBS at uncensored.citadel.org\n"
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"
322 " <.> <H>elp: Displays help files. Type .H followed by a help file\n"
323 " name. You are now reading <.H>elp SUMMARY\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"
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"
334 " <.> list <Z>apped rooms Shows all rooms you've <Z>apped (forgotten)\n"
337 " Terminate (logoff) commands:\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"
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"
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"
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"
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"
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"
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"
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"
387 " Wholist commands:\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"
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"
408 " Administrative commands: \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"
437 #define IFNEXPERT if ((userflags&US_EXPERT)==0)
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;
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];
454 int next_lazy_cmd = 5;
456 extern int screenwidth, screenheight;
458 extern CtdlIPC *ipc_for_signal_handlers; /* KLUDGE cover your eyes */
460 struct citcmd *cmdlist = NULL;
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 */
473 * If an interesting key has been pressed, return its value, otherwise 0
475 char was_a_key_pressed(void) {
485 retval = select(1, &rfds, NULL, NULL, &tv);
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.
491 if ((retval > 0) && FD_ISSET(0, &rfds)) {
492 set_keepalives(KA_NO);
493 the_character = inkey();
494 set_keepalives(KA_YES);
499 return(the_character);
507 * print_instant() - print instant messages if there are any
509 void print_instant(void)
518 char *listing = NULL;
519 int r; /* IPC result code */
521 if (instant_msgs == 0)
527 if (IsEmptyStr(rc_exp_cmd)) {
532 while (instant_msgs != 0) {
533 r = CtdlIPCGetInstantMessage(ipc_for_signal_handlers, &listing, buf);
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);
544 localtime_r(×tamp, &stamp);
546 /* If the page is a Logoff Request, honor it. */
552 if (!IsEmptyStr(rc_exp_cmd)) {
553 outpipe = popen(rc_exp_cmd, "w");
554 if (outpipe != NULL) {
555 /* Header derived from flags */
558 "Please log off now, as requested ");
560 fprintf(outpipe, "Broadcast message ");
562 fprintf(outpipe, "Chat request ");
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",
569 stamp.tm_hour ? 'p' : 'a');
570 else if (stamp.tm_hour > 12) /* pm */
571 fprintf(outpipe, "at %d:%02dpm",
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);
582 if (instant_msgs == 0)
587 /* fall back to built-in instant message display */
590 /* Header derived from flags */
592 scr_printf("Please log off now, as requested ");
594 scr_printf("Broadcast message ");
596 scr_printf("Chat request ");
598 scr_printf("Message ");
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);
608 scr_printf("at %d:%02dam", stamp.tm_hour, stamp.tm_min);
611 scr_printf(" from %s", sender);
613 /* Remote node, if any */
614 if (strncmp(ipc_for_signal_handlers->ServInfo.nodename, node, 32))
615 scr_printf(" @%s", node);
618 fmout(screenwidth, NULL, listing, NULL, 0);
622 scr_printf("\n---\n");
629 void set_keepalives(int s)
631 keepalives_enabled = (char) s;
635 * This loop handles the "keepalive" messages sent to the server when idling.
638 static time_t idlet = 0;
639 static void really_do_keepalive(void) {
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
646 if (!ipc_for_signal_handlers)
649 /* If full keepalives are enabled, send a NOOP to the server and
650 * wait for a response.
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", "");
658 scr_printf("%s%c ", room_name,
659 room_prompt(room_flags));
665 /* If half keepalives are enabled, send a QNOP to the server (if the
666 * server supports it) and then do nothing.
668 if ( (keepalives_enabled == KA_HALF)
669 && (ipc_for_signal_handlers->ServInfo.supports_qnop > 0) ) {
670 CtdlIPC_chat_send(ipc_for_signal_handlers, "QNOP");
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
678 void do_keepalive(void)
683 if ((now - idlet) < ((long) S_KEEPALIVE))
688 /* Do a space-backspace to keep terminal sessions from idling out */
689 scr_printf(" %c", 8);
692 really_do_keepalive();
698 { /* get a character from the keyboard, with */
699 int a; /* the watchdog timer in effect if necessary */
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.
717 tv.tv_sec = S_KEEPALIVE;
720 select(1, &rfds, NULL, NULL, &tv);
721 } while (!FD_ISSET(0, &rfds));
723 /* At this point, there's input, so fetch it.
724 * (There's a hole in the bucket...)
726 a = scr_getc(SCR_BLOCK);
739 { /* Returns 1 for yes, 0 for no */
755 /* Returns 1 for yes, 0 for no, arg is default value */
779 * Function to read a line of text from the terminal.
781 * string Pointer to string buffer
783 * noshow Echo asterisks instead of keystrokes?
784 * bs Allow backspacing out of the prompt? (returns -1 if this happens)
786 * returns: string length
788 int ctdl_getline(char *string, int lim, int noshow, int bs)
790 int pos = strlen(string);
793 if (noshow && !IsEmptyStr(string)) {
794 int num_stars = strlen(string);
795 while (num_stars--) {
800 scr_printf("%s", string);
806 if ((ch == 8) && (pos > 0)) { /* backspace */
808 scr_putc(8); scr_putc(32); scr_putc(8);
811 else if ((ch == 8) && (pos == 0) && (bs)) { /* backspace out of the prompt */
815 else if ((ch == 23) && (pos > 0)) { /* Ctrl-W deletes a word */
816 while ((pos > 0) && !isspace(string[pos])) {
818 scr_putc(8); scr_putc(32); scr_putc(8);
820 while ((pos > 0) && !isspace(string[pos-1])) {
822 scr_putc(8); scr_putc(32); scr_putc(8);
826 else if (ch == 10) { /* return */
832 else if (isprint(ch)) { /* payload characters */
833 scr_putc((noshow ? '*' : ch));
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()
846 void strprompt(char *prompt, char *str, int len)
850 scr_printf("%s", prompt);
854 ctdl_getline(str, abs(len), (len<0), 0);
859 * boolprompt() - prompt for a yes/no, print the existing value and
860 * allow the user to press return to keep it...
862 int boolprompt(char *prompt, int prev_val)
867 scr_printf("%s ", prompt);
870 color(BRIGHT_MAGENTA);
871 scr_printf("%s", (prev_val ? "Yes" : "No"));
875 r = (yesno_d(prev_val));
881 * intprompt() - like strprompt(), except for an integer
882 * (note that it RETURNS the new value!)
884 int intprompt(char *prompt, int ival, int imin, int imax)
892 snprintf(buf, sizeof buf, "%d", i);
893 strprompt(prompt, buf, 15);
895 for (p=0; !IsEmptyStr(&buf[p]); ++p) {
896 if ( (!isdigit(buf[p]))
897 && ( (buf[p]!='-') || (p!=0) ) )
901 scr_printf("*** Must be no less than %d.\n", imin);
903 scr_printf("*** Must be no more than %d.\n", imax);
904 } while ((i < imin) || (i > imax));
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()
913 void newprompt(char *prompt, char *str, int len)
916 color(BRIGHT_MAGENTA);
917 scr_printf("%s", prompt);
919 ctdl_getline(str, abs(len), (len<0), 0);
925 { /* returns a lower case value */
934 * parse the citadel.rc file
936 void load_command_set(void)
941 struct citcmd *lastcmd = NULL;
945 /* first, set up some defaults for non-required variables */
947 strcpy(editor_path, "");
948 strcpy(printcmd, "");
949 strcpy(imagecmd, "");
950 strcpy(rc_username, "");
951 strcpy(rc_password, "");
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;
961 strcpy(rc_url_cmd, "");
962 strcpy(rc_open_cmd, "");
963 strcpy(rc_gotmail_cmd, "");
965 rc_encrypt = RC_DEFAULT;
968 /* now try to open the citadel.rc file */
971 if (getenv("HOME") != NULL) {
972 snprintf(buf, sizeof buf, "%s/.citadelrc", getenv("HOME"));
973 ccfile = fopen(buf, "r");
975 if (ccfile == NULL) {
976 ccfile = fopen(file_citadel_rc, "r");
978 if (ccfile == NULL) {
979 ccfile = fopen("/etc/citadel.rc", "r");
981 if (ccfile == NULL) {
982 ccfile = fopen("./citadel.rc", "r");
984 if (ccfile == NULL) {
985 perror("commands: cannot open citadel.rc");
988 while (fgets(buf, sizeof buf, ccfile) != NULL) {
989 while ((!IsEmptyStr(buf)) ? (isspace(buf[strlen(buf) - 1])) : 0)
990 buf[strlen(buf) - 1] = 0;
992 if (!strncasecmp(buf, "encrypt=", 8)) {
993 if (!strcasecmp(&buf[8], "yes")) {
997 fprintf(stderr, "citadel.rc requires encryption support but citadel is not compiled with OpenSSL");
1002 else if (!strcasecmp(&buf[8], "no")) {
1005 else if (!strcasecmp(&buf[8], "default")) {
1006 rc_encrypt = RC_DEFAULT;
1011 if (!strncasecmp(buf, "editor=", 7)) {
1012 strcpy(editor_path, &buf[7]);
1015 if (!strncasecmp(buf, "printcmd=", 9))
1016 strcpy(printcmd, &buf[9]);
1018 if (!strncasecmp(buf, "imagecmd=", 9))
1019 strcpy(imagecmd, &buf[9]);
1021 if (!strncasecmp(buf, "expcmd=", 7))
1022 strcpy(rc_exp_cmd, &buf[7]);
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;
1032 if (!strncasecmp(buf, "beep=", 5)) {
1033 rc_exp_beep = atoi(&buf[5]);
1035 if (!strncasecmp(buf, "allow_attachments=", 18)) {
1036 rc_allow_attachments = atoi(&buf[18]);
1038 if (!strncasecmp(buf, "idle_threshold=", 15)) {
1039 rc_idle_threshold = atol(&buf[15]);
1041 if (!strncasecmp(buf, "remember_passwords=", 19)) {
1042 rc_remember_passwords = atoi(&buf[19]);
1044 if (!strncasecmp(buf, "display_message_numbers=", 24)) {
1045 rc_display_message_numbers = atoi(&buf[24]);
1047 if (!strncasecmp(buf, "force_mail_prompts=", 19)) {
1048 rc_force_mail_prompts = atoi(&buf[19]);
1050 if (!strncasecmp(buf, "ansi_color=", 11)) {
1051 if (!strncasecmp(&buf[11], "on", 2))
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 */
1058 if (!strncasecmp(buf, "status_line=", 12)) {
1059 if (!strncasecmp(&buf[12], "on", 2))
1060 enable_status_line = 1;
1062 if (!strncasecmp(buf, "use_background=", 15)) {
1063 if (!strncasecmp(&buf[15], "on", 2))
1064 rc_color_use_bg = 9;
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 */
1072 if (!strncasecmp(buf, "username=", 9))
1073 strcpy(rc_username, &buf[9]);
1075 if (!strncasecmp(buf, "password=", 9))
1076 strcpy(rc_password, &buf[9]);
1078 if (!strncasecmp(buf, "urlcmd=", 7))
1079 strcpy(rc_url_cmd, &buf[7]);
1081 if (!strncasecmp(buf, "opencmd=", 7))
1082 strcpy(rc_open_cmd, &buf[8]);
1084 if (!strncasecmp(buf, "gotmailcmd=", 11))
1085 strcpy(rc_gotmail_cmd, &buf[11]);
1087 if (!strncasecmp(buf, "cmd=", 4)) {
1088 strcpy(buf, &buf[4]);
1090 cptr = (struct citcmd *) malloc(sizeof(struct citcmd));
1092 cptr->c_cmdnum = atoi(buf);
1093 for (d = strlen(buf); d >= 0; --d)
1096 strcpy(buf, &buf[b + 1]);
1098 cptr->c_axlevel = atoi(buf);
1099 for (d = strlen(buf); d >= 0; --d)
1102 strcpy(buf, &buf[b + 1]);
1104 for (a = 0; a < 5; ++a)
1105 cptr->c_keys[a][0] = 0;
1109 buf[strlen(buf) + 1] = 0;
1110 while (!IsEmptyStr(buf)) {
1112 for (d = strlen(buf); d >= 0; --d)
1115 strncpy(cptr->c_keys[a], buf, b);
1116 cptr->c_keys[a][b] = 0;
1118 strcpy(buf, &buf[b + 1]);
1125 if (cmdlist == NULL)
1128 lastcmd->next = cptr;
1138 * return the key associated with a command
1140 char keycmd(char *cmdstr)
1144 for (a = 0; !IsEmptyStr(&cmdstr[a]); ++a)
1145 if (cmdstr[a] == '&')
1146 return (tolower(cmdstr[a + 1]));
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
1155 char *cmd_expand(char *strbuf, int mode)
1158 static char exp[64];
1161 strcpy(exp, strbuf);
1163 for (a = 0; exp[a]; ++a) {
1164 if (strbuf[a] == '&') {
1166 /* dont echo these non mnemonic command keys */
1167 int noecho = strbuf[a+1] == '<' || strbuf[a+1] == '>' || strbuf[a+1] == '+' || strbuf[a+1] == '-';
1170 strcpy(&exp[a], &exp[a + 1 + noecho]);
1174 strcpy(buf, &exp[a + 2]);
1180 if (!strncmp(&exp[a], "^r", 2)) {
1182 strcpy(&exp[a], room_name);
1183 strcat(exp, &buf[a + 2]);
1185 if (!strncmp(&exp[a], "^c", 2)) {
1187 strcpy(&exp[a + 1], &exp[a + 2]);
1197 * Comparison function to determine if entered commands match a
1198 * command loaded from the config file.
1200 int cmdmatch(char *cmdbuf, struct citcmd *cptr, int ncomp)
1211 for (a = 0; a < ncomp; ++a) {
1212 if ((tolower(cmdbuf[a]) != keycmd(cptr->c_keys[a]))
1213 || (cptr->c_axlevel > cmdax))
1221 * This function returns 1 if a given command requires a string input
1223 int requires_string(struct citcmd *cptr, int ncomp)
1228 strcpy(buf, cptr->c_keys[ncomp - 1]);
1229 for (a = 0; !IsEmptyStr(&buf[a]); ++a) {
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.
1242 int getcmd(CtdlIPC *ipc, char *argbuf)
1251 struct citcmd *cptr;
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.
1260 /* Switch color support on or off if we're in user mode */
1261 if (rc_ansi_color == 3) {
1262 if (userflags & US_COLOR)
1267 /* if we're running in idiot mode, display a cute little menu */
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");
1279 scr_printf(" (Type .Help SUMMARY for extended commands, <X> to hide this menu)\n");
1280 scr_printf("-----------------------------------------------------------------------\n");
1286 for (a = 0; a < 5; ++a)
1288 /* now the room prompt... */
1289 ok_to_interrupt = 1;
1290 color(BRIGHT_WHITE);
1291 scr_printf("\n%s", room_name);
1293 scr_printf("%c ", room_prompt(room_flags));
1297 ok_to_interrupt = 0;
1299 /* Handle the backspace key, but only if there's something
1300 * to backspace over...
1302 if ((ch == 8) && (cmdpos > 0)) {
1303 back(cmdspaces[cmdpos - 1] + 1);
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)
1312 if (this_lazy_cmd == 5)
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));
1321 return (this_lazy_cmd);
1325 return (this_lazy_cmd);
1327 /* Otherwise, process the command */
1328 cmdbuf[cmdpos] = tolower(ch);
1330 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1331 if (cmdmatch(cmdbuf, cptr, cmdpos + 1)) {
1333 scr_printf("%s", cmd_expand(cptr->c_keys[cmdpos], 0));
1334 cmdspaces[cmdpos] = strlen(
1335 cmd_expand(cptr->c_keys[cmdpos], 0));
1337 if ((cptr->c_keys[cmdpos + 1]) != 0)
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)) {
1348 ctdl_getline(argbuf, 64, 0, 0);
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"
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))
1365 /* If this command is "read new"
1366 * then the next lazy-command (space bar)
1369 if (cptr->c_cmdnum == 13)
1372 return (cptr->c_cmdnum);
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));
1390 scr_printf("\n%s%c ", room_name, room_prompt(room_flags));
1392 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1393 if ((got == 0) && (cmdmatch(cmdbuf, cptr, cmdpos))) {
1394 for (a = 0; a < cmdpos; ++a) {
1396 cmd_expand(cptr->c_keys[a], 0));
1411 * set tty modes. commands are:
1413 * 01- set to Citadel mode
1414 * 2 - save current settings for later restoral
1415 * 3 - restore saved settings
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;
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;
1434 live.c_cc[VINTR] = 0;
1435 live.c_cc[VQUIT] = 0;
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);
1448 tcgetattr(0, &saved_settings);
1451 tcsetattr(0, TCSADRAIN, &saved_settings);
1457 // this is the old version which uses sgtty.h instead of termios.h
1459 void stty_ctdl(int cmd)
1460 { /* BSD version of stty_ctdl() */
1462 static struct sgttyb saved_settings;
1463 static int last_cmd = 0;
1470 if ((cmd == 0) || (cmd == 1)) {
1472 live.sg_flags |= CBREAK;
1473 live.sg_flags |= CRMOD;
1474 live.sg_flags |= NL1;
1475 live.sg_flags &= ~ECHO;
1477 live.sg_flags |= NOFLSH;
1481 gtty(0, &saved_settings);
1484 stty(0, &saved_settings);
1491 * display_help() - help text viewer
1493 void display_help(CtdlIPC *ipc, char *name)
1496 int num_helps = sizeof(helpnames) / sizeof(char *) ;
1498 for (i=0; i<num_helps; ++i) {
1499 if (!strcasecmp(name, helpnames[i])) {
1500 fmout(screenwidth, NULL, helptexts[i], NULL, 0);
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]);
1513 * fmout() - Citadel text formatter and paginator
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 */
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 */
1529 /* Space for a single word, which can be at most screenwidth */
1530 word = (char *)calloc(1, width);
1532 scr_printf("Can't alloc memory to print message: %s!\n",
1537 /* Read the entire message body into memory */
1539 buffer = load_message_from_file(fpin);
1541 scr_printf("Can't print message: %s!\n",
1550 /* Run the message body */
1552 /* Catch characters that shouldn't be there at all */
1557 /* First, are we looking at a newline? */
1560 if (*e == ' ') { /* Paragraph */
1562 fprintf(fpout, "\n");
1567 } else if (old != ' ') {/* Don't print two spaces */
1569 fprintf(fpout, " ");
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) ) {
1587 /* Or are we looking at a space? */
1590 if (column >= width - 1) {
1591 /* Are we in the rightmost column? */
1593 fprintf(fpout, "\n");
1598 } else if (!(column == 0 && old == ' ')) {
1599 /* Eat only the first space on a line */
1601 fprintf(fpout, " ");
1607 /* ONLY eat the FIRST space on a line */
1613 /* Read a word, slightly messy */
1616 if (!isprint(e[i]) && !isspace(e[i]))
1623 /* We should never see these, but... slightly messy */
1624 if (e[i] == '\t' || e[i] == '\f' || e[i] == '\v')
1627 /* Break up really long words */
1628 /* TODO: auto-hyphenation someday? */
1631 strncpy(word, e, i);
1634 /* Decide where to print the word */
1635 if (column + i >= width) {
1636 /* Wrap to the next line */
1638 fprintf(fpout, "\n");
1645 /* Print the word */
1647 fprintf(fpout, "%s", word);
1649 scr_printf("%s", word);
1652 e += i; /* Start over with the whitepsace! */
1656 if (fpin) /* We allocated this, remember? */
1659 /* Is this necessary? It makes the output kind of spacey. */
1661 fprintf(fpout, "\n");
1671 * support ANSI color if defined
1673 void color(int colornum)
1675 static int hold_color;
1676 static int current_color;
1678 if (colornum == COLOR_PUSH) {
1679 hold_color = current_color;
1683 if (colornum == COLOR_POP) {
1688 current_color = colornum;
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.
1696 if (colornum == ORIGINAL_PAIR)
1697 printf("\033[0;39;49m");
1699 printf("\033[%d;3%d;4%dm",
1700 (colornum & 8) ? 1 : 0,
1707 void cls(int colornum)
1710 printf("\033[4%dm\033[2J\033[H\033[0m",
1711 colornum ? colornum : rc_color_use_bg);
1717 * Detect whether ANSI color is available (answerback)
1719 void send_ansi_detect(void)
1721 if (rc_ansi_color == 2) {
1728 void look_for_ansi(void)
1736 if (rc_ansi_color == 0) {
1738 } else if (rc_ansi_color == 1) {
1740 } else if (rc_ansi_color == 2) {
1742 /* otherwise, do the auto-detect */
1747 if ((now - AnsiDetect) < 2)
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);
1761 scr_printf("failed to read after select: %s",
1766 } while (FD_ISSET(0, &rfds));
1768 for (a = 0; !IsEmptyStr(&abuf[a]); ++a) {
1769 if ((abuf[a] == 27) && (abuf[a + 1] == '[')
1770 && (abuf[a + 2] == '?')) {
1779 * Display key options (highlight hotkeys inside angle brackets)
1781 void keyopt(char *buf) {
1785 for (i=0; !IsEmptyStr(&buf[i]); ++i) {
1787 scr_printf("%c", buf[i]);
1788 color(BRIGHT_MAGENTA);
1790 if (buf[i]=='>'&& buf[i+1] != '>') {
1793 scr_printf("%c", buf[i]);
1802 * Present a key-menu line choice type of thing
1804 char keymenu(char *menuprompt, char *menustring) {
1810 int display_prompt = 1;
1812 choices = num_tokens(menustring, '|');
1814 if (menuprompt != NULL) do_prompt = 1;
1815 if ((menuprompt != NULL) && (IsEmptyStr(menuprompt))) do_prompt = 0;
1818 if (display_prompt) {
1820 scr_printf("%s ", menuprompt);
1823 for (i=0; i<choices; ++i) {
1824 extract_token(buf, menustring, i, '|', sizeof buf);
1834 if ( (do_prompt) && (ch=='?') ) {
1835 scr_printf("\rOne of... ");
1837 for (i=0; i<choices; ++i) {
1838 extract_token(buf, menustring, i, '|', sizeof buf);
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]))
1852 && (buf[c+1]=='>') ) {
1853 for (a=0; !IsEmptyStr(&buf[a]); ++a) {
1854 if ( (a!=(c-1)) && (a!=(c+1))) {