From: Art Cancro Date: Mon, 5 Sep 2022 22:51:53 +0000 (-0400) Subject: More CSS cleanup X-Git-Tag: v958~32 X-Git-Url: https://code.citadel.org/?p=citadel.git;a=commitdiff_plain;h=e6398091193173d89f0ce9eafde9732b181193b2 More CSS cleanup --- diff --git a/textclient/citadel.c~ b/textclient/citadel.c~ new file mode 100644 index 000000000..0bab0a566 --- /dev/null +++ b/textclient/citadel.c~ @@ -0,0 +1,2099 @@ +// Main source module for the client program. +// +// Copyright (c) 1987-2022 by the citadel.org team +// +// This program is open source software. Use, duplication, and/or +// disclosure are subject to the GNU General Purpose License version 3. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +#include "textclient.h" + +#define IFEXPERT if (userflags&US_EXPERT) +#define IFNEXPERT if ((userflags&US_EXPERT)==0) +#define IFAIDE if (axlevel>=AxAideU) +#define IFNAIDE if (axlevelbandon room cmd */ +long maxmsgnum; /* used for oto */ +char sigcaught = 0; +char rc_username[USERNAME_SIZE]; +char rc_password[32]; +char hostbuf[SIZ]; +char portbuf[SIZ]; +char rc_floor_mode; +char floor_mode; +char curr_floor = 0; /* number of current floor */ +char floorlist[128][SIZ]; /* names of floors */ +int termn8 = 0; /* Set to nonzero to cause a logoff */ +int secure; /* Set to nonzero when wire is encrypted */ + +extern char instant_msgs; /* instant messages waiting! */ +extern int rc_ansi_color; /* ansi color value from citadel.rc */ +extern int next_lazy_cmd; + +CtdlIPC *ipc_for_signal_handlers; /* KLUDGE cover your eyes */ +int enable_syslog = 0; + + +/* + * here is our 'clean up gracefully and exit' routine + */ +void ctdl_logoff(char *file, int line, CtdlIPC * ipc, int code) +{ + int lp; + + if (editor_pid > 0) { /* kill the editor if it's running */ + kill(editor_pid, SIGHUP); + } + + /* Free the ungoto list */ + for (lp = 0; lp < uglistsize; lp++) { + free(uglist[lp]); + } + +/* Shut down the server connection ... but not if the logoff code is 3, + * because that means we're exiting because we already lost the server. + */ + if (code != 3) { + CtdlIPCQuit(ipc); + } + +/* + * now clean up various things + */ + unlink(temp); + unlink(temp2); + nukedir(tempdir); + + /* Violently kill off any child processes if Citadel is + * the login shell. + */ + if (getppid() == 1) { + kill(0 - getpgrp(), SIGTERM); + sleep(1); + kill(0 - getpgrp(), SIGKILL); + } + color(ORIGINAL_PAIR); /* Restore the old color settings */ + stty_ctdl(SB_RESTORE); /* return the old terminal settings */ + /* + * uncomment the following if you need to know why Citadel exited + printf("*** Exit code %d at %s:%d\n", code, file, line); + sleep(2); + */ + exit(code); /* exit with the proper exit code */ +} + + + +/* + * signal catching function for hangups... + */ +void dropcarr(int signum) +{ + logoff(NULL, 3); /* No IPC when server's already gone! */ +} + + + +/* + * catch SIGCONT to reset terminal modes when were are put back into the + * foreground. + */ +void catch_sigcont(int signum) +{ + stty_ctdl(SB_LAST); + signal(SIGCONT, catch_sigcont); +} + + +/* general purpose routines */ + +/* display a file */ +void formout(CtdlIPC * ipc, char *name) +{ + int r; /* IPC return code */ + char buf[SIZ]; + char *text = NULL; + + r = CtdlIPCSystemMessage(ipc, name, &text, buf); + if (r / 100 != 1) { + scr_printf("%s\n", buf); + return; + } + if (text) { + fmout(screenwidth, NULL, text, NULL, 1); + free(text); + } +} + + +void userlist(CtdlIPC * ipc, char *patn) { + char buf[SIZ]; + char fl[26]; // a buffer this small will prevent it overrunning the column + struct tm tmbuf; + time_t lc; + int r; // IPC response code + char *listing = NULL; + + r = CtdlIPCUserListing(ipc, patn, &listing, buf); + if (r / 100 != 1) { + scr_printf("%s\n", buf); + return; + } + + scr_printf(" User Name Num L Last Visit Logins Messages\n"); + scr_printf("------------------------- ----- - ---------- ------ --------\n"); + if (listing != NULL) + while (!IsEmptyStr(listing)) { + extract_token(buf, listing, 0, '\n', sizeof buf); + remove_token(listing, 0, '\n'); + + if (sigcaught == 0) { + extract_token(fl, buf, 0, '|', sizeof fl); + if (pattern(fl, patn) >= 0) { + scr_printf("%-25s ", fl); + scr_printf("%5ld %d ", extract_long(buf, 2), extract_int(buf, 1)); + lc = extract_long(buf, 3); + localtime_r(&lc, &tmbuf); + scr_printf("%02d/%02d/%04d ", (tmbuf.tm_mon + 1), tmbuf.tm_mday, (tmbuf.tm_year + 1900)); + scr_printf("%6ld %8ld\n", extract_long(buf, 4), extract_long(buf, 5)); + } + + } + } + free(listing); + scr_printf("\n"); +} + + +/* + * grab assorted info about the user... + */ +void load_user_info(char *params) { + extract_token(fullname, params, 0, '|', sizeof fullname); + axlevel = extract_int(params, 1); + timescalled = extract_int(params, 2); + posted = extract_int(params, 3); + userflags = extract_int(params, 4); + usernum = extract_long(params, 5); + lastcall = extract_long(params, 6); +} + + +/* + * Remove a room from the march list. 'floornum' is ignored unless + * 'roomname' is set to _FLOOR_, in which case all rooms on the requested + * floor will be removed from the march list. + */ +void remove_march(char *roomname, int floornum) { + struct march *mptr, *mptr2; + + if (marchptr == NULL) + return; + + if ((!strcasecmp(marchptr->march_name, roomname)) + || ((!strcasecmp(roomname, "_FLOOR_")) && (marchptr->march_floor == floornum))) { + mptr = marchptr->next; + free(marchptr); + marchptr = mptr; + return; + } + mptr2 = marchptr; + for (mptr = marchptr; mptr != NULL; mptr = mptr->next) { + + if ((!strcasecmp(mptr->march_name, roomname)) + || ((!strcasecmp(roomname, "_FLOOR_")) + && (mptr->march_floor == floornum))) { + + mptr2->next = mptr->next; + free(mptr); + mptr = mptr2; + } + else { + mptr2 = mptr; + } + } +} + + +/* + * Locate the room on the march list which we most want to go to. Each room + * is measured given a "weight" of preference based on various factors. + */ +char *pop_march(int desired_floor, struct march *_march) +{ + static char TheRoom[ROOMNAMELEN]; + int TheWeight = 0; + int weight; + struct march *mptr = NULL; + + strcpy(TheRoom, "_BASEROOM_"); + if (_march == NULL) + return (TheRoom); + + for (mptr = _march; mptr != NULL; mptr = mptr->next) { + weight = 0; + if ((strcasecmp(mptr->march_name, "_BASEROOM_"))) + weight = weight + 10000; + if (mptr->march_floor == desired_floor) + weight = weight + 5000; + + weight = weight + ((128 - (mptr->march_floor)) * 128); + weight = weight + (128 - (mptr->march_order)); + + if (weight > TheWeight) { + TheWeight = weight; + strcpy(TheRoom, mptr->march_name); + } + } + return (TheRoom); +} + + +/* + * jump directly to a room + */ +void dotgoto(CtdlIPC * ipc, char *towhere, int display_name, int fromungoto) +{ + char aaa[SIZ], bbb[SIZ]; + static long ls = 0L; + int newmailcount = 0; + int partial_match, best_match; + char from_floor; + int ugpos = uglistsize; + int r; /* IPC result code */ + struct ctdlipcroom *room = NULL; + int rv = 0; + + /* store ungoto information */ + if (fromungoto == 0) { + /* sloppy slide them all down, hey it's the client, who cares. :-) */ + if (uglistsize >= (UGLISTLEN - 1)) { + int lp; + free(uglist[0]); + for (lp = 0; lp < (UGLISTLEN - 1); lp++) { + uglist[lp] = uglist[lp + 1]; + uglistlsn[lp] = uglistlsn[lp + 1]; + } + ugpos--; + } else { + uglistsize++; + } + + uglist[ugpos] = malloc(strlen(room_name) + 1); + strcpy(uglist[ugpos], room_name); + uglistlsn[ugpos] = ls; + } + + /* first try an exact match */ + r = CtdlIPCGotoRoom(ipc, towhere, "", &room, aaa); + if (r / 10 == 54) { + newprompt("Enter room password: ", bbb, 9); + r = CtdlIPCGotoRoom(ipc, towhere, bbb, &room, aaa); + if (r / 10 == 54) { + scr_printf("Wrong password.\n"); + return; + } + } + + /* + * If a match is not found, try a partial match. + * Partial matches anywhere in the string carry a weight of 1, + * left-aligned matches carry a weight of 2. Pick the room that + * has the highest-weighted match. Do not match on forgotten + * rooms. + */ + if (r / 100 != 2) { + struct march *march = NULL; + + best_match = 0; + strcpy(bbb, ""); + + r = CtdlIPCKnownRooms(ipc, SubscribedRooms, AllFloors, &march, aaa); + if (r / 100 == 1) { + /* Run the roomlist; free the data as we go */ + struct march *mp = march; /* Current */ + + while (mp) { + partial_match = 0; + if (pattern(mp->march_name, towhere) >= 0) { + partial_match = 1; + } + if (!strncasecmp(mp->march_name, towhere, strlen(towhere))) { + partial_match = 2; + } + if (partial_match > best_match) { + strcpy(bbb, mp->march_name); + best_match = partial_match; + } + /* Both pointers are NULL at end of list */ + march = mp->next; + free(mp); + mp = march; + } + } + + if (IsEmptyStr(bbb)) { + scr_printf("No room '%s'.\n", towhere); + return; + } + r = CtdlIPCGotoRoom(ipc, bbb, "", &room, aaa); + } + if (r / 100 != 1 && r / 100 != 2) { + scr_printf("%s\n", aaa); + return; + } + strncpy(room_name, room->RRname, ROOMNAMELEN); + room_flags = room->RRflags; + room_flags2 = room->RRflags2; + from_floor = curr_floor; + curr_floor = room->RRfloor; + + // Determine, based on the room's default view, whether an nter message command will be valid here. + switch (room->RRdefaultview) { + case VIEW_BBS: + case VIEW_MAILBOX: + entmsg_ok = ENTMSG_OK_YES; + break; + case VIEW_BLOG: + entmsg_ok = ENTMSG_OK_BLOG; + break; + default: + entmsg_ok = ENTMSG_OK_NO; + break; + } + + remove_march(room_name, 0); + if (!strcasecmp(towhere, "_BASEROOM_")) + remove_march(towhere, 0); + if (!room->RRunread) + next_lazy_cmd = 5; /* Don't read new if no new msgs */ + if ((from_floor != curr_floor) && (display_name > 0) && (floor_mode == 1)) { + if (floorlist[(int) curr_floor][0] == 0) + load_floorlist(ipc); + scr_printf("(Entering floor: %s)\n", &floorlist[(int) curr_floor][0]); + } + if (display_name == 1) { + color(BRIGHT_WHITE); + scr_printf("%s ", room_name); + color(DIM_WHITE); + scr_printf("- "); + } + if (display_name != 2) { + color(BRIGHT_YELLOW); + scr_printf("%d ", room->RRunread); + color(DIM_WHITE); + scr_printf("new of "); + color(BRIGHT_YELLOW); + scr_printf("%d ", room->RRtotal); + color(DIM_WHITE); + scr_printf("messages.\n"); + } + highest_msg_read = room->RRlastread; + maxmsgnum = room->RRhighest; + is_mail = room->RRismailbox; + is_room_aide = room->RRaide; + ls = room->RRlastread; + + /* read info file if necessary */ + if (room->RRinfoupdated > 0) + readinfo(ipc); + + /* check for newly arrived mail if we can */ + newmailcount = room->RRnewmail; + if (newmailcount > 0) { + color(BRIGHT_RED); + if (newmailcount == 1) { + scr_printf("*** A new mail message has arrived.\n"); + } + else { + scr_printf("*** %d new mail messages have arrived.\n", newmailcount); + } + color(DIM_WHITE); + if (!IsEmptyStr(rc_gotmail_cmd)) { + rv = system(rc_gotmail_cmd); + if (rv) + scr_printf("*** failed to check for mail calling %s Reason %d.\n", rc_gotmail_cmd, rv); + } + } + free(room); + + if (screenwidth > 5) + snprintf(&status_line[1], screenwidth - 1, "%s | %s | %s | %s | %d new mail |", + (secure ? "Encrypted" : "Unencrypted"), + ipc->ServInfo.humannode, ipc->ServInfo.site_location, room_name, newmailcount); +} + + +/* Goto next room having unread messages. + * We want to skip over rooms that the user has already been to, and take the + * user back to the lobby when done. The room we end up in is placed in + * newroom - which is set to 0 (the lobby) initially. + */ +void gotonext(CtdlIPC * ipc) { + char buf[SIZ]; + struct march *mptr, *mptr2; + char next_room[ROOMNAMELEN]; + + /* Check to see if the march-mode list is already allocated. + * If it is, pop the first room off the list and go there. + */ + if (marchptr == NULL) { + CtdlIPCKnownRooms(ipc, SubscribedRoomsWithNewMessages, AllFloors, &marchptr, buf); + + // Add _BASEROOM_ to the end of the march list, so the user will end up + // in the system base room (usually the Lobby>) at the end of the loop. + mptr = (struct march *) malloc(sizeof(struct march)); + mptr->next = NULL; + mptr->march_order = 0; + mptr->march_floor = 0; + strcpy(mptr->march_name, "_BASEROOM_"); + if (marchptr == NULL) { + marchptr = mptr; + } else { + mptr2 = marchptr; + while (mptr2->next != NULL) + mptr2 = mptr2->next; + mptr2->next = mptr; + } + + // ...and remove the room we're currently in, so a oto doesn't make us walk around in circles + remove_march(room_name, 0); + } + if (marchptr != NULL) { + strcpy(next_room, pop_march(curr_floor, marchptr)); + } + else { + strcpy(next_room, "_BASEROOM_"); + } + remove_march(next_room, 0); + dotgoto(ipc, next_room, 1, 0); +} + + +/* + * forget all rooms on a given floor + */ +void forget_all_rooms_on(CtdlIPC *ipc, int ffloor) { + char buf[SIZ]; + struct march *flist = NULL; + struct march *fptr = NULL; + struct ctdlipcroom *room = NULL; + int r; /* IPC response code */ + + scr_printf("Forgetting all rooms on %s...\n", &floorlist[ffloor][0]); + remove_march("_FLOOR_", ffloor); + r = CtdlIPCKnownRooms(ipc, AllAccessibleRooms, ffloor, &flist, buf); + if (r / 100 != 1) { + scr_printf("Error %d: %s\n", r, buf); + return; + } + while (flist) { + r = CtdlIPCGotoRoom(ipc, flist->march_name, "", &room, buf); + if (r / 100 == 2) { + r = CtdlIPCForgetRoom(ipc, buf); + if (r / 100 != 2) { + scr_printf("Error %d: %s\n", r, buf); + } + + } + fptr = flist; + flist = flist->next; + free(fptr); + } + if (room) + free(room); +} + + +/* + * routine called by gotofloor() to move to a new room on a new floor + */ +void gf_toroom(CtdlIPC * ipc, char *towhere, int mode) +{ + int floor_being_left; + + floor_being_left = curr_floor; + + if (mode == GF_GOTO) { /* <;G>oto mode */ + updatels(ipc); + dotgoto(ipc, towhere, 1, 0); + } else if (mode == GF_SKIP) { /* <;S>kip mode */ + dotgoto(ipc, towhere, 1, 0); + remove_march("_FLOOR_", floor_being_left); + } else if (mode == GF_ZAP) { /* <;Z>ap mode */ + dotgoto(ipc, towhere, 1, 0); + remove_march("_FLOOR_", floor_being_left); + forget_all_rooms_on(ipc, floor_being_left); + } +} + + +/* + * go to a new floor + */ +void gotofloor(CtdlIPC * ipc, char *towhere, int mode) +{ + int a, tofloor; + int r; /* IPC response code */ + struct march *mptr; + char buf[SIZ], targ[SIZ]; + + if (floorlist[0][0] == 0) + load_floorlist(ipc); + tofloor = (-1); + for (a = 0; a < 128; ++a) + if (!strcasecmp(&floorlist[a][0], towhere)) + tofloor = a; + + if (tofloor < 0) { + for (a = 0; a < 128; ++a) { + if (!strncasecmp(&floorlist[a][0], towhere, strlen(towhere))) { + tofloor = a; + } + } + } + if (tofloor < 0) { + for (a = 0; a < 128; ++a) + if (pattern(towhere, &floorlist[a][0]) > 0) + tofloor = a; + } + if (tofloor < 0) { + scr_printf("No floor '%s'.\n", towhere); + return; + } + for (mptr = marchptr; mptr != NULL; mptr = mptr->next) { + if ((mptr->march_floor) == tofloor) { + gf_toroom(ipc, mptr->march_name, mode); + return; + } + } + + /* Find first known room on the floor */ + + strcpy(targ, ""); + mptr = NULL; + r = CtdlIPCKnownRooms(ipc, SubscribedRooms, tofloor, &mptr, buf); + if (r / 100 == 1) { + struct march *tmp = mptr; + + /*. . . according to room order */ + if (mptr) + strcpy(targ, pop_march(tofloor, mptr)); + while (mptr) { + tmp = mptr->next; + free(mptr); + mptr = tmp; + } + } + if (!IsEmptyStr(targ)) { + gf_toroom(ipc, targ, mode); + return; + } + + /* No known rooms on the floor; unzap the first one then */ + + strcpy(targ, ""); + mptr = NULL; + r = CtdlIPCKnownRooms(ipc, AllAccessibleRooms, tofloor, &mptr, buf); + if (r / 100 == 1) { + struct march *tmp = mptr; + + /*. . . according to room order */ + if (mptr) + strcpy(targ, pop_march(tofloor, mptr)); + while (mptr) { + tmp = mptr->next; + free(mptr); + mptr = tmp; + } + } + if (!IsEmptyStr(targ)) { + gf_toroom(ipc, targ, mode); + } else { + scr_printf("There are no rooms on '%s'.\n", &floorlist[tofloor][0]); + } +} + + +/* + * Indexing mechanism for a room list, called by gotoroomstep() + */ +void room_tree_list_query(struct ctdlroomlisting *rp, char *findrmname, int findrmslot, char *rmname, int *rmslot, int *rmtotal) +{ + char roomname[ROOMNAMELEN]; + static int cur_rmslot = 0; + + if (rp == NULL) { + cur_rmslot = 0; + return; + } + + if (rp->lnext != NULL) { + room_tree_list_query(rp->lnext, findrmname, findrmslot, rmname, rmslot, rmtotal); + } + + if (sigcaught == 0) { + strcpy(roomname, rp->rlname); + + if (rmname != NULL) { + if (cur_rmslot == findrmslot) { + strcpy(rmname, roomname); + } + } + if (rmslot != NULL) { + if (!strcmp(roomname, findrmname)) { + *rmslot = cur_rmslot; + } + } + cur_rmslot++; + } + + if (rp->rnext != NULL) { + room_tree_list_query(rp->rnext, findrmname, findrmslot, rmname, rmslot, rmtotal); + } + + if ((rmname == NULL) && (rmslot == NULL)) + free(rp); + + if (rmtotal != NULL) { + *rmtotal = cur_rmslot; + } +} + +/* + * step through rooms on current floor + */ +void gotoroomstep(CtdlIPC * ipc, int direction, int mode) +{ + struct march *listing = NULL; + struct march *mptr; + int r; /* IPC response code */ + char buf[SIZ]; + struct ctdlroomlisting *rl = NULL; + struct ctdlroomlisting *rp; + struct ctdlroomlisting *rs; + int list_it; + char rmname[ROOMNAMELEN]; + int rmslot = 0; + int rmtotal; + + /* Ask the server for a room list */ + r = CtdlIPCKnownRooms(ipc, SubscribedRooms, (-1), &listing, buf); + if (r / 100 != 1) { + listing = NULL; + } + + load_floorlist(ipc); + + for (mptr = listing; mptr != NULL; mptr = mptr->next) { + list_it = 1; + + if (floor_mode && (mptr->march_floor != curr_floor)) + list_it = 0; + + if (list_it) { + rp = malloc(sizeof(struct ctdlroomlisting)); + strncpy(rp->rlname, mptr->march_name, ROOMNAMELEN); + rp->rlflags = mptr->march_flags; + rp->rlfloor = mptr->march_floor; + rp->rlorder = mptr->march_order; + rp->lnext = NULL; + rp->rnext = NULL; + + rs = rl; + if (rl == NULL) { + rl = rp; + } else { + while (rp != NULL) { + if (rordercmp(rp, rs) < 0) { + if (rs->lnext == NULL) { + rs->lnext = rp; + rp = NULL; + } else { + rs = rs->lnext; + } + } else { + if (rs->rnext == NULL) { + rs->rnext = rp; + rp = NULL; + } else { + rs = rs->rnext; + } + } + } + } + } + } + + /* Find position of current room */ + room_tree_list_query(NULL, NULL, 0, NULL, NULL, NULL); + room_tree_list_query(rl, room_name, 0, NULL, &rmslot, &rmtotal); + + if (direction == 0) { /* Previous room */ + /* If we're at the first room, wrap to the last room */ + if (rmslot == 0) { + rmslot = rmtotal - 1; + } + else { + rmslot--; + } + } + else { /* Next room */ + /* If we're at the last room, wrap to the first room */ + if (rmslot == rmtotal - 1) { + rmslot = 0; + } + else { + rmslot++; + } + } + + /* Get name of next/previous room */ + room_tree_list_query(NULL, NULL, 0, NULL, NULL, NULL); + room_tree_list_query(rl, NULL, rmslot, rmname, NULL, NULL); + + /* Free the tree */ + room_tree_list_query(rl, NULL, 0, NULL, NULL, NULL); + + if (mode == 0) { /* not skipping */ + updatels(ipc); + } + + /* Free the room list */ + while (listing) { + mptr = listing->next; + free(listing); + listing = mptr; + }; + + dotgoto(ipc, rmname, 1, 0); +} + + +/* + * step through floors on system + */ +void gotofloorstep(CtdlIPC * ipc, int direction, int mode) { + int tofloor; + + if (floorlist[0][0] == 0) + load_floorlist(ipc); + + empty_keep_going: + + if (direction == 0) { /* Previous floor */ + if (curr_floor) + tofloor = curr_floor - 1; + else + tofloor = 127; + + while (!floorlist[tofloor][0]) + tofloor--; + } else { /* Next floor */ + if (curr_floor < 127) + tofloor = curr_floor + 1; + else + tofloor = 0; + + while (!floorlist[tofloor][0] && tofloor < 127) + tofloor++; + if (!floorlist[tofloor][0]) + tofloor = 0; + } + /* ;g works when not in floor mode so . . . */ + if (!floor_mode) { + scr_printf("(%s)\n", floorlist[tofloor]); + } + + gotofloor(ipc, floorlist[tofloor], mode); + if (curr_floor != tofloor) { /* gotofloor failed */ + curr_floor = tofloor; + goto empty_keep_going; + } +} + + +/* + * Display user 'preferences'. + */ +extern int rc_prompt_control; +void read_config(CtdlIPC * ipc) +{ + char buf[SIZ]; + char *resp = NULL; + int r; /* IPC response code */ + char _fullname[USERNAME_SIZE]; + long _usernum; + int _axlevel, _timescalled, _posted; + time_t _lastcall; + struct ctdluser *user = NULL; + + /* get misc user info */ + r = CtdlIPCGetBio(ipc, fullname, &resp, buf); + if (r / 100 != 1) { + scr_printf("%s\n", buf); + return; + } + extract_token(_fullname, buf, 1, '|', sizeof fullname); + _usernum = extract_long(buf, 2); + _axlevel = extract_int(buf, 3); + _lastcall = extract_long(buf, 4); + _timescalled = extract_int(buf, 5); + _posted = extract_int(buf, 6); + free(resp); + resp = NULL; + + /* get preferences */ + r = CtdlIPCGetConfig(ipc, &user, buf); + if (r / 100 != 2) { + scr_printf("%s\n", buf); + free(user); + return; + } + + /* show misc user info */ + scr_printf("%s\nAccess level: %d (%s)\n" + "User #%ld / %d Calls / %d Posts", _fullname, _axlevel, axdefs[(int) _axlevel], _usernum, _timescalled, _posted); + if (_lastcall > 0L) { + scr_printf(" / Curr login: %s", asctime(localtime(&_lastcall))); + } + scr_printf("\n"); + + /* show preferences */ + scr_printf("Are you an experienced Citadel user: "); + color(BRIGHT_CYAN); + scr_printf("%s\n", (user->flags & US_EXPERT) ? "Yes" : "No"); + color(DIM_WHITE); + scr_printf("Print last old message on New message request: "); + color(BRIGHT_CYAN); + scr_printf("%s\n", (user->flags & US_LASTOLD) ? "Yes" : "No"); + color(DIM_WHITE); + scr_printf("Prompt after each message: "); + color(BRIGHT_CYAN); + scr_printf("%s\n", (!(user->flags & US_NOPROMPT)) ? "Yes" : "No"); + color(DIM_WHITE); + if ((user->flags & US_NOPROMPT) == 0) { + scr_printf("Use 'disappearing' prompts: "); + color(BRIGHT_CYAN); + scr_printf("%s\n", (user->flags & US_DISAPPEAR) ? "Yes" : "No"); + color(DIM_WHITE); + } + scr_printf("Pause after each screenful of text: "); + color(BRIGHT_CYAN); + scr_printf("%s\n", (user->flags & US_PAGINATOR) ? "Yes" : "No"); + color(DIM_WHITE); + if (rc_prompt_control == 3 && (user->flags & US_PAGINATOR)) { + scr_printf("ext and top work at paginator prompt: "); + color(BRIGHT_CYAN); + scr_printf("%s\n", (user->flags & US_PROMPTCTL) ? "Yes" : "No"); + color(DIM_WHITE); + } + if (rc_floor_mode == RC_DEFAULT) { + scr_printf("View rooms by floor: "); + color(BRIGHT_CYAN); + scr_printf("%s\n", (user->flags & US_FLOORS) ? "Yes" : "No"); + color(DIM_WHITE); + } + if (rc_ansi_color == 3) { + scr_printf("Enable color support: "); + color(BRIGHT_CYAN); + scr_printf("%s\n", (user->flags & US_COLOR) ? "Yes" : "No"); + color(DIM_WHITE); + } + scr_printf("Be unlisted in userlog: "); + color(BRIGHT_CYAN); + scr_printf("%s\n", (user->flags & US_UNLISTED) ? "Yes" : "No"); + color(DIM_WHITE); + if (!IsEmptyStr(editor_path)) { + scr_printf("Always enter messages with the full-screen editor: "); + color(BRIGHT_CYAN); + scr_printf("%s\n", (user->flags & US_EXTEDIT) ? "Yes" : "No"); + color(DIM_WHITE); + } + free(user); +} + + +/* + * Display system statistics. + */ +void system_info(CtdlIPC * ipc) +{ + char buf[SIZ]; + char *resp = NULL; + size_t bytes; + int mrtg_users, mrtg_active_users; + char mrtg_server_uptime[40]; + long mrtg_himessage; + + /* get #users, #active & server uptime */ + CtdlIPCGenericCommand(ipc, "MRTG|users", NULL, 0, &resp, &bytes, buf); + mrtg_users = extract_int(resp, 0); + remove_token(resp, 0, '\n'); + mrtg_active_users = extract_int(resp, 0); + remove_token(resp, 0, '\n'); + extract_token(mrtg_server_uptime, resp, 0, '\n', sizeof mrtg_server_uptime); + free(resp); + resp = NULL; + + /* get high message# */ + CtdlIPCGenericCommand(ipc, "MRTG|messages", NULL, 0, &resp, &bytes, buf); + mrtg_himessage = extract_long(resp, 0); + free(resp); + resp = NULL; + + /* refresh server info just in case */ + CtdlIPCServerInfo(ipc, buf); + + scr_printf("You are connected to %s (%s) @%s\n", ipc->ServInfo.nodename, ipc->ServInfo.humannode, ipc->ServInfo.fqdn); + scr_printf("running %s with text client v%.2f,\n", ipc->ServInfo.software, (float) CLIENT_VERSION / 100); + scr_printf("server build %s,\n", ipc->ServInfo.svn_revision, (float) CLIENT_VERSION / 100); + scr_printf("and located in %s.\n", ipc->ServInfo.site_location); + scr_printf("Connected users %d / Active users %d / Highest message #%ld\n", mrtg_users, mrtg_active_users, mrtg_himessage); + scr_printf("Server uptime: %s\n", mrtg_server_uptime); + scr_printf("Your system administrator is %s.\n", ipc->ServInfo.sysadm); +} + + +/* + * forget all rooms on current floor + */ +void forget_this_floor(CtdlIPC * ipc) +{ + if (curr_floor == 0) { + scr_printf("Can't forget this floor.\n"); + return; + } + if (floorlist[0][0] == 0) { + load_floorlist(ipc); + } + scr_printf("Are you sure you want to forget all rooms on %s? ", &floorlist[(int) curr_floor][0]); + if (yesno() == 0) { + return; + } + + gf_toroom(ipc, "_BASEROOM_", GF_ZAP); +} + + +/* + * set floor mode depending on client, server, and user settings + */ +void set_floor_mode(CtdlIPC * ipc) +{ + if (ipc->ServInfo.ok_floors == 0) { + floor_mode = 0; /* Don't use floors if the server */ + } + /* doesn't support them! */ + else { + if (rc_floor_mode == RC_NO) { /* never use floors */ + floor_mode = 0; + } + if (rc_floor_mode == RC_YES) { /* always use floors */ + floor_mode = 1; + } + if (rc_floor_mode == RC_DEFAULT) { /* user choice */ + floor_mode = ((userflags & US_FLOORS) ? 1 : 0); + } + } +} + +/* + * Set or change the user's password + */ +int set_password(CtdlIPC * ipc) +{ + char pass1[20]; + char pass2[20]; + char buf[SIZ]; + + if (!IsEmptyStr(rc_password)) { + strcpy(pass1, rc_password); + strcpy(pass2, rc_password); + } else { + IFNEXPERT formout(ipc, "changepw"); + newprompt("Enter a new password: ", pass1, -19); + newprompt("Enter it again to confirm: ", pass2, -19); + } + strproc(pass1); + strproc(pass2); + if (!strcasecmp(pass1, pass2)) { + CtdlIPCChangePassword(ipc, pass1, buf); + scr_printf("%s\n", buf); + offer_to_remember_password(ipc, hostbuf, portbuf, fullname, pass1); + return (0); + } else { + scr_printf("*** They don't match... try again.\n"); + return (1); + } +} + + +/* + * get info about the server we've connected to + */ +void get_serv_info(CtdlIPC * ipc, char *supplied_hostname) +{ + char buf[SIZ]; + + CtdlIPCServerInfo(ipc, buf); + moreprompt = ipc->ServInfo.moreprompt; + + /* be nice and identify ourself to the server */ + CtdlIPCIdentifySoftware(ipc, CLIENT_TYPE, 0, CLIENT_VERSION, + (ipc->isLocal ? "local" : "Citadel text mode client"), (supplied_hostname) ? supplied_hostname : + /* Look up the , in the bible if you're confused */ + (locate_host(ipc, buf), buf), buf); + + /* Indicate to the server that we prefer to decode Base64 and + * quoted-printable on the client side. + */ + if ((CtdlIPCSpecifyPreferredFormats(ipc, buf, "dont_decode") / 100) != 2) { + scr_printf("ERROR: Extremely old server; MSG4 framework not supported.\n"); + logoff(ipc, 0); + } + + /* + * Tell the server what our preferred content formats are. + * + * Originally we preferred HTML over plain text because we can format + * it to the reader's screen width, but since our HTML-to-text parser + * isn't really all that great, it's probably better to just go with + * the plain text when we have it available. + */ + if ((CtdlIPCSpecifyPreferredFormats(ipc, buf, "text/plain|text/html|text/x-markdown") / 100) != 2) { + scr_printf("ERROR: Extremely old server; MSG4 framework not supported.\n"); + logoff(ipc, 0); + } +} + + +/* + * Session username compare function for SortOnlineUsers() + */ +int rwho_username_cmp(const void *rec1, const void *rec2) +{ + char *u1, *u2; + + u1 = strchr(rec1, '|'); + u2 = strchr(rec2, '|'); + + return strcasecmp((u1 ? ++u1 : ""), (u2 ? ++u2 : "")); +} + + +/* + * Idle time compare function for SortOnlineUsers() + */ +int idlecmp(const void *rec1, const void *rec2) +{ + time_t i1, i2; + + i1 = extract_long(rec1, 5); + i2 = extract_long(rec2, 5); + + if (i1 < i2) + return (1); + if (i1 > i2) + return (-1); + return (0); +} + + +/* + * Sort the list of online users by idle time. + * This function frees the supplied buffer, and returns a new one + * to the caller. The caller is responsible for freeing the returned buffer. + * + * If 'condense' is nonzero, multiple sessions for the same user will be + * combined into one for brevity. + */ +char *SortOnlineUsers(char *listing, int condense) +{ + int rows; + char *sortbuf; + char *retbuf; + char buf[SIZ]; + int i; + + rows = num_tokens(listing, '\n'); + sortbuf = malloc(rows * SIZ); + if (sortbuf == NULL) + return (listing); + retbuf = malloc(rows * SIZ); + if (retbuf == NULL) { + free(sortbuf); + return (listing); + } + + /* Copy the list into a fixed-record-size array for sorting */ + for (i = 0; i < rows; ++i) { + memset(buf, 0, SIZ); + extract_token(buf, listing, i, '\n', sizeof buf); + memcpy(&sortbuf[i * SIZ], buf, (size_t) SIZ); + } + + /* Sort by idle time */ + qsort(sortbuf, rows, SIZ, idlecmp); + + /* Combine multiple sessions for the same user */ + if (condense) { + qsort(sortbuf, rows, SIZ, rwho_username_cmp); + if (rows > 1) + for (i = 1; i < rows; ++i) + if (i > 0) { + char u1[USERNAME_SIZE]; + char u2[USERNAME_SIZE]; + extract_token(u1, &sortbuf[(i - 1) * SIZ], 1, '|', sizeof u1); + extract_token(u2, &sortbuf[i * SIZ], 1, '|', sizeof u2); + if (!strcasecmp(u1, u2)) { + memcpy(&sortbuf[i * SIZ], &sortbuf[(i + 1) * SIZ], (rows - i - 1) * SIZ); + --rows; + --i; + } + } + + qsort(sortbuf, rows, SIZ, idlecmp); /* idle sort again */ + } + + /* Copy back to a \n delimited list */ + strcpy(retbuf, ""); + for (i = 0; i < rows; ++i) { + if (!IsEmptyStr(&sortbuf[i * SIZ])) { + strcat(retbuf, &sortbuf[i * SIZ]); + if (i < (rows - 1)) + strcat(retbuf, "\n"); + } + } + free(listing); + free(sortbuf); + return (retbuf); +} + + +/* + * Display list of users currently logged on to the server + */ +void who_is_online(CtdlIPC * ipc, int longlist) +{ + char buf[SIZ], username[SIZ], roomname[SIZ], fromhost[SIZ]; + char flags[SIZ]; + char actual_user[SIZ], actual_room[SIZ], actual_host[SIZ]; + char clientsoft[SIZ]; + time_t timenow = 0; + time_t idletime, idlehours, idlemins, idlesecs; + int last_session = (-1); + int skipidle = 0; + char *listing = NULL; + int r; /* IPC response code */ + + if (longlist == 2) { + longlist = 0; + skipidle = 1; + } + + if (!longlist) { + color(BRIGHT_WHITE); + scr_printf(" User Name Room "); + if (screenwidth >= 80) + scr_printf(" Idle From host"); + scr_printf("\n"); + color(DIM_WHITE); + scr_printf(" ------------------------- --------------------"); + if (screenwidth >= 80) + scr_printf(" ---- ------------------------"); + scr_printf("\n"); + } + r = CtdlIPCOnlineUsers(ipc, &listing, &timenow, buf); + listing = SortOnlineUsers(listing, (!longlist)); + if (r / 100 == 1) { + while (!IsEmptyStr(listing)) { + int isidle = 0; + + /* Get another line */ + extract_token(buf, listing, 0, '\n', sizeof buf); + remove_token(listing, 0, '\n'); + + extract_token(username, buf, 1, '|', sizeof username); + extract_token(roomname, buf, 2, '|', sizeof roomname); + extract_token(fromhost, buf, 3, '|', sizeof fromhost); + extract_token(clientsoft, buf, 4, '|', sizeof clientsoft); + extract_token(flags, buf, 7, '|', sizeof flags); + + idletime = timenow - extract_long(buf, 5); + idlehours = idletime / 3600; + idlemins = (idletime - (idlehours * 3600)) / 60; + idlesecs = (idletime - (idlehours * 3600) - (idlemins * 60)); + + if (idletime > rc_idle_threshold) { + if (skipidle) { + isidle = 1; + } + } + + if (longlist) { + extract_token(actual_user, buf, 8, '|', sizeof actual_user); + extract_token(actual_room, buf, 9, '|', sizeof actual_room); + extract_token(actual_host, buf, 10, '|', sizeof actual_host); + + scr_printf(" Flags: %s\n", flags); + scr_printf("Session: %d\n", extract_int(buf, 0)); + scr_printf(" Name: %s\n", username); + scr_printf("In room: %s\n", roomname); + scr_printf(" Host: %s\n", fromhost); + scr_printf(" Client: %s\n", clientsoft); + scr_printf(" Idle: %ld:%02ld:%02ld\n", (long) idlehours, (long) idlemins, (long) idlesecs); + + if ((!IsEmptyStr(actual_user) && !IsEmptyStr(actual_room) && !IsEmptyStr(actual_host))) { + scr_printf("(really "); + if (!IsEmptyStr(actual_user)) + scr_printf("<%s> ", actual_user); + if (!IsEmptyStr(actual_room)) + scr_printf("in <%s> ", actual_room); + if (!IsEmptyStr(actual_host)) + scr_printf("from <%s> ", actual_host); + scr_printf(")\n"); + } + scr_printf("\n"); + + } + else { + if (isidle == 0) { + if (extract_int(buf, 0) == last_session) { + scr_printf(" "); + } + else { + color(BRIGHT_MAGENTA); + scr_printf("%-3s", flags); + } + last_session = extract_int(buf, 0); + color(BRIGHT_CYAN); + scr_printf("%-25s ", username); + color(BRIGHT_MAGENTA); + roomname[20] = 0; + scr_printf("%-20s", roomname); + + if (screenwidth >= 80) { + scr_printf(" "); + if (idletime > rc_idle_threshold) { + /* over 1000d, must be gone fishing */ + if (idlehours > 23999) { + scr_printf("fish"); + /* over 10 days */ + } + else if (idlehours > 239) { + scr_printf("%3ldd", idlehours / 24); + /* over 10 hours */ + } + else if (idlehours > 9) { + scr_printf("%1ldd%02ld", idlehours / 24, idlehours % 24); + /* less than 10 hours */ + } + else { + scr_printf("%1ld:%02ld", idlehours, idlemins); + } + } + else { + scr_printf(" "); + } + scr_printf(" "); + color(BRIGHT_CYAN); + fromhost[24] = '\0'; + scr_printf("%-24s", fromhost); + } + scr_printf("\n"); + color(DIM_WHITE); + } + } + } + } + free(listing); +} + + +void enternew(CtdlIPC * ipc, char *desc, char *buf, int maxlen) +{ + char bbb[128]; + snprintf(bbb, sizeof bbb, "Enter in your new %s: ", desc); + newprompt(bbb, buf, maxlen); +} + + +int shift(int argc, char **argv, int start, int count) { + int i; + + for (i = start; i < (argc - count); ++i) { + argv[i] = argv[i + count]; + } + argc = argc - count; + return argc; +} + + +static void statusHook(char *s) { + scr_printf(s); +} + + +/* + * main + */ +int main(int argc, char **argv) { + int a, b, mcmd; + char aaa[100], bbb[100]; /* general purpose variables */ + char argbuf[64]; /* command line buf */ + char *telnet_client_host = NULL; + char *sptr, *sptr2; /* USed to extract the nonce */ + char password[SIZ]; + struct ctdlipcmisc chek; + struct ctdluser *myself = NULL; + CtdlIPC *ipc; /* Our server connection */ + int r; /* IPC result code */ + int rv = 0; /* fetch but ignore syscall return value to suppress warnings */ + + int relh = 0; + int home = 0; + char relhome[PATH_MAX] = ""; + char ctdldir[PATH_MAX] = CTDLDIR; + int lp; + calc_dirs_n_files(relh, home, relhome, ctdldir, 0); + +#ifdef HAVE_BACKTRACE + bzero(¶ms, sizeof(params)); + params.debugLevel = ECRASH_DEBUG_VERBOSE; + params.dumpAllThreads = TRUE; + params.useBacktraceSymbols = 1; + params.signals[0] = SIGSEGV; + params.signals[1] = SIGILL; + params.signals[2] = SIGBUS; + params.signals[3] = SIGABRT; +#endif + setIPCErrorPrintf(scr_printf); + setCryptoStatusHook(statusHook); + + stty_ctdl(SB_SAVE); /* Store the old terminal parameters */ + load_command_set(); /* parse the citadel.rc file */ + stty_ctdl(SB_NO_INTR); /* Install the new ones */ + signal(SIGPIPE, dropcarr); /* Cleanup gracefully if local conn. dropped */ + signal(SIGTERM, dropcarr); /* Cleanup gracefully if terminated */ + signal(SIGCONT, catch_sigcont); /* Catch SIGCONT so we can reset terminal */ +#ifdef SIGWINCH + signal(SIGWINCH, scr_winch); /* Window resize signal */ +#endif + +#ifdef HAVE_OPENSSL + arg_encrypt = RC_DEFAULT; +#endif + + // Handle command line options as if we were called like /bin/login (i.e. from in.telnetd) + for (a = 0; a < argc; ++a) { + if ((argc > a + 1) && (!strcmp(argv[a], "-h"))) { + telnet_client_host = argv[a + 1]; + argc = shift(argc, argv, a, 2); + } + if (!strcmp(argv[a], "-x")) { +#ifdef HAVE_OPENSSL + arg_encrypt = RC_NO; +#endif + argc = shift(argc, argv, a, 1); + } + if (!strcmp(argv[a], "-X")) { +#ifdef HAVE_OPENSSL + arg_encrypt = RC_YES; + argc = shift(argc, argv, a, 1); +#else + fprintf(stderr, "Not compiled with encryption support"); + return 1; +#endif + } + if (!strcmp(argv[a], "-p")) { + // ignore this argument when called from telnetd + argc = shift(argc, argv, a, 1); + } + } + + screen_new(); + /* Get screen dimensions. First we go to a default of 80x24. + * Then attempt to read the actual screen size from the terminal. + */ + check_screen_dims(); + + scr_printf("Attaching to server...\n"); + ipc = CtdlIPC_new(argc, argv, hostbuf, portbuf); + if (!ipc) { + error_printf("Can't connect: %s\n", strerror(errno)); + logoff(NULL, 3); + } + + CtdlIPC_SetNetworkStatusCallback(ipc, scr_wait_indicator); + + if (!(ipc->isLocal)) { + scr_printf("Connected to %s [%s].\n", ipc->ip_hostname, ipc->ip_address); + } + + ipc_for_signal_handlers = ipc; /* KLUDGE cover your eyes */ + + CtdlIPC_chat_recv(ipc, aaa); + if (aaa[0] != '2') { + scr_printf("%s\n", &aaa[4]); + logoff(ipc, atoi(aaa)); + } + +#ifdef HAVE_OPENSSLLLLLL + /* Evaluate encryption preferences */ + if (arg_encrypt != RC_NO && rc_encrypt != RC_NO) { + if (!ipc->isLocal || arg_encrypt == RC_YES || rc_encrypt == RC_YES) { + secure = (CtdlIPCStartEncryption(ipc, aaa) / 100 == 2) ? 1 : 0; + if (!secure) + error_printf("Can't encrypt: %s\n", aaa); + } + } +#endif + + get_serv_info(ipc, telnet_client_host); + scr_printf("%-24s\n%s\n%s\n", ipc->ServInfo.software, ipc->ServInfo.humannode, ipc->ServInfo.site_location); + + scr_printf(" pause next stop\n"); + scr_printf(" ctrl-s ctrl-o ctrl-c\n\n"); + formout(ipc, "hello"); /* print the opening greeting */ + scr_printf("\n"); + + GSTA: /* See if we have a username and password on disk */ + if (rc_remember_passwords) { + get_stored_password(hostbuf, portbuf, fullname, password); + if (!IsEmptyStr(fullname)) { + r = CtdlIPCTryLogin(ipc, fullname, aaa); + if (r / 100 == 3) { + r = CtdlIPCTryPassword(ipc, password, aaa); + } + if (r / 100 == 2) { + load_user_info(aaa); + goto PWOK; + } + else { + set_stored_password(hostbuf, portbuf, "", ""); + } + } + } + + termn8 = 0; + newnow = 0; + do { + if (!IsEmptyStr(rc_username)) { + strcpy(fullname, rc_username); + } else { + newprompt("Enter your name: ", fullname, 29); + } + strproc(fullname); + if (!strcasecmp(fullname, "new")) { /* just in case */ + scr_printf("Please enter the name you wish to log in with.\n"); + } + } while ((!strcasecmp(fullname, "bbs")) + || (!strcasecmp(fullname, "new")) + || (IsEmptyStr(fullname))); + + if (!strcasecmp(fullname, "off")) { + mcmd = 29; + goto TERMN8; + } + + /* FIXME this is a stupid way to do guest mode but it's a reasonable test harness FIXME */ + if ((ipc->ServInfo.guest_logins) && (!strcasecmp(fullname, "guest"))) { + goto PWOK; + } + + /* sign on to the server */ + r = CtdlIPCTryLogin(ipc, fullname, aaa); + if (r / 100 != 3) + goto NEWUSR; + + /* password authentication */ + if (!IsEmptyStr(rc_password)) { + strcpy(password, rc_password); + } else { + newprompt("\rPlease enter your password: ", password, -(SIZ - 1)); + } + + r = CtdlIPCTryPassword(ipc, password, aaa); + if (r / 100 != 2) { + strproc(password); + r = CtdlIPCTryPassword(ipc, password, aaa); + } + + if (r / 100 == 2) { + load_user_info(aaa); + offer_to_remember_password(ipc, hostbuf, portbuf, fullname, password); + goto PWOK; + } + scr_printf("<< wrong password >>\n"); + if (!IsEmptyStr(rc_password)) + logoff(ipc, 2); + goto GSTA; + + NEWUSR:if (IsEmptyStr(rc_password)) { + scr_printf("'%s' not found.\n", fullname); + scr_printf("Type 'off' if you would like to exit.\n"); + if (ipc->ServInfo.newuser_disabled == 1) { + goto GSTA; + } + scr_printf("Do you want to create a new user account called '%s'? ", fullname); + if (yesno() == 0) { + goto GSTA; + } + } + + r = CtdlIPCCreateUser(ipc, fullname, 1, aaa); + if (r / 100 != 2) { + scr_printf("%s\n", aaa); + goto GSTA; + } + load_user_info(aaa); + + while (set_password(ipc) != 0); + newnow = 1; + + enter_config(ipc, 1); + + PWOK: + /* Switch color support on or off if we're in user mode */ + if (rc_ansi_color == 3) { + if (userflags & US_COLOR) + enable_color = 1; + else + enable_color = 0; + } + + color(BRIGHT_WHITE); + scr_printf("\n%s\nAccess level: %d (%s)\n" + "User #%ld / Login #%d", fullname, axlevel, axdefs[(int) axlevel], usernum, timescalled); + if (lastcall > 0L) { + scr_printf(" / Last login: %s", asctime(localtime(&lastcall))); + } + scr_printf("\n"); + + r = CtdlIPCMiscCheck(ipc, &chek, aaa); + if (r / 100 == 2) { + b = chek.newmail; + if (b > 0) { + color(BRIGHT_RED); + if (b == 1) + scr_printf("*** You have a new private message in Mail>\n"); + if (b > 1) + scr_printf("*** You have %d new private messages in Mail>\n", b); + color(DIM_WHITE); + if (!IsEmptyStr(rc_gotmail_cmd)) { + rv = system(rc_gotmail_cmd); + if (rv) + scr_printf("*** failed to check for mail calling %s Reason %d.\n", rc_gotmail_cmd, rv); + + } + } + if ((axlevel >= AxAideU) && (chek.needvalid > 0)) { + scr_printf("*** Users need validation\n"); + } + if (chek.needregis > 0) { + scr_printf("*** Please register.\n"); + formout(ipc, "register"); + entregis(ipc); + } + } + /* Make up some temporary filenames for use in various parts of the + * program. Don't mess with these once they've been set, because we + * will be unlinking them later on in the program and we don't + * want to delete something that we didn't create. */ + CtdlMakeTempFileName(temp, sizeof temp); + CtdlMakeTempFileName(temp2, sizeof temp2); + CtdlMakeTempFileName(tempdir, sizeof tempdir); + + r = CtdlIPCGetConfig(ipc, &myself, aaa); + set_floor_mode(ipc); + + /* Enter the lobby */ + dotgoto(ipc, "_BASEROOM_", 1, 0); + + /* Main loop for the system... user is logged in. */ + free(uglist[0]); + uglistsize = 0; + + if (newnow == 1) + readmsgs(ipc, LastMessages, ReadForward, 5); + else + readmsgs(ipc, NewMessages, ReadForward, 0); + + /* MAIN COMMAND LOOP */ + do { + mcmd = getcmd(ipc, argbuf); /* Get keyboard command */ + +#ifdef TIOCGWINSZ + check_screen_dims(); /* get screen size */ +#endif + + if (termn8 == 0) + switch (mcmd) { + case 1: + display_help(ipc, "help"); + break; + case 4: + entmsg(ipc, 0, ((userflags & US_EXTEDIT) ? 2 : 0), 0); + break; + case 36: + entmsg(ipc, 0, 1, 0); + break; + case 46: + entmsg(ipc, 0, 2, 0); + break; + case 78: + entmsg(ipc, 0, ((userflags & US_EXTEDIT) ? 2 : 0), 1); + break; + case 5: /* oto */ + updatels(ipc); + gotonext(ipc); + break; + case 47: /* bandon */ + gotonext(ipc); + break; + case 90: /* <.A>bandon */ + dotgoto(ipc, argbuf, 0, 0); + break; + case 58: /* ail */ + updatelsa(ipc); + dotgoto(ipc, "_MAIL_", 1, 0); + break; + case 20: + if (!IsEmptyStr(argbuf)) { + updatels(ipc); + dotgoto(ipc, argbuf, 0, 0); + } + break; + case 52: + if (!IsEmptyStr(argbuf)) { + dotgoto(ipc, argbuf, 0, 0); + } + break; + case 95: /* what exactly is the numbering scheme supposed to be anyway? --Ford, there isn't one. -IO */ + dotungoto(ipc, argbuf); + break; + case 10: + readmsgs(ipc, AllMessages, ReadForward, 0); + break; + case 9: + readmsgs(ipc, LastMessages, ReadForward, 5); + break; + case 13: + readmsgs(ipc, NewMessages, ReadForward, 0); + break; + case 11: + readmsgs(ipc, AllMessages, ReadReverse, 0); + break; + case 12: + readmsgs(ipc, OldMessages, ReadReverse, 0); + break; + case 71: + readmsgs(ipc, LastMessages, ReadForward, atoi(argbuf)); + break; + case 7: + forget(ipc); + break; + case 18: + subshell(); + break; + case 38: + updatels(ipc); + entroom(ipc); + break; + case 22: + killroom(ipc); + break; + case 32: + userlist(ipc, argbuf); + break; + case 27: + invite(ipc); + break; + case 28: + kickout(ipc); + break; + case 23: + editthisroom(ipc); + break; + case 14: + roomdir(ipc); + break; + case 33: + download(ipc, 0); + break; + case 34: + download(ipc, 1); + break; + case 31: + download(ipc, 2); + break; + case 43: + download(ipc, 3); + break; + case 45: + download(ipc, 4); + break; + case 55: + download(ipc, 5); + break; + case 39: + upload(ipc, 0); + break; + case 40: + upload(ipc, 1); + break; + case 42: + upload(ipc, 2); + break; + case 44: + upload(ipc, 3); + break; + case 57: + cli_upload(ipc); + break; + case 16: + ungoto(ipc); + break; + case 24: + whoknows(ipc); + break; + case 26: + validate(ipc); + break; + case 29: + case 30: + updatels(ipc); + termn8 = 1; + break; + case 48: + enterinfo(ipc); + break; + case 49: + readinfo(ipc); + break; + case 72: + cli_image_upload(ipc, "_userpic_"); + break; + case 73: + cli_image_upload(ipc, "_roompic_"); + break; + case 35: + set_password(ipc); + break; + + case 21: + if (argbuf[0] == 0) { + strcpy(argbuf, "?"); + } + display_help(ipc, argbuf); + break; + + case 41: + formout(ipc, "register"); + entregis(ipc); + break; + + case 15: + scr_printf("Are you sure (y/n)? "); + if (yesno() == 1) { + updatels(ipc); + a = 0; + termn8 = 1; + } + break; + + case 85: + scr_printf("All users will be disconnected! " "Really terminate the server? "); + if (yesno() == 1) { + updatels(ipc); + r = CtdlIPCTerminateServerNow(ipc, aaa); + scr_printf("%s\n", aaa); + if (r / 100 == 2) { + a = 0; + termn8 = 1; + } + } + break; + + case 86: + scr_printf("Do you really want to schedule a " "server shutdown? "); + if (yesno() == 1) { + r = CtdlIPCTerminateServerScheduled(ipc, 1, aaa); + if (r / 100 == 2) { + if (atoi(aaa)) { + scr_printf + ("The Citadel server will terminate when all users are logged off.\n"); + } else { + scr_printf("The Citadel server will not terminate.\n"); + } + } + } + break; + + case 87: + network_config_management(ipc, "listrecp", "Message-by-message mailing list recipients"); + break; + + case 94: + network_config_management(ipc, "digestrecp", "Digest mailing list recipients"); + break; + + case 6: + gotonext(ipc); + break; + + case 3: + chatmode(ipc); + break; + + case 17: + who_is_online(ipc, 0); + break; + + case 79: + who_is_online(ipc, 1); + break; + + case 91: + who_is_online(ipc, 2); + break; + + case 80: + do_system_configuration(ipc); + break; + + case 82: + do_internet_configuration(ipc); + break; + + case 84: + quiet_mode(ipc); + break; + + case 93: + stealth_mode(ipc); + break; + + case 50: + enter_config(ipc, 2); + break; + + case 37: + enter_config(ipc, 0); + set_floor_mode(ipc); + break; + + case 59: + enter_config(ipc, 3); + set_floor_mode(ipc); + break; + + case 60: + gotofloor(ipc, argbuf, GF_GOTO); + break; + + case 61: + gotofloor(ipc, argbuf, GF_SKIP); + break; + + case 62: + forget_this_floor(ipc); + break; + + case 63: + create_floor(ipc); + break; + + case 64: + edit_floor(ipc); + break; + + case 65: + kill_floor(ipc); + break; + + case 66: + enter_bio(ipc); + break; + + case 67: + read_bio(ipc); + break; + + case 25: + edituser(ipc, 25); + break; + + case 96: + edituser(ipc, 96); + break; + + case 8: + knrooms(ipc, floor_mode); + scr_printf("\n"); + break; + + case 68: + knrooms(ipc, 2); + scr_printf("\n"); + break; + + case 69: + misc_server_cmd(ipc, argbuf); + break; + + case 70: + edit_system_message(ipc, argbuf); + break; + + case 19: + listzrooms(ipc); + scr_printf("\n"); + break; + + case 51: + deletefile(ipc); + break; + + case 54: + movefile(ipc); + break; + + case 56: + page_user(ipc); + break; + + case 110: /* <+> Next room */ + gotoroomstep(ipc, 1, 0); + break; + + case 111: /* <-> Previous room */ + gotoroomstep(ipc, 0, 0); + break; + + case 112: /* <>> Next floor */ + gotofloorstep(ipc, 1, GF_GOTO); + break; + + case 113: /* <<> Previous floor */ + gotofloorstep(ipc, 0, GF_GOTO); + break; + + case 116: /* <.> skip to <+> Next room */ + gotoroomstep(ipc, 1, 1); + break; + + case 117: /* <.> skip to <-> Previous room */ + gotoroomstep(ipc, 0, 1); + break; + + case 118: /* <.> skip to <>> Next floor */ + gotofloorstep(ipc, 1, GF_SKIP); + break; + + case 119: /* <.> skip to <<> Previous floor */ + gotofloorstep(ipc, 0, GF_SKIP); + break; + + case 114: + read_config(ipc); + break; + + case 115: + system_info(ipc); + break; + + case 120: /* .KAnonymous */ + dotknown(ipc, 0, NULL); + break; + + case 121: /* .KDirectory */ + dotknown(ipc, 1, NULL); + break; + + case 122: /* .KMatch */ + dotknown(ipc, 2, argbuf); + break; + + case 123: /* .KpreferredOnly */ + dotknown(ipc, 3, NULL); + break; + + case 124: /* .KPrivate */ + dotknown(ipc, 4, NULL); + break; + + case 125: /* .KRead only */ + dotknown(ipc, 5, NULL); + break; + + case 127: /* Configure POP3 aggregation */ + do_pop3client_configuration(ipc); + break; + + case 128: /* Configure XML/RSS feed retrieval */ + do_rssclient_configuration(ipc); + break; + + default: + break; + } /* end switch */ + } while (termn8 == 0); + + TERMN8:scr_printf("%s logged out.", fullname); + termn8 = 0; + color(ORIGINAL_PAIR); + scr_printf("\n"); + while (marchptr != NULL) { + remove_march(marchptr->march_name, 0); + } + if (mcmd == 30) { + scr_printf("\n\nType 'off' to disconnect, or next user...\n"); + } + CtdlIPCLogout(ipc); + if ((mcmd == 29) || (mcmd == 15)) { + stty_ctdl(SB_RESTORE); + formout(ipc, "goodbye"); + logoff(ipc, 0); + } + /* Free the ungoto list */ + for (lp = 0; lp < uglistsize; lp++) { + free(uglist[lp]); + } + uglistsize = 0; + goto GSTA; + +} /* end main() */ diff --git a/textclient/client_chat.c~ b/textclient/client_chat.c~ new file mode 100644 index 000000000..55ccb9b4d --- /dev/null +++ b/textclient/client_chat.c~ @@ -0,0 +1,247 @@ +// front end for multiuser chat +// +// Copyright (c) 1987-2016 by the citadel.org team +// +// This program is open source software. Use, duplication, and/or +// disclosure are subject to the GNU General Purpose License version 3. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +#include "textclient.h" + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +extern char temp[]; +char last_paged[SIZ] = ""; + +void chatmode(CtdlIPC *ipc) { + char wbuf[SIZ]; + char buf[SIZ]; + char response[SIZ]; + char c_user[SIZ]; + char c_text[SIZ]; + char last_user[SIZ]; + int send_complete_line; + char ch; + int a, pos; + int seq = 0; + + fd_set rfds; + struct timeval tv; + int retval; + + CtdlIPC_chat_send(ipc, "RCHT enter"); + CtdlIPC_chat_recv(ipc, buf); + if (buf[0] != '2') { + scr_printf("%s\n", &buf[4]); + return; + } + scr_printf("Entering chat mode (type /quit to exit)\n"); + + strcpy(buf, ""); + strcpy(wbuf, ""); + strcpy(last_user, ""); + color(BRIGHT_YELLOW); + scr_printf("\n"); + scr_printf("> "); + send_complete_line = 0; + + while (1) { + scr_flush(); + FD_ZERO(&rfds); + FD_SET(0, &rfds); + tv.tv_sec = 1; + tv.tv_usec = 0; + retval = select(1, &rfds, NULL, NULL, &tv); + + if (retval < 0) { + color(BRIGHT_WHITE); + scr_printf("Server gone Exiting chat mode\n"); + scr_flush(); + return; + } + + /* If there's data from the keyboard... */ + if (FD_ISSET(0, &rfds)) { + ch = scr_getc(SCR_BLOCK); + if ((ch == 10) || (ch == 13)) { + send_complete_line = 1; + } else if ((ch == 8) || (ch == 127)) { + if (!IsEmptyStr(wbuf)) { + wbuf[strlen(wbuf) - 1] = 0; + scr_printf("%c %c", 8, 8); + } + } else { + scr_putc(ch); + wbuf[strlen(wbuf) + 1] = 0; + wbuf[strlen(wbuf)] = ch; + } + } + + /* if the user hit return, send the line */ + if (send_complete_line) { + + if (!strcasecmp(wbuf, "/quit")) { + CtdlIPC_chat_send(ipc, "RCHT exit"); + CtdlIPC_chat_recv(ipc, response); /* don't care about the result */ + color(BRIGHT_WHITE); + scr_printf("\rExiting chat mode\n"); + scr_flush(); + return; + } + + CtdlIPC_chat_send(ipc, "RCHT send"); + CtdlIPC_chat_recv(ipc, response); + if (response[0] == '4') { + CtdlIPC_chat_send(ipc, wbuf); + CtdlIPC_chat_send(ipc, "000"); + } + strcpy(wbuf, ""); + send_complete_line = 0; + } + + /* if it's time to word wrap, send a partial line */ + if (strlen(wbuf) >= (77 - strlen(fullname))) { + pos = 0; + for (a = 0; !IsEmptyStr(&wbuf[a]); ++a) { + if (wbuf[a] == 32) + pos = a; + } + if (pos == 0) { + CtdlIPC_chat_send(ipc, "RCHT send"); + CtdlIPC_chat_recv(ipc, response); + if (response[0] == '4') { + CtdlIPC_chat_send(ipc, wbuf); + CtdlIPC_chat_send(ipc, "000"); + } + strcpy(wbuf, ""); + send_complete_line = 0; + } else { + wbuf[pos] = 0; + CtdlIPC_chat_send(ipc, "RCHT send"); + CtdlIPC_chat_recv(ipc, response); + if (response[0] == '4') { + CtdlIPC_chat_send(ipc, wbuf); + CtdlIPC_chat_send(ipc, "000"); + } + strcpy(wbuf, &wbuf[pos + 1]); + } + } + + /* poll for incoming chat messages */ + snprintf(buf, sizeof buf, "RCHT poll|%d", seq); + CtdlIPC_chat_send(ipc, buf); + CtdlIPC_chat_recv(ipc, response); + + if (response[0] == '1') { + seq = extract_int(&response[4], 0); + extract_token(c_user, &response[4], 2, '|', sizeof c_user); + while (CtdlIPC_chat_recv(ipc, c_text), strcmp(c_text, "000")) { + scr_printf("\r%79s\r", ""); + if (!strcmp(c_user, fullname)) { + color(BRIGHT_YELLOW); + } else if (!strcmp(c_user, ":")) { + color(BRIGHT_RED); + } else { + color(BRIGHT_GREEN); + } + if (strcmp(c_user, last_user)) { + snprintf(buf, sizeof buf, "%s: %s", c_user, c_text); + } else { + size_t i = MIN(sizeof buf - 1, strlen(c_user) + 2); + memset(buf, ' ', i); + strncpy(&buf[i], c_text, sizeof buf - i); + } + while (strlen(buf) < 79) { + strcat(buf, " "); + } + if (strcmp(c_user, last_user)) { + scr_printf("\r%79s\n", ""); + strcpy(last_user, c_user); + } + scr_printf("\r%s\n", buf); + scr_flush(); + } + } + color(BRIGHT_YELLOW); + scr_printf("\r> %s", wbuf); + scr_flush(); + strcpy(buf, ""); + } +} + + +// send an instant message +void page_user(CtdlIPC * ipc) { + char buf[SIZ], touser[SIZ], msg[SIZ]; + FILE *pagefp; + + strcpy(touser, last_paged); + strprompt("Page who", touser, 30); + + snprintf(buf, sizeof buf, "SEXP %s||", touser); + CtdlIPC_chat_send(ipc, buf); + CtdlIPC_chat_recv(ipc, buf); + if (buf[0] != '2') { + scr_printf("%s\n", &buf[4]); + return; + } + if (client_make_message(ipc, temp, touser, 0, 0, 0, NULL, 0) != 0) { + scr_printf("No message sent.\n"); + return; + } + pagefp = fopen(temp, "r"); + unlink(temp); + snprintf(buf, sizeof buf, "SEXP %s|-", touser); + CtdlIPC_chat_send(ipc, buf); + CtdlIPC_chat_recv(ipc, buf); + if (buf[0] == '4') { + strcpy(last_paged, touser); + while (fgets(buf, sizeof buf, pagefp) != NULL) { + buf[strlen(buf) - 1] = 0; + CtdlIPC_chat_send(ipc, buf); + } + fclose(pagefp); + CtdlIPC_chat_send(ipc, "000"); + scr_printf("Message sent.\n"); + } + else { + scr_printf("%s\n", &buf[4]); + } +} + + +void quiet_mode(CtdlIPC * ipc) +{ + static int quiet = 0; + char cret[SIZ]; + int r; + + r = CtdlIPCEnableInstantMessageReceipt(ipc, !quiet, cret); + if (r / 100 == 2) { + quiet = !quiet; + scr_printf("Quiet mode %sabled (%sother users may page you)\n", (quiet) ? "en" : "dis", (quiet) ? "no " : ""); + } else { + scr_printf("Unable to change quiet mode: %s\n", cret); + } +} + + +void stealth_mode(CtdlIPC * ipc) +{ + static int stealth = 0; + char cret[SIZ]; + int r; + + r = CtdlIPCStealthMode(ipc, !stealth, cret); + if (r / 100 == 2) { + stealth = !stealth; + scr_printf("Stealth mode %sabled (you are %s)\n", + (stealth) ? "en" : "dis", (stealth) ? "invisible" : "listed as online"); + } else { + scr_printf("Unable to change stealth mode: %s\n", cret); + } +} diff --git a/textclient/client_passwords.c~ b/textclient/client_passwords.c~ new file mode 100644 index 000000000..040e1783f --- /dev/null +++ b/textclient/client_passwords.c~ @@ -0,0 +1,131 @@ +// Functions which allow the client to remember usernames and passwords for +// various sites. +// +// Copyright (c) 1987-2016 by the citadel.org team +// +// This program is open source software. Use, duplication, and/or +// disclosure are subject to the GNU General Purpose License version 3. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +#include "textclient.h" + +#define PWFILENAME "%s/.citadel.passwords" + +void determine_pwfilename(char *pwfile, size_t n) +{ + struct passwd *p; + + p = getpwuid(getuid()); + if (p == NULL) + strcpy(pwfile, ""); + snprintf(pwfile, n, PWFILENAME, p->pw_dir); +} + + +/* + * Check the password file for a host/port match; if found, stuff the user + * name and password into the user/pass buffers + */ +void get_stored_password(char *host, char *port, char *username, char *password) +{ + + char pwfile[PATH_MAX]; + FILE *fp; + char buf[SIZ]; + char buf64[SIZ]; + char hostbuf[256], portbuf[256], ubuf[256], pbuf[256]; + + strcpy(username, ""); + strcpy(password, ""); + + determine_pwfilename(pwfile, sizeof pwfile); + if (IsEmptyStr(pwfile)) + return; + + fp = fopen(pwfile, "r"); + if (fp == NULL) + return; + while (fgets(buf64, sizeof buf64, fp) != NULL) { + CtdlDecodeBase64(buf, buf64, sizeof(buf64)); + extract_token(hostbuf, buf, 0, '|', sizeof hostbuf); + extract_token(portbuf, buf, 1, '|', sizeof portbuf); + extract_token(ubuf, buf, 2, '|', sizeof ubuf); + extract_token(pbuf, buf, 3, '|', sizeof pbuf); + + if (!strcasecmp(hostbuf, host)) { + if (!strcasecmp(portbuf, port)) { + strcpy(username, ubuf); + strcpy(password, pbuf); + } + } + } + fclose(fp); +} + + +/* + * Set (or clear) stored passwords. + */ +void set_stored_password(char *host, char *port, char *username, char *password) +{ + + char pwfile[PATH_MAX]; + FILE *fp, *oldfp; + char buf[SIZ]; + char buf64[SIZ]; + char hostbuf[256], portbuf[256], ubuf[256], pbuf[256]; + + determine_pwfilename(pwfile, sizeof pwfile); + if (IsEmptyStr(pwfile)) + return; + + oldfp = fopen(pwfile, "r"); + if (oldfp == NULL) + oldfp = fopen("/dev/null", "r"); + unlink(pwfile); + fp = fopen(pwfile, "w"); + if (fp == NULL) + fp = fopen("/dev/null", "w"); + while (fgets(buf64, sizeof buf64, oldfp) != NULL) { + CtdlDecodeBase64(buf, buf64, sizeof(buf64)); + extract_token(hostbuf, buf, 0, '|', sizeof hostbuf); + extract_token(portbuf, buf, 1, '|', sizeof portbuf); + extract_token(ubuf, buf, 2, '|', sizeof ubuf); + extract_token(pbuf, buf, 3, '|', sizeof pbuf); + + if ((strcasecmp(hostbuf, host)) + || (strcasecmp(portbuf, port))) { + snprintf(buf, sizeof buf, "%s|%s|%s|%s|", hostbuf, portbuf, ubuf, pbuf); + CtdlEncodeBase64(buf64, buf, strlen(buf), BASE64_NO_LINEBREAKS); + fprintf(fp, "%s\n", buf64); + } + } + if (!IsEmptyStr(username)) { + snprintf(buf, sizeof buf, "%s|%s|%s|%s|", host, port, username, password); + CtdlEncodeBase64(buf64, buf, strlen(buf), BASE64_NO_LINEBREAKS); + fprintf(fp, "%s\n", buf64); + } + fclose(oldfp); + fclose(fp); + chmod(pwfile, 0600); +} + + +/* + * Set the password if the user wants to, clear it otherwise + */ +void offer_to_remember_password(CtdlIPC * ipc, char *host, char *port, char *username, char *password) +{ + + if (rc_remember_passwords) { + if (boolprompt("Remember username/password for this site", 0)) { + set_stored_password(host, port, username, password); + } else { + set_stored_password(host, port, "", ""); + } + } +} diff --git a/textclient/commands.c~ b/textclient/commands.c~ new file mode 100644 index 000000000..5685d2f10 --- /dev/null +++ b/textclient/commands.c~ @@ -0,0 +1,1731 @@ +// This file contains functions which implement parts of the +// text-mode user interface. +// +// Copyright (c) 1987-2022 by the citadel.org team +// +// This program is open source software. Use, duplication, and/or +// disclosure are subject to the GNU General Purpose License version 3. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +#include "textclient.h" + + +// The help "files" are now just an embedded set of Very Long Strings. helpnames[] is +// an array of "file names" and helptexts[] is the "content". + +char *helpnames[] = { + "help", + "admin", + "floors", + "intro", + "mail", + "network", + "software", + "summary" +}; + +char *helptexts[] = { + + // <.H>elp HELP + " Citadel Help Menu\n" + " \n" + " ? Help. (Typing a '?' will give you a menu almost anywhere)\n" + " A Abandon this room where you stopped reading, goto next room.\n" + " C Chat (multiuser chat, where available)\n" + " D Prints directory, if there is one in the current room.\n" + " E Enter a message.\n" + " F Read all messages in the room, forward.\n" + " G Goto next room which has UNREAD messages.\n" + " H Help. Same as '?'\n" + " I Reads the Information file for this room.\n" + " K List of Known rooms.\n" + " L Reads the last five messages in the room.\n" + " N Reads all new messages in the room.\n" + " O Reads all old messages, backwards.\n" + " P Page another user (send an instant message)\n" + " R Reads all messages in the room, in reverse order.\n" + " S Skips current room without making its messages old.\n" + " T Terminate (logout)\n" + " U Ungoto (returns to the last room you were in)\n" + " W Displays who is currently logged in.\n" + " X Toggle eXpert mode (menus and help blurbs on/off)\n" + " Z Zap (forget) room. (Removes the room from your list)\n" + " + - Goto next, previous room on current floor.\n" + " > < Goto next, previous floor.\n" + " \n" + " In addition, there are dot commands. You hit the . (dot), then press the\n" + "first letter of each word of the command. As you hit the letters, the words\n" + "pop onto your screen. Exceptions: after you hit .Help or .Goto, the remainder\n" + "of the command is a help file name or room name.\n" + " \n" + " *** USE .elp ? for additional help *** \n", + + // <.H>elp ADMIN + "The following commands are available only to Admins. A subset of these\n" + "commands are available to room aides when they are currently in the room\n" + "they are room aide for.\n" + "\n" + " <.> dmin ill this room (Delete the current room)\n" + " <.> dmin dit this room (Edit the current room's parameters)\n" + " <.> dmin ho knows room (List users with access to this room)\n" + " <.> dmin edit ser (Change user's access level, password, etc.)\n" + " <.> dmin alidate new users (Process new user registrations)\n" + " <.> dmin enter nfo file (Create/change this room's banner)\n" + " <.> dmin oom nvite user (Grant access to an invitation-only room)\n" + " <.> dmin oom ick out user (Revoke access to an invitation-only room)\n" + " <.> dmin ile elete (Delete a file from the room's directory)\n" + " <.> dmin ile end over net (Transmit a file to another node)\n" + " <.> dmin ile ove (Move a file to another room's directory)\n" + " <.> dmin essage edit: (Edit system banners)\n" + " <.> dmin

ost (Post a message on behalf of another user)\n" + " <.> dmin ystem configuration eneral (Edit global site config)\n" + " <.> dmin ystem configuration nternet (Edit Internet domains)\n" + " <.> dmin ystem configuration check essage base (Internal checks)\n" + " <.> dmin ystem configuration etwork (Netting with other Citadels)\n" + " <.> dmin ystem configuration network ilter list\n" + " <.> dmin erminate server ow (Shut down Citadel server now)\n" + " <.> dmin erminate server cheduled (Shut down Citadel server later)\n" + " <.> dmin mailing ist recipients (For mailing list rooms)\n" + " <.> dmin mailing list igest recipients (For mailing list rooms)\n" + " <.> dmin etwork room sharing (Replication with other Citadels)\n" + " \n" " In addition, the ove and elete commands are available at the\n" "message prompt.\n", + + // <.H>elp FLOORS + " Floors\n" + " ------\n" + " Floors in Citadel are used to group rooms into related subject areas,\n" + "just as rooms are used to group messages into manageable groups.\n" + " \n" + " You, as a user, do NOT have to use floors. If you choose not to, you suffer\n" + "no penalty; you will not lose access to any rooms. You may use .EC or ;C (the\n" + "latter is easier to use) to decide if you want to use floors. Feel free to\n" + "experiment.\n" + " \n" + " Floor options are accessed two ways. First, if you are in floor mode, the\n" + "oto and kip commands take you to the next room with new messages on the\n" + "current floor; if there are none left, then the system will automatically\n" + "switch floors (and let you know) and put you in the first room with new messages\n" + "on that level. (Notice that your pattern of basic use of Citadel therefore\n" + "doesn't really change.)\n" + " \n" + " Direct access to floor options is via the use of a ';' command.\n" + "The following commands are currently available (more can be\n" + "added if needed):\n" + " \n" + " <;C>onfigure\n" + " This command toggles your floor mode.\n" + " \n" + " <;G>oto FLOORNAME\n" + " This command causes the system to take you to the named floor.\n" + " \n" + " <;K>nown rooms on floors\n" + " List all rooms on all floors. This is a very readable way to get a list of\n" + "all rooms on the system.\n" + " \n" + " <;S>kip FLOORNAME\n" + " This command causes the system to mark all rooms on the current floor as\n" + "Skipped and takes you to the floor that you specify.\n" + " \n" + " <;Z>Forget floor\n" + " This command causes you to forget all the rooms currently on the current\n" + "floor. Unfortunately, it doesn't apply to rooms that are subsequently created\n" + "or moved to this floor. (Sorry.)\n" + " \n" + " Feel free to experiment, you can't hurt yourself or the system with the\n" + "floor stuff unless you ZForget a floor by accident.\n", + + // <.H>elp INTRO + " New User's Introduction to the site\n" + " \n" + " This is an introduction to the Citadel BBS concept. It is intended\n" + "for new users so that they can more easily become acquainted to using\n" + "Citadel when accessing it in the form of a text-based BBS. Of\n" + "course, old users might learn something new each time they read\n" + "through it.\n" + " \n" + " Full help for the BBS commands can be obtained by typing <.H>elp SUMMARY\n" + " \n" + " The CITADEL BBS room concept\n" + " ----------------------------\n" + " The term BBS stands for 'Bulletin Board System'. The analogy is\n" + "appropriate: one posts messages so that others may read them. In\n" + "order to organize the posts, people can post in different areas of the\n" + "BBS, called rooms.\n" + " In order to post in a certain room, you need to be 'in' that room.\n" + "Your current prompt is usually the room that you are in, followed the\n" + "greater-than-sign, such as:\n" + " \n" + " Lobby>\n" + " \n" + " The easiest way to traverse the room structure is with the 'Goto'\n" + "command, on the 'G' key. Pressing 'G' will take you to the next room\n" + "in the 'march list' (see below) that has new messages in it. You can\n" + "read these new messages with the 'N' key.\n" + " Once you've 'Gotoed' every room in the system (or all of the ones\n" + "you choose to read) you return to the 'Lobby,' the first and last room\n" + "in the system. If new messages get posted to rooms you've already\n" + "read during your session you will be brought BACK to those rooms so\n" + "you can read them.\n" + " \n" + " March List\n" + " ----------\n" + " All the room names are stored in a march list, which is just a\n" + "list containing all the room names. When you oto or kip a\n" + "room, you are placed in the next room in your march list THAT HAS NEW\n" + "MESSAGES. If you have no new messages in any of the rooms on your\n" + "march list, you will keep going to the Lobby>. You can choose not to\n" + "read certain rooms (that don't interest you) by 'Z'apping them. When\n" + "you ap a room, you are merely deleting it from your march list (but\n" + "not from anybody else's).\n" + " \n" + " You can use the <.G>oto (note the period before the G. You can also use\n" + "ump on some systems) to go to any room in the\n" + "system. You don't have to type in the complete name of a room to\n" + "'jump' to it; you merely need to type in enough to distinguish it from\n" + "the other rooms. Left-aligned matches carry a heavier weight, so if you\n" + "typed (for example) '.Goto TECH', you might be taken to a room called\n" + "'Tech Area>' even if it found a room called 'Biotech/Ethics>' first.\n" + " \n" + " To return to a room you have previously apped, use the <.G>oto command\n" + "to enter it, and it will be re-inserted into your march list. In the case\n" + "of returning to Zapped rooms, you must type the room name in its entirety.\n" + "REMEMBER, rooms with no new messages will not show on your\n" + "march list! You must <.G>oto to a room with no new messages.\n" + "Incidentally, you cannot change the order of the rooms on your march list.\n" + "It's the same for everybody.\n" + " \n" + " Special rooms\n" + " -------------\n" + " There are two special rooms on a Citadel that you should know about.\n" + " \n" + " The first is the Lobby>. It's used for system announcements and other\n" + "such administrativia. You cannot ap the Lobby>. Each time you first\n" + "login, you will be placed in the Lobby>.\n" + " \n" + " The second is Mail>. In Mail>, when you post a messages, you are\n" + "prompted to enter the screen name of the person who you want to send the\n" + "message to. Only the person who you send the message to can read the\n" + "message. NO ONE else can read it, not even the admins. Mail> is the\n" + "first room on the march list, and is un-appable, so you can be sure\n" + "that the person will get the message.\n" + " \n" + " System admins\n" + " -------------\n" + " These people, along with the room admins, keep the site running smoothly.\n" + "\n" + " Among the many things that admins do are: create rooms, delete\n" + "rooms, set access levels, invite users, check registration, grant\n" + "room admin status, and countless other things. They have access to the\n" + "Aide> room, a special room only for admins.\n" + " \n" + " If you enter a mail message to 'Sysop' it will be placed in the\n" + "Aide> room so that the next admin online will read it and deal with it.\n" + "Admins cannot ap rooms. All the rooms are always on each admin's\n" + "march list. Admins can read *any* and *every* room, but they *CAN* *NOT*\n" + "read other users' Mail!\n" + " \n" + " Room admins\n" + " -----------\n" + " Room admins are granted special privileges in specific rooms.\n" + "They are *NOT* true system admins; their power extends only over the\n" + "rooms that they control, and they answer to the system admins.\n" + " \n" + " A room admin's job is to keep the topic of the their room on track,\n" + "with nudges in the right direction now and then. A room admin can also\n" + "move an off topic post to another room, or delete a post, if he/she\n" + "feels it is necessary. \n" + " \n" + " Currently, very few rooms have room admins. Most rooms do not need\n" + "their own specific room admin. Being a room admin requires a certain\n" + "amount of trust, due to the additional privileges granted.\n" + " \n" + " Citadel messages\n" + " ----------------\n" + " Most of the time, the BBS code does not print a lot of messages\n" + "to your screen. This is a great benefit once you become familiar\n" + "with the system, because you do not have endless menus and screens\n" + "to navigate through. nevertheless, there are some messages which you\n" + "might see from time to time.\n" + " \n" + " 'There were messages posted while you were entering.'\n" + " \n" + " This is also known as 'simulposting.' When you start entering a \n" + "message, the system knows where you last left off. When you save\n" + "your message, the system checks to see if any messages were entered\n" + "while you were typing. This is so that you know whether you need\n" + "to go back and re-read the last few messages. This message may appear\n" + "in any room.\n" + " \n" + " '*** You have new mail'\n" + " \n" + " This message is essentially the same as the above message, but can\n" + "appear at any time. It simply means that new mail has arrived for you while\n" + "you are logged in. Simply go to the Mail> room to read it.\n" + " \n" + " Who list\n" + " --------\n" + " The ho command shows you the names of all users who are currently\n" + "online. It also shows you the name of the room they are currently in. If\n" + "they are in any type of private room, however, the room name will simply\n" + "display as ''. Along with this information is displayed the\n" + "name of the host computer the user is logged in from.\n", + + // <.H>elp MAIL + "To send mail on this system, go to the Mail> room (using the command .G Mail)\n" + "and press E to enter a message. You will be prompted with:\n" + " \n" + " Enter Recipient:\n" + " \n" + " At this point you may enter the name of another user on the system. Private\n" + "mail is only readable by the sender and recipient. There is no need to delete\n" + "mail after it is read; it will scroll out automatically.\n" + " \n" + " To send mail to another user on the Citadel network, simply type the\n" + "user's name, followed by @ and then the system name. For example,\n" + " \n" + " Enter Recipient: Joe Schmoe @ citadrool\n" + " \n" + " If your account is enabled for Internet mail, you can also send email to\n" + "anyone on the Internet here. Simply enter their address at the prompt:\n" + " \n" " Enter Recipient: ajc@herring.fishnet.com\n", + + // <.H>elp NETWORK + " Welcome to the network. Messages entered in a network room will appear in\n" + "that room on all other systems carrying it (The name of the room, however,\n" "may be different on other systems).\n", + + // <.H>elp SOFTWARE + " Citadel is the premier 'online community' (i.e. Bulletin Board System)\n" + "software. It runs on all POSIX-compliant systems, including Linux. It is an\n" + "advanced client/server application, and is being actively maintained.\n" + " \n" " For more info, visit UNCENSORED! BBS at uncensored.citadel.org\n", + + // <.H>elp SUMMARY + "Extended commands are available using the period ( . ) key. To use\n" + "a dot command, press the . key, and then enter the first letter of\n" + "each word in the command. The words will appear as you enter the keys.\n" + "You can also backspace over partially entered commands. The following\n" + "commands are available:\n" + "\n" + " <.> elp: Displays help files. Type .H followed by a help file\n" + " name. You are now reading <.H>elp SUMMARY\n" + " \n" + " <.> oto: Jumps directly to the room you specify. You can also\n" + " type a partial room name, just enough to make it unique,\n" + " and it'll find the room you're looking for. As with the\n" + " regular oto command, messages in the current room will\n" + " be marked as read.\n" + " \n" + " <.> kip, goto: This is similar to <.G>oto, except it doesn't mark\n" + " messages in the current room as read.\n" + " \n" + " <.> list apped rooms Shows all rooms you've apped (forgotten)\n" + "\n" + " \n" + " Terminate (logoff) commands:\n" + " \n" + " <.> erminate and uit Log off and disconnect.\n" + " <.> erminate and tay online Log in as a different user.\n" + " \n" + " \n" + " Read commands:\n" + "\n" + " <.> ead ew messages Same as ew\n" + " <.> ead ld msgs reverse Same as ld\n" + " <.> ead ast five msgs Same as ast5\n" + " <.> read ast: Allows you to specify how many\n" + " messages you wish to read.\n" + "\n" + " <.> ead ser listing: Lists all users on the system if\n" + " you just hit enter, otherwise\n" + " you can specify a partial match\n" + "\n" + " <.> ead extfile formatted File 'download' commands.\n" + " <.> ead file using modem \n" + " <.> ead file using modem \n" + " <.> ead file using modem \n" + " <.> ead ile unformatted \n" + " <.> ead irectory \n" + "\n" + " <.> ead nfo file Read the room info file.\n" + " <.> ead io Read other users' 'bio' files.\n" + " <.> ead onfiguration Display your 'preferences'.\n" + " <.> ead ystem info Display system statistics.\n" + "\n" + " \n" + " Enter commands:\n" + "\n" + " <.> nter essage Post a message in this room.\n" + " <.> nter message with ditor Post using a full-screen editor.\n" + " <.> nter SCII message Post 'raw' (use this when 'pasting'\n" + " a message from your clipboard).\n" + "\n" + " <.> nter

assword Change your password.\n" + " <.> nter onfiguration Change your 'preferences'.\n" + " <.> nter a new oom Create a new room.\n" + " <.> nter reistration Register (name, address, etc.)\n" + " <.> nter io Enter/change your 'bio' file.\n" + "\n" + " <.> nter extfile File 'upload' commands.\n" + " <.> nter file using modem \n" + " <.> nter file using modem \n" + " <.> nter file using modem \n" + " \n" + " \n" + " Wholist commands:\n" + " \n" + " <.> holist ong Same as ho is online, but displays\n" + " more detailed information.\n" + " <.> holist oomname Masquerade your room name (other users\n" + " see the name you enter rather than the\n" + " actual name of the room you're in)\n" + " <.> holist ostname Masquerade your host name\n" + " <.> nter sername Masquerade your user name (Admins only)\n" + " <.> holist tealth mode Enter/exit 'stealth mode' (when in stealth\n" + " mode you are invisible on the wholist)\n" + " \n" + " \n" + " Floor commands (if using floor mode)\n" + " ;onfigure floor mode - turn floor mode on or off\n" + " ;oto floor: - jump to a specific floor\n" + " ;nown rooms - list all rooms on all floors\n" + " ;kip to floor: - skip current floor, jump to another\n" + " ;ap floor - zap (forget) all rooms on this floor\n" + " \n" + " \n" + " Administrative commands: \n" + " \n" + " <.> dmin ill this room \n" + " <.> dmin dit this room \n" + " <.> dmin ho knows room \n" + " <.> dmin edit ser \n" + " <.> dmin alidate new users \n" + " <.> dmin enter nfo file \n" + " <.> dmin oom nvite user \n" + " <.> dmin oom ick out user \n" + " <.> dmin ile elete \n" + " <.> dmin ile end over net \n" + " <.> dmin ile ove \n" + " <.> dmin essage edit: \n" + " <.> dmin

ost \n" + " <.> dmin ystem configuration \n" + " <.> dmin erminate server ow\n" " <.> dmin erminate server cheduled\n" +}; + + +struct citcmd { + struct citcmd *next; + int c_cmdnum; + int c_axlevel; + char c_keys[5][64]; +}; + +#define IFNEXPERT if ((userflags&US_EXPERT)==0) + + +int rc_exp_beep; +char rc_exp_cmd[1024]; +int rc_allow_attachments; +int rc_display_message_numbers; +int rc_force_mail_prompts; +int rc_remember_passwords; +int rc_ansi_color; +int rc_color_use_bg; +int rc_prompt_control = 0; +time_t rc_idle_threshold = (time_t) 900; +char rc_url_cmd[SIZ]; +char rc_open_cmd[SIZ]; +char rc_gotmail_cmd[SIZ]; + +int next_lazy_cmd = 5; + +extern int screenwidth, screenheight; +extern int termn8; +extern CtdlIPC *ipc_for_signal_handlers; // KLUDGE cover your eyes +struct citcmd *cmdlist = NULL; + + +// these variables are local to this module +char keepalives_enabled = KA_YES; // send NOOPs to server when idle +int ok_to_interrupt = 0; // print instant msgs asynchronously +time_t AnsiDetect; // when did we send the detect code? +int enable_color = 0; // nonzero for ANSI color + + +// If an interesting key has been pressed, return its value, otherwise 0 +char was_a_key_pressed(void) { + fd_set rfds; + struct timeval tv; + int the_character; + int retval; + + FD_ZERO(&rfds); + FD_SET(0, &rfds); + tv.tv_sec = 0; + tv.tv_usec = 0; + retval = select(1, &rfds, NULL, NULL, &tv); + + // Careful! Disable keepalives during keyboard polling; we're probably + // in the middle of a data transfer from the server, in which case + // sending a NOOP would throw the client protocol out of sync. + if ((retval > 0) && FD_ISSET(0, &rfds)) { + set_keepalives(KA_NO); + the_character = inkey(); + set_keepalives(KA_YES); + } + else { + the_character = 0; + } + return (the_character); +} + + +// display_instant_messages() - print instant messages if there are any +void display_instant_messages(void) { + char buf[1024]; + FILE *outpipe; + time_t timestamp; + struct tm stamp; + int flags = 0; + char sender[64]; + char node[64]; + char *listing = NULL; + int r; // IPC result code + + if (instant_msgs == 0) { + return; + } + + if (rc_exp_beep) { + ctdl_beep(); + } + + if (IsEmptyStr(rc_exp_cmd)) { + color(BRIGHT_RED); + scr_printf("\r---"); + } + + while (instant_msgs != 0) { + r = CtdlIPCGetInstantMessage(ipc_for_signal_handlers, &listing, buf); + if (r / 100 != 1) { + return; + } + + instant_msgs = extract_int(buf, 0); + timestamp = extract_long(buf, 1); + flags = extract_int(buf, 2); + extract_token(sender, buf, 3, '|', sizeof sender); + extract_token(node, buf, 4, '|', sizeof node); + strcpy(last_paged, sender); + + localtime_r(×tamp, &stamp); + + // If the message contains a Logoff Request, honor it. + if (flags & 2) { + termn8 = 1; + return; + } + + if (!IsEmptyStr(rc_exp_cmd)) { + outpipe = popen(rc_exp_cmd, "w"); + if (outpipe != NULL) { + // Header derived from flags + if (flags & 2) + fprintf(outpipe, "Please log off now, as requested "); + else if (flags & 1) + fprintf(outpipe, "Broadcast message "); + else if (flags & 4) + fprintf(outpipe, "Chat request "); + else + fprintf(outpipe, "Message "); + // Timestamp. Can this be improved? + if (stamp.tm_hour == 0 || stamp.tm_hour == 12) + fprintf(outpipe, "at 12:%02d%cm", stamp.tm_min, stamp.tm_hour ? 'p' : 'a'); + else if (stamp.tm_hour > 12) // pm + fprintf(outpipe, "at %d:%02dpm", stamp.tm_hour - 12, stamp.tm_min); + else // am + fprintf(outpipe, "at %d:%02dam", stamp.tm_hour, stamp.tm_min); + fprintf(outpipe, " from %s", sender); + if (strncmp(ipc_for_signal_handlers->ServInfo.nodename, node, 32)) + fprintf(outpipe, " @%s", node); + fprintf(outpipe, ":\n%s\n", listing); + pclose(outpipe); + if (instant_msgs == 0) + return; + continue; + } + } + // fall back to built-in instant message display + scr_printf("\n"); + + // Header derived from flags + if (flags & 2) + scr_printf("Please log off now, as requested "); + else if (flags & 1) + scr_printf("Broadcast message "); + else if (flags & 4) + scr_printf("Chat request "); + else + scr_printf("Message "); + + // Timestamp. Can this be improved? + if (stamp.tm_hour == 0 || stamp.tm_hour == 12) // 12am/12pm + scr_printf("at 12:%02d%cm", stamp.tm_min, stamp.tm_hour ? 'p' : 'a'); + else if (stamp.tm_hour > 12) // pm + scr_printf("at %d:%02dpm", stamp.tm_hour - 12, stamp.tm_min); + else // am + scr_printf("at %d:%02dam", stamp.tm_hour, stamp.tm_min); + + // Sender + scr_printf(" from %s", sender); + + // Remote node, if any + if (strncmp(ipc_for_signal_handlers->ServInfo.nodename, node, 32)) + scr_printf(" @%s", node); + + scr_printf(":\n"); + fmout(screenwidth, NULL, listing, NULL, 0); + free(listing); + + } + scr_printf("\n---\n"); + color(BRIGHT_WHITE); + + +} + + +void set_keepalives(int s) { + keepalives_enabled = (char) s; +} + + +// This loop handles the "keepalive" messages sent to the server when idling. +static time_t idlet = 0; +static void really_do_keepalive(void) { + time(&idlet); + + // This may sometimes get called before we are actually connected + // to the server. Don't do anything if we aren't connected. -IO + if (!ipc_for_signal_handlers) + return; + + // If full keepalives are enabled, send a NOOP to the server and + // wait for a response. + if (keepalives_enabled == KA_YES) { + CtdlIPCNoop(ipc_for_signal_handlers); + if (instant_msgs > 0) { + if (ok_to_interrupt == 1) { + scr_printf("\r%64s\r", ""); + display_instant_messages(); + scr_printf("%s%c ", room_name, room_prompt(room_flags)); + scr_flush(); + } + } + } + + // If half keepalives are enabled, send a QNOP to the server, then do nothing. + if (keepalives_enabled == KA_HALF) { + CtdlIPC_chat_send(ipc_for_signal_handlers, "QNOP"); + } +} + + +// I changed this from static to not because I need to call it from +// screen.c, either that or make something in screen.c not static. +// Fix it how you like. Why all the staticness? stu +void do_keepalive(void) { + time_t now; + + time(&now); + if ((now - idlet) < ((long) S_KEEPALIVE)) { + return; + } + + // Do a space-backspace to keep terminal sessions from idling out + scr_printf(" %c", 8); + scr_flush(); + + really_do_keepalive(); +} + + +// Get a character from the keyboard, with the watchdog timer in effect if necessary. +int inkey(void) { + int a; + fd_set rfds; + struct timeval tv; + time_t start_time; + + scr_flush(); + time(&start_time); + + do { + // This loop waits for keyboard input. If the keepalive + // timer expires, it sends a keepalive to the server if + // necessary and then waits again. + do { + do_keepalive(); + + FD_ZERO(&rfds); + FD_SET(0, &rfds); + tv.tv_sec = S_KEEPALIVE; + tv.tv_usec = 0; + + select(1, &rfds, NULL, NULL, &tv); + } while (!FD_ISSET(0, &rfds)); + + // At this point, there's input, so fetch it. + // (There's a hole in the bucket...) + a = scr_getc(SCR_BLOCK); + if (a == 127) { + a = 8; + } + if (a == 13) { + a = 10; + } + } while (a == 0); + return (a); +} + + +// Returns 1 for yes, 0 for no +int yesno(void) { + int a; + while (1) { + a = inkey(); + a = tolower(a); + if (a == 'y') { + scr_printf("Yes\n"); + return (1); + } + if (a == 'n') { + scr_printf("No\n"); + return (0); + } + } +} + + +// Returns 1 for yes, 0 for no, arg is default value +int yesno_d(int d) { + int a; + while (1) { + a = inkey(); + a = tolower(a); + if (a == 10) + a = (d ? 'y' : 'n'); + if (a == 'y') { + scr_printf("Yes\n"); + return (1); + } + if (a == 'n') { + scr_printf("No\n"); + return (0); + } + } +} + + +// Function to read a line of text from the terminal. +// +// string Pointer to string buffer +// lim Maximum length +// noshow Echo asterisks instead of keystrokes? +// bs Allow backspacing out of the prompt? (returns -1 if this happens) +// +// returns: string length +int ctdl_getline(char *string, int lim, int noshow, int bs) { + int pos = strlen(string); + int ch; + + if (noshow && !IsEmptyStr(string)) { + int num_stars = strlen(string); + while (num_stars--) { + scr_putc('*'); + } + } else { + scr_printf("%s", string); + } + + while (1) { + ch = inkey(); + + if ((ch == 8) && (pos > 0)) { // backspace + --pos; + scr_putc(8); + scr_putc(32); + scr_putc(8); + } + + else if ((ch == 8) && (pos == 0) && (bs)) { // backspace out of the prompt + return (-1); + } + + else if ((ch == 23) && (pos > 0)) { // Ctrl-W deletes a word + while ((pos > 0) && !isspace(string[pos])) { + --pos; + scr_putc(8); + scr_putc(32); + scr_putc(8); + } + while ((pos > 0) && !isspace(string[pos - 1])) { + --pos; + scr_putc(8); + scr_putc(32); + scr_putc(8); + } + } + + else if (ch == 10) { // return + string[pos] = 0; + scr_printf("\n"); + return (pos); + } + + else if (isprint(ch)) { // payload characters + scr_putc((noshow ? '*' : ch)); + string[pos] = ch; + ++pos; + } + } +} + + +// prompt for a string, print the existing value, and allow the user to press return to keep it... +// If len is negative, pass the "noshow" flag to ctdl_getline() +void strprompt(char *prompt, char *str, int len) { + display_instant_messages(); + color(DIM_WHITE); + scr_printf("%s", prompt); + color(DIM_WHITE); + scr_printf(": "); + color(BRIGHT_CYAN); + ctdl_getline(str, abs(len), (len < 0), 0); + color(DIM_WHITE); +} + + +// prompt for a yes/no, print the existing value and allow the user to press return to keep it... +int boolprompt(char *prompt, int prev_val) { + int r; + + color(DIM_WHITE); + scr_printf("%s ", prompt); + color(DIM_MAGENTA); + scr_printf("["); + color(BRIGHT_MAGENTA); + scr_printf("%s", (prev_val ? "Yes" : "No")); + color(DIM_MAGENTA); + scr_printf("]: "); + color(BRIGHT_CYAN); + r = (yesno_d(prev_val)); + color(DIM_WHITE); + return r; +} + + +// like strprompt(), except for an integer (note that it RETURNS the new value!) +int intprompt(char *prompt, int ival, int imin, int imax) { + char buf[16]; + int i; + int p; + + do { + i = ival; + snprintf(buf, sizeof buf, "%d", i); + strprompt(prompt, buf, 15); + i = atoi(buf); + for (p = 0; !IsEmptyStr(&buf[p]); ++p) { + if ((!isdigit(buf[p])) + && ((buf[p] != '-') || (p != 0))) + i = imin - 1; + } + if (i < imin) + scr_printf("*** Must be no less than %d.\n", imin); + if (i > imax) + scr_printf("*** Must be no more than %d.\n", imax); + } while ((i < imin) || (i > imax)); + return (i); +} + + +// prompt for a string with no existing value (clears out string buffer first) +// If len is negative, pass the "noshow" flag to ctdl_getline() +void newprompt(char *prompt, char *str, int len) { + str[0] = 0; + color(BRIGHT_MAGENTA); + scr_printf("%s", prompt); + color(DIM_MAGENTA); + ctdl_getline(str, abs(len), (len < 0), 0); + color(DIM_WHITE); +} + + +// returns a lower case value +int lkey(void) { + int a; + a = inkey(); + if (isupper(a)) + a = tolower(a); + return (a); +} + + +// parse the citadel.rc file +void load_command_set(void) { + FILE *ccfile; + char buf[1024]; + struct citcmd *cptr; + struct citcmd *lastcmd = NULL; + int a, d; + int b = 0; + + // first, set up some defaults for non-required variables + strcpy(editor_path, ""); + strcpy(printcmd, ""); + strcpy(imagecmd, ""); + strcpy(rc_username, ""); + strcpy(rc_password, ""); + rc_floor_mode = 0; + rc_exp_beep = 1; + rc_allow_attachments = 0; + rc_remember_passwords = 0; + strcpy(rc_exp_cmd, ""); + rc_display_message_numbers = 0; + rc_force_mail_prompts = 0; + rc_ansi_color = 0; + rc_color_use_bg = 0; + strcpy(rc_url_cmd, ""); + strcpy(rc_open_cmd, ""); + strcpy(rc_gotmail_cmd, ""); +#ifdef HAVE_OPENSSL + rc_encrypt = RC_DEFAULT; +#endif + + // now try to open the citadel.rc file + ccfile = NULL; + if (getenv("HOME") != NULL) { + snprintf(buf, sizeof buf, "%s/.citadelrc", getenv("HOME")); + ccfile = fopen(buf, "r"); + } + if (getenv("APPDIR") != NULL) { + snprintf(buf, sizeof buf, "%s/citadel.rc", getenv("APPDIR")); + ccfile = fopen(buf, "r"); + } + if (ccfile == NULL) { + ccfile = fopen(file_citadel_rc, "r"); + } + if (ccfile == NULL) { + ccfile = fopen("/usr/local/etc/citadel.rc", "r"); + } + if (ccfile == NULL) { + ccfile = fopen("/etc/citadel.rc", "r"); + } + if (ccfile == NULL) { + ccfile = fopen("./citadel.rc", "r"); + } + if (ccfile == NULL) { + perror("commands: cannot open citadel.rc"); + logoff(NULL, 3); + } + while (fgets(buf, sizeof buf, ccfile) != NULL) { + while ((!IsEmptyStr(buf)) ? (isspace(buf[strlen(buf) - 1])) : 0) + buf[strlen(buf) - 1] = 0; + + if (!strncasecmp(buf, "encrypt=", 8)) { + if (!strcasecmp(&buf[8], "yes")) { +#ifdef HAVE_OPENSSL + rc_encrypt = RC_YES; +#else + fprintf(stderr, "citadel.rc requires encryption support but citadel is not compiled with OpenSSL"); + logoff(NULL, 3); +#endif + } +#ifdef HAVE_OPENSSL + else if (!strcasecmp(&buf[8], "no")) { + rc_encrypt = RC_NO; + } else if (!strcasecmp(&buf[8], "default")) { + rc_encrypt = RC_DEFAULT; + } +#endif + } + + if (!strncasecmp(buf, "editor=", 7)) { + strcpy(editor_path, &buf[7]); + } + + if (!strncasecmp(buf, "printcmd=", 9)) + strcpy(printcmd, &buf[9]); + + if (!strncasecmp(buf, "imagecmd=", 9)) + strcpy(imagecmd, &buf[9]); + + if (!strncasecmp(buf, "expcmd=", 7)) + strcpy(rc_exp_cmd, &buf[7]); + + if (!strncasecmp(buf, "use_floors=", 11)) { + if (!strcasecmp(&buf[11], "yes")) + rc_floor_mode = RC_YES; + if (!strcasecmp(&buf[11], "no")) + rc_floor_mode = RC_NO; + if (!strcasecmp(&buf[11], "default")) + rc_floor_mode = RC_DEFAULT; + } + if (!strncasecmp(buf, "beep=", 5)) { + rc_exp_beep = atoi(&buf[5]); + } + if (!strncasecmp(buf, "allow_attachments=", 18)) { + rc_allow_attachments = atoi(&buf[18]); + } + if (!strncasecmp(buf, "idle_threshold=", 15)) { + rc_idle_threshold = atol(&buf[15]); + } + if (!strncasecmp(buf, "remember_passwords=", 19)) { + rc_remember_passwords = atoi(&buf[19]); + } + if (!strncasecmp(buf, "display_message_numbers=", 24)) { + rc_display_message_numbers = atoi(&buf[24]); + } + if (!strncasecmp(buf, "force_mail_prompts=", 19)) { + rc_force_mail_prompts = atoi(&buf[19]); + } + if (!strncasecmp(buf, "ansi_color=", 11)) { + if (!strncasecmp(&buf[11], "on", 2)) + rc_ansi_color = 1; + if (!strncasecmp(&buf[11], "auto", 4)) + rc_ansi_color = 2; // autodetect + if (!strncasecmp(&buf[11], "user", 4)) + rc_ansi_color = 3; // user config + } + if (!strncasecmp(buf, "status_line=", 12)) { + if (!strncasecmp(&buf[12], "on", 2)) + enable_status_line = 1; + } + if (!strncasecmp(buf, "use_background=", 15)) { + if (!strncasecmp(&buf[15], "on", 2)) + rc_color_use_bg = 9; + } + if (!strncasecmp(buf, "prompt_control=", 15)) { + if (!strncasecmp(&buf[15], "on", 2)) + rc_prompt_control = 1; + if (!strncasecmp(&buf[15], "user", 4)) + rc_prompt_control = 3; // user config + } + if (!strncasecmp(buf, "username=", 9)) + strcpy(rc_username, &buf[9]); + + if (!strncasecmp(buf, "password=", 9)) + strcpy(rc_password, &buf[9]); + + if (!strncasecmp(buf, "urlcmd=", 7)) + strcpy(rc_url_cmd, &buf[7]); + + if (!strncasecmp(buf, "opencmd=", 7)) + strcpy(rc_open_cmd, &buf[8]); + + if (!strncasecmp(buf, "gotmailcmd=", 11)) + strcpy(rc_gotmail_cmd, &buf[11]); + + if (!strncasecmp(buf, "cmd=", 4)) { + strcpy(buf, &buf[4]); + + cptr = (struct citcmd *) malloc(sizeof(struct citcmd)); + + cptr->c_cmdnum = atoi(buf); + for (d = strlen(buf); d >= 0; --d) + if (buf[d] == ',') + b = d; + strcpy(buf, &buf[b + 1]); + + cptr->c_axlevel = atoi(buf); + for (d = strlen(buf); d >= 0; --d) + if (buf[d] == ',') + b = d; + strcpy(buf, &buf[b + 1]); + + for (a = 0; a < 5; ++a) + cptr->c_keys[a][0] = 0; + + a = 0; + b = 0; + buf[strlen(buf) + 1] = 0; + while (!IsEmptyStr(buf)) { + b = strlen(buf); + for (d = strlen(buf); d >= 0; --d) + if (buf[d] == ',') + b = d; + strncpy(cptr->c_keys[a], buf, b); + cptr->c_keys[a][b] = 0; + if (buf[b] == ',') + strcpy(buf, &buf[b + 1]); + else + strcpy(buf, ""); + ++a; + } + + cptr->next = NULL; + if (cmdlist == NULL) + cmdlist = cptr; + else + lastcmd->next = cptr; + lastcmd = cptr; + } + } + fclose(ccfile); +} + + +// return the key associated with a command +char keycmd(char *cmdstr) { + int a; + + for (a = 0; !IsEmptyStr(&cmdstr[a]); ++a) + if (cmdstr[a] == '&') + return (tolower(cmdstr[a + 1])); + return (0); +} + + +// Output the string from a key command without the ampersand +// "mode" should be set to 0 for normal or 1 for ommand key highlighting +char *cmd_expand(char *strbuf, int mode) { + int a; + static char exp[64]; + char buf[1024]; + + strcpy(exp, strbuf); + + for (a = 0; exp[a]; ++a) { + if (strbuf[a] == '&') { + + // don't echo these non-mnemonic command keys + int noecho = strbuf[a + 1] == '<' || strbuf[a + 1] == '>' || strbuf[a + 1] == '+' || strbuf[a + 1] == '-'; + + if (mode == 0) { + strcpy(&exp[a], &exp[a + 1 + noecho]); + } + if (mode == 1) { + exp[a] = '<'; + strcpy(buf, &exp[a + 2]); + exp[a + 2] = '>'; + exp[a + 3] = 0; + strcat(exp, buf); + } + } + if (!strncmp(&exp[a], "^r", 2)) { + strcpy(buf, exp); + strcpy(&exp[a], room_name); + strcat(exp, &buf[a + 2]); + } + if (!strncmp(&exp[a], "^c", 2)) { + exp[a] = ','; + strcpy(&exp[a + 1], &exp[a + 2]); + } + } + + return (exp); +} + + +// Comparison function to determine if entered commands match a command loaded from the config file. +int cmdmatch(char *cmdbuf, struct citcmd *cptr, int ncomp) { + int a; + int cmdax; + + cmdax = 0; + if (is_room_aide) + cmdax = 1; + if (axlevel >= 6) + cmdax = 2; + + for (a = 0; a < ncomp; ++a) { + if ((tolower(cmdbuf[a]) != keycmd(cptr->c_keys[a])) + || (cptr->c_axlevel > cmdax)) + return (0); + } + return (1); +} + + +// This function returns 1 if a given command requires a string input +int requires_string(struct citcmd *cptr, int ncomp) { + int a; + char buf[64]; + + strcpy(buf, cptr->c_keys[ncomp - 1]); + for (a = 0; !IsEmptyStr(&buf[a]); ++a) { + if (buf[a] == ':') + return (1); + } + return (0); +} + + +// Input a command at the main prompt. +// This function returns an integer command number. If the command prompts +// for a string then it is placed in the supplied buffer. +int getcmd(CtdlIPC * ipc, char *argbuf) { + char cmdbuf[5]; + int cmdspaces[5]; + int cmdpos; + int ch; + int a; + int got; + int this_lazy_cmd; + struct citcmd *cptr; + + // Starting a new command now, so set sigcaught to 0. This variable + // is set to nonzero (usually NEXT_KEY or STOP_KEY) if a command has + // been interrupted by a keypress. + sigcaught = 0; + + // Switch color support on or off if we're in user mode + if (rc_ansi_color == 3) { + if (userflags & US_COLOR) + enable_color = 1; + else + enable_color = 0; + } + + // if we're running in idiot mode, display a cute little menu + IFNEXPERT { + scr_printf("-----------------------------------------------------------------------\n"); + scr_printf("Room cmds: nown rooms, oto next room, <.G>oto a specific room,\n"); + scr_printf(" kip this room, bandon this room, ap this room,\n"); + scr_printf(" ngoto (move back)\n"); + scr_printf("Message cmds: ew msgs, orward read, everse read, ld msgs,\n"); + scr_printf(" ast five msgs, nter a message\n"); + scr_printf("General cmds: help, erminate, hat, ho is online\n"); + scr_printf("Misc: toggle eXpert mode, irectory\n"); + scr_printf("\n"); + scr_printf(" (Type .Help SUMMARY for extended commands, to hide this menu)\n"); + scr_printf("-----------------------------------------------------------------------\n"); + } + + display_instant_messages(); + strcpy(argbuf, ""); + cmdpos = 0; + for (a = 0; a < 5; ++a) { + cmdbuf[a] = 0; + } + + // now the room prompt... + ok_to_interrupt = 1; + color(BRIGHT_WHITE); + scr_printf("\n%s", room_name); + color(DIM_WHITE); + scr_printf("%c ", room_prompt(room_flags)); + + while (1) { + ch = inkey(); + ok_to_interrupt = 0; + + // Handle the backspace key, but only if there's something to backspace over... + if ((ch == 8) && (cmdpos > 0)) { + back(cmdspaces[cmdpos - 1] + 1); + cmdbuf[cmdpos] = 0; + --cmdpos; + } + + // Spacebar invokes "lazy traversal" commands + if ((ch == 32) && (cmdpos == 0)) { + this_lazy_cmd = next_lazy_cmd; + if (this_lazy_cmd == 13) + next_lazy_cmd = 5; + if (this_lazy_cmd == 5) + next_lazy_cmd = 13; + for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) { + if (cptr->c_cmdnum == this_lazy_cmd) { + for (a = 0; a < 5; ++a) + if (cptr->c_keys[a][0] != 0) + scr_printf("%s ", cmd_expand(cptr->c_keys[a], 0)); + scr_printf("\n"); + return (this_lazy_cmd); + } + } + scr_printf("\n"); + return (this_lazy_cmd); + } + + // Otherwise, process the command + cmdbuf[cmdpos] = tolower(ch); + + for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) { + if (cmdmatch(cmdbuf, cptr, cmdpos + 1)) { + + scr_printf("%s", cmd_expand(cptr->c_keys[cmdpos], 0)); + cmdspaces[cmdpos] = strlen(cmd_expand(cptr->c_keys[cmdpos], 0)); + if (cmdpos < 4) + if ((cptr->c_keys[cmdpos + 1]) != 0) + scr_putc(' '); + ++cmdpos; + } + } + + for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) { + if (cmdmatch(cmdbuf, cptr, 5)) { + if (requires_string(cptr, cmdpos)) { // We found our command. + argbuf[0] = 0; + ctdl_getline(argbuf, 64, 0, 0); + } + else { + scr_printf("\n"); + } + + // If this command is one that changes rooms, then the next lazy-command + // (space bar) should be "read new" instead of "goto" + if ( (cptr->c_cmdnum == 5) + || (cptr->c_cmdnum == 6) + || (cptr->c_cmdnum == 47) + || (cptr->c_cmdnum == 52) + || (cptr->c_cmdnum == 16) + || (cptr->c_cmdnum == 20) + ) { + next_lazy_cmd = 13; + } + + // If this command is "read new" then the next lazy-command (space bar) should be "goto" + if (cptr->c_cmdnum == 13) { + next_lazy_cmd = 5; + } + + return (cptr->c_cmdnum); + + } + } + + if (ch == '?') { + scr_printf("\rOne of ... \n"); + for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) { + if (cmdmatch(cmdbuf, cptr, cmdpos)) { + for (a = 0; a < 5; ++a) { + keyopt(cmd_expand(cptr->c_keys[a], 1)); + scr_printf(" "); + } + scr_printf("\n"); + } + } + sigcaught = 0; + + scr_printf("\n%s%c ", room_name, room_prompt(room_flags)); + got = 0; + for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) { + if ((got == 0) && (cmdmatch(cmdbuf, cptr, cmdpos))) { + for (a = 0; a < cmdpos; ++a) { + scr_printf("%s ", cmd_expand(cptr->c_keys[a], 0)); + } + got = 1; + } + } + } + } + +} + + +// set tty modes. commands are: +// +// 01- set to Citadel mode +// 2 - save current settings for later restoral +// 3 - restore saved settings +void stty_ctdl(int cmd) { + struct termios live; + static struct termios saved_settings; + static int last_cmd = 0; + + if (cmd == SB_LAST) { + cmd = last_cmd; + } + else { + last_cmd = cmd; + } + + if ((cmd == 0) || (cmd == 1)) { + tcgetattr(0, &live); + + // Character-by-character input instead of line mode + live.c_iflag = ISTRIP | IXON | IXANY; + live.c_oflag = OPOST | ONLCR; + live.c_lflag = ISIG | NOFLSH; + + // Key bindings + live.c_cc[VINTR] = 0; + live.c_cc[VQUIT] = 0; + live.c_cc[VERASE] = 8; + live.c_cc[VKILL] = 24; + live.c_cc[VEOF] = 1; + live.c_cc[VEOL] = 255; + live.c_cc[VEOL2] = 0; + live.c_cc[VSTART] = 0; + tcsetattr(0, TCSADRAIN, &live); + } + + if (cmd == 2) { + tcgetattr(0, &saved_settings); + } + + if (cmd == 3) { + tcsetattr(0, TCSADRAIN, &saved_settings); + } + +} + + +// display_help() - help text viewer +void display_help(CtdlIPC * ipc, char *name) { + int i; + int num_helps = sizeof(helpnames) / sizeof(char *); + + for (i = 0; i < num_helps; ++i) { + if (!strcasecmp(name, helpnames[i])) { + fmout(screenwidth, NULL, helptexts[i], NULL, 0); + return; + } + } + + scr_printf("'%s' not found. Enter one of:\n", name); + for (i = 0; i < num_helps; ++i) { + scr_printf(" %s\n", helpnames[i]); + } +} + + +// fmout() - Citadel text formatter and paginator +int fmout(int width, // screen width to use + FILE * fpin, // file to read from, or NULL to format given text + char *text, // text to be formatted (when fpin is NULL + FILE * fpout, // file to write to, or NULL to write to screen + int subst) { // nonzero if we should use hypertext mode + char *buffer = NULL; // The current message + char *word = NULL; // What we are about to actually print + char *e; // Pointer to position in text + char old = 0; // The previous character + int column = 0; // Current column + size_t i; // Generic counter + + // Space for a single word, which can be at most screenwidth + word = (char *) calloc(1, width); + if (!word) { + scr_printf("Can't alloc memory to print message: %s!\n", strerror(errno)); + logoff(NULL, 3); + } + + // Read the entire message body into memory + if (fpin) { + buffer = load_message_from_file(fpin); + if (!buffer) { + scr_printf("Can't print message: %s!\n", strerror(errno)); + logoff(NULL, 3); + } + } + else { + buffer = text; + } + e = buffer; + + // Run the message body + while (*e) { + // Catch characters that shouldn't be there at all + if (*e == '\r') { + e++; + continue; + } + if (*e == '\n') { // newline? + e++; + if (*e == ' ') { // paragraph? + if (fpout) { + fprintf(fpout, "\n"); + } else { + scr_printf("\n"); + } + column = 0; + } + else if (old != ' ') { // Don't print two spaces + if (fpout) { + fprintf(fpout, " "); + } + else { + scr_printf(" "); + } + column++; + } + old = '\n'; + continue; + } + + // Or are we looking at a space? + if (*e == ' ') { + e++; + if (column >= width - 1) { + // Are we in the rightmost column? + if (fpout) { + fprintf(fpout, "\n"); + } + else { + scr_printf("\n"); + } + column = 0; + } + else if (!(column == 0 && old == ' ')) { + // Eat only the first space on a line + if (fpout) { + fprintf(fpout, " "); + } + else { + scr_printf(" "); + } + column++; + } + // ONLY eat the FIRST space on a line + old = ' '; + continue; + } + old = *e; + + // Read a word, slightly messy + i = 0; + while (e[i]) { + if (!isprint(e[i]) && !isspace(e[i])) + e[i] = ' '; + if (isspace(e[i])) + break; + i++; + } + + // We should never see these, but... slightly messy + if (e[i] == '\t' || e[i] == '\f' || e[i] == '\v') + e[i] = ' '; + + // Break up really long words + if (i >= width) { + i = width - 1; + } + strncpy(word, e, i); + word[i] = 0; + + // Decide where to print the word + if (column + i >= width) { + // Wrap to the next line + if (fpout) { + fprintf(fpout, "\n"); + } + else { + scr_printf("\n"); + } + column = 0; + } + + // Print the word + if (fpout) { + fprintf(fpout, "%s", word); + } + else { + scr_printf("%s", word); + } + column += i; + e += i; // Start over with the whitepsace! + } + + free(word); + if (fpin) /* We allocated this, remember? */ + free(buffer); + + // Is this necessary? It makes the output kind of spacey. + if (fpout) { + fprintf(fpout, "\n"); + } + else { + scr_printf("\n"); + } + + return sigcaught; +} + + +// support ANSI color if defined +void color(int colornum) { + static int hold_color; + static int current_color; + + if (colornum == COLOR_PUSH) { + hold_color = current_color; + return; + } + + if (colornum == COLOR_POP) { + color(hold_color); + return; + } + + current_color = colornum; + if (enable_color) { + // When switching to dim white, actually output an 'original + // pair' sequence -- this looks better on black-on-white + // terminals. - Changed to ORIGINAL_PAIR as this actually + // wound up looking horrible on black-on-white terminals, not + // to mention transparent terminals. + if (colornum == ORIGINAL_PAIR) + printf("\033[0;39;49m"); + else + printf("\033[%d;3%d;4%dm", (colornum & 8) ? 1 : 0, (colornum & 7), rc_color_use_bg); + + } +} + + +// Clear the screen +void cls(int colornum) { + if (enable_color) { + printf("\033[4%dm\033[2J\033[H\033[0m", colornum ? colornum : rc_color_use_bg); + } +} + + +// Detect whether ANSI color is available (answerback) +void send_ansi_detect(void) { + if (rc_ansi_color == 2) { + printf("\033[c"); + scr_flush(); + time(&AnsiDetect); + } +} + + +void look_for_ansi(void) { + fd_set rfds; + struct timeval tv; + char abuf[512]; + time_t now; + int a, rv; + + if (rc_ansi_color == 0) { + enable_color = 0; + } + else if (rc_ansi_color == 1) { + enable_color = 1; + } + else if (rc_ansi_color == 2) { + + /* otherwise, do the auto-detect */ + + strcpy(abuf, ""); + + time(&now); + if ((now - AnsiDetect) < 2) + sleep(1); + + do { + FD_ZERO(&rfds); + FD_SET(0, &rfds); + tv.tv_sec = 0; + tv.tv_usec = 1; + + select(1, &rfds, NULL, NULL, &tv); + if (FD_ISSET(0, &rfds)) { + abuf[strlen(abuf) + 1] = 0; + rv = read(0, &abuf[strlen(abuf)], 1); + if (rv < 0) { + scr_printf("failed to read after select: %s", strerror(errno)); + break; + } + } + } while (FD_ISSET(0, &rfds)); + + for (a = 0; !IsEmptyStr(&abuf[a]); ++a) { + if ((abuf[a] == 27) && (abuf[a + 1] == '[') + && (abuf[a + 2] == '?')) { + enable_color = 1; + } + } + } +} + + +// Display key options (highlight hotkeys inside angle brackets) +void keyopt(char *buf) { + int i; + + color(DIM_WHITE); + for (i = 0; !IsEmptyStr(&buf[i]); ++i) { + if (buf[i] == '<') { + scr_printf("%c", buf[i]); + color(BRIGHT_MAGENTA); + } else { + if (buf[i] == '>' && buf[i + 1] != '>') { + color(DIM_WHITE); + } + scr_printf("%c", buf[i]); + } + } + color(DIM_WHITE); +} + + +// Present a key-menu line choice type of thing +char keymenu(char *menuprompt, char *menustring) { + int i, c, a; + int choices; + int do_prompt = 0; + char buf[1024]; + int ch; + int display_prompt = 1; + + choices = num_tokens(menustring, '|'); + + if (menuprompt != NULL) + do_prompt = 1; + if ((menuprompt != NULL) && (IsEmptyStr(menuprompt))) + do_prompt = 0; + + while (1) { + if (display_prompt) { + if (do_prompt) { + scr_printf("%s ", menuprompt); + } + else { + for (i = 0; i < choices; ++i) { + extract_token(buf, menustring, i, '|', sizeof buf); + keyopt(buf); + scr_printf(" "); + } + } + scr_printf("-> "); + display_prompt = 0; + } + ch = lkey(); + + if ((do_prompt) && (ch == '?')) { + scr_printf("\rOne of... "); + scr_printf(" \n"); + for (i = 0; i < choices; ++i) { + extract_token(buf, menustring, i, '|', sizeof buf); + scr_printf(" "); + keyopt(buf); + scr_printf("\n"); + } + scr_printf("\n"); + display_prompt = 1; + } + + for (i = 0; i < choices; ++i) { + extract_token(buf, menustring, i, '|', sizeof buf); + for (c = 1; !IsEmptyStr(&buf[c]); ++c) { + if ((ch == tolower(buf[c])) + && (buf[c - 1] == '<') + && (buf[c + 1] == '>')) { + for (a = 0; !IsEmptyStr(&buf[a]); ++a) { + if ((a != (c - 1)) && (a != (c + 1))) { + scr_putc(buf[a]); + } + } + scr_printf("\n"); + return ch; + } + } + } + } +} diff --git a/textclient/ipc_c_tcp.c~ b/textclient/ipc_c_tcp.c~ new file mode 100644 index 000000000..efb997dcc --- /dev/null +++ b/textclient/ipc_c_tcp.c~ @@ -0,0 +1,57 @@ +// Client-side IPC functions +// +// Copyright (c) 1987-2018 by the citadel.org team +// +// This program is open source software. Use, duplication, and/or +// disclosure are subject to the GNU General Purpose License version 3. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + + +#include "textclient.h" + + +/* Note that some of these functions may not work with multiple instances. */ + +static void (*deathHook) (void) = NULL; +int (*error_printf) (char *s, ...) = (int (*)(char *, ...)) printf; + +void setIPCDeathHook(void (*hook) (void)) +{ + deathHook = hook; +} + +void setIPCErrorPrintf(int (*func) (char *s, ...)) +{ + error_printf = func; +} + +void connection_died(CtdlIPC * ipc, int using_ssl) +{ + if (deathHook != NULL) { + deathHook(); + } + + stty_ctdl(SB_RESTORE); + fprintf(stderr, "\r\n\n\n"); + fprintf(stderr, "Your connection to %s is broken.\n", ipc->ServInfo.humannode); + +#ifdef HAVE_OPENSSL + if (using_ssl) { + fprintf(stderr, "Last error: %s\n", ERR_reason_error_string(ERR_get_error())); + SSL_free(ipc->ssl); + ipc->ssl = NULL; + } else +#endif + fprintf(stderr, "Last error: %s\n", strerror(errno)); + + fprintf(stderr, "Please re-connect and log in again.\n"); + fflush(stderr); + fflush(stdout); + shutdown(ipc->sock, 2); + ipc->sock = -1; + exit(1); +} diff --git a/textclient/messages.c~ b/textclient/messages.c~ new file mode 100644 index 000000000..34e188228 --- /dev/null +++ b/textclient/messages.c~ @@ -0,0 +1,1922 @@ +// Text client functions for reading and writing of messages +// +// Beware: this is really old and crappy code, written in the +// late 1980s when my coding style was absolute garbage. It +// works, but we probably should replace most of it. +// +// Copyright (c) 1987-2022 by the citadel.org team +// +// This program is open source software. Use, duplication, and/or +// disclosure are subject to the GNU General Purpose License version 3. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +#include "textclient.h" + +#define MAXWORDBUF SIZ +#define NO_REPLY_TO "nobody ... xxxxxx" + +char reply_to[SIZ]; +char reply_subject[SIZ]; +char reply_references[SIZ]; +char reply_inreplyto[SIZ]; + +struct cittext { + struct cittext *next; + char text[MAXWORDBUF]; +}; + +void stty_ctdl(int cmd); +int haschar(const char *st, int ch); +int file_checksum(char *filename); +void progress(CtdlIPC * ipc, unsigned long curr, unsigned long cmax); + +unsigned long *msg_arr = NULL; +int msg_arr_size = 0; +int num_msgs; +extern char room_name[]; +extern char tempdir[]; +extern unsigned room_flags; +extern unsigned room_flags2; +extern int entmsg_ok; +extern long highest_msg_read; +extern char temp[]; +extern char temp2[]; +extern int screenwidth; +extern int screenheight; +extern long maxmsgnum; +extern char is_mail; +extern char is_aide; +extern char is_room_aide; +extern char fullname[]; +extern char axlevel; +extern unsigned userflags; +extern char sigcaught; +extern char printcmd[]; +extern int rc_allow_attachments; +extern int rc_display_message_numbers; +extern int rc_force_mail_prompts; +extern int editor_pid; +extern CtdlIPC *ipc_for_signal_handlers; /* KLUDGE cover your eyes */ +int num_urls = 0; +char urls[MAXURLS][SIZ]; +char imagecmd[SIZ]; +int has_images = 0; /* Current msg has images */ +struct parts *last_message_parts = NULL; /* Parts from last msg */ + + +void ka_sigcatch(int signum) { + alarm(S_KEEPALIVE); + signal(SIGALRM, ka_sigcatch); + CtdlIPCNoop(ipc_for_signal_handlers); +} + + +/* + * server keep-alive version of wait() (needed for external editor) + */ +pid_t ka_wait(int *kstatus) { + pid_t p; + + alarm(S_KEEPALIVE); + signal(SIGALRM, ka_sigcatch); + do { + errno = 0; + p = wait(kstatus); + } while (errno == EINTR); + signal(SIGALRM, SIG_IGN); + alarm(0); + return (p); +} + + +/* + * version of system() that uses ka_wait() + */ +int ka_system(char *shc) { + pid_t childpid; + pid_t waitpid; + int retcode; + + childpid = fork(); + if (childpid < 0) { + color(BRIGHT_RED); + perror("Cannot fork"); + color(DIM_WHITE); + return ((pid_t) childpid); + } + + if (childpid == 0) { + execlp("/bin/sh", "sh", "-c", shc, NULL); + exit(127); + } + + if (childpid > 0) { + do { + waitpid = ka_wait(&retcode); + } while (waitpid != childpid); + return (retcode); + } + + return (-1); +} + + +/* + * add a newline to the buffer... + */ +void add_newline(struct cittext *textlist) { + struct cittext *ptr; + + ptr = textlist; + while (ptr->next != NULL) + ptr = ptr->next; + + while (ptr->text[strlen(ptr->text) - 1] == 32) + ptr->text[strlen(ptr->text) - 1] = 0; + + ptr->next = (struct cittext *) + malloc(sizeof(struct cittext)); + ptr = ptr->next; + ptr->next = NULL; + strcpy(ptr->text, ""); +} + + +/* + * add a word to the buffer... + */ +void add_word(struct cittext *textlist, char *wordbuf) { + struct cittext *ptr; + + ptr = textlist; + while (ptr->next != NULL) + ptr = ptr->next; + + if (3 + strlen(ptr->text) + strlen(wordbuf) > screenwidth) { + ptr->next = (struct cittext *) + malloc(sizeof(struct cittext)); + ptr = ptr->next; + ptr->next = NULL; + strcpy(ptr->text, ""); + } + + strcat(ptr->text, wordbuf); + strcat(ptr->text, " "); +} + + +/* + * begin editing of an opened file pointed to by fp + */ +void citedit(FILE * fp) { + int a, prev, finished, b, last_space; + int appending = 0; + struct cittext *textlist = NULL; + struct cittext *ptr; + char wordbuf[MAXWORDBUF]; + int rv = 0; + + /* first, load the text into the buffer */ + fseek(fp, 0L, 0); + textlist = (struct cittext *) malloc(sizeof(struct cittext)); + textlist->next = NULL; + strcpy(textlist->text, ""); + + strcpy(wordbuf, ""); + prev = (-1); + while (a = getc(fp), a >= 0) { + appending = 1; + if ((a == 32) || (a == 9) || (a == 13) || (a == 10)) { + add_word(textlist, wordbuf); + strcpy(wordbuf, ""); + if ((prev == 13) || (prev == 10)) { + add_word(textlist, "\n"); + add_newline(textlist); + add_word(textlist, ""); + } + } + else { + wordbuf[strlen(wordbuf) + 1] = 0; + wordbuf[strlen(wordbuf)] = a; + } + if (strlen(wordbuf) + 3 > screenwidth) { + add_word(textlist, wordbuf); + strcpy(wordbuf, ""); + } + prev = a; + } + + /* get text */ + finished = 0; + prev = (appending ? 13 : (-1)); + strcpy(wordbuf, ""); + do { + a = inkey(); + if (a == 10) + a = 13; + if (a == 9) + a = 32; + if (a == 127) + a = 8; + + if ((a != 32) && (prev == 13)) { + add_word(textlist, "\n"); + scr_printf(" "); + } + + if ((a == 32) && (prev == 13)) { + add_word(textlist, "\n"); + add_newline(textlist); + } + + if (a == 8) { + if (!IsEmptyStr(wordbuf)) { + wordbuf[strlen(wordbuf) - 1] = 0; + scr_putc(8); + scr_putc(32); + scr_putc(8); + } + } + else if (a == 23) { + do { + wordbuf[strlen(wordbuf) - 1] = 0; + scr_putc(8); + scr_putc(32); + scr_putc(8); + } while (!IsEmptyStr(wordbuf) && wordbuf[strlen(wordbuf) - 1] != ' '); + } + else if (a == 13) { + scr_printf("\n"); + if (IsEmptyStr(wordbuf)) + finished = 1; + else { + for (b = 0; b < strlen(wordbuf); ++b) + if (wordbuf[b] == 32) { + wordbuf[b] = 0; + add_word(textlist, wordbuf); + strcpy(wordbuf, &wordbuf[b + 1]); + b = 0; + } + add_word(textlist, wordbuf); + strcpy(wordbuf, ""); + } + } + else { + scr_putc(a); + wordbuf[strlen(wordbuf) + 1] = 0; + wordbuf[strlen(wordbuf)] = a; + } + if ((strlen(wordbuf) + 3) > screenwidth) { + last_space = (-1); + for (b = 0; b < strlen(wordbuf); ++b) + if (wordbuf[b] == 32) + last_space = b; + if (last_space >= 0) { + for (b = 0; b < strlen(wordbuf); ++b) + if (wordbuf[b] == 32) { + wordbuf[b] = 0; + add_word(textlist, wordbuf); + strcpy(wordbuf, &wordbuf[b + 1]); + b = 0; + } + for (b = 0; b < strlen(wordbuf); ++b) { + scr_putc(8); + scr_putc(32); + scr_putc(8); + } + scr_printf("\n%s", wordbuf); + } + else { + add_word(textlist, wordbuf); + strcpy(wordbuf, ""); + scr_printf("\n"); + } + } + prev = a; + } while (finished == 0); + + /* write the buffer back to disk */ + fseek(fp, 0L, 0); + for (ptr = textlist; ptr != NULL; ptr = ptr->next) { + fprintf(fp, "%s", ptr->text); + } + putc(10, fp); + fflush(fp); + rv = ftruncate(fileno(fp), ftell(fp)); + if (rv < 0) + scr_printf("failed to set message buffer: %s\n", strerror(errno)); + + + /* and deallocate the memory we used */ + while (textlist != NULL) { + ptr = textlist->next; + free(textlist); + textlist = ptr; + } +} + + +/* + * Free the struct parts + */ +void free_parts(struct parts *p) { + struct parts *a_part = p; + + while (a_part) { + struct parts *q; + + q = a_part; + a_part = a_part->next; + free(q); + } +} + + +/* + * This is a mini RFC2047 decoder. + * It only handles strings encoded from UTF-8 as Quoted-printable. + * We can do this "in place" because the converted string will always be smaller than the source string. + */ +void mini_2047_decode(char *s) { + if (!s) { // no null strings allowed! + return; + } + + char *qstart = strstr(s, "=?UTF-8?Q?"); // Must start with this string + if (!qstart) { + return; + } + + char *qend = strstr(qstart + 10, "?="); // Must end with this string + if (!qend) { + return; + } + + if (qend <= qstart) { // And there must be something in between them. + return; + } + + // The string has qualified for conversion. + + strcpy(qend, ""); // Strip the trailer + strcpy(qstart, &qstart[10]); // Strip the header + + char *r = qstart; // Pointer to where in the string we're reading + char *w = s; // Pointer to where in the string we're writing + + while (*r) { // Loop through the source string + if (r[0] == '=') { // "=" means read a hex character + char ch[3]; + ch[0] = r[1]; + ch[1] = r[2]; + ch[2] = r[3]; + int c; + sscanf(ch, "%02x", &c); + w[0] = c; + r += 3; + ++w; + } + else if (r[0] == '_') { // "_" is a space + w[0] = ' '; + ++r; + ++w; + } + else { // anything else pass through literally + w[0] = r[0]; + ++r; + ++w; + } + } + w[0] = 0; // null terminate +} + + +/* + * Read a message from the server + */ +int read_message(CtdlIPC * ipc, long num, /* message number */ + int pagin, /* 0 = normal read, 1 = read with pagination, 2 = header */ + FILE * dest /* Destination file, NULL for screen */ + ) { + char buf[SIZ]; + char now[256]; + int format_type = 0; + int fr = 0; + int nhdr = 0; + struct ctdlipcmessage *message = NULL; + int r; /* IPC response code */ + char *converted_text = NULL; + char *lineptr; + char *nextline; + char *searchptr; + int i; + char ch; + int linelen; + int final_line_is_blank = 0; + has_images = 0; + + sigcaught = 0; + stty_ctdl(1); + + strcpy(reply_to, NO_REPLY_TO); + strcpy(reply_subject, ""); + strcpy(reply_references, ""); + strcpy(reply_inreplyto, ""); + + r = CtdlIPCGetSingleMessage(ipc, num, (pagin == READ_HEADER ? 1 : 0), 4, &message, buf); + if (r / 100 != 1) { + scr_printf("*** msg #%ld: %d %s\n", num, r, buf); + stty_ctdl(0); + free(message->text); + free_parts(message->attachments); + free(message); + return (0); + } + + if (dest) { + fprintf(dest, "\n "); + } + else { + scr_printf("\n"); + if (pagin != 2) { + scr_printf(" "); + } + } + if (pagin == 1 && !dest) { + color(BRIGHT_CYAN); + } + + /* View headers only */ + if (pagin == 2) { + scr_printf("nhdr=%s\nfrom=%s\ntype=%d\nmsgn=%s\n", + message->nhdr ? "yes" : "no", message->author, message->type, message->msgid); + if (!IsEmptyStr(message->subject)) { + scr_printf("subj=%s\n", message->subject); + } + if (!IsEmptyStr(message->email)) { + scr_printf("rfca=%s\n", message->email); + } + scr_printf("room=%s\ntime=%s", message->room, asctime(localtime(&message->time))); + if (!IsEmptyStr(message->recipient)) { + scr_printf("rcpt=%s\n", message->recipient); + } + if (message->attachments) { + struct parts *ptr; + + for (ptr = message->attachments; ptr; ptr = ptr->next) { + scr_printf("part=%s|%s|%s|%s|%s|%ld\n", + ptr->name, ptr->filename, ptr->number, ptr->disposition, ptr->mimetype, ptr->length); + } + } + scr_printf("\n"); + stty_ctdl(0); + free(message->text); + free_parts(message->attachments); + free(message); + return (0); + } + + if (rc_display_message_numbers) { + if (dest) { + fprintf(dest, "[#%s] ", message->msgid); + } + else { + color(DIM_WHITE); + scr_printf("["); + color(BRIGHT_WHITE); + scr_printf("#%s", message->msgid); + color(DIM_WHITE); + scr_printf("] "); + } + } + if (nhdr == 1 && !is_room_aide) { + if (dest) { + fprintf(dest, " ****"); + } + else { + scr_printf(" ****"); + } + } + else { + struct tm thetime; + localtime_r(&message->time, &thetime); + strftime(now, sizeof now, "%F %R", &thetime); + if (dest) { + fprintf(dest, "%s from %s ", now, message->author); + if (!message->is_local) { + fprintf(dest, "<%s> ", message->email); + } + } + else { + color(BRIGHT_CYAN); + scr_printf("%s ", now); + color(DIM_WHITE); + scr_printf("from "); + color(BRIGHT_CYAN); + scr_printf("%s ", message->author); + if (!message->is_local) { + color(DIM_WHITE); + scr_printf("<"); + color(BRIGHT_BLUE); + scr_printf("%s", message->email); + color(DIM_WHITE); + scr_printf("> "); + } + } + if (strcasecmp(message->room, room_name) && (IsEmptyStr(message->email))) { + if (dest) { + fprintf(dest, "in %s> ", message->room); + } + else { + color(DIM_WHITE); + scr_printf("in "); + color(BRIGHT_MAGENTA); + scr_printf("%s> ", message->room); + } + } + if (!IsEmptyStr(message->recipient)) { + if (dest) { + fprintf(dest, "to %s ", message->recipient); + } + else { + color(DIM_WHITE); + scr_printf("to "); + color(BRIGHT_CYAN); + scr_printf("%s ", message->recipient); + } + } + } + + if (dest) { + fprintf(dest, "\n"); + } + else { + scr_printf("\n"); + } + + // Set the reply-to address to an Internet e-mail address if possible + if ((message->email != NULL) && (!IsEmptyStr(message->email))) { + if (!IsEmptyStr(message->author)) { + snprintf(reply_to, sizeof reply_to, "%s <%s>", message->author, message->email); + } + else { + strncpy(reply_to, message->email, sizeof reply_to); + } + } + + // But if we can't do that, set it to a Citadel address. + if (!strcmp(reply_to, NO_REPLY_TO)) { + strncpy(reply_to, message->author, sizeof(reply_to)); + } + + if (message->msgid != NULL) { + strncpy(reply_inreplyto, message->msgid, sizeof reply_inreplyto); + } + + if (message->references != NULL) { + if (!IsEmptyStr(message->references)) { + strncpy(reply_references, message->references, sizeof reply_references); + } + } + + if (message->subject != NULL) { + strncpy(reply_subject, message->subject, sizeof reply_subject); + if (!IsEmptyStr(message->subject)) { + if (dest) { + fprintf(dest, "Subject: %s\n", message->subject); + } + else { + color(DIM_WHITE); + scr_printf("Subject: "); + color(BRIGHT_CYAN); + mini_2047_decode(message->subject); + scr_printf("%s\n", message->subject); + + } + } + } + + if (pagin == 1 && !dest) { + color(BRIGHT_WHITE); + } + + /******* end of header output, start of message text output *******/ + + /* + * Convert HTML to plain text, formatting for the actual width + * of the client screen. + */ + if (!strcasecmp(message->content_type, "text/html")) { + converted_text = html_to_ascii(message->text, 0, screenwidth); + if (converted_text != NULL) { + free(message->text); + message->text = converted_text; + format_type = 1; + } + } + + /* Text/plain is a different type */ + if (!strcasecmp(message->content_type, "text/plain")) { + format_type = 1; + } + + /* Render text/x-markdown as plain text */ + if (!strcasecmp(message->content_type, "text/x-markdown")) { + format_type = 1; + } + + /* Extract URL's */ + static char *urlprefixes[] = { + "http://", + "https://", + "ftp://" + }; + int p = 0; + num_urls = 0; /* Start with a clean slate */ + for (p = 0; p < (sizeof urlprefixes / sizeof(char *)); ++p) { + searchptr = message->text; + while ((searchptr != NULL) && (num_urls < MAXURLS)) { + searchptr = strstr(searchptr, urlprefixes[p]); + if (searchptr != NULL) { + strncpy(urls[num_urls], searchptr, sizeof(urls[num_urls])); + for (i = 0; i < strlen(urls[num_urls]); i++) { + ch = urls[num_urls][i]; + if (ch == '>' || ch == '\"' || ch == ')' || ch == ' ' || ch == '\n') { + urls[num_urls][i] = 0; + break; + } + } + num_urls++; + ++searchptr; + } + } + } + + /* + * Here we go + */ + if (format_type == 0) { + fr = fmout(screenwidth, NULL, message->text, dest, 1); + } + else { + /* renderer for text/plain */ + + lineptr = message->text; + + do { + nextline = strchr(lineptr, '\n'); + if (nextline != NULL) { + *nextline = 0; + ++nextline; + if (*nextline == 0) + nextline = NULL; + } + + if (sigcaught == 0) { + linelen = strlen(lineptr); + if (linelen && (lineptr[linelen - 1] == '\r')) { + lineptr[--linelen] = 0; + } + if (dest) { + fprintf(dest, "%s\n", lineptr); + } + else { + scr_printf("%s\n", lineptr); + } + } + if (lineptr[0] == 0) + final_line_is_blank = 1; + else + final_line_is_blank = 0; + lineptr = nextline; + } while (nextline); + fr = sigcaught; + } + if (!final_line_is_blank) { + if (dest) { + fprintf(dest, "\n"); + } + else { + scr_printf("\n"); + fr = sigcaught; + } + } + + /* Enumerate any attachments */ + if ((pagin == 1) && (message->attachments)) { + struct parts *ptr; + + for (ptr = message->attachments; ptr; ptr = ptr->next) { + if ((!strcasecmp(ptr->disposition, "attachment")) + || (!strcasecmp(ptr->disposition, "inline")) + || (!strcasecmp(ptr->disposition, "")) + ) { + if ((strcasecmp(ptr->number, message->mime_chosen)) + && (!IsEmptyStr(ptr->mimetype)) + ) { + color(DIM_WHITE); + scr_printf("Part "); + color(BRIGHT_MAGENTA); + scr_printf("%s", ptr->number); + color(DIM_WHITE); + scr_printf(": "); + color(BRIGHT_CYAN); + scr_printf("%s", ptr->filename); + color(DIM_WHITE); + scr_printf(" (%s, %ld bytes)\n", ptr->mimetype, ptr->length); + if (!strncmp(ptr->mimetype, "image/", 6)) { + has_images++; + } + } + } + } + } + + /* Save the attachments info for later */ + last_message_parts = message->attachments; + + /* Now we're done */ + free(message->text); + free(message); + + if (pagin == 1 && !dest) + color(DIM_WHITE); + stty_ctdl(0); + return (fr); +} + + +/* + * replace string function for the built-in editor + */ +void replace_string(char *filename, long int startpos) { + char buf[512]; + char srch_str[128]; + char rplc_str[128]; + FILE *fp; + int a; + long rpos, wpos; + char *ptr; + int substitutions = 0; + long msglen = 0L; + int rv; + + newprompt("Enter text to be replaced: ", srch_str, (sizeof(srch_str) - 1)); + if (IsEmptyStr(srch_str)) { + return; + } + + newprompt("Enter text to replace it with: ", rplc_str, (sizeof(rplc_str) - 1)); + + fp = fopen(filename, "r+"); + if (fp == NULL) { + return; + } + + wpos = startpos; + fseek(fp, startpos, 0); + strcpy(buf, ""); + while (a = getc(fp), a > 0) { + ++msglen; + buf[strlen(buf) + 1] = 0; + buf[strlen(buf)] = a; + if (strlen(buf) >= strlen(srch_str)) { + ptr = (&buf[strlen(buf) - strlen(srch_str)]); + if (!strncmp(ptr, srch_str, strlen(srch_str))) { + strcpy(ptr, rplc_str); + ++substitutions; + } + } + if (strlen(buf) > 384) { + rpos = ftell(fp); + fseek(fp, wpos, 0); + rv = fwrite((char *) buf, 128, 1, fp); + if (rv < 0) { + scr_printf("failed to replace string: %s\n", strerror(errno)); + break; /*whoopsi! */ + } + strcpy(buf, &buf[128]); + wpos = ftell(fp); + fseek(fp, rpos, 0); + } + } + fseek(fp, wpos, 0); + if (!IsEmptyStr(buf)) { + rv = fwrite((char *) buf, strlen(buf), 1, fp); + } + wpos = ftell(fp); + fclose(fp); + rv = truncate(filename, wpos); + scr_printf("eplace made %d substitution(s).\n\n", substitutions); +} + + +// Function to begin composing a new message +int client_make_message(CtdlIPC * ipc, char *filename, // temporary file name + char *recipient, // NULL if it's not mail + int is_anonymous, int format_type, int mode, char *subject, // buffer to store subject line + int subject_required) { + FILE *fp; + int a, b, e_ex_code; + long beg; + char datestr[256]; + char header[SIZ]; + int cksum = 0; + + if ((mode == 2) && (IsEmptyStr(editor_path))) { + scr_printf("*** No editor available; using built-in editor.\n"); + mode = 0; + } + + struct tm thetime; + time_t now = time(NULL); + localtime_r(&now, &thetime); + strftime(datestr, sizeof datestr, "%F %R", &thetime); + header[0] = 0; + + if (room_flags & QR_ANONONLY && !recipient) { + snprintf(header, sizeof header, " ****"); + } + else { + snprintf(header, sizeof header, " %s from %s", datestr, (is_anonymous ? "[anonymous]" : fullname)); + if (!IsEmptyStr(recipient)) { + size_t tmp = strlen(header); + snprintf(&header[tmp], sizeof header - tmp, " to %s", recipient); + } + } + scr_printf("%s\n", header); + if (subject != NULL) + if (!IsEmptyStr(subject)) { + scr_printf("Subject: %s\n", subject); + } + + if ((subject_required) && (IsEmptyStr(subject))) { + newprompt("Subject: ", subject, 70); + } + + if (mode == 1) { + scr_printf("(Press ctrl-d when finished)\n"); + } + + if (mode == 0) { + fp = fopen(filename, "r"); + if (fp != NULL) { + fmout(screenwidth, fp, NULL, NULL, 0); + beg = ftell(fp); + if (beg < 0) + scr_printf("failed to get stream position %s\n", strerror(errno)); + fclose(fp); + } + else { + fp = fopen(filename, "w"); + if (fp == NULL) { + scr_printf("*** Error opening temp file!\n %s: %s\n", filename, strerror(errno)); + return (1); + } + fclose(fp); + } + } + + ME1:switch (mode) { + + case 0: + fp = fopen(filename, "r+"); + if (fp == NULL) { + scr_printf("*** Error opening temp file!\n %s: %s\n", filename, strerror(errno) + ); + return (1); + } + citedit(fp); + fclose(fp); + goto MECR; + + case 1: + fp = fopen(filename, "a"); + if (fp == NULL) { + scr_printf("*** Error opening temp file!\n" " %s: %s\n", filename, strerror(errno)); + return (1); + } + do { + a = inkey(); + if (a == 255) + a = 32; + if (a == 13) + a = 10; + if (a != 4) { + putc(a, fp); + scr_putc(a); + } + if (a == 10) + scr_putc(10); + } while (a != 4); + fclose(fp); + break; + + case 2: + default: /* allow 2+ modes */ + e_ex_code = 1; /* start with a failed exit code */ + stty_ctdl(SB_RESTORE); + editor_pid = fork(); + cksum = file_checksum(filename); + if (editor_pid == 0) { + char tmp[SIZ]; + + chmod(filename, 0600); + snprintf(tmp, sizeof tmp, "WINDOW_TITLE=%s", header); + putenv(tmp); + execlp(editor_path, editor_path, filename, NULL); + exit(1); + } + if (editor_pid > 0) + do { + e_ex_code = 0; + b = ka_wait(&e_ex_code); + } while ((b != editor_pid) && (b >= 0)); + editor_pid = (-1); + stty_ctdl(0); + break; + } + + MECR:if (mode >= 2) { + if (file_checksum(filename) == cksum) { + scr_printf("*** Aborted message.\n"); + e_ex_code = 1; + } + if (e_ex_code == 0) { + goto MEFIN; + } + goto MEABT2; + } + + b = keymenu("Entry command (? for options)", + "bort|" + "ontinue|" "ave message|" "

rint formatted|" "add sbject|" "eplace string|" "old message"); + + if (b == 'a') + goto MEABT; + if (b == 'c') + goto ME1; + if (b == 's') + goto MEFIN; + if (b == 'p') { + scr_printf(" %s from %s", datestr, fullname); + if (!IsEmptyStr(recipient)) { + scr_printf(" to %s", recipient); + } + scr_printf("\n"); + if (subject != NULL) + if (!IsEmptyStr(subject)) { + scr_printf("Subject: %s\n", subject); + } + fp = fopen(filename, "r"); + if (fp != NULL) { + fmout(screenwidth, fp, NULL, NULL, 0); + beg = ftell(fp); + if (beg < 0) + scr_printf("failed to get stream position %s\n", strerror(errno)); + fclose(fp); + } + goto MECR; + } + if (b == 'r') { + replace_string(filename, 0L); + goto MECR; + } + if (b == 'h') { + return (2); + } + if (b == 'u') { + if (subject != NULL) { + newprompt("Subject: ", subject, 70); + } + goto MECR; + } + + MEFIN:return (0); + + MEABT:scr_printf("Are you sure? "); + if (yesno() == 0) { + goto ME1; + } + MEABT2:unlink(filename); + return (2); +} + + +/* + * Make sure there's room in msg_arr[] for at least one more. + */ +void check_msg_arr_size(void) { + if ((num_msgs + 1) > msg_arr_size) { + msg_arr_size += 512; + msg_arr = realloc(msg_arr, ((sizeof(long)) * msg_arr_size)); + } +} + + +/* + * break_big_lines() - break up lines that are >1024 characters + * otherwise the server will truncate + */ +void break_big_lines(char *msg) { + char *ptr; + char *break_here; + + if (msg == NULL) { + return; + } + + ptr = msg; + while (strlen(ptr) > 1000) { + break_here = strchr(&ptr[900], ' '); + if ((break_here == NULL) || (break_here > &ptr[999])) { + break_here = &ptr[999]; + } + *break_here = '\n'; + ptr = break_here++; + } +} + + +/* + * entmsg() - edit and create a message + * returns 0 if message was saved + */ +int entmsg(CtdlIPC * ipc, int is_reply, /* nonzero if this was a eply command */ + int c, /* mode */ + int masquerade /* prompt for a non-default display name? */ + ) { + char buf[SIZ]; + int a, b; + int need_recp = 0; + int mode; + long highmsg = 0L; + FILE *fp; + char subject[SIZ]; + struct ctdlipcmessage message; + unsigned long *msgarr = NULL; + int r; /* IPC response code */ + int subject_required = 0; + + /* + * First, check to see if we have permission to enter a message in + * this room. The server will return an error code if we can't. + */ + if (entmsg_ok == ENTMSG_OK_YES) { + /* no problem, go right ahead */ + } + else if (entmsg_ok == ENTMSG_OK_BLOG) { + if (!is_reply) { + scr_printf("WARNING: this is a BLOG room.\n"); + scr_printf("The 'nter Message' command will create a BLOG POST.\n"); + scr_printf("If you want to leave a comment or reply to a comment, use the 'eply' command.\n"); + scr_printf("Do you really want to create a new blog post? "); + if (!yesno()) { + return (1); + } + } + } + else { + scr_printf("You may not enter messages in this type of room.\n"); + return (1); + } + + if (c > 0) { + mode = 1; + } + else { + mode = 0; + } + + strcpy(subject, ""); + + strcpy(message.recipient, ""); + strcpy(message.author, ""); + strcpy(message.subject, ""); + strcpy(message.references, ""); + message.text = ""; /* point to "", changes later */ + message.anonymous = 0; + message.type = mode; + + if (masquerade) { + newprompt("Display name for this message: ", message.author, 40); + } + + if (is_reply) { + + if (!IsEmptyStr(reply_subject)) { + if (!strncasecmp(reply_subject, "Re: ", 3)) { + strcpy(message.subject, reply_subject); + } + else { + snprintf(message.subject, sizeof message.subject, "Re: %s", reply_subject); + } + } + + /* Trim down excessively long lists of thread references. We eliminate the + * second one in the list so that the thread root remains intact. + */ + int rrtok = num_tokens(reply_references, '|'); + int rrlen = strlen(reply_references); + if (((rrtok >= 3) && (rrlen > 900)) || (rrtok > 10)) { + remove_token(reply_references, 1, '|'); + } + + snprintf(message.references, sizeof message.references, "%s%s%s", + reply_references, (IsEmptyStr(reply_references) ? "" : "|"), reply_inreplyto); + } + + r = CtdlIPCPostMessage(ipc, 0, &subject_required, &message, buf); + + if (r / 100 != 2 && r / 10 != 57) { + scr_printf("%s\n", buf); + return (1); + } + + /* Error code 570 is special. It means that we CAN enter a message + * in this room, but a recipient needs to be specified. + */ + need_recp = 0; + if (r / 10 == 57) { + need_recp = 1; + } + + /* If the user is a dumbass, tell them how to type. */ + if ((userflags & US_EXPERT) == 0) { + scr_printf("Entering message. Word wrap will give you soft linebreaks. Pressing the\n"); + scr_printf("'enter' key will give you a hard linebreak and an indent. Press 'enter' twice\n"); + scr_printf("when finished.\n"); + } + + /* Handle the selection of a recipient, if necessary. */ + strcpy(buf, ""); + if (need_recp == 1) { + if (axlevel >= AxProbU) { + if (is_reply) { + strcpy(buf, reply_to); + } + else { + newprompt("Enter recipient: ", buf, SIZ - 100); + if (IsEmptyStr(buf)) { + return (1); + } + } + } + else { + strcpy(buf, "sysop"); + } + } + strcpy(message.recipient, buf); + + if (room_flags & QR_ANONOPT) { + scr_printf("Anonymous (Y/N)? "); + if (yesno() == 1) + message.anonymous = 1; + } + + /* If it's mail, we've got to check the validity of the recipient... */ + if (!IsEmptyStr(message.recipient)) { + r = CtdlIPCPostMessage(ipc, 0, &subject_required, &message, buf); + if (r / 100 != 2) { + scr_printf("%s\n", buf); + return (1); + } + } + + /* Learn the number of the newest message in in the room, so we can + * tell upon saving whether someone else has posted too. + */ + num_msgs = 0; + r = CtdlIPCGetMessages(ipc, LastMessages, 1, NULL, &msgarr, buf); + if (r / 100 != 1) { + scr_printf("%s\n", buf); + } + else { + for (num_msgs = 0; msgarr[num_msgs]; num_msgs++); + } + + /* Now compose the message... */ + if (client_make_message(ipc, temp, message.recipient, message.anonymous, 0, c, message.subject, subject_required) != 0) { + if (msgarr) + free(msgarr); + return (2); + } + + /* Reopen the temp file that was created, so we can send it */ + fp = fopen(temp, "r"); + + if (!fp || !(message.text = load_message_from_file(fp))) { + scr_printf("*** Internal error while trying to save message!\n" "%s: %s\n", temp, strerror(errno)); + unlink(temp); + return (errno); + } + + if (fp) + fclose(fp); + + /* Break lines that are >1024 characters, otherwise the server + * will truncate them. + */ + break_big_lines(message.text); + + /* Transmit message to the server */ + r = CtdlIPCPostMessage(ipc, 1, NULL, &message, buf); + if (r / 100 != 4) { + scr_printf("%s\n", buf); + return (1); + } + + /* Yes, unlink it now, so it doesn't stick around if we crash */ + unlink(temp); + + if (num_msgs >= 1) + highmsg = msgarr[num_msgs - 1]; + + if (msgarr) + free(msgarr); + msgarr = NULL; + r = CtdlIPCGetMessages(ipc, NewMessages, 0, NULL, &msgarr, buf); + if (r / 100 != 1) { + scr_printf("%s\n", buf); + } + else { + for (num_msgs = 0; msgarr[num_msgs]; num_msgs++); + } + + /* get new highest message number in room to set lrp for goto... */ + maxmsgnum = msgarr[num_msgs - 1]; + + /* now see if anyone else has posted in here */ + b = (-1); + for (a = 0; a < num_msgs; ++a) { + if (msgarr[a] > highmsg) { + ++b; + } + } + if (msgarr) { + free(msgarr); + } + msgarr = NULL; + + /* In the Mail> room, this algorithm always counts one message + * higher than in public rooms, so we decrement it by one. + */ + if (need_recp) { + --b; + } + + if (b == 1) { + scr_printf("*** 1 additional message has been entered in this room by another user.\n"); + } + else if (b > 1) { + scr_printf("*** %d additional messages have been entered in this room by other users.\n", b); + } + free(message.text); + + return (0); +} + + +/* + * Do editing on a quoted file + */ +void process_quote(void) { + FILE *qfile, *tfile; + char buf[128]; + int line, qstart, qend; + + // Unlink the second temp file as soon as it's opened, so it'll get + // deleted even if the program dies + qfile = fopen(temp2, "r"); + unlink(temp2); + + /* Display the quotable text with line numbers added */ + line = 0; + if (fgets(buf, 128, qfile) == NULL) { + /* we're skipping a line here */ + } + while (fgets(buf, 128, qfile) != NULL) { + scr_printf("%3d %s", ++line, buf); + } + + qstart = intprompt("Begin quoting at", 1, 1, line); + qend = intprompt(" End quoting at", line, qstart, line); + + rewind(qfile); + line = 0; + if (fgets(buf, 128, qfile) == NULL) { + /* we're skipping a line here */ + } + tfile = fopen(temp, "w"); + while (fgets(buf, 128, qfile) != NULL) { + if ((++line >= qstart) && (line <= qend)) { + fprintf(tfile, " >%s", buf); + } + } + fprintf(tfile, " \n"); + fclose(qfile); + fclose(tfile); + chmod(temp, 0666); +} + + +/* + * List the URLs which were embedded in the previous message + */ +void list_urls(CtdlIPC * ipc) { + int i; + char cmd[SIZ]; + int rv; + + if (num_urls == 0) { + scr_printf("There were no URLs in the previous message.\n\n"); + return; + } + + for (i = 0; i < num_urls; ++i) { + scr_printf("%3d %s\n", i + 1, urls[i]); + } + + if ((i = num_urls) != 1) { + i = intprompt("Display which one", 1, 1, num_urls); + } + + snprintf(cmd, sizeof cmd, rc_url_cmd, urls[i - 1]); + rv = system(cmd); + scr_printf("\n"); +} + + +/* + * Run image viewer in background + */ +int do_image_view(const char *filename) { + char cmd[SIZ]; + pid_t childpid; + + snprintf(cmd, sizeof cmd, imagecmd, filename); + childpid = fork(); + if (childpid < 0) { + unlink(filename); + return childpid; + } + + if (childpid == 0) { + int retcode; + pid_t grandchildpid; + + grandchildpid = fork(); + if (grandchildpid < 0) { + return grandchildpid; + } + + if (grandchildpid == 0) { + int nullfd; + int outfd = -1; + int errfd = -1; + + nullfd = open("/dev/null", O_WRONLY); + if (nullfd > -1) { + dup2(1, outfd); + dup2(2, errfd); + dup2(nullfd, 1); + dup2(nullfd, 2); + } + retcode = system(cmd); + if (nullfd > -1) { + dup2(outfd, 1); + dup2(errfd, 2); + close(nullfd); + } + unlink(filename); + exit(retcode); + } + + if (grandchildpid > 0) { + exit(0); + } + } + + if (childpid > 0) { + int retcode; + + waitpid(childpid, &retcode, 0); + return retcode; + } + + return -1; +} + + +/* + * View an image attached to a message + */ +void image_view(CtdlIPC * ipc, unsigned long msg) { + struct parts *ptr = last_message_parts; + char part[SIZ]; + int found = 0; + + /* Run through available parts */ + for (ptr = last_message_parts; ptr; ptr = ptr->next) { + if ((!strcasecmp(ptr->disposition, "attachment") + || !strcasecmp(ptr->disposition, "inline")) + && !strncmp(ptr->mimetype, "image/", 6)) { + found++; + if (found == 1) { + strcpy(part, ptr->number); + } + } + } + + while (found > 0) { + if (found > 1) + strprompt("View which part (0 when done)", part, SIZ - 1); + found = -found; + for (ptr = last_message_parts; ptr; ptr = ptr->next) { + if ((!strcasecmp(ptr->disposition, "attachment") + || !strcasecmp(ptr->disposition, "inline")) + && !strncmp(ptr->mimetype, "image/", 6) + && !strcasecmp(ptr->number, part)) { + char tmp[PATH_MAX]; + char buf[SIZ]; + void *file = NULL; /* The downloaded file */ + int r; + + /* view image */ + found = -found; + r = CtdlIPCAttachmentDownload(ipc, msg, ptr->number, &file, progress, buf); + if (r / 100 != 2) { + scr_printf("%s\n", buf); + } + else { + size_t len; + + len = (size_t) extract_long(buf, 0); + progress(ipc, len, len); + scr_flush(); + CtdlMakeTempFileName(tmp, sizeof tmp); + strcat(tmp, ptr->filename); + save_buffer(file, len, tmp); + free(file); + do_image_view(tmp); + } + break; + } + } + if (found == 1) + break; + } +} + + +/* + * Read the messages in the current room + */ +void readmsgs(CtdlIPC * ipc, enum MessageList c, // see listing in citadel_ipc.h + enum MessageDirection rdir, // 1=Forward (-1)=Reverse + int q // Number of msgs to read (if c==3) + ) { + int a, e, f, g, start; + int savedpos; + int hold_sw = 0; + char arcflag = 0; + char quotflag = 0; + int hold_color = 0; + char prtfile[PATH_MAX]; + char pagin; + char cmd[SIZ]; + char targ[ROOMNAMELEN]; + char filename[PATH_MAX]; + char save_to[PATH_MAX]; + void *attachment = NULL; /* Downloaded attachment */ + FILE *dest = NULL; /* Alternate destination other than screen */ + int r; /* IPC response code */ + static int att_seq = 0; /* Attachment download sequence number */ + int rv = 0; /* silence the stupid warn_unused_result warnings */ + + CtdlMakeTempFileName(prtfile, sizeof prtfile); + + if (msg_arr) { + free(msg_arr); + msg_arr = NULL; + } + r = CtdlIPCGetMessages(ipc, c, q, NULL, &msg_arr, cmd); + if (r / 100 != 1) { + scr_printf("%s\n", cmd); + } + else { + for (num_msgs = 0; msg_arr[num_msgs]; num_msgs++); + } + + if (num_msgs == 0) { + if (c == LastMessages) { + return; + } + scr_printf("*** There are no "); + if (c == NewMessages) + scr_printf("new "); + if (c == OldMessages) + scr_printf("old "); + scr_printf("messages in this room.\n"); + return; + } + + /* this loop cycles through each message... */ + start = ((rdir == 1) ? 0 : (num_msgs - 1)); + for (a = start; ((a < num_msgs) && (a >= 0)); a = a + rdir) { + while (msg_arr[a] == 0L) { + a = a + rdir; + if ((a == num_msgs) || (a == (-1))) + return; + } + + RAGAIN:pagin = ((arcflag == 0) + && (quotflag == 0) + && (userflags & US_PAGINATOR)) ? 1 : 0; + + /* If we're doing a quote, set the screenwidth to 72 */ + if (quotflag) { + hold_sw = screenwidth; + screenwidth = 72; + } + + /* If printing or archiving, set the screenwidth to 80 */ + if (arcflag) { + hold_sw = screenwidth; + screenwidth = 80; + } + + /* clear parts list */ + free_parts(last_message_parts); + last_message_parts = NULL; + + /* now read the message... */ + e = read_message(ipc, msg_arr[a], pagin, dest); + + /* ...and set the screenwidth back if we have to */ + if ((quotflag) || (arcflag)) { + screenwidth = hold_sw; + } + RMSGREAD: + highest_msg_read = msg_arr[a]; + if (quotflag) { + fclose(dest); + dest = NULL; + quotflag = 0; + enable_color = hold_color; + process_quote(); + e = 'r'; + goto DONE_QUOTING; + } + if (arcflag) { + fclose(dest); + dest = NULL; + arcflag = 0; + enable_color = hold_color; + f = fork(); + if (f == 0) { + if (freopen(prtfile, "r", stdin) == NULL) { + /* we probably should handle the error condition here */ + } + stty_ctdl(SB_RESTORE); + ka_system(printcmd); + stty_ctdl(SB_NO_INTR); + unlink(prtfile); + exit(0); + } + if (f > 0) + do { + g = wait(NULL); + } while ((g != f) && (g >= 0)); + scr_printf("Message printed.\n"); + } + if (e == SIGQUIT) + return; + if (((userflags & US_NOPROMPT) || (e == SIGINT)) + && (((room_flags & QR_MAILBOX) == 0) + || (rc_force_mail_prompts == 0))) { + e = 'n'; + } + else { + color(DIM_WHITE); + scr_printf("("); + color(BRIGHT_WHITE); + scr_printf("%d", num_msgs - a - 1); + color(DIM_WHITE); + scr_printf(") "); + + keyopt("ack gain eply replyuoted ext top "); + if (rc_url_cmd[0] && num_urls) + keyopt("RLview "); + if (has_images > 0 && !IsEmptyStr(imagecmd)) + keyopt("mages "); + keyopt("help -> "); + + do { + e = (inkey() & 127); + e = tolower(e); + +/* return key same as */ if (e == 10) + e = 'n'; + +/* space key same as */ if (e == 32) + e = 'n'; + +/* del/move for aides only */ + if ((!is_room_aide) + && ((room_flags & QR_MAILBOX) == 0) + && ((room_flags2 & QR2_COLLABDEL) == 0) + ) { + if ((e == 'd') || (e == 'm')) + e = 0; + } + +/* print only if available */ + if ((e == 'p') && (IsEmptyStr(printcmd))) + e = 0; + +/* can't file if not allowed */ + if ((e == 'f') + && (rc_allow_attachments == 0)) + e = 0; + +/* link only if browser avail*/ + if ((e == 'u') + && (IsEmptyStr(rc_url_cmd))) + e = 0; + if ((e == 'i') + && (IsEmptyStr(imagecmd) || !has_images)) + e = 0; + } while ((e != 'a') && (e != 'n') && (e != 's') + && (e != 'd') && (e != 'm') && (e != 'p') + && (e != 'q') && (e != 'b') && (e != 'h') + && (e != 'r') && (e != 'f') && (e != '?') + && (e != 'u') && (e != 'c') && (e != 'y') + && (e != 'i') && (e != 'o')); + switch (e) { + case 's': + scr_printf("Stop"); + break; + case 'a': + scr_printf("Again"); + break; + case 'd': + scr_printf("Delete"); + break; + case 'm': + scr_printf("Move"); + break; + case 'c': + scr_printf("Copy"); + break; + case 'n': + scr_printf("Next"); + break; + case 'p': + scr_printf("Print"); + break; + case 'q': + scr_printf("reply Quoted"); + break; + case 'b': + scr_printf("Back"); + break; + case 'h': + scr_printf("Header"); + break; + case 'r': + scr_printf("Reply"); + break; + case 'o': + scr_printf("Open attachments"); + break; + case 'f': + scr_printf("File"); + break; + case 'u': + scr_printf("URL's"); + break; + case 'y': + scr_printf("mY next"); + break; + case 'i': + break; + case '?': + scr_printf("? "); + break; + } + if (userflags & US_DISAPPEAR || e == 'i') + scr_printf("\r%79s\r", ""); + else + scr_printf("\n"); + } + DONE_QUOTING:switch (e) { + case '?': + scr_printf("Options available here:\n" + " ? Help (prints this message)\n" + " S Stop reading immediately\n" + " A Again (repeats last message)\n" + " N Next (continue with next message)\n" + " Y My Next (continue with next message you authored)\n" + " B Back (go back to previous message)\n"); + if ((is_room_aide) || (room_flags & QR_MAILBOX) || (room_flags2 & QR2_COLLABDEL)) { + scr_printf(" D Delete this message\n" " M Move message to another room\n"); + } + scr_printf(" C Copy message to another room\n"); + if (!IsEmptyStr(printcmd)) + scr_printf(" P Print this message\n"); + scr_printf(" Q Reply to this message, quoting portions of it\n" + " H Headers (display message headers only)\n"); + if (is_mail) + scr_printf(" R Reply to this message\n"); + if (rc_allow_attachments) { + scr_printf(" O (Open attachments)\n"); + scr_printf(" F (save attachments to a File)\n"); + } + if (!IsEmptyStr(rc_url_cmd)) + scr_printf(" U (list URL's for display)\n"); + if (!IsEmptyStr(imagecmd) && has_images > 0) + scr_printf(" I Image viewer\n"); + scr_printf("\n"); + goto RMSGREAD; + case 'p': + scr_flush(); + dest = fopen(prtfile, "w"); + arcflag = 1; + hold_color = enable_color; + enable_color = 0; + goto RAGAIN; + case 'q': + scr_flush(); + dest = fopen(temp2, "w"); + quotflag = 1; + hold_color = enable_color; + enable_color = 0; + goto RAGAIN; + case 's': + return; + case 'a': + goto RAGAIN; + case 'b': + a = a - (rdir * 2); + break; + case 'm': + case 'c': + newprompt("Enter target room: ", targ, ROOMNAMELEN - 1); + if (!IsEmptyStr(targ)) { + r = CtdlIPCMoveMessage(ipc, (e == 'c' ? 1 : 0), msg_arr[a], targ, cmd); + scr_printf("%s\n", cmd); + if (r / 100 == 2) + msg_arr[a] = 0L; + } + else { + goto RMSGREAD; + } + if (r / 100 != 2) /* r will be init'ed, FIXME */ + goto RMSGREAD; /* the logic here sucks */ + break; + case 'o': + case 'f': + newprompt("Which section? ", filename, ((sizeof filename) - 1)); + r = CtdlIPCAttachmentDownload(ipc, msg_arr[a], filename, &attachment, progress, cmd); + if (r / 100 != 2) { + scr_printf("%s\n", cmd); + } + else { + extract_token(filename, cmd, 2, '|', sizeof filename); + /* + * Part 1 won't have a filename; use the + * subject of the message instead. IO + */ + if (IsEmptyStr(filename)) { + strcpy(filename, reply_subject); + } + if (e == 'o') { /* open attachment */ + mkdir(tempdir, 0700); + snprintf(save_to, sizeof save_to, "%s/%04x.%s", tempdir, ++att_seq, filename); + save_buffer(attachment, extract_unsigned_long(cmd, 0), save_to); + snprintf(cmd, sizeof cmd, rc_open_cmd, save_to); + rv = system(cmd); + if (rv != 0) + scr_printf("failed to save %s Reason %d\n", cmd, rv); + } + else { /* save attachment to disk */ + destination_directory(save_to, filename); + save_buffer(attachment, extract_unsigned_long(cmd, 0), save_to); + } + } + if (attachment) { + free(attachment); + attachment = NULL; + } + goto RMSGREAD; + case 'd': + scr_printf("*** Delete this message? "); + if (yesno() == 1) { + r = CtdlIPCDeleteMessage(ipc, msg_arr[a], cmd); + scr_printf("%s\n", cmd); + if (r / 100 == 2) + msg_arr[a] = 0L; + } + else { + goto RMSGREAD; + } + break; + case 'h': + read_message(ipc, msg_arr[a], READ_HEADER, NULL); + goto RMSGREAD; + case 'r': + savedpos = num_msgs; + entmsg(ipc, 1, ((userflags & US_EXTEDIT) ? 2 : 0), 0); + num_msgs = savedpos; + goto RMSGREAD; + case 'u': + list_urls(ipc); + goto RMSGREAD; + case 'i': + image_view(ipc, msg_arr[a]); + goto RMSGREAD; + case 'y': + { /* hack hack hack */ + /* find the next message by me, stay here if we find nothing */ + int finda; + int lasta = a; + for (finda = (a + rdir); ((finda < num_msgs) && (finda >= 0)); finda += rdir) { + /* This is repetitively dumb, but that's what computers are for. + We have to load up messages until we find one by us */ + char buf[SIZ]; + int founda = 0; + struct ctdlipcmessage *msg = NULL; + + /* read the header so we can get 'from=' */ + r = CtdlIPCGetSingleMessage(ipc, msg_arr[finda], 1, 0, &msg, buf); + if (!strncasecmp(msg->author, fullname, sizeof(fullname))) { + a = lasta; /* meesa current */ + founda = 1; + } + + free(msg); + + if (founda) + break; /* for */ + lasta = finda; /* keep one behind or we skip on the reentrance to the for */ + } /* for */ + } /* case 'y' */ + } /* switch */ + } /* end for loop */ +} /* end read routine */ + + +/* + * View and edit a system message + */ +void edit_system_message(CtdlIPC * ipc, char *which_message) { + char desc[SIZ]; + char read_cmd[SIZ]; + char write_cmd[SIZ]; + + snprintf(desc, sizeof desc, "system message '%s'", which_message); + snprintf(read_cmd, sizeof read_cmd, "MESG %s", which_message); + snprintf(write_cmd, sizeof write_cmd, "EMSG %s", which_message); + do_edit(ipc, desc, read_cmd, "NOOP", write_cmd); +} + + +/* + * Loads the contents of a file into memory. Caller must free the allocated memory. + */ +char *load_message_from_file(FILE * src) { + size_t i; + size_t got = 0; + char *dest = NULL; + + fseek(src, 0, SEEK_END); + i = ftell(src); + rewind(src); + + dest = (char *) calloc(1, i + 1); + if (!dest) + return NULL; + + while (got < i) { + size_t g; + + g = fread(dest + got, 1, i - got, src); + got += g; + if (g < i - got) { + /* Interrupted system call, keep going */ + if (errno == EINTR) + continue; + /* At this point we have either EOF or error */ + i = got; + break; + } + dest[i] = 0; + } + + return dest; +} diff --git a/textclient/rooms.c~ b/textclient/rooms.c~ new file mode 100644 index 000000000..be579c1d7 --- /dev/null +++ b/textclient/rooms.c~ @@ -0,0 +1,1313 @@ +// Client-side functions which perform room operations +// +// Copyright (c) 1987-2022 by the citadel.org team +// +// This program is open source software. Use, duplication, and/or +// disclosure are subject to the GNU General Purpose License version 3. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +#include "textclient.h" + +#define IFNEXPERT if ((userflags&US_EXPERT)==0) + + +void stty_ctdl(int cmd); +void dotgoto(CtdlIPC * ipc, char *towhere, int display_name, int fromungoto); +void progress(CtdlIPC * ipc, unsigned long curr, unsigned long cmax); +int pattern(char *search, char *patn); +int file_checksum(char *filename); +int nukedir(char *dirname); + +extern unsigned room_flags; +extern char room_name[]; +extern char temp[]; +extern char tempdir[]; +extern int editor_pid; +extern int screenwidth; +extern int screenheight; +extern char fullname[]; +extern char sigcaught; +extern char floor_mode; +extern char curr_floor; + + +extern int ugnum; +extern long uglsn; +extern char *uglist[]; +extern long uglistlsn[]; +extern int uglistsize; + +extern char floorlist[128][SIZ]; + + +void load_floorlist(CtdlIPC * ipc) +{ + int a; + char buf[SIZ]; + char *listing = NULL; + int r; /* IPC response code */ + + for (a = 0; a < 128; ++a) + floorlist[a][0] = 0; + + r = CtdlIPCFloorListing(ipc, &listing, buf); + if (r / 100 != 1) { + strcpy(floorlist[0], "Main Floor"); + return; + } + while (*listing && !IsEmptyStr(listing)) { + extract_token(buf, listing, 0, '\n', sizeof buf); + remove_token(listing, 0, '\n'); + extract_token(floorlist[extract_int(buf, 0)], buf, 1, '|', SIZ); + } + free(listing); +} + + +void room_tree_list(struct ctdlroomlisting *rp) +{ + static int c = 0; + char rmname[ROOMNAMELEN]; + int f; + + if (rp == NULL) { + c = 1; + return; + } + + if (rp->lnext != NULL) { + room_tree_list(rp->lnext); + } + + if (sigcaught == 0) { + strcpy(rmname, rp->rlname); + f = rp->rlflags; + if ((c + strlen(rmname) + 4) > screenwidth) { + + /* line break, check the paginator */ + scr_printf("\n"); + c = 1; + } + if (f & QR_MAILBOX) { + color(BRIGHT_YELLOW); + } else if (f & QR_PRIVATE) { + color(BRIGHT_RED); + } else { + color(DIM_WHITE); + } + scr_printf("%s", rmname); + if (f & QR_DIRECTORY) { + scr_printf("] "); + } else { + scr_printf("> "); + } + c = c + strlen(rmname) + 3; + } + + if (rp->rnext != NULL) { + room_tree_list(rp->rnext); + } + + free(rp); +} + + +/* + * Room ordering stuff (compare first by floor, then by order) + */ +int rordercmp(struct ctdlroomlisting *r1, struct ctdlroomlisting *r2) +{ + if ((r1 == NULL) && (r2 == NULL)) + return (0); + if (r1 == NULL) + return (-1); + if (r2 == NULL) + return (1); + if (r1->rlfloor < r2->rlfloor) + return (-1); + if (r1->rlfloor > r2->rlfloor) + return (1); + if (r1->rlorder < r2->rlorder) + return (-1); + if (r1->rlorder > r2->rlorder) + return (1); + return (0); +} + + +/* + * Common code for all room listings + */ +static void listrms(struct march *listing, int new_only, int floor_only, unsigned int flags, char *match) +{ + struct march *mptr; + struct ctdlroomlisting *rl = NULL; + struct ctdlroomlisting *rp; + struct ctdlroomlisting *rs; + int list_it; + + for (mptr = listing; mptr != NULL; mptr = mptr->next) { + list_it = 1; + + if ((new_only == LISTRMS_NEW_ONLY) + && ((mptr->march_access & UA_HASNEWMSGS) == 0)) + list_it = 0; + + if ((new_only == LISTRMS_OLD_ONLY) + && ((mptr->march_access & UA_HASNEWMSGS) != 0)) + list_it = 0; + + if ((floor_only >= 0) + && (mptr->march_floor != floor_only)) + list_it = 0; + + if (flags && (mptr->march_flags & flags) == 0) + list_it = 0; + + if (match && (pattern(mptr->march_name, match) == -1)) + list_it = 0; + + if (list_it) { + rp = malloc(sizeof(struct ctdlroomlisting)); + strncpy(rp->rlname, mptr->march_name, ROOMNAMELEN); + rp->rlflags = mptr->march_flags; + rp->rlfloor = mptr->march_floor; + rp->rlorder = mptr->march_order; + rp->lnext = NULL; + rp->rnext = NULL; + + rs = rl; + if (rl == NULL) { + rl = rp; + } else { + while (rp != NULL) { + if (rordercmp(rp, rs) < 0) { + if (rs->lnext == NULL) { + rs->lnext = rp; + rp = NULL; + } else { + rs = rs->lnext; + } + } else { + if (rs->rnext == NULL) { + rs->rnext = rp; + rp = NULL; + } else { + rs = rs->rnext; + } + } + } + } + } + } + + room_tree_list(NULL); + room_tree_list(rl); + color(DIM_WHITE); +} + + +void list_other_floors(void) +{ + int a, c; + + c = 1; + for (a = 0; a < 128; ++a) { + if ((strlen(floorlist[a]) > 0) && (a != curr_floor)) { + if ((c + strlen(floorlist[a]) + 4) > screenwidth) { + scr_printf("\n"); + c = 1; + } + scr_printf("%s: ", floorlist[a]); + c = c + strlen(floorlist[a]) + 3; + } + } +} + + +/* + * List known rooms. kn_floor_mode should be set to 0 for a 'flat' listing, + * 1 to list rooms on the current floor, or 2 to list rooms on all floors. + */ +void knrooms(CtdlIPC * ipc, int kn_floor_mode) +{ + int a; + struct march *listing = NULL; + struct march *mptr; + int r; /* IPC response code */ + char buf[SIZ]; + + + /* Ask the server for a room list */ + r = CtdlIPCKnownRooms(ipc, SubscribedRooms, (-1), &listing, buf); + if (r / 100 != 1) { + listing = NULL; + } + + load_floorlist(ipc); + + + if (kn_floor_mode == 0) { + color(BRIGHT_CYAN); + scr_printf("\n Rooms with unread messages:\n"); + listrms(listing, LISTRMS_NEW_ONLY, -1, 0, NULL); + color(BRIGHT_CYAN); + scr_printf("\n\n No unseen messages in:\n"); + listrms(listing, LISTRMS_OLD_ONLY, -1, 0, NULL); + scr_printf("\n"); + } + + if (kn_floor_mode == 1) { + color(BRIGHT_CYAN); + scr_printf("\n Rooms with unread messages on %s:\n", floorlist[(int) curr_floor]); + listrms(listing, LISTRMS_NEW_ONLY, curr_floor, 0, NULL); + color(BRIGHT_CYAN); + scr_printf("\n\n Rooms with no new messages on %s:\n", floorlist[(int) curr_floor]); + listrms(listing, LISTRMS_OLD_ONLY, curr_floor, 0, NULL); + color(BRIGHT_CYAN); + scr_printf("\n\n Other floors:\n"); + list_other_floors(); + scr_printf("\n"); + } + + if (kn_floor_mode == 2) { + for (a = 0; a < 128; ++a) { + if (floorlist[a][0] != 0) { + color(BRIGHT_CYAN); + scr_printf("\n Rooms on %s:\n", floorlist[a]); + listrms(listing, LISTRMS_ALL, a, 0, NULL); + scr_printf("\n"); + } + } + } + + /* Free the room list */ + while (listing) { + mptr = listing->next; + free(listing); + listing = mptr; + }; + + color(DIM_WHITE); +} + + +void listzrooms(CtdlIPC * ipc) +{ /* list public forgotten rooms */ + struct march *listing = NULL; + struct march *mptr; + int r; /* IPC response code */ + char buf[SIZ]; + + + /* Ask the server for a room list */ + r = CtdlIPCKnownRooms(ipc, UnsubscribedRooms, (-1), &listing, buf); + if (r / 100 != 1) { + listing = NULL; + } + + color(BRIGHT_CYAN); + scr_printf("\n Forgotten public rooms:\n"); + listrms(listing, LISTRMS_ALL, -1, 0, NULL); + scr_printf("\n"); + + /* Free the room list */ + while (listing) { + mptr = listing->next; + free(listing); + listing = mptr; + }; + + color(DIM_WHITE); +} + +void dotknown(CtdlIPC * ipc, int what, char *match) +{ /* list rooms according to attribute */ + struct march *listing = NULL; + struct march *mptr; + int r; /* IPC response code */ + char buf[SIZ]; + + /* Ask the server for a room list */ + r = CtdlIPCKnownRooms(ipc, AllAccessibleRooms, (-1), &listing, buf); + if (r / 100 != 1) { + listing = NULL; + } + + color(BRIGHT_CYAN); + + switch (what) { + case 0: + scr_printf("\n Anonymous rooms:\n"); + listrms(listing, LISTRMS_ALL, -1, QR_ANONONLY | QR_ANONOPT, NULL); + scr_printf("\n"); + break; + case 1: + scr_printf("\n Directory rooms:\n"); + listrms(listing, LISTRMS_ALL, -1, QR_DIRECTORY, NULL); + scr_printf("\n"); + break; + case 2: + scr_printf("\n Matching \"%s\" rooms:\n", match); + listrms(listing, LISTRMS_ALL, -1, 0, match); + scr_printf("\n"); + break; + case 3: + scr_printf("\n Preferred only rooms:\n"); + listrms(listing, LISTRMS_ALL, -1, QR_PREFONLY, NULL); + scr_printf("\n"); + break; + case 4: + scr_printf("\n Private rooms:\n"); + listrms(listing, LISTRMS_ALL, -1, QR_PRIVATE, NULL); + scr_printf("\n"); + break; + case 5: + scr_printf("\n Read only rooms:\n"); + listrms(listing, LISTRMS_ALL, -1, QR_READONLY, NULL); + scr_printf("\n"); + break; + } + + /* Free the room list */ + while (listing) { + mptr = listing->next; + free(listing); + listing = mptr; + }; + + color(DIM_WHITE); +} + + +int set_room_attr(CtdlIPC * ipc, unsigned int ibuf, char *prompt, unsigned int sbit) +{ + int a; + + a = boolprompt(prompt, (ibuf & sbit)); + ibuf = (ibuf | sbit); + if (!a) { + ibuf = (ibuf ^ sbit); + } + return (ibuf); +} + + + +/* + * Select a floor (used in several commands) + * The supplied argument is the 'default' floor number. + * This function returns the selected floor number. + */ +int select_floor(CtdlIPC * ipc, int rfloor) +{ + int a, newfloor; + char floorstr[SIZ]; + + if (floor_mode == 1) { + if (floorlist[(int) curr_floor][0] == 0) { + load_floorlist(ipc); + } + + do { + newfloor = (-1); + strncpy(floorstr, floorlist[rfloor], sizeof floorstr); + strprompt("Which floor", floorstr, 255); + for (a = 0; a < 128; ++a) { + if (!strcasecmp(floorstr, &floorlist[a][0])) + newfloor = a; + if ((newfloor < 0) + && (!strncasecmp(floorstr, &floorlist[a][0], strlen(floorstr)))) + newfloor = a; + if ((newfloor < 0) + && (pattern(&floorlist[a][0], floorstr) + >= 0)) + newfloor = a; + } + if (newfloor < 0) { + scr_printf("\n One of:\n"); + for (a = 0; a < 128; ++a) { + if (floorlist[a][0] != 0) { + scr_printf("%s\n", &floorlist[a][0]); + } + } + } + } while (newfloor < 0); + return (newfloor); + } + + else { + scr_printf("Floor selection bypassed because you have " "floor mode disabled.\n"); + } + + return (rfloor); +} + + + + +/* + * .ide dit room + */ +void editthisroom(CtdlIPC * ipc) +{ + int rbump = 0; + char room_admin_name[USERNAME_SIZE]; + char buf[SIZ]; + struct ctdlroom *attr = NULL; + struct ExpirePolicy *eptr = NULL; + int r; /* IPC response code */ + + /* Fetch the existing room config */ + r = CtdlIPCGetRoomAttributes(ipc, &attr, buf); + if (r / 100 != 2) { + scr_printf("%s\n", buf); + return; + } + eptr = &(attr->QRep); + + /* Fetch the name of the current room admin */ + r = CtdlIPCGetRoomAide(ipc, buf); + if (r / 100 == 2) { + strncpy(room_admin_name, buf, sizeof room_admin_name); + } else { + strcpy(room_admin_name, ""); + } + if (IsEmptyStr(room_admin_name)) { + strcpy(room_admin_name, "none"); + } + + /* Fetch the expire policy (this will silently fail on old servers, + * resulting in "default" policy) + */ + r = CtdlIPCGetMessageExpirationPolicy(ipc, 0, &eptr, buf); + + /* Now interact with the user. */ + + strprompt("Room name", attr->QRname, ROOMNAMELEN - 1); + attr->QRfloor = select_floor(ipc, attr->QRfloor); + attr->QRflags = set_room_attr(ipc, attr->QRflags, "Private room", QR_PRIVATE); + if (attr->QRflags & QR_PRIVATE) { + attr->QRflags = set_room_attr(ipc, attr->QRflags, + "Hidden room (accessible to anyone who knows the room name)", QR_GUESSNAME); + } + + /* if it's public, clear the privacy classes */ + if ((attr->QRflags & QR_PRIVATE) == 0) { + if (attr->QRflags & QR_GUESSNAME) { + attr->QRflags = attr->QRflags - QR_GUESSNAME; + } + if (attr->QRflags & QR_PASSWORDED) { + attr->QRflags = attr->QRflags - QR_PASSWORDED; + } + } + + /* if it's private, choose the privacy classes */ + if ((attr->QRflags & QR_PRIVATE) + && ((attr->QRflags & QR_GUESSNAME) == 0)) { + attr->QRflags = set_room_attr(ipc, attr->QRflags, "Accessible by entering a password", QR_PASSWORDED); + } + if ((attr->QRflags & QR_PRIVATE) + && ((attr->QRflags & QR_PASSWORDED) == QR_PASSWORDED)) { + strprompt("Room password", attr->QRpasswd, 9); + } + + if ((attr->QRflags & QR_PRIVATE) == QR_PRIVATE) { + rbump = boolprompt("Cause current users to forget room", 0); + } + + attr->QRflags = set_room_attr(ipc, attr->QRflags, "Preferred users only", QR_PREFONLY); + attr->QRflags = set_room_attr(ipc, attr->QRflags, "Read-only room", QR_READONLY); + attr->QRflags2 = set_room_attr(ipc, attr->QRflags2, "Allow message deletion by anyone who can post", QR2_COLLABDEL); + attr->QRflags = set_room_attr(ipc, attr->QRflags, "Permanent room", QR_PERMANENT); + attr->QRflags2 = set_room_attr(ipc, attr->QRflags2, "Subject Required (Force users to specify a message subject)", QR2_SUBJECTREQ); + attr->QRflags = set_room_attr(ipc, attr->QRflags, "Directory room", QR_DIRECTORY); + if (attr->QRflags & QR_DIRECTORY) { + strprompt("Directory name", attr->QRdirname, 14); + attr->QRflags = set_room_attr(ipc, attr->QRflags, "Uploading allowed", QR_UPLOAD); + attr->QRflags = set_room_attr(ipc, attr->QRflags, "Downloading allowed", QR_DOWNLOAD); + attr->QRflags = set_room_attr(ipc, attr->QRflags, "Visible directory", QR_VISDIR); + } + attr->QRflags2 = set_room_attr(ipc, attr->QRflags2, "Self-service list subscribe/unsubscribe", QR2_SELFLIST); + attr->QRflags2 = set_room_attr(ipc, attr->QRflags2, "Allow non-subscribers to mail to this room", QR2_SMTP_PUBLIC); + attr->QRflags2 = set_room_attr(ipc, attr->QRflags2, "Moderated mailing list", QR2_MODERATED); + attr->QRflags = set_room_attr(ipc, attr->QRflags, "Automatically make all messages anonymous", QR_ANONONLY); + if ((attr->QRflags & QR_ANONONLY) == 0) { + attr->QRflags = set_room_attr(ipc, attr->QRflags, "Ask users whether to make messages anonymous", QR_ANONOPT); + } + attr->QRorder = intprompt("Listing order", attr->QRorder, 0, 127); + + /* Ask about the room admin */ + do { + strprompt("Room admin (or 'none')", room_admin_name, 29); + if (!strcasecmp(room_admin_name, "none")) { + strcpy(room_admin_name, ""); + break; + } + else { + r = CtdlIPCQueryUsername(ipc, room_admin_name, buf); + if (r / 100 != 2) { + scr_printf("%s\n", buf); + } + } + } while (r / 100 != 2); + + /* Angels and demons dancing in my head... */ + do { + snprintf(buf, sizeof buf, "%d", attr->QRep.expire_mode); + strprompt("Message expire policy (? for list)", buf, 1); + if (buf[0] == '?') { + scr_printf("\n" + "0. Use the default for this floor\n" + "1. Never automatically expire messages\n" + "2. Expire by message count\n" "3. Expire by message age\n"); + } + } while ((buf[0] < 48) || (buf[0] > 51)); + attr->QRep.expire_mode = buf[0] - 48; + + /* ...lunatics and monsters underneath my bed */ + if (attr->QRep.expire_mode == 2) { + snprintf(buf, sizeof buf, "%d", attr->QRep.expire_value); + strprompt("Keep how many messages online?", buf, 10); + attr->QRep.expire_value = atol(buf); + } + + if (attr->QRep.expire_mode == 3) { + snprintf(buf, sizeof buf, "%d", attr->QRep.expire_value); + strprompt("Keep messages for how many days?", buf, 10); + attr->QRep.expire_value = atol(buf); + } + + /* Give 'em a chance to change their minds */ + scr_printf("Save changes (y/n)? "); + + if (yesno() == 1) { + r = CtdlIPCSetRoomAide(ipc, room_admin_name, buf); + if (r / 100 != 2) { + scr_printf("%s\n", buf); + } + + r = CtdlIPCSetMessageExpirationPolicy(ipc, 0, eptr, buf); + if (r / 100 != 2) { + scr_printf("%s\n", buf); + } + + r = CtdlIPCSetRoomAttributes(ipc, rbump, attr, buf); + scr_printf("%s\n", buf); + strncpy(buf, attr->QRname, ROOMNAMELEN); + free(attr); + if (r / 100 == 2) { + dotgoto(ipc, buf, 2, 0); + } + } + else { + free(attr); + } +} + + +/* + * un-goto the previous room, or a specified room + */ +void dotungoto(CtdlIPC * ipc, char *towhere) { + // Find this 'towhere' room in the list ungoto from this room to + // that at the messagepointer position in that room in our ungoto list. + // I suppose I could be a real dick and just ungoto that many places + // in our list. + int found = -1; + int lp; + char buf[SIZ]; + struct ctdlipcroom *rret = NULL; // ignored + int r; + + if (uglistsize == 0) { + scr_printf("No rooms to ungoto.\n"); + return; + } + if (towhere == NULL) { + scr_printf("Must specify a room to ungoto.\n"); + return; + } + if (IsEmptyStr(towhere)) { + scr_printf("Must specify a room to ungoto.\n"); + return; + } + for (lp = uglistsize - 1; lp >= 0; lp--) { + if (strcasecmp(towhere, uglist[lp]) == 0) { + found = lp; + break; + } + } + if (found == -1) { + scr_printf("Room: %s not in ungoto list.\n", towhere); + return; + } + + r = CtdlIPCGotoRoom(ipc, uglist[found], "", &rret, buf); + if (rret) + free(rret); // ignored + if (r / 100 != 2) { + scr_printf("%s\n", buf); + return; + } + r = CtdlIPCSetLastRead(ipc, uglistlsn[found] ? uglistlsn[found] : 1, buf); + if (r / 100 != 2) { + scr_printf("%s\n", buf); + } + strncpy(buf, uglist[found], sizeof(buf)); + // we queue ungoto information here, because we're not really + // ungotoing, we're really going to a random spot in some arbitrary + // room list. + dotgoto(ipc, buf, 0, 0); +} + + +void ungoto(CtdlIPC * ipc) { + char buf[SIZ]; + struct ctdlipcroom *rret = NULL; // ignored + int r; + + if (uglistsize == 0) { + return; + } + + r = CtdlIPCGotoRoom(ipc, uglist[uglistsize - 1], "", &rret, buf); + if (rret) { + free(rret); // ignored + } + if (r / 100 != 2) { + scr_printf("%s\n", buf); + return; + } + r = CtdlIPCSetLastRead(ipc, uglistlsn[uglistsize - 1] ? uglistlsn[uglistsize - 1] : 1, buf); + if (r / 100 != 2) { + scr_printf("%s\n", buf); + } + strncpy(buf, uglist[uglistsize - 1], sizeof(buf)); + uglistsize--; + free(uglist[uglistsize]); + // Don't queue ungoto info or we end up in a loop + dotgoto(ipc, buf, 0, 1); +} + + +/* + * saves filelen bytes from file at pathname + */ +int save_buffer(void *file, size_t filelen, const char *pathname) { + size_t block = 0; + size_t bytes_written = 0; + FILE *fp; + + fp = fopen(pathname, "w"); + if (!fp) { + scr_printf("Cannot open '%s': %s\n", pathname, strerror(errno)); + return 0; + } + do { + block = fwrite((char *) file + bytes_written, 1, filelen - bytes_written, fp); + bytes_written += block; + } while (errno == EINTR && bytes_written < filelen); + fclose(fp); + + if (bytes_written < filelen) { + scr_printf("Trouble saving '%s': %s\n", pathname, strerror(errno)); + return 0; + } + return 1; +} + + +/* + * Save supplied_filename in dest directory; gets the name only + */ +void destination_directory(char *dest, const char *supplied_filename) { + static char save_dir[SIZ] = { 0 }; + + if (IsEmptyStr(save_dir)) { + if (getenv("HOME") == NULL) { + strcpy(save_dir, "."); + } else { + sprintf(save_dir, "%s/Desktop", getenv("HOME")); + if (access(save_dir, W_OK) != 0) { + sprintf(save_dir, "%s", getenv("HOME")); + if (access(save_dir, W_OK) != 0) { + sprintf(save_dir, "."); + } + } + } + } + + sprintf(dest, "%s/%s", save_dir, supplied_filename); + strprompt("Save as", dest, PATH_MAX); + + /* Remember the directory for next time */ + strcpy(save_dir, dest); + if (strrchr(save_dir, '/') != NULL) { + strcpy(strrchr(save_dir, '/'), ""); + } else { + strcpy(save_dir, "."); + } +} + + +/* + * download() - download a file or files. The argument passed to this + * function determines which protocol to use. + * proto - 0 = paginate, 1 = xmodem, 2 = raw, 3 = ymodem, 4 = zmodem, 5 = save + */ +void download(CtdlIPC * ipc, int proto) { + char buf[SIZ]; + char filename[PATH_MAX]; + char tempname[PATH_MAX]; + char transmit_cmd[SIZ]; + FILE *tpipe = NULL; +/* int broken = 0;*/ + int r; + int rv = 0; + void *file = NULL; /* The downloaded file */ + size_t filelen = 0L; /* The downloaded file length */ + + if ((room_flags & QR_DOWNLOAD) == 0) { + scr_printf("*** You cannot download from this room.\n"); + return; + } + + newprompt("Enter filename: ", filename, PATH_MAX); + + /* Save to local disk, for folks with their own copy of the client */ + if (proto == 5) { + destination_directory(tempname, filename); + r = CtdlIPCFileDownload(ipc, filename, &file, 0, progress, buf); + if (r / 100 != 2) { + scr_printf("%s\n", buf); + return; + } + save_buffer(file, (size_t) extract_long(buf, 0), tempname); + free(file); + return; + } + + r = CtdlIPCFileDownload(ipc, filename, &file, 0, progress, buf); + if (r / 100 != 2) { + scr_printf("%s\n", buf); + return; + } + filelen = extract_unsigned_long(buf, 0); + + /* Meta-download for public clients */ + /* scr_printf("Fetching file from Citadel server...\n"); */ + mkdir(tempdir, 0700); + snprintf(tempname, sizeof tempname, "%s/%s", tempdir, filename); + tpipe = fopen(tempname, "wb"); + if (fwrite(file, filelen, 1, tpipe) < filelen) { + /* FIXME: restart syscall on EINTR + broken = 1; */ + } + fclose(tpipe); + if (file) + free(file); + + if (proto == 0) { + /* FIXME: display internally instead */ + snprintf(transmit_cmd, sizeof transmit_cmd, + "SHELL=/dev/null; export SHELL; TERM=dumb; export TERM; exec more -d <%s", tempname); + } else if (proto == 1) + snprintf(transmit_cmd, sizeof transmit_cmd, "exec sx %s", tempname); + else if (proto == 3) + snprintf(transmit_cmd, sizeof transmit_cmd, "exec sb %s", tempname); + else if (proto == 4) + snprintf(transmit_cmd, sizeof transmit_cmd, "exec sz %s", tempname); + else + /* FIXME: display internally instead */ + snprintf(transmit_cmd, sizeof transmit_cmd, "exec cat %s", tempname); + + stty_ctdl(SB_RESTORE); + rv = system(transmit_cmd); + if (rv != 0) + scr_printf("failed to download '%s': %d\n", transmit_cmd, rv); + stty_ctdl(SB_NO_INTR); + + /* clean up the temporary directory */ + nukedir(tempdir); + ctdl_beep(); /* Beep beep! */ +} + + +/* + * read directory of this room + */ +void roomdir(CtdlIPC * ipc) +{ + char flnm[256]; + char flsz[32]; + char comment[256]; + char mimetype[256]; + char buf[256]; + char *listing = NULL; /* Returned directory listing */ + int r; + + r = CtdlIPCReadDirectory(ipc, &listing, buf); + if (r / 100 != 1) { + scr_printf("%s\n", buf); + return; + } + + extract_token(comment, buf, 0, '|', sizeof comment); + extract_token(flnm, buf, 1, '|', sizeof flnm); + scr_printf("\nDirectory of %s on %s\n", flnm, comment); + scr_printf("-----------------------\n"); + while (listing && *listing && !IsEmptyStr(listing)) { + extract_token(buf, listing, 0, '\n', sizeof buf); + remove_token(listing, 0, '\n'); + + extract_token(flnm, buf, 0, '|', sizeof flnm); + extract_token(flsz, buf, 1, '|', sizeof flsz); + extract_token(mimetype, buf, 2, '|', sizeof mimetype); + extract_token(comment, buf, 3, '|', sizeof comment); + if (strlen(flnm) <= 14) + scr_printf("%-14s %8s %s [%s]\n", flnm, flsz, comment, mimetype); + else + scr_printf("%s\n%14s %8s %s [%s]\n", flnm, "", flsz, comment, mimetype); + } + if (listing) + free(listing); +} + + +/* + * add a user to a private room + */ +void invite(CtdlIPC * ipc) +{ + char username[USERNAME_SIZE]; + char buf[SIZ]; + + newprompt("Name of user? ", username, USERNAME_SIZE); + if (username[0] == 0) + return; + + CtdlIPCInviteUserToRoom(ipc, username, buf); + scr_printf("%s\n", buf); +} + + +/* + * kick a user out of a room + */ +void kickout(CtdlIPC * ipc) +{ + char username[USERNAME_SIZE]; + char buf[SIZ]; + + newprompt("Name of user? ", username, USERNAME_SIZE); + if (username[0] == 0) + return; + + CtdlIPCKickoutUserFromRoom(ipc, username, buf); + scr_printf("%s\n", buf); +} + + +/* + * aide command: kill the current room + */ +void killroom(CtdlIPC * ipc) +{ + char aaa[100]; + int r; + + r = CtdlIPCDeleteRoom(ipc, 0, aaa); + if (r / 100 != 2) { + scr_printf("%s\n", aaa); + return; + } + + scr_printf("Are you sure you want to kill this room? "); + if (yesno() == 0) + return; + + r = CtdlIPCDeleteRoom(ipc, 1, aaa); + scr_printf("%s\n", aaa); + if (r / 100 != 2) + return; + dotgoto(ipc, "_BASEROOM_", 0, 0); +} + +void forget(CtdlIPC * ipc) +{ /* forget the current room */ + char buf[SIZ]; + + scr_printf("Are you sure you want to forget this room? "); + if (yesno() == 0) + return; + + remove_march(room_name, 0); + if (CtdlIPCForgetRoom(ipc, buf) / 100 != 2) { + scr_printf("%s\n", buf); + return; + } + + /* now return to the lobby */ + dotgoto(ipc, "_BASEROOM_", 0, 0); +} + + +/* + * create a new room + */ +void entroom(CtdlIPC * ipc) +{ + char buf[SIZ]; + char new_room_name[ROOMNAMELEN]; + int new_room_type; + char new_room_pass[10]; + int new_room_floor; + int a, b; + int r; /* IPC response code */ + + /* Check permission to create room */ + r = CtdlIPCCreateRoom(ipc, 0, "", 1, "", 0, buf); + if (r / 100 != 2) { + scr_printf("%s\n", buf); + return; + } + + newprompt("Name for new room? ", new_room_name, ROOMNAMELEN - 1); + if (IsEmptyStr(new_room_name)) { + return; + } + for (a = 0; !IsEmptyStr(&new_room_name[a]); ++a) { + if (new_room_name[a] == '|') { + new_room_name[a] = '_'; + } + } + + new_room_floor = select_floor(ipc, (int) curr_floor); + + IFNEXPERT formout(ipc, "roomaccess"); + do { + scr_printf("Help\n" + "<1>Public room (shown to all users by default)\n" + "<2>Hidden room (accessible to anyone who knows the room name)\n" + "<3>Passworded room (hidden, plus requires a password to enter)\n" + "<4>Invitation-only room (requires access to be granted by an Admin)\n" + "<5>Personal room (accessible to you only)\n" "Enter room type: "); + do { + b = inkey(); + } while (((b < '1') || (b > '5')) && (b != '?')); + if (b == '?') { + scr_printf("?\n"); + formout(ipc, "roomaccess"); + } + } while ((b < '1') || (b > '5')); + b -= '0'; /* Portable */ + scr_printf("%d\n", b); + new_room_type = b - 1; + if (new_room_type == 2) { + newprompt("Enter a room password: ", new_room_pass, 9); + for (a = 0; !IsEmptyStr(&new_room_pass[a]); ++a) + if (new_room_pass[a] == '|') + new_room_pass[a] = '_'; + } else { + strcpy(new_room_pass, ""); + } + + scr_printf("\042%s\042, a", new_room_name); + if (b == 1) + scr_printf(" public room."); + if (b == 2) + scr_printf(" hidden room."); + if (b == 3) + scr_printf(" passworded room, password: %s", new_room_pass); + if (b == 4) + scr_printf("n invitation-only room."); + if (b == 5) + scr_printf(" personal room."); + scr_printf("\nInstall it? (y/n) : "); + if (yesno() == 0) { + return; + } + + r = CtdlIPCCreateRoom(ipc, 1, new_room_name, new_room_type, new_room_pass, new_room_floor, buf); + if (r / 100 != 2) { + scr_printf("%s\n", buf); + return; + } + + /* command succeeded... now GO to the new room! */ + dotgoto(ipc, new_room_name, 0, 0); +} + + + +void readinfo(CtdlIPC * ipc) +{ /* read info file for current room */ + char buf[SIZ]; + char room_admin_name[64]; + int r; /* IPC response code */ + char *text = NULL; + + /* Name of currernt room admin */ + r = CtdlIPCGetRoomAide(ipc, buf); + if (r / 100 == 2) + strncpy(room_admin_name, buf, sizeof room_admin_name); + else + strcpy(room_admin_name, ""); + + if (!IsEmptyStr(room_admin_name)) + scr_printf("Room admin is %s.\n\n", room_admin_name); + + r = CtdlIPCRoomInfo(ipc, &text, buf); + if (r / 100 != 1) + return; + + if (text) { + fmout(screenwidth, NULL, text, NULL, 1); + free(text); + } +} + + +/* + * ho knows room... + */ +void whoknows(CtdlIPC * ipc) +{ + char buf[256]; + char *listing = NULL; + int r; + + r = CtdlIPCWhoKnowsRoom(ipc, &listing, buf); + if (r / 100 != 1) { + scr_printf("%s\n", buf); + return; + } + while (!IsEmptyStr(listing)) { + extract_token(buf, listing, 0, '\n', sizeof buf); + remove_token(listing, 0, '\n'); + if (sigcaught == 0) + scr_printf("%s\n", buf); + } + free(listing); +} + + +void do_edit(CtdlIPC * ipc, char *desc, char *read_cmd, char *check_cmd, char *write_cmd) +{ + FILE *fp; + char cmd[SIZ]; + int b, cksum, editor_exit; + + if (IsEmptyStr(editor_path)) { + scr_printf("Do you wish to re-enter %s? ", desc); + if (yesno() == 0) + return; + } + + fp = fopen(temp, "w"); + fclose(fp); + + CtdlIPC_chat_send(ipc, check_cmd); + CtdlIPC_chat_recv(ipc, cmd); + if (cmd[0] != '2') { + scr_printf("%s\n", &cmd[4]); + return; + } + + if (!IsEmptyStr(editor_path)) { + CtdlIPC_chat_send(ipc, read_cmd); + CtdlIPC_chat_recv(ipc, cmd); + if (cmd[0] == '1') { + fp = fopen(temp, "w"); + while (CtdlIPC_chat_recv(ipc, cmd), strcmp(cmd, "000")) { + fprintf(fp, "%s\n", cmd); + } + fclose(fp); + } + } + + cksum = file_checksum(temp); + + if (!IsEmptyStr(editor_path)) { + char tmp[SIZ]; + + snprintf(tmp, sizeof tmp, "WINDOW_TITLE=%s", desc); + putenv(tmp); + stty_ctdl(SB_RESTORE); + editor_pid = fork(); + if (editor_pid == 0) { + chmod(temp, 0600); + execlp(editor_path, editor_path, temp, NULL); + exit(1); + } + if (editor_pid > 0) + do { + editor_exit = 0; + b = ka_wait(&editor_exit); + } while ((b != editor_pid) && (b >= 0)); + editor_pid = (-1); + scr_printf("Executed %s\n", editor_path); + stty_ctdl(0); + } else { + scr_printf("Entering %s. Press return twice when finished.\n", desc); + fp = fopen(temp, "r+"); + citedit(fp); + fclose(fp); + } + + if (file_checksum(temp) == cksum) { + scr_printf("*** Aborted.\n"); + } + + else { + CtdlIPC_chat_send(ipc, write_cmd); + CtdlIPC_chat_recv(ipc, cmd); + if (cmd[0] != '4') { + scr_printf("%s\n", &cmd[4]); + return; + } + + fp = fopen(temp, "r"); + while (fgets(cmd, SIZ - 1, fp) != NULL) { + cmd[strlen(cmd) - 1] = 0; + CtdlIPC_chat_send(ipc, cmd); + } + fclose(fp); + CtdlIPC_chat_send(ipc, "000"); + } + + unlink(temp); +} + + +void enterinfo(CtdlIPC * ipc) +{ /* edit info file for current room */ + do_edit(ipc, "the Info file for this room", "RINF", "EINF 0", "EINF 1"); +} + +void enter_bio(CtdlIPC * ipc) +{ + char cmd[SIZ]; + snprintf(cmd, sizeof cmd, "RBIO %s", fullname); + do_edit(ipc, "your Bio", cmd, "NOOP", "EBIO"); +} + +/* + * create a new floor + */ +void create_floor(CtdlIPC * ipc) +{ + char buf[SIZ]; + char newfloorname[SIZ]; + int r; /* IPC response code */ + + load_floorlist(ipc); + + r = CtdlIPCCreateFloor(ipc, 0, "", buf); + if ((r / 100 != 2) && (r != ERROR + ILLEGAL_VALUE)) { + scr_printf("%s\n", buf); + return; + } + + newprompt("Name for new floor: ", newfloorname, 255); + if (!*newfloorname) + return; + r = CtdlIPCCreateFloor(ipc, 1, newfloorname, buf); + if (r / 100 == 2) { + scr_printf("Floor has been created.\n"); + } else { + scr_printf("%s\n", buf); + } + + load_floorlist(ipc); +} + +/* + * edit the current floor + */ +void edit_floor(CtdlIPC * ipc) +{ + char buf[SIZ]; + struct ExpirePolicy *ep = NULL; + + load_floorlist(ipc); + + /* Fetch the expire policy (this will silently fail on old servers, + * resulting in "default" policy) + */ + CtdlIPCGetMessageExpirationPolicy(ipc, 1, &ep, buf); + + /* Interact with the user */ + scr_printf("You are editing the floor called \"%s\"\n", &floorlist[(int) curr_floor][0]); + strprompt("Floor name", &floorlist[(int) curr_floor][0], 255); + + /* Angels and demons dancing in my head... */ + do { + snprintf(buf, sizeof buf, "%d", ep->expire_mode); + strprompt("Floor default message expire policy (? for list)", buf, 1); + if (buf[0] == '?') { + scr_printf("\n" + "0. Use the system default\n" + "1. Never automatically expire messages\n" + "2. Expire by message count\n" "3. Expire by message age\n"); + } + } while ((buf[0] < '0') || (buf[0] > '3')); + ep->expire_mode = buf[0] - '0'; + + /* ...lunatics and monsters underneath my bed */ + if (ep->expire_mode == 2) { + snprintf(buf, sizeof buf, "%d", ep->expire_value); + strprompt("Keep how many messages online?", buf, 10); + ep->expire_value = atol(buf); + } + + if (ep->expire_mode == 3) { + snprintf(buf, sizeof buf, "%d", ep->expire_value); + strprompt("Keep messages for how many days?", buf, 10); + ep->expire_value = atol(buf); + } + + /* Save it */ + CtdlIPCSetMessageExpirationPolicy(ipc, 1, ep, buf); + CtdlIPCEditFloor(ipc, curr_floor, &floorlist[(int) curr_floor][0], buf); + scr_printf("%s\n", buf); + load_floorlist(ipc); +} + + + + +/* + * kill the current floor + */ +void kill_floor(CtdlIPC * ipc) +{ + int floornum_to_delete, a; + char buf[SIZ]; + + load_floorlist(ipc); + do { + floornum_to_delete = (-1); + scr_printf("(Press return to abort)\n"); + newprompt("Delete which floor? ", buf, 255); + if (IsEmptyStr(buf)) + return; + for (a = 0; a < 128; ++a) + if (!strcasecmp(&floorlist[a][0], buf)) + floornum_to_delete = a; + if (floornum_to_delete < 0) { + scr_printf("No such floor. Select one of:\n"); + for (a = 0; a < 128; ++a) + if (floorlist[a][0] != 0) + scr_printf("%s\n", &floorlist[a][0]); + } + } while (floornum_to_delete < 0); + CtdlIPCDeleteFloor(ipc, 1, floornum_to_delete, buf); + scr_printf("%s\n", buf); + load_floorlist(ipc); +} diff --git a/textclient/routines.c~ b/textclient/routines.c~ new file mode 100644 index 000000000..b488dc9d1 --- /dev/null +++ b/textclient/routines.c~ @@ -0,0 +1,570 @@ +// Client-side support functions. +// +// Copyright (c) 1987-2016 by the citadel.org team +// +// This program is open source software. Use, duplication, and/or +// disclosure are subject to the GNU General Purpose License version 3. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +#include "textclient.h" + +#define IFAIDE if(axlevel>=AxAideU) +#define IFNAIDE if (axleveldd|elete|ave|uit"); + switch (ch) { + case 'a': + newprompt("Enter new email address: ", buf, 50); + striplt(buf); + if (!IsEmptyStr(buf)) { + // FIXME validate the email address (format, our own domain, addr does not belong to another user) + ++num_recs; + if (num_recs == 1) { + recs = malloc(sizeof(char *)); + } else { + recs = realloc(recs, (sizeof(char *)) * num_recs); + } + recs[num_recs - 1] = strdup(buf); + } + modified = 1; + break; + case 'd': + i = intprompt("Delete which address", 1, 1, num_recs) - 1; + free(recs[i]); + --num_recs; + for (j = i; j < num_recs; ++j) { + recs[j] = recs[j + 1]; + } + modified = 1; + break; + case 's': + r = 1; + for (i = 0; i < num_recs; i++) + r += 1 + strlen(recs[i]); + resp = (char *) calloc(1, r); + if (!resp) { + scr_printf("Can't save config - out of memory!\n"); + logoff(ipc, 1); + } + if (num_recs) + for (i = 0; i < num_recs; i++) { + strcat(resp, recs[i]); + strcat(resp, "\n"); + } + r = CtdlIPCAideSetEmailAddresses(ipc, who, resp, buf); + if (r / 100 != 4) { + scr_printf("%s\n", buf); + } else { + scr_printf("Saved %d addresses.\n", num_recs); + modified = 0; + quitting = 1; + } + free(resp); + break; + case 'q': + quitting = !modified || boolprompt("Quit without saving", 0); + break; + default: + break; + } + } while (!quitting); + + if (recs != NULL) { + for (i = 0; i < num_recs; ++i) + free(recs[i]); + free(recs); + } +} + + +/* + * Edit or delete a user (cmd=25 to edit/create, 96 to delete) + */ +void edituser(CtdlIPC * ipc, int cmd) +{ + char buf[SIZ]; + char who[USERNAME_SIZE]; + char newname[USERNAME_SIZE]; + struct ctdluser *user = NULL; + int newnow = 0; + int r; /* IPC response code */ + int change_name = 0; + + strcpy(newname, ""); + + newprompt("User name: ", who, 29); + while ((r = CtdlIPCAideGetUserParameters(ipc, who, &user, buf)) / 100 != 2) { + scr_printf("%s\n", buf); + if (cmd == 25) { + scr_printf("Do you want to create this user? "); + if (yesno()) { + r = CtdlIPCCreateUser(ipc, who, 0, buf); + if (r / 100 == 2) { + newnow = 1; + continue; + } + scr_printf("%s\n", buf); + } + } + free(user); + return; + } + + if (cmd == 25) { // user edit + + /* val_user(ipc, user->fullname, 0); we used to display the vCard here but there's really no need */ + + if (!newnow) { + change_name = 1; + while (change_name == 1) { + if (boolprompt("Change name", 0)) { + strprompt("New name", newname, USERNAME_SIZE - 1); + r = CtdlIPCRenameUser(ipc, user->fullname, newname, buf); + if (r / 100 != 2) { + scr_printf("%s\n", buf); + } else { + strcpy(user->fullname, newname); + change_name = 0; + } + } else { + change_name = 0; + } + } + } + + if (newnow || boolprompt("Change password", 0)) { + strprompt("Password", user->password, -19); + } + + user->axlevel = intprompt("Access level", user->axlevel, 0, 6); + if (boolprompt("Permission to send Internet mail", (user->flags & US_INTERNET))) { + user->flags |= US_INTERNET; + } else { + user->flags &= ~US_INTERNET; + } + if (boolprompt("Ask user to register again", !(user->flags & US_REGIS))) { + user->flags &= ~US_REGIS; + } else { + user->flags |= US_REGIS; + } + user->timescalled = intprompt("Times called", user->timescalled, 0, INT_MAX); + user->posted = intprompt("Messages posted", user->posted, 0, INT_MAX); + user->lastcall = boolprompt("Set last login to now", 0) ? time(NULL) : user->lastcall; + user->USuserpurge = intprompt("Purge time (in days, 0 for system default", user->USuserpurge, 0, INT_MAX); + } + + if (cmd == 96) { + scr_printf("Do you want to delete this user? "); + if (!yesno()) { + free(user); + return; + } + user->axlevel = AxDeleted; + } + + r = CtdlIPCAideSetUserParameters(ipc, user, buf); + if (r / 100 != 2) { + scr_printf("%s\n", buf); + } + free(user); + + if (boolprompt("Edit this user's Internet email addresses", 0)) { + edit_user_internet_email_addresses(ipc, who); + } + +} + + +/* Display a prompt and flip a bit based on whether the user answers + * yes or no. Yes=1 and No=0, unless 'backwards' is set to a nonzero value + * in which case No=1 and Yes=0. + */ +int set_attr(CtdlIPC * ipc, unsigned int sval, char *prompt, unsigned int sbit, int backwards) +{ + int a; + int temp; + + temp = sval; + color(DIM_WHITE); + scr_printf("%50s ", prompt); + color(DIM_MAGENTA); + scr_printf("["); + color(BRIGHT_MAGENTA); + + if (backwards) { + scr_printf("%3s", ((temp & sbit) ? "No" : "Yes")); + } else { + scr_printf("%3s", ((temp & sbit) ? "Yes" : "No")); + } + + color(DIM_MAGENTA); + scr_printf("]? "); + color(BRIGHT_CYAN); + a = (temp & sbit); + if (a != 0) + a = 1; + if (backwards) + a = 1 - a; + a = yesno_d(a); + if (backwards) + a = 1 - a; + color(DIM_WHITE); + temp = (temp | sbit); + if (!a) + temp = (temp ^ sbit); + return (temp); +} + +/* + * modes are: 0 - .EC command, 1 - .EC for new user, + * 2 - toggle Xpert mode 3 - toggle floor mode + */ +void enter_config(CtdlIPC * ipc, int mode) +{ + char buf[SIZ]; + struct ctdluser *user = NULL; + int r; /* IPC response code */ + + r = CtdlIPCGetConfig(ipc, &user, buf); + if (r / 100 != 2) { + scr_printf("%s\n", buf); + free(user); + return; + } + + if (mode == 0 || mode == 1) { + + user->flags = set_attr(ipc, user->flags, "Are you an experienced Citadel user", US_EXPERT, 0); + if ((user->flags & US_EXPERT) == 0 && mode == 1) { + free(user); + return; + } + + user->flags = set_attr(ipc, user->flags, "Print last old message on New message request", US_LASTOLD, 0); + + user->flags = set_attr(ipc, user->flags, "Prompt after each message", US_NOPROMPT, 1); + + if ((user->flags & US_NOPROMPT) == 0) { + user->flags = set_attr(ipc, user->flags, "Use 'disappearing' prompts", US_DISAPPEAR, 0); + } + + user->flags = set_attr(ipc, user->flags, "Pause after each screenful of text", US_PAGINATOR, 0); + + if (rc_prompt_control == 3 && (user->flags & US_PAGINATOR)) { + user->flags = set_attr(ipc, user->flags, "ext and top work at paginator prompt", US_PROMPTCTL, 0); + } + + if (rc_floor_mode == RC_DEFAULT) { + user->flags = set_attr(ipc, user->flags, "View rooms by floor", US_FLOORS, 0); + } + + if (rc_ansi_color == 3) { + user->flags = set_attr(ipc, user->flags, "Enable color support", US_COLOR, 0); + } + + if ((user->flags & US_EXPERT) == 0) { + formout(ipc, "unlisted"); + } + + user->flags = set_attr(ipc, user->flags, "Be unlisted in userlog", US_UNLISTED, 0); + + if (!IsEmptyStr(editor_path)) { + user->flags = set_attr(ipc, + user->flags, "Always enter messages with the full-screen editor", US_EXTEDIT, 0); + } + + } + + if (mode == 2) { + if (user->flags & US_EXPERT) { + user->flags ^= US_EXPERT; + scr_printf("Expert mode now OFF\n"); + } else { + user->flags |= US_EXPERT; + scr_printf("Expert mode now ON\n"); + } + } + + if (mode == 3) { + if (user->flags & US_FLOORS) { + user->flags ^= US_FLOORS; + scr_printf("Floor mode now OFF\n"); + } else { + user->flags |= US_FLOORS; + scr_printf("Floor mode now ON\n"); + } + } + + r = CtdlIPCSetConfig(ipc, user, buf); + if (r / 100 != 2) + scr_printf("%s\n", buf); + userflags = user->flags; + free(user); +} + +/* + * getstring() - get a line of text from a file + * ignores lines beginning with "#" + */ +int getstring(FILE * fp, char *string) +{ + int a, c; + do { + strcpy(string, ""); + a = 0; + do { + c = getc(fp); + if (c < 0) { + string[a] = 0; + return (-1); + } + string[a++] = c; + } while (c != 10); + string[a - 1] = 0; + } while (string[0] == '#'); + return (strlen(string)); +} + + +/* Searches for patn in search string */ +int pattern(char *search, char *patn) +{ + int a, b, len; + + len = strlen(patn); + for (a = 0; !IsEmptyStr(&search[a]); ++a) { + b = strncasecmp(&search[a], patn, len); + if (b == 0) + return (b); + } + return (-1); +} + + +void strproc(char *string) +{ + int a; + + if (IsEmptyStr(string)) + return; + + /* Convert non-printable characters to blanks */ + for (a = 0; !IsEmptyStr(&string[a]); ++a) { + if (string[a] < 32) + string[a] = 32; + if (string[a] > 126) + string[a] = 32; + } + + /* Remove leading and trailing blanks */ + while (string[0] < 33) + strcpy(string, &string[1]); + while (string[strlen(string) - 1] < 33) + string[strlen(string) - 1] = 0; + + /* Remove double blanks */ + for (a = 0; a < strlen(string); ++a) { + if ((string[a] == 32) && (string[a + 1] == 32)) { + strcpy(&string[a], &string[a + 1]); + a = 0; + } + } + + /* remove characters which would interfere with the network */ + for (a = 0; a < strlen(string); ++a) { + if (string[a] == '!') + strcpy(&string[a], &string[a + 1]); + if (string[a] == '@') + strcpy(&string[a], &string[a + 1]); + if (string[a] == '_') + strcpy(&string[a], &string[a + 1]); + if (string[a] == ',') + strcpy(&string[a], &string[a + 1]); + if (string[a] == '%') + strcpy(&string[a], &string[a + 1]); + if (string[a] == '|') + strcpy(&string[a], &string[a + 1]); + } + +} + + +void progress(CtdlIPC * ipc, unsigned long curr, unsigned long cmax) +{ + static char dots[] = "**************************************************"; + char dots_printed[51]; + char fmt[42]; + unsigned long a; + + if (curr >= cmax) { + scr_printf("\r%79s\r", ""); + } else { + /* a will be range 0-50 rather than 0-100 */ + a = (curr * 50) / cmax; + sprintf(fmt, "[%%s%%%lds] %%3ld%%%% %%10ld/%%10ld\r", 50 - a); + strncpy(dots_printed, dots, a); + dots_printed[a] = 0; + scr_printf(fmt, dots_printed, "", curr * 100 / cmax, curr, cmax); + scr_flush(); + } +} + + +/* + * NOT the same locate_host() in locate_host.c. This one just does a + * 'who am i' to try to discover where the user is... + */ +void locate_host(CtdlIPC * ipc, char *hbuf) +{ + FILE *who = (FILE *) popen("who am i", "r"); + if (who == NULL) { + strcpy(hbuf, ipc->ServInfo.fqdn); + return; + } + fgets(hbuf, SIZ, who); + pclose(who); + stripallbut(hbuf, '(', ')'); +} + +/* + * miscellaneous server commands (testing, etc.) + */ +void misc_server_cmd(CtdlIPC * ipc, char *cmd) +{ + char buf[SIZ]; + + CtdlIPC_chat_send(ipc, cmd); + CtdlIPC_chat_recv(ipc, buf); + scr_printf("%s\n", buf); + if (buf[0] == '1') { + set_keepalives(KA_HALF); + while (CtdlIPC_chat_recv(ipc, buf), strcmp(buf, "000")) { + scr_printf("%s\n", buf); + } + set_keepalives(KA_YES); + return; + } + if (buf[0] == '4') { + do { + newprompt("> ", buf, 255); + CtdlIPC_chat_send(ipc, buf); + } while (strcmp(buf, "000")); + return; + } +} + + +/* + * compute the checksum of a file + */ +int file_checksum(char *filename) +{ + int cksum = 0; + int ch; + FILE *fp; + + fp = fopen(filename, "r"); + if (fp == NULL) + return (0); + + /* yes, this algorithm may allow cksum to overflow, but that's ok + * as long as it overflows consistently, which it will. + */ + while (ch = getc(fp), ch >= 0) { + cksum = (cksum + ch); + } + + fclose(fp); + return (cksum); +} + +/* + * nuke a directory and its contents + */ +int nukedir(char *dirname) +{ + DIR *dp; + struct dirent *d; + char filename[SIZ]; + + dp = opendir(dirname); + if (dp == NULL) { + return (errno); + } + + while (d = readdir(dp), d != NULL) { + snprintf(filename, sizeof filename, "%s/%s", dirname, d->d_name); + unlink(filename); + } + + closedir(dp); + return (rmdir(dirname)); +} diff --git a/textclient/routines2.c~ b/textclient/routines2.c~ new file mode 100644 index 000000000..77fbe133c --- /dev/null +++ b/textclient/routines2.c~ @@ -0,0 +1,564 @@ +// More client-side support functions. +// +// Copyright (c) 1987-2017 by the citadel.org team +// +// This program is open source software. Use, duplication, and/or +// disclosure are subject to the GNU General Purpose License version 3. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +#include "textclient.h" + +// work around solaris include files +#ifdef reg +#undef reg +#endif + +extern char temp[]; +extern char tempdir[]; +extern char *axdefs[8]; +extern long highest_msg_read; +extern long maxmsgnum; +extern unsigned room_flags; +extern int screenwidth; + + +// return proper room prompt character +int room_prompt(unsigned int qrflags) { + int a; + a = '>'; + if (qrflags & QR_DIRECTORY) { + a = ']'; + } + return (a); +} + + +// Register with name and address +void entregis(CtdlIPC * ipc) { + + char buf[SIZ]; + char tmpname[30]; + char tmpaddr[25]; + char tmpcity[15]; + char tmpstate[3]; + char tmpzip[11]; + char tmpphone[15]; + char tmpemail[SIZ]; + char tmpcountry[32]; + char diruser[256]; + char dirnode[256]; + char holdemail[SIZ]; + char *reg = NULL; + int ok = 0; + int r; /* IPC response code */ + + strcpy(tmpname, ""); + strcpy(tmpaddr, ""); + strcpy(tmpcity, ""); + strcpy(tmpstate, ""); + strcpy(tmpzip, ""); + strcpy(tmpphone, ""); + strcpy(tmpemail, ""); + strcpy(tmpcountry, ""); + + r = CtdlIPCGetUserRegistration(ipc, NULL, ®, buf); + if (r / 100 == 1) { + int a = 0; + + while (reg && !IsEmptyStr(reg)) { + + extract_token(buf, reg, 0, '\n', sizeof buf); + remove_token(reg, 0, '\n'); + + if (a == 2) + strncpy(tmpname, buf, sizeof tmpname); + else if (a == 3) + strncpy(tmpaddr, buf, sizeof tmpaddr); + else if (a == 4) + strncpy(tmpcity, buf, sizeof tmpcity); + else if (a == 5) + strncpy(tmpstate, buf, sizeof tmpstate); + else if (a == 6) + strncpy(tmpzip, buf, sizeof tmpzip); + else if (a == 7) + strncpy(tmpphone, buf, sizeof tmpphone); + else if (a == 9) + strncpy(tmpemail, buf, sizeof tmpemail); + else if (a == 10) + strncpy(tmpcountry, buf, sizeof tmpcountry); + ++a; + } + } + strprompt("REAL name", tmpname, 29); + strprompt("Address", tmpaddr, 24); + strprompt("City/town", tmpcity, 14); + strprompt("State/province", tmpstate, 2); + strprompt("ZIP/Postal Code", tmpzip, 10); + strprompt("Country", tmpcountry, 31); + strprompt("Telephone number", tmpphone, 14); + + do { + ok = 1; + strncpy(holdemail, tmpemail, sizeof holdemail); + strprompt("Email address", tmpemail, 31); + r = CtdlIPCDirectoryLookup(ipc, tmpemail, buf); + if (r / 100 == 2) { + extract_token(diruser, buf, 0, '@', sizeof diruser); + extract_token(dirnode, buf, 1, '@', sizeof dirnode); + striplt(diruser); + striplt(dirnode); + if ((strcasecmp(diruser, fullname)) + || (strcasecmp(dirnode, ipc->ServInfo.nodename))) { + scr_printf("\nYou can't use %s as your address.\n", tmpemail); + scr_printf("It is already in use by %s @ %s.\n", diruser, dirnode); + ok = 0; + strncpy(tmpemail, holdemail, sizeof tmpemail); + } + } + } while (ok == 0); + + /* now send the registration info back to the server */ + reg = (char *) realloc(reg, SIZ); + if (reg) { + sprintf(reg, "%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n", + tmpname, tmpaddr, tmpcity, tmpstate, tmpzip, tmpphone, tmpemail, tmpcountry); + r = CtdlIPCSetRegistration(ipc, reg, buf); + if (r / 100 != 4) + scr_printf("%s\n", buf); + free(reg); + } + scr_printf("\n"); +} + +/* + * make all messages old in current room + */ +void updatels(CtdlIPC * ipc) +{ + char buf[256]; + int r; /* IPC response code */ + + r = CtdlIPCSetLastRead(ipc, (maxmsgnum > highest_msg_read) ? maxmsgnum : highest_msg_read, buf); + + if (r / 100 != 2) + scr_printf("%s\n", buf); +} + +/* + * only make messages old in this room that have been read + */ +void updatelsa(CtdlIPC * ipc) +{ + char buf[256]; + int r; /* IPC response code */ + + r = CtdlIPCSetLastRead(ipc, highest_msg_read, buf); + if (r / 100 != 2) + scr_printf("%s\n", &buf[4]); +} + + +/* + * client-based uploads (for users with their own clientware) + */ +void cli_upload(CtdlIPC * ipc) +{ + char flnm[PATH_MAX]; + char desc[151]; + char buf[256]; + char tbuf[256]; + int r; /* IPC response code */ + int a; + int fd; + + if ((room_flags & QR_UPLOAD) == 0) { + scr_printf("*** You cannot upload to this room.\n"); + return; + } + newprompt("File to be uploaded: ", flnm, 55); + fd = open(flnm, O_RDONLY); + if (fd < 0) { + scr_printf("Cannot open '%s': %s\n", flnm, strerror(errno)); + return; + } + scr_printf("Enter a description of this file:\n"); + newprompt(": ", desc, 75); + + /* Keep generating filenames in hope of finding a unique one */ + a = 0; + while (a < 10) { + /* basename of filename */ + strcpy(tbuf, flnm); + if (haschar(tbuf, '/')) + extract_token(tbuf, flnm, num_tokens(tbuf, '/') - 1, '/', sizeof tbuf); + /* filename.1, filename.2, etc */ + if (a > 0) { + sprintf(&tbuf[strlen(tbuf)], ".%d", a); + } + /* Try upload */ + r = CtdlIPCFileUpload(ipc, tbuf, desc, flnm, progress, buf); + if (r / 100 == 5 || r < 0) + scr_printf("%s\n", buf); + else + break; + ++a; + } + if (a > 0) + scr_printf("Saved as '%s'\n", tbuf); +} + + +/* + * Function used for various image upload commands + */ +void cli_image_upload(CtdlIPC * ipc, char *keyname) +{ + char flnm[PATH_MAX]; + char buf[256]; + int r; + + /* Can we upload this image? */ + r = CtdlIPCImageUpload(ipc, 0, NULL, keyname, NULL, buf); + if (r / 100 != 2) { + scr_printf("%s\n", buf); + return; + } + newprompt("Image file to be uploaded: ", flnm, 55); + r = CtdlIPCImageUpload(ipc, 1, flnm, keyname, progress, buf); + if (r / 100 == 5) { + scr_printf("%s\n", buf); + } + else if (r < 0) { + scr_printf("Cannot upload '%s': %s\n", flnm, strerror(errno)); + } + /* else upload succeeded */ +} + + +/* + * protocol-based uploads (Xmodem, Ymodem, Zmodem) + */ +void upload(CtdlIPC * ipc, int c) +{ /* c = upload mode */ + char flnm[PATH_MAX]; + char desc[151]; + char buf[256]; + char tbuf[4096]; + int xfer_pid; + int a, b; + FILE *fp, *lsfp; + int rv; + + if ((room_flags & QR_UPLOAD) == 0) { + scr_printf("*** You cannot upload to this room.\n"); + return; + } + /* we don't need a filename when receiving batch y/z modem */ + if ((c == 2) || (c == 3)) + strcpy(flnm, "x"); + else + newprompt("Enter filename: ", flnm, 15); + + for (a = 0; !IsEmptyStr(&flnm[a]); ++a) + if ((flnm[a] == '/') || (flnm[a] == '\\') || (flnm[a] == '>') + || (flnm[a] == '?') || (flnm[a] == '*') + || (flnm[a] == ';') || (flnm[a] == '&')) + flnm[a] = '_'; + + /* create a temporary directory... */ + if (mkdir(tempdir, 0700) != 0) { + scr_printf("*** Could not create temporary directory %s: %s\n", tempdir, strerror(errno)); + return; + } + /* now do the transfer ... in a separate process */ + xfer_pid = fork(); + if (xfer_pid == 0) { + rv = chdir(tempdir); + if (rv < 0) { + scr_printf("failed to change into %s Reason %s\nAborting now.\n", tempdir, strerror(errno)); + nukedir(tempdir); + return; + } + switch (c) { + case 0: + stty_ctdl(0); + scr_printf("Receiving %s - press Ctrl-D to end.\n", flnm); + fp = fopen(flnm, "w"); + do { + b = inkey(); + if (b == 13) { + b = 10; + } + if (b != 4) { + scr_printf("%c", b); + putc(b, fp); + } + } while (b != 4); + fclose(fp); + exit(0); + case 1: + stty_ctdl(3); + execlp("rx", "rx", flnm, NULL); + exit(1); + case 2: + stty_ctdl(3); + execlp("rb", "rb", NULL); + exit(1); + case 3: + stty_ctdl(3); + execlp("rz", "rz", NULL); + exit(1); + } + } + else do { + b = ka_wait(&a); + } while ((b != xfer_pid) && (b != (-1))); + stty_ctdl(0); + + if (a != 0) { + scr_printf("\r*** Transfer unsuccessful.\n"); + nukedir(tempdir); + return; + } + scr_printf("\r*** Transfer successful.\n"); + snprintf(buf, sizeof buf, "cd %s; ls", tempdir); + lsfp = popen(buf, "r"); + if (lsfp != NULL) { + while (fgets(flnm, sizeof flnm, lsfp) != NULL) { + flnm[strlen(flnm) - 1] = 0; /* chop newline */ + snprintf(buf, sizeof buf, "Enter a short description of '%s':\n: ", flnm); + newprompt(buf, desc, 150); + snprintf(buf, sizeof buf, "%s/%s", tempdir, flnm); + CtdlIPCFileUpload(ipc, flnm, desc, buf, progress, tbuf); + scr_printf("%s\n", tbuf); + } + pclose(lsfp); + } + nukedir(tempdir); +} + + +/* + * validate a user (returns 0 for successful validation, nonzero if quitting) + */ +int val_user(CtdlIPC * ipc, char *user, int do_validate) +{ + int a; + char cmd[256]; + char buf[256]; + char *resp = NULL; + int ax = 0; + char answer[2]; + int r; /* IPC response code */ + + scr_printf("\n"); + r = CtdlIPCGetUserRegistration(ipc, user, &resp, cmd); + if (r / 100 == 1) { + a = 0; + do { + extract_token(buf, resp, 0, '\n', sizeof buf); + remove_token(resp, 0, '\n'); + ++a; + if (a == 1) + scr_printf("User #%s - %s ", buf, cmd); + if (a == 2) + scr_printf("PW: %s\n", (IsEmptyStr(buf) ? "" : "")); + if (a == 3) + scr_printf("%s\n", buf); + if (a == 4) + scr_printf("%s\n", buf); + if (a == 5) + scr_printf("%s, ", buf); + if (a == 6) + scr_printf("%s ", buf); + if (a == 7) + scr_printf("%s\n", buf); + if (a == 8) + scr_printf("%s\n", buf); + if (a == 9) + ax = atoi(buf); + if (a == 10) + scr_printf("%s\n", buf); + if (a == 11) + scr_printf("%s\n", buf); + } while (!IsEmptyStr(resp)); + +/* TODODRW: discrepancy here. Parts of the code refer to axdefs[7] as the highest + * but most of it limits it to axdefs[6]. + * Webcit limits to 6 as does the code here but there are 7 in axdefs.h + */ + scr_printf("Current access level: %d (%s)\n", ax, axdefs[ax]); + } else { + scr_printf("%s\n%s\n", user, &cmd[4]); + } + if (resp) + free(resp); + + if (do_validate) { + /* now set the access level */ + while (1) { + sprintf(answer, "%d", ax); + strprompt("New access level (? for help, q to quit)", answer, 1); + if ((answer[0] >= '0') && (answer[0] <= '6')) { + ax = atoi(answer); + r = CtdlIPCValidateUser(ipc, user, ax, cmd); + if (r / 100 != 2) + scr_printf("%s\n\n", cmd); + return (0); + } + if (tolower(answer[0]) == 'q') { + scr_printf("*** Aborted.\n\n"); + return (1); + } + if (answer[0] == '?') { + scr_printf("Available access levels:\n"); + for (a = 0; a < 7; ++a) { + scr_printf("%d - %s\n", a, axdefs[a]); + } + } + } + } + return (0); +} + + +/* + * Validate new users + */ +void validate(CtdlIPC * ipc) +{ + char cmd[256]; + char buf[256]; + int finished = 0; + int r; /* IPC response code */ + + do { + r = CtdlIPCNextUnvalidatedUser(ipc, cmd); + if (r / 100 != 3) + finished = 1; + if (r / 100 == 2) + scr_printf("%s\n", cmd); + if (r / 100 == 3) { + extract_token(buf, cmd, 0, '|', sizeof buf); + if (val_user(ipc, buf, 1) != 0) + finished = 1; + } + } while (finished == 0); +} + + +void subshell(void) +{ + int a, b; + + stty_ctdl(SB_RESTORE); + a = fork(); + if (a == 0) { + signal(SIGINT, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + execlp(getenv("SHELL"), getenv("SHELL"), NULL); + scr_printf("Could not open a shell: %s\n", strerror(errno)); + exit(errno); + } + do { + b = ka_wait(NULL); + } while ((a != b) && (a != (-1))); + stty_ctdl(0); +} + +/* + * <.A>ide ile elete command + */ +void deletefile(CtdlIPC * ipc) +{ + char filename[32]; + char buf[256]; + + newprompt("Filename: ", filename, 31); + if (IsEmptyStr(filename)) + return; + CtdlIPCDeleteFile(ipc, filename, buf); + scr_printf("%s\n", buf); +} + + +/* + * <.A>ide ile ove command + */ +void movefile(CtdlIPC * ipc) +{ + char filename[64]; + char newroom[ROOMNAMELEN]; + char buf[256]; + + newprompt("Filename: ", filename, 63); + if (IsEmptyStr(filename)) + return; + newprompt("Enter target room: ", newroom, ROOMNAMELEN - 1); + CtdlIPCMoveFile(ipc, filename, newroom, buf); + scr_printf("%s\n", buf); +} + + +/* + * list of users who have filled out a bio + */ +void list_bio(CtdlIPC * ipc) +{ + char buf[256]; + char *resp = NULL; + int pos = 1; + int r; /* IPC response code */ + + r = CtdlIPCListUsersWithBios(ipc, &resp, buf); + if (r / 100 != 1) { + scr_printf("%s\n", buf); + return; + } + while (resp && !IsEmptyStr(resp)) { + extract_token(buf, resp, 0, '\n', sizeof buf); + remove_token(resp, 0, '\n'); + if ((pos + strlen(buf) + 5) > screenwidth) { + scr_printf("\n"); + pos = 1; + } + scr_printf("%s, ", buf); + pos = pos + strlen(buf) + 2; + } + scr_printf("%c%c \n\n", 8, 8); + if (resp) + free(resp); +} + + +// read bio +void read_bio(CtdlIPC * ipc) { + char who[256]; + char buf[256]; + char *resp = NULL; + int r; /* IPC response code */ + + do { + newprompt("Read bio for who ('?' for list) : ", who, 25); + scr_printf("\n"); + if (!strcmp(who, "?")) + list_bio(ipc); + } while (!strcmp(who, "?")); + + r = CtdlIPCGetBio(ipc, who, &resp, buf); + if (r / 100 != 1) { + scr_printf("%s\n", buf); + return; + } + while (!IsEmptyStr(resp)) { + extract_token(buf, resp, 0, '\n', sizeof buf); + remove_token(resp, 0, '\n'); + scr_printf("%s\n", buf); + } + if (resp) + free(resp); +} diff --git a/textclient/screen.c~ b/textclient/screen.c~ new file mode 100644 index 000000000..45112f7a5 --- /dev/null +++ b/textclient/screen.c~ @@ -0,0 +1,248 @@ +// Screen output handling +// +// Copyright (c) 1987-2021 by the citadel.org team +// +// This program is open source software. Use, duplication, and/or +// disclosure are subject to the GNU General Purpose License version 3. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +#include "textclient.h" + +int enable_status_line = 0; +char status_line[1024] = " "; + +/* the default paginator prompt will be replaced by the server's prompt when we learn it */ +char *moreprompt = " -- more -- "; + +int screenheight = 24; +int screenwidth = 80; +int lines_printed = 0; +int cols_printed = 0; + +extern int rc_ansi_color; +extern int rc_prompt_control; +void do_keepalive(void); + +/* + * Attempt to discover the screen dimensions. + * WARNING: This is sometimes called from a signal handler. + */ +void check_screen_dims(void) { +#ifdef TIOCGWINSZ + struct { + unsigned short height; /* rows */ + unsigned short width; /* columns */ + unsigned short xpixels; + unsigned short ypixels; /* pixels */ + } xwinsz; + + if (ioctl(0, TIOCGWINSZ, &xwinsz) == 0) { + if (xwinsz.height) + screenheight = (int) xwinsz.height; + if (xwinsz.width) + screenwidth = (int) xwinsz.width; + } +#endif +} + + +/* + * Initialize the screen + */ +void screen_new(void) { + send_ansi_detect(); + look_for_ansi(); + cls(0); + color(DIM_WHITE); +} + + +/* + * Beep. + */ +void ctdl_beep(void) { + putc(7, stdout); +} + + +/* + * scr_printf() outputs to the terminal + */ +int scr_printf(char *fmt, ...) { + static char outbuf[4096]; /* static for performance -- not re-entrant -- change if needed */ + va_list ap; + int retval; + int i, len; + + va_start(ap, fmt); + retval = vsnprintf(outbuf, sizeof outbuf, fmt, ap); + va_end(ap); + + len = strlen(outbuf); + for (i = 0; i < len; ++i) { + scr_putc(outbuf[i]); + } + return retval; +} + + +/* + * Read one character from the terminal + */ +int scr_getc(int delay) { + unsigned char buf; + + scr_flush(); + + buf = '\0'; + if (!read(0, &buf, 1)) + logoff(NULL, 3); + + lines_printed = 0; + return buf; +} + + +/* + * Issue the paginator prompt (more / hit any key to continue) + */ +void hit_any_key(void) { + int a, b; + + color(COLOR_PUSH); + color(DIM_RED); + scr_printf("%s\r", moreprompt); + color(COLOR_POP); + b = inkey(); + for (a = 0; a < screenwidth; ++a) { + scr_putc(' '); + } + scr_printf("\r"); + + if ((rc_prompt_control == 1) || ((rc_prompt_control == 3) && (userflags & US_PROMPTCTL))) { + if (b == 'q' || b == 'Q' || b == 's' || b == 'S') { + b = STOP_KEY; + } + if (b == 'n' || b == 'N') { + b = NEXT_KEY; + } + } + + if (b == NEXT_KEY) + sigcaught = SIGINT; + if (b == STOP_KEY) + sigcaught = SIGQUIT; +} + + +/* + * Output one character to the terminal + */ +int scr_putc(int c) { + /* handle tabs normally */ + if (c == '\t') { + do { + scr_putc(' '); + } while ((cols_printed % 8) != 0); + return (c); + } + + /* Output the character... */ + if (putc(c, stdout) == EOF) { + logoff(NULL, 3); + } + + if (c == '\n') { + ++lines_printed; + cols_printed = 0; + } + else if (c == '\r') { + cols_printed = 0; + } + else if (isprint(c)) { + ++cols_printed; + if ((screenwidth > 0) && (cols_printed > screenwidth)) { + ++lines_printed; + cols_printed = 0; + } + } + + /* How many lines output before stopping for the paginator? + * Depends on whether we are displaying a status line. + */ + int height_offset = (((enable_color) && (screenwidth > 0) && (enable_status_line)) ? (3) : (2)); + + /* Ok, go check it. Stop and display the paginator prompt if necessary. */ + if ((screenheight > 0) && (lines_printed > (screenheight - height_offset))) { + lines_printed = 0; + hit_any_key(); + lines_printed = 0; + cols_printed = 0; + } + + return c; +} + + +void scr_flush(void) { + if ((enable_color) && (screenwidth > 0) && (enable_status_line)) { + if (strlen(status_line) < screenwidth) { + memset(&status_line[strlen(status_line)], 32, screenwidth - strlen(status_line)); + } + printf("\033[s\033[1;1H\033[K\033[7m"); + fwrite(status_line, screenwidth, 1, stdout); + printf("\033[27m\033[u"); + } + fflush(stdout); +} + + +static volatile int caught_sigwinch = 0; + + +/* + * scr_winch() handles window size changes from SIGWINCH + * resizes all our windows for us + */ +void scr_winch(int signum) { + /* if we receive this signal, we must be running + * in a terminal that supports resizing. + */ + caught_sigwinch = 1; + check_screen_dims(); + signal(SIGWINCH, scr_winch); +} + + +/* + * Display a 3270-style "wait" indicator at the bottom of the screen + */ +void scr_wait_indicator(int state) { + int sp = (screenwidth - 2); + + if (!enable_status_line) + return; + + if (screenwidth > 0) { + switch (state) { + default: + case 0: // Idle + status_line[sp] = ' '; + break; + case 1: // Waiting + status_line[sp] = 'X'; + break; + case 2: // Receiving + status_line[sp] = '<'; + break; + case 3: // Sending + status_line[sp] = '>'; + break; + } + scr_flush(); + } +} diff --git a/textclient/tuiconfig.c~ b/textclient/tuiconfig.c~ new file mode 100644 index 000000000..12038ba91 --- /dev/null +++ b/textclient/tuiconfig.c~ @@ -0,0 +1,858 @@ +// Configuration screens that are part of the text mode client. +// +// Copyright (c) 1987-2022 by the citadel.org team +// +// This program is open source software. Use, duplication, and/or +// disclosure are subject to the GNU General Purpose License version 3. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +#include "textclient.h" + +extern char temp[]; +extern char tempdir[]; +extern char *axdefs[8]; +extern long highest_msg_read; +extern long maxmsgnum; +extern unsigned room_flags; +extern int screenwidth; +char editor_path[PATH_MAX]; + + +// General system configuration command +void do_system_configuration(CtdlIPC * ipc) { + char buf[256]; + char sc[NUM_CONFIGS][256]; + char *resp = NULL; + struct ExpirePolicy *site_expirepolicy = NULL; + struct ExpirePolicy *mbx_expirepolicy = NULL; + int a; + int logpages = 0; + int r; // IPC response code + int server_configs = 0; + + // Clear out the config buffers + memset(&sc[0][0], 0, sizeof(sc)); + + // Fetch the current config + r = CtdlIPCGetSystemConfig(ipc, &resp, buf); + if (r / 100 == 1) { + server_configs = num_tokens(resp, '\n'); + for (a = 0; a < server_configs; ++a) { + if (a < NUM_CONFIGS) { + extract_token(&sc[a][0], resp, a, '\n', sizeof sc[a]); + } + } + } + if (resp) { + free(resp); + } + resp = NULL; + + // Fetch the expire policy (this will silently fail on old servers, resulting in "default" policy) + r = CtdlIPCGetMessageExpirationPolicy(ipc, 2, &site_expirepolicy, buf); + r = CtdlIPCGetMessageExpirationPolicy(ipc, 3, &mbx_expirepolicy, buf); + + // Identification parameters + strprompt("Node name", &sc[0][0], 15); + strprompt("Fully qualified domain name", &sc[1][0], 63); + strprompt("Human readable node name", &sc[2][0], 20); + strprompt("Telephone number", &sc[3][0], 15); + strprompt("Geographic location of this system", &sc[12][0], 31); + strprompt("Name of system administrator", &sc[13][0], 25); + strprompt("Paginator prompt", &sc[10][0], 79); + + // Security parameters + snprintf(sc[7], sizeof sc[7], "%d", (boolprompt("Require registration for new users", atoi(&sc[7][0])))); + snprintf(sc[29], sizeof sc[29], "%d", (boolprompt("Disable self-service user account creation", atoi(&sc[29][0])))); + strprompt("Initial access level for new users", &sc[6][0], 1); + strprompt("Access level required to create rooms", &sc[19][0], 1); + snprintf(sc[67], sizeof sc[67], "%d", (boolprompt("Allow anonymous guest logins", atoi(&sc[67][0])))); + snprintf(sc[4], sizeof sc[4], "%d", (boolprompt("Automatically give room admin privs to a user who creates a private room", atoi(&sc[4][0])))); + snprintf(sc[8], sizeof sc[8], "%d", (boolprompt("Automatically move problem user messages to twit room", atoi(&sc[8][0])))); + strprompt("Name of twit room", &sc[9][0], ROOMNAMELEN); + snprintf(sc[11], sizeof sc[11], "%d", (boolprompt("Restrict Internet mail to only those with that privilege", atoi(&sc[11][0])))); + snprintf(sc[26], sizeof sc[26], "%d", (boolprompt("Allow admins to Zap (forget) rooms", atoi(&sc[26][0])))); + + if (!IsEmptyStr(&sc[18][0])) { + logpages = 1; + } + else { + logpages = 0; + } + logpages = boolprompt("Log all instant messages", logpages); + if (logpages) { + strprompt("Name of logging room", &sc[18][0], ROOMNAMELEN); + } + else { + sc[18][0] = 0; + } + + // Server tuning + strprompt("Server connection idle timeout (in seconds)", &sc[5][0], 4); + strprompt("Maximum concurrent sessions", &sc[14][0], 4); + strprompt("Maximum message length", &sc[20][0], 20); + strprompt("Minimum number of worker threads", &sc[21][0], 3); + strprompt("Maximum number of worker threads", &sc[22][0], 3); + snprintf(sc[43], sizeof sc[43], "%d", (boolprompt("Automatically delete committed database logs", atoi(&sc[43][0])))); + strprompt("Server IP address (* for 'any')", &sc[37][0], 15); + strprompt("POP3 server port (-1 to disable)", &sc[23][0], 5); + strprompt("POP3S server port (-1 to disable)", &sc[40][0], 5); + strprompt("IMAP server port (-1 to disable)", &sc[27][0], 5); + strprompt("IMAPS server port (-1 to disable)", &sc[39][0], 5); + strprompt("SMTP MTA server port (-1 to disable)", &sc[24][0], 5); + strprompt("SMTP MSA server port (-1 to disable)", &sc[38][0], 5); + strprompt("SMTPS server port (-1 to disable)", &sc[41][0], 5); + strprompt("NNTP server port (-1 to disable)", &sc[70][0], 5); + strprompt("NNTPS server port (-1 to disable)", &sc[71][0], 5); + strprompt("Postfix TCP Dictionary Port server port (-1 to disable)", &sc[50][0], 5); + strprompt("ManageSieve server port (-1 to disable)", &sc[51][0], 5); + strprompt("XMPP (Jabber) client to server port (-1 to disable)", &sc[62][0], 5); + // strprompt("XMPP (Jabber) server to server port (-1 to disable)", &sc[63][0], 5); This is just a placeholder. + + // This logic flips the question around, because it's one of those situations where 0=yes and 1=no + a = atoi(sc[25]); + a = (a ? 0 : 1); + a = boolprompt("Correct forged From: lines during authenticated SMTP", a); + a = (a ? 0 : 1); + snprintf(sc[25], sizeof sc[25], "%d", a); + + snprintf(sc[66], sizeof sc[66], "%d", (boolprompt("Flag messages as spam instead of rejecting", atoi(&sc[66][0])))); + + // This logic flips the question around, because it's one of those situations where 0=yes and 1=no + a = atoi(sc[61]); + a = (a ? 0 : 1); + a = boolprompt("Force IMAP posts in public rooms to be from the user who submitted them", a); + a = (a ? 0 : 1); + snprintf(sc[61], sizeof sc[61], "%d", a); + + snprintf(sc[45], sizeof sc[45], "%d", (boolprompt("Allow unauthenticated SMTP clients to spoof my domains", atoi(&sc[45][0])))); + snprintf(sc[57], sizeof sc[57], "%d", (boolprompt("Perform RBL checks at greeting instead of after RCPT", atoi(&sc[57][0])))); + + // LDAP settings + if (ipc->ServInfo.supports_ldap) { + a = strlen(&sc[32][0]); + a = (a ? 1 : 0); // Set only to 1 or 0 + a = boolprompt("Do you want to configure LDAP authentication?", a); + if (a) { + strprompt("Host name of LDAP server", &sc[32][0], 127); + strprompt("Port number of LDAP service", &sc[33][0], 5); + strprompt("Base DN", &sc[34][0], 255); + strprompt("Bind DN (or blank for anonymous bind)", &sc[35][0], 255); + strprompt("Password for bind DN (or blank for anonymous bind)", &sc[36][0], 255); + } + else { + strcpy(&sc[32][0], ""); + } + } + + // Expiry settings + strprompt("Default user purge time (days)", &sc[16][0], 5); + strprompt("Default room purge time (days)", &sc[17][0], 5); + + // Angels and demons dancing in my head... + do { + snprintf(buf, sizeof buf, "%d", site_expirepolicy->expire_mode); + strprompt("System default message expire policy (? for list)", buf, 1); + if (buf[0] == '?') { + scr_printf("\n" + "1. Never automatically expire messages\n" + "2. Expire by message count\n" "3. Expire by message age\n"); + } + } while ((buf[0] < '1') || (buf[0] > '3')); + site_expirepolicy->expire_mode = buf[0] - '0'; + + // ...lunatics and monsters underneath my bed + if (site_expirepolicy->expire_mode == 2) { + snprintf(buf, sizeof buf, "%d", site_expirepolicy->expire_value); + strprompt("Keep how many messages online?", buf, 10); + site_expirepolicy->expire_value = atol(buf); + } + if (site_expirepolicy->expire_mode == 3) { + snprintf(buf, sizeof buf, "%d", site_expirepolicy->expire_value); + strprompt("Keep messages for how many days?", buf, 10); + site_expirepolicy->expire_value = atol(buf); + } + + // Media messiahs preying on my fears... + do { + snprintf(buf, sizeof buf, "%d", mbx_expirepolicy->expire_mode); + strprompt("Mailbox default message expire policy (? for list)", buf, 1); + if (buf[0] == '?') { + scr_printf("\n" + "0. Go with the system default\n" + "1. Never automatically expire messages\n" + "2. Expire by message count\n" "3. Expire by message age\n"); + } + } while ((buf[0] < '0') || (buf[0] > '3')); + mbx_expirepolicy->expire_mode = buf[0] - '0'; + + // ...Pop culture prophets playing in my ears + if (mbx_expirepolicy->expire_mode == 2) { + snprintf(buf, sizeof buf, "%d", mbx_expirepolicy->expire_value); + strprompt("Keep how many messages online?", buf, 10); + mbx_expirepolicy->expire_value = atol(buf); + } + if (mbx_expirepolicy->expire_mode == 3) { + snprintf(buf, sizeof buf, "%d", mbx_expirepolicy->expire_value); + strprompt("Keep messages for how many days?", buf, 10); + mbx_expirepolicy->expire_value = atol(buf); + } + + strprompt("How often to run network jobs (in seconds)", &sc[28][0], 5); + strprompt("Default frequency to run POP3 collection (in seconds)", &sc[64][0], 5); + strprompt("Fastest frequency to run POP3 collection (in seconds)", &sc[65][0], 5); + strprompt("Hour to run purges (0-23)", &sc[31][0], 2); + snprintf(sc[42], sizeof sc[42], "%d", (boolprompt("Enable full text search index (warning: resource intensive)", atoi(&sc[42][0])))); + snprintf(sc[46], sizeof sc[46], "%d", (boolprompt("Perform journaling of email messages", atoi(&sc[46][0])))); + snprintf(sc[47], sizeof sc[47], "%d", (boolprompt("Perform journaling of non-email messages", atoi(&sc[47][0])))); + if ((atoi(&sc[46][0])) || (atoi(&sc[47][0]))) { + strprompt("Email destination of journalized messages", &sc[48][0], 127); + } + + // No more Funambol + sc[53][0] = 0; + sc[54][0] = 0; + sc[55][0] = 0; + sc[56][0] = 0; + + // External pager stuff + int yes_pager = 0; + if (strlen(sc[60]) > 0) { + yes_pager = 1; + } + yes_pager = boolprompt("Configure an external pager tool", yes_pager); + if (yes_pager) { + strprompt("External pager tool", &sc[60][0], 255); + } + else { + sc[60][0] = 0; + } + + // Save it + scr_printf("Save this configuration? "); + if (yesno()) { + r = 1; + for (a = 0; a < NUM_CONFIGS; a++) { + r += 1 + strlen(sc[a]); + } + resp = (char *) calloc(1, r); + if (!resp) { + scr_printf("Can't save config - out of memory!\n"); + logoff(ipc, 1); + } + for (a = 0; a < NUM_CONFIGS; a++) { + strcat(resp, sc[a]); + strcat(resp, "\n"); + } + r = CtdlIPCSetSystemConfig(ipc, resp, buf); + if (r / 100 != 4) { + scr_printf("%s\n", buf); + } + free(resp); + + r = CtdlIPCSetMessageExpirationPolicy(ipc, 2, site_expirepolicy, buf); + if (r / 100 != 2) { + scr_printf("%s\n", buf); + } + + r = CtdlIPCSetMessageExpirationPolicy(ipc, 3, mbx_expirepolicy, buf); + if (r / 100 != 2) { + scr_printf("%s\n", buf); + } + + } + if (site_expirepolicy) + free(site_expirepolicy); + if (mbx_expirepolicy) + free(mbx_expirepolicy); +} + + +// support function for do_internet_configuration() +void get_inet_rec_type(CtdlIPC * ipc, char *buf) { + int sel; + + keyopt(" <1> localhost (Alias for this computer)\n"); + keyopt(" <2> smart host (Forward all outbound mail to this host)\n"); + keyopt(" <3> fallback host (Send mail to this host only if direct delivery fails)\n"); + keyopt(" <4> SpamAssassin (Address of SpamAssassin server)\n"); + keyopt(" <5> RBL (domain suffix of spam hunting RBL)\n"); + keyopt(" <6> masq domains (Domains as which users are allowed to masquerade)\n"); + keyopt(" <7> ClamAV (Address of ClamAV clamd server)\n"); + sel = intprompt("Which one", 1, 1, 8); + switch (sel) { + case 1: + strcpy(buf, "localhost"); + return; + case 2: + strcpy(buf, "smarthost"); + return; + case 3: + strcpy(buf, "fallbackhost"); + return; + case 4: + strcpy(buf, "spamassassin"); + return; + case 5: + strcpy(buf, "rbl"); + return; + case 6: + strcpy(buf, "masqdomain"); + return; + case 7: + strcpy(buf, "clamav"); + return; + } +} + + +// Internet mail configuration +void do_internet_configuration(CtdlIPC * ipc) { + char buf[256]; + char *resp = NULL; + int num_recs = 0; + char **recs = NULL; + char ch; + int i, j; + int quitting = 0; + int modified = 0; + int r; + + r = CtdlIPCGetSystemConfigByType(ipc, INTERNETCFG, &resp, buf); + if (r / 100 == 1) { + while (!IsEmptyStr(resp)) { + extract_token(buf, resp, 0, '\n', sizeof buf); + remove_token(resp, 0, '\n'); + + // "directory" is no longer used. replace it with "localhost" + char *d = strstr(buf, "|directory"); + if (d != NULL) { + strcpy(d, "|localhost"); + } + + ++num_recs; + if (num_recs == 1) + recs = malloc(sizeof(char *)); + else + recs = realloc(recs, (sizeof(char *)) * num_recs); + recs[num_recs - 1] = malloc(strlen(buf) + 1); + strcpy(recs[num_recs - 1], buf); + } + } + if (resp) { + free(resp); + } + + do { + scr_printf("\n"); + color(BRIGHT_WHITE); + scr_printf("### Host or domain Record type \n"); + color(DIM_WHITE); + scr_printf("--- -------------------------------------------------- --------------------\n"); + for (i = 0; i < num_recs; ++i) { + color(DIM_WHITE); + scr_printf("%3d ", i + 1); + extract_token(buf, recs[i], 0, '|', sizeof buf); + color(BRIGHT_CYAN); + scr_printf("%-50s ", buf); + extract_token(buf, recs[i], 1, '|', sizeof buf); + color(BRIGHT_MAGENTA); + scr_printf("%-20s\n", buf); + color(DIM_WHITE); + } + + ch = keymenu("", "dd|elete|ave|uit"); + switch (ch) { + case 'a': + newprompt("Enter host name: ", buf, 50); + striplt(buf); + if (!IsEmptyStr(buf)) { + ++num_recs; + if (num_recs == 1) { + recs = malloc(sizeof(char *)); + } + else { + recs = realloc(recs, (sizeof(char *)) * num_recs); + } + strcat(buf, "|"); + get_inet_rec_type(ipc, &buf[strlen(buf)]); + recs[num_recs - 1] = strdup(buf); + } + modified = 1; + break; + case 'd': + i = intprompt("Delete which one", 1, 1, num_recs) - 1; + free(recs[i]); + --num_recs; + for (j = i; j < num_recs; ++j) { + recs[j] = recs[j + 1]; + } + modified = 1; + break; + case 's': + r = 1; + for (i = 0; i < num_recs; i++) { + r += 1 + strlen(recs[i]); + } + resp = (char *) calloc(1, r); + if (!resp) { + scr_printf("Can't save config - out of memory!\n"); + logoff(ipc, 1); + } + if (num_recs) + for (i = 0; i < num_recs; i++) { + strcat(resp, recs[i]); + strcat(resp, "\n"); + } + r = CtdlIPCSetSystemConfigByType(ipc, INTERNETCFG, resp, buf); + if (r / 100 != 4) { + scr_printf("%s\n", buf); + } + else { + scr_printf("Wrote %d records.\n", num_recs); + modified = 0; + } + free(resp); + break; + case 'q': + quitting = !modified || boolprompt("Quit without saving", 0); + break; + default: + break; + } + } while (!quitting); + + if (recs != NULL) { + for (i = 0; i < num_recs; ++i) + free(recs[i]); + free(recs); + } +} + + +// Edit network configuration for room sharing, mailing lists, etc. +void network_config_management(CtdlIPC * ipc, char *entrytype, char *comment) { + char filename[PATH_MAX]; + char changefile[PATH_MAX]; + int e_ex_code; + pid_t editor_pid; + int cksum; + int b, i, tokens; + char buf[1024]; + char instr[1024]; + char addr[1024]; + FILE *tempfp; + FILE *changefp; + char *listing = NULL; + int r; + + if (IsEmptyStr(editor_path)) { + scr_printf("You must have an external editor configured in order to use this function.\n"); + return; + } + + CtdlMakeTempFileName(filename, sizeof filename); + CtdlMakeTempFileName(changefile, sizeof changefile); + + tempfp = fopen(filename, "w"); + if (tempfp == NULL) { + scr_printf("Cannot open %s: %s\n", filename, strerror(errno)); + return; + } + + fprintf(tempfp, "# Configuration for room: %s\n", room_name); + fprintf(tempfp, "# %s\n", comment); + fprintf(tempfp, "# Specify one per line.\n" "\n\n"); + + r = CtdlIPCGetRoomNetworkConfig(ipc, &listing, buf); + if (r / 100 == 1) { + while (listing && !IsEmptyStr(listing)) { + extract_token(buf, listing, 0, '\n', sizeof buf); + remove_token(listing, 0, '\n'); + extract_token(instr, buf, 0, '|', sizeof instr); + if (!strcasecmp(instr, entrytype)) { + tokens = num_tokens(buf, '|'); + for (i = 1; i < tokens; ++i) { + extract_token(addr, buf, i, '|', sizeof addr); + fprintf(tempfp, "%s", addr); + if (i < (tokens - 1)) { + fprintf(tempfp, "|"); + } + } + fprintf(tempfp, "\n"); + } + } + } + if (listing) { + free(listing); + listing = NULL; + } + fclose(tempfp); + + e_ex_code = 1; // start with a failed exit code + stty_ctdl(SB_RESTORE); + editor_pid = fork(); + cksum = file_checksum(filename); + if (editor_pid == 0) { + chmod(filename, 0600); + putenv("WINDOW_TITLE=Network configuration"); + execlp(editor_path, editor_path, filename, NULL); + exit(1); + } + if (editor_pid > 0) { + do { + e_ex_code = 0; + b = ka_wait(&e_ex_code); + } while ((b != editor_pid) && (b >= 0)); + editor_pid = (-1); + stty_ctdl(0); + } + + if (file_checksum(filename) == cksum) { + scr_printf("*** No changes to save.\n"); + e_ex_code = 1; + } + + if (e_ex_code == 0) { // Save changes + changefp = fopen(changefile, "w"); + + // Load all netconfig entries that are *not* of the type we are editing + r = CtdlIPCGetRoomNetworkConfig(ipc, &listing, buf); + if (r / 100 == 1) { + while (listing && !IsEmptyStr(listing)) { + extract_token(buf, listing, 0, '\n', sizeof buf); + remove_token(listing, 0, '\n'); + extract_token(instr, buf, 0, '|', sizeof instr); + if (strcasecmp(instr, entrytype)) { + fprintf(changefp, "%s\n", buf); + } + } + } + if (listing) { + free(listing); + listing = NULL; + } + + // ...and merge that with the data we just edited + tempfp = fopen(filename, "r"); + while (fgets(buf, sizeof buf, tempfp) != NULL) { + for (i = 0; i < strlen(buf); ++i) { + if (buf[i] == '#') + buf[i] = 0; + } + striplt(buf); + if (!IsEmptyStr(buf)) { + fprintf(changefp, "%s|%s\n", entrytype, buf); + } + } + fclose(tempfp); + fclose(changefp); + + // now write it to the server... + changefp = fopen(changefile, "r"); + if (changefp != NULL) { + listing = load_message_from_file(changefp); + if (listing) { + r = CtdlIPCSetRoomNetworkConfig(ipc, listing, buf); + free(listing); + listing = NULL; + } + fclose(changefp); + } + } + + unlink(filename); // Delete the temporary files + unlink(changefile); +} + + +// POP3 aggregation client configuration +void do_pop3client_configuration(CtdlIPC * ipc) { + char buf[SIZ]; + int num_recs = 0; + char **recs = NULL; + char ch; + int i, j; + int quitting = 0; + int modified = 0; + char *listing = NULL; + char *other_listing = NULL; + int r; + char instr[SIZ]; + + r = CtdlIPCGetRoomNetworkConfig(ipc, &listing, buf); + if (r / 100 == 1) { + while (listing && !IsEmptyStr(listing)) { + extract_token(buf, listing, 0, '\n', sizeof buf); + remove_token(listing, 0, '\n'); + extract_token(instr, buf, 0, '|', sizeof instr); + if (!strcasecmp(instr, "pop3client")) { + + ++num_recs; + if (num_recs == 1) + recs = malloc(sizeof(char *)); + else + recs = realloc(recs, (sizeof(char *)) * num_recs); + recs[num_recs - 1] = malloc(SIZ); + strcpy(recs[num_recs - 1], buf); + + } + } + } + if (listing) { + free(listing); + listing = NULL; + } + + do { + scr_printf("\n"); + color(BRIGHT_WHITE); + scr_printf("### " " Remote POP3 host " " User name " "Keep on server? " "\n"); + color(DIM_WHITE); + scr_printf("--- " "---------------------------- " "---------------------------- " "--------------- " "\n"); + for (i = 0; i < num_recs; ++i) { + color(DIM_WHITE); + scr_printf("%3d ", i + 1); + + extract_token(buf, recs[i], 1, '|', sizeof buf); + color(BRIGHT_CYAN); + scr_printf("%-28s ", buf); + + extract_token(buf, recs[i], 2, '|', sizeof buf); + color(BRIGHT_MAGENTA); + scr_printf("%-28s ", buf); + + color(BRIGHT_CYAN); + scr_printf("%-15s\n", (extract_int(recs[i], 4) ? "Yes" : "No")); + color(DIM_WHITE); + } + + ch = keymenu("", "dd|elete|ave|uit"); + switch (ch) { + case 'a': + ++num_recs; + if (num_recs == 1) { + recs = malloc(sizeof(char *)); + } else { + recs = realloc(recs, (sizeof(char *)) * num_recs); + } + strcpy(buf, "pop3client|"); + newprompt("Enter host name: ", &buf[strlen(buf)], 28); + strcat(buf, "|"); + newprompt("Enter user name: ", &buf[strlen(buf)], 28); + strcat(buf, "|"); + newprompt("Enter password : ", &buf[strlen(buf)], 16); + strcat(buf, "|"); + scr_printf("Keep messages on server instead of deleting them? "); + sprintf(&buf[strlen(buf)], "%d", yesno()); + strcat(buf, "|"); + recs[num_recs - 1] = strdup(buf); + modified = 1; + break; + case 'd': + i = intprompt("Delete which one", 1, 1, num_recs) - 1; + free(recs[i]); + --num_recs; + for (j = i; j < num_recs; ++j) + recs[j] = recs[j + 1]; + modified = 1; + break; + case 's': + r = 1; + for (i = 0; i < num_recs; ++i) { + r += 1 + strlen(recs[i]); + } + listing = (char *) calloc(1, r); + if (!listing) { + scr_printf("Can't save config - out of memory!\n"); + logoff(ipc, 1); + } + if (num_recs) + for (i = 0; i < num_recs; ++i) { + strcat(listing, recs[i]); + strcat(listing, "\n"); + } + + // Retrieve all the *other* records for merging + r = CtdlIPCGetRoomNetworkConfig(ipc, &other_listing, buf); + if (r / 100 == 1) { + for (i = 0; i < num_tokens(other_listing, '\n'); ++i) { + extract_token(buf, other_listing, i, '\n', sizeof buf); + if (strncasecmp(buf, "pop3client|", 11)) { + listing = realloc(listing, strlen(listing) + strlen(buf) + 10); + strcat(listing, buf); + strcat(listing, "\n"); + } + } + } + free(other_listing); + r = CtdlIPCSetRoomNetworkConfig(ipc, listing, buf); + free(listing); + listing = NULL; + + if (r / 100 != 4) { + scr_printf("%s\n", buf); + } + else { + scr_printf("Wrote %d records.\n", num_recs); + modified = 0; + } + quitting = 1; + break; + case 'q': + quitting = !modified || boolprompt("Quit without saving", 0); + break; + default: + break; + } + } while (!quitting); + + if (recs != NULL) { + for (i = 0; i < num_recs; ++i) { + free(recs[i]); + } + free(recs); + } +} + + +// RSS feed retrieval client configuration +void do_rssclient_configuration(CtdlIPC * ipc) { + char buf[SIZ]; + int num_recs = 0; + char **recs = NULL; + char ch; + int i, j; + int quitting = 0; + int modified = 0; + char *listing = NULL; + char *other_listing = NULL; + int r; + char instr[SIZ]; + + r = CtdlIPCGetRoomNetworkConfig(ipc, &listing, buf); + if (r / 100 == 1) { + while (listing && !IsEmptyStr(listing)) { + extract_token(buf, listing, 0, '\n', sizeof buf); + remove_token(listing, 0, '\n'); + extract_token(instr, buf, 0, '|', sizeof instr); + if (!strcasecmp(instr, "rssclient")) { + + ++num_recs; + if (num_recs == 1) + recs = malloc(sizeof(char *)); + else + recs = realloc(recs, (sizeof(char *)) * num_recs); + recs[num_recs - 1] = malloc(SIZ); + strcpy(recs[num_recs - 1], buf); + + } + } + } + if (listing) { + free(listing); + listing = NULL; + } + + do { + scr_printf("\n"); + color(BRIGHT_WHITE); + scr_printf("### Feed URL\n"); + color(DIM_WHITE); + scr_printf("--- " "---------------------------------------------------------------------------" "\n"); + + for (i = 0; i < num_recs; ++i) { + color(DIM_WHITE); + scr_printf("%3d ", i + 1); + + extract_token(buf, recs[i], 1, '|', sizeof buf); + color(BRIGHT_CYAN); + scr_printf("%-75s\n", buf); + + color(DIM_WHITE); + } + + ch = keymenu("", "dd|elete|ave|uit"); + switch (ch) { + case 'a': + ++num_recs; + if (num_recs == 1) { + recs = malloc(sizeof(char *)); + } + else { + recs = realloc(recs, (sizeof(char *)) * num_recs); + } + strcpy(buf, "rssclient|"); + newprompt("Enter feed URL: ", &buf[strlen(buf)], 75); + strcat(buf, "|"); + recs[num_recs - 1] = strdup(buf); + modified = 1; + break; + case 'd': + i = intprompt("Delete which one", 1, 1, num_recs) - 1; + free(recs[i]); + --num_recs; + for (j = i; j < num_recs; ++j) + recs[j] = recs[j + 1]; + modified = 1; + break; + case 's': + r = 1; + for (i = 0; i < num_recs; ++i) { + r += 1 + strlen(recs[i]); + } + listing = (char *) calloc(1, r); + if (!listing) { + scr_printf("Can't save config - out of memory!\n"); + logoff(ipc, 1); + } + if (num_recs) + for (i = 0; i < num_recs; ++i) { + strcat(listing, recs[i]); + strcat(listing, "\n"); + } + + // Retrieve all the *other* records for merging + r = CtdlIPCGetRoomNetworkConfig(ipc, &other_listing, buf); + if (r / 100 == 1) { + for (i = 0; i < num_tokens(other_listing, '\n'); ++i) { + extract_token(buf, other_listing, i, '\n', sizeof buf); + if (strncasecmp(buf, "rssclient|", 10)) { + listing = realloc(listing, strlen(listing) + strlen(buf) + 10); + strcat(listing, buf); + strcat(listing, "\n"); + } + } + } + free(other_listing); + r = CtdlIPCSetRoomNetworkConfig(ipc, listing, buf); + free(listing); + listing = NULL; + + if (r / 100 != 4) { + scr_printf("%s\n", buf); + } + else { + scr_printf("Wrote %d records.\n", num_recs); + modified = 0; + } + quitting = 1; + break; + case 'q': + quitting = !modified || boolprompt("Quit without saving", 0); + break; + default: + break; + } + } while (!quitting); + + if (recs != NULL) { + for (i = 0; i < num_recs; ++i) + free(recs[i]); + free(recs); + } +} diff --git a/webcit-ng/static/css/webcit.css b/webcit-ng/static/css/webcit.css index f4dd27364..20c7c64de 100644 --- a/webcit-ng/static/css/webcit.css +++ b/webcit-ng/static/css/webcit.css @@ -17,7 +17,7 @@ .ctdl-grid-navbar-item { grid-area: menu; - background-color: GhostWhite; + background-color: #f0f0f0; } .ctdl-grid-main-item { @@ -79,8 +79,13 @@ html,body,h1,h2,h3,h4,h5 { .ctdl-sidebar-class button { width: 100%; text-align: left; - background-color: #888888; - color: #FFFFFF; + border-color: #f0f0f0; + background-color: #f0f0f0; + color: #000000; +} + +.ctdl-sidebar-class button:hover { + background-color: #ffffff; } .ctdl-grid-banner-item button { diff --git a/webcit-ng/tls2.c~ b/webcit-ng/tls2.c~ new file mode 100644 index 000000000..d3bc51a0f --- /dev/null +++ b/webcit-ng/tls2.c~ @@ -0,0 +1,237 @@ +// Functions in this module handle SSL encryption when WebCit is running +// as an HTTPS server. +// +// Copyright (c) 1996-2022 by the citadel.org team +// +// This program is open source software. Use, duplication, or +// disclosure are subject to the GNU General Public License v3. + +#include "webcit.h" + +SSL_CTX *ssl_ctx; // global SSL context +char key_file[PATH_MAX] = ""; +char cert_file[PATH_MAX] = ""; +char *ssl_cipher_list = DEFAULT_SSL_CIPHER_LIST; + + +// Set the private key and certificate chain for the global SSL Context. +// This is called during initialization, and can be called again later if the certificate changes. +void bind_to_key_and_certificate(void) +{ + SSL_CTX *old_ctx, *new_ctx; + + if (!(new_ctx = SSL_CTX_new(SSLv23_server_method()))) { + syslog(LOG_WARNING, "SSL_CTX_new failed: %s", + ERR_reason_error_string(ERR_get_error())); + return; + } + + syslog(LOG_INFO, "Requesting cipher list: %s", ssl_cipher_list); + if (!(SSL_CTX_set_cipher_list(new_ctx, ssl_cipher_list))) { + syslog(LOG_WARNING, "SSL_CTX_set_cipher_list failed: %s", + ERR_reason_error_string(ERR_get_error())); + return; + } + + if (IsEmptyStr(key_file)) { + snprintf(key_file, sizeof key_file, "%s/keys/citadel.key", + ctdl_dir); + } + if (IsEmptyStr(cert_file)) { + snprintf(cert_file, sizeof key_file, "%s/keys/citadel.cer", + ctdl_dir); + } + + syslog(LOG_DEBUG, + "crypto: [re]installing key \"%s\" and certificate \"%s\"", + key_file, cert_file); + + SSL_CTX_use_certificate_chain_file(new_ctx, cert_file); + SSL_CTX_use_PrivateKey_file(new_ctx, key_file, SSL_FILETYPE_PEM); + + if (!SSL_CTX_check_private_key(new_ctx)) { + syslog(LOG_WARNING, + "crypto: cannot install certificate: %s", + ERR_reason_error_string(ERR_get_error())); + } + + old_ctx = ssl_ctx; + ssl_ctx = new_ctx; + sleep(1); + SSL_CTX_free(old_ctx); +} + + +// Initialize ssl engine, load certs and initialize openssl internals +void init_ssl(void) +{ + + // Initialize the OpenSSL library + SSL_load_error_strings(); + ERR_load_crypto_strings(); + OpenSSL_add_all_algorithms(); + SSL_library_init(); + + // Now try to bind to the key and certificate. + bind_to_key_and_certificate(); +} + + +// Check the modification time of the key and certificate -- reload if they changed +void update_key_and_cert_if_needed(void) +{ + static time_t previous_mtime = 0; + struct stat keystat; + struct stat certstat; + + if (stat(key_file, &keystat) != 0) { + syslog(LOG_ERR, "%s: %s", key_file, strerror(errno)); + return; + } + if (stat(cert_file, &certstat) != 0) { + syslog(LOG_ERR, "%s: %s", cert_file, strerror(errno)); + return; + } + + if ((keystat.st_mtime + certstat.st_mtime) != previous_mtime) { + bind_to_key_and_certificate(); + previous_mtime = keystat.st_mtime + certstat.st_mtime; + } +} + + +// starts SSL/TLS encryption for the current session. +void starttls(struct client_handle *ch) +{ + int retval, bits, alg_bits; + + if (!ssl_ctx) { + return; + } + // Check the modification time of the key and certificate -- reload if they changed + update_key_and_cert_if_needed(); + + if (!(ch->ssl_handle = SSL_new(ssl_ctx))) { + syslog(LOG_WARNING, "SSL_new failed: %s", + ERR_reason_error_string(ERR_get_error())); + return; + } + if (!(SSL_set_fd(ch->ssl_handle, ch->sock))) { + syslog(LOG_WARNING, "SSL_set_fd failed: %s", + ERR_reason_error_string(ERR_get_error())); + SSL_free(ch->ssl_handle); + return; + } + retval = SSL_accept(ch->ssl_handle); + if (retval < 1) { + syslog(LOG_WARNING, "SSL_accept failed: %s", + ERR_reason_error_string(ERR_get_error())); + } else { + syslog(LOG_INFO, "SSL_accept success"); + } + bits = + SSL_CIPHER_get_bits(SSL_get_current_cipher(ch->ssl_handle), + &alg_bits); + syslog(LOG_INFO, "SSL/TLS using %s on %s (%d of %d bits)", + SSL_CIPHER_get_name(SSL_get_current_cipher(ch->ssl_handle)), + SSL_CIPHER_get_version(SSL_get_current_cipher + (ch->ssl_handle)), bits, alg_bits); + syslog(LOG_INFO, "SSL started"); +} + + +// shuts down the TLS connection +void endtls(struct client_handle *ch) +{ + syslog(LOG_INFO, "Ending SSL/TLS"); + if (ch->ssl_handle != NULL) { + SSL_shutdown(ch->ssl_handle); + SSL_get_SSL_CTX(ch->ssl_handle); + SSL_free(ch->ssl_handle); + } + ch->ssl_handle = NULL; +} + + +// Send binary data to the client encrypted. +int client_write_ssl(struct client_handle *ch, char *buf, int nbytes) +{ + int retval; + int nremain; + char junk[1]; + + if (ch->ssl_handle == NULL) + return (-1); + + nremain = nbytes; + while (nremain > 0) { + if (SSL_want_write(ch->ssl_handle)) { + if ((SSL_read(ch->ssl_handle, junk, 0)) < 1) { + syslog(LOG_WARNING, + "SSL_read in client_write: %s", + ERR_reason_error_string + (ERR_get_error())); + } + } + retval = + SSL_write(ch->ssl_handle, &buf[nbytes - nremain], + nremain); + if (retval < 1) { + long errval; + + errval = SSL_get_error(ch->ssl_handle, retval); + if (errval == SSL_ERROR_WANT_READ + || errval == SSL_ERROR_WANT_WRITE) { + sleep(1); + continue; + } + syslog(LOG_WARNING, "SSL_write: %s", + ERR_reason_error_string(ERR_get_error())); + if (retval == -1) { + syslog(LOG_WARNING, "errno is %d", errno); + endtls(ch); + } + return -1; + } + nremain -= retval; + } + return 0; +} + + +// read data from the encrypted layer +int client_read_ssl(struct client_handle *ch, char *buf, int nbytes) +{ + int bytes_read = 0; + int rlen = 0; + char junk[1]; + + if (ch->ssl_handle == NULL) + return (-1); + + while (bytes_read < nbytes) { + if (SSL_want_read(ch->ssl_handle)) { + if ((SSL_write(ch->ssl_handle, junk, 0)) < 1) { + syslog(LOG_WARNING, + "SSL_write in client_read"); + } + } + rlen = + SSL_read(ch->ssl_handle, &buf[bytes_read], + nbytes - bytes_read); + if (rlen < 1) { + long errval; + errval = SSL_get_error(ch->ssl_handle, rlen); + if (errval == SSL_ERROR_WANT_READ + || errval == SSL_ERROR_WANT_WRITE) { + sleep(1); + continue; + } + syslog(LOG_WARNING, "SSL_read error %ld", errval); + endtls(ch); + return (-1); + } + bytes_read += rlen; + } + return (bytes_read); +}