]> code.citadel.org Git - citadel.git/commitdiff
* Use a new IPC API (in citadel_ipc.c). Partially converted citadel.c to
authorMichael Hampton <io_error@uncensored.citadel.org>
Sat, 22 Jun 2002 20:09:16 +0000 (20:09 +0000)
committerMichael Hampton <io_error@uncensored.citadel.org>
Sat, 22 Jun 2002 20:09:16 +0000 (20:09 +0000)
  use the new API.

citadel/ChangeLog
citadel/Makefile.in
citadel/citadel.c
citadel/citadel.h
citadel/citadel_ipc.c [new file with mode: 0644]
citadel/citadel_ipc.h [new file with mode: 0644]

index fbe9df470e85e7c4c7f08a7338546b9840afa691..781088d4cbac75d005dd50a62082a44c836317ad 100644 (file)
@@ -1,4 +1,8 @@
  $Log$
+ Revision 591.51  2002/06/22 20:09:16  error
+ * Use a new IPC API (in citadel_ipc.c).  Partially converted citadel.c to
+   use the new API.
+
  Revision 591.50  2002/06/21 12:32:48  ajc
  * Minor documentation update
 
@@ -3756,3 +3760,4 @@ Sat Jul 11 00:20:48 EDT 1998 Nathan Bryant <bryant@cs.usm.maine.edu>
 
 Fri Jul 10 1998 Art Cancro <ajc@uncensored.citadel.org>
        * Initial CVS import
+
index 59652a93cc96ea3fa9e94cf3b9fc8d7a2317b979..9dd2e2e81376ab95ba4a16fa269be64d67e645cb 100644 (file)
@@ -88,7 +88,7 @@ SOURCES=aidepost.c citadel.c citmail.c citserver.c client_chat.c \
        serv_smtp.c serv_pop3.c internet_addressing.c parsedate.c genstamp.c \
        $(DOMAIN) clientsocket.c serv_inetcfg.c serv_rwho.c serv_bio.c \
        client_passwords.c imap_misc.c serv_netfilter.c serv_mrtg.c \
-       serv_spam.c \
+       serv_spam.c citadel_ipc.c \
        serv_imap.c imap_tools.c imap_fetch.c imap_search.c imap_store.c \
        serv_network.c serv_pas2.c serv_ical.c md5.c server_main.c
 
@@ -108,11 +108,11 @@ serv_modules: $(SERV_MODULES)
 #
 
 citadel$(EXEEXT): ipc_c_tcp.o citadel.o rooms.o routines.o \
-       routines2.o messages.o screen.o \
+       routines2.o messages.o screen.o citadel_ipc.o \
        client_passwords.o md5.o client_crypto.o \
        commands.o client_chat.o serv_info.o tools.o $(LIBOBJS)
        $(CC) ipc_c_tcp.o citadel.o rooms.o routines.o \
-       routines2.o messages.o screen.o \
+       routines2.o messages.o screen.o citadel_ipc.o \
        commands.o client_chat.o serv_info.o tools.o \
        client_passwords.o md5.o client_crypto.o \
        $(LIBOBJS) $(LDFLAGS) -o citadel $(LIBS)
index 035677e1f1de14c08cfe9161ff4f0932a2b0abc3..7d5a9f5529a1f5ee3f2bbe6210d6d798a7bb2448 100644 (file)
@@ -42,6 +42,7 @@
 #include "messages.h"
 #include "commands.h"
 #include "ipc.h"
+#include "citadel_ipc.h"
 #include "client_chat.h"
 #include "client_passwords.h"
 #include "citadel_decls.h"
 
 #include "md5.h"
 
-struct march {
-       struct march *next;
-       char march_name[ROOMNAMELEN];
-       char march_floor;
-       char march_order;
-       };
-
 #define IFEXPERT if (userflags&US_EXPERT)
 #define IFNEXPERT if ((userflags&US_EXPERT)==0)
 #define IFAIDE if (axlevel>=6)
@@ -132,7 +126,7 @@ void logoff(int code)
  * that means we're exiting because we already lost the server
  */
        if (code != 3)
-               serv_puts("QUIT");
+               CtdlIPCQuit();
 
 /*
  * now clean up various things
@@ -335,47 +329,43 @@ void dotgoto(char *towhere, int display_name, int fromungoto)
        static int oldmailcount = (-1);
        int partial_match, best_match;
        char from_floor;
-    int ugpos = uglistsize;
+       int ugpos = uglistsize;
+       int r;                          /* IPC result code */
+       static struct ctdlipcroom *roomrec = NULL;
 
        /* store ungoto information */
-    if (fromungoto == 0)
-      {
-        if (uglistsize >= (UGLISTLEN-1))
-          {  /* sloppy slide them all down, hey it's the client, who cares. :-) */
-            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++;
+       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;
-      }
+               uglist[ugpos] = malloc(strlen(room_name)+1);
+               strcpy(uglist[ugpos], room_name);
+               uglistlsn[ugpos] = ls;
+       }
       
+       if (roomrec) {
+               free(roomrec);
+               roomrec = NULL;
+       }
+
        /* first try an exact match */
-       snprintf(aaa, sizeof aaa, "GOTO %s", towhere);
-       serv_puts(aaa);
-       serv_gets(aaa);
-       if (aaa[3] == '*')
-               express_msgs = 1;
-       if (!strncmp(aaa, "54", 2)) {
+       r = CtdlIPCGotoRoom(towhere, "", &roomrec, aaa);
+       if (r / 10 == 54) {
                newprompt("Enter room password: ", bbb, 9);
-               snprintf(aaa, sizeof aaa, "GOTO %s|%s", towhere, bbb);
-               serv_puts(aaa);
-               serv_gets(aaa);
-               if (aaa[3] == '*')
-                       express_msgs = 1;
-       }
-       if (!strncmp(aaa, "54", 2)) {
-               scr_printf("Wrong password.\n");
-               return;
+               r = CtdlIPCGotoRoom(towhere, bbb, &roomrec, aaa);
+               if (r / 10 == 54) {
+                       scr_printf("Wrong password.\n");
+                       return;
+               }
        }
        /*
         * If a match is not found, try a partial match.
@@ -383,7 +373,7 @@ void dotgoto(char *towhere, int display_name, int fromungoto)
         * left-aligned matches carry a weight of 2.  Pick the room that
         * has the highest-weighted match.
         */
-       if (aaa[0] != '2') {
+       if (r / 100 != 2) {
                best_match = 0;
                strcpy(bbb, "");
                serv_puts("LKRA");
@@ -407,25 +397,21 @@ void dotgoto(char *towhere, int display_name, int fromungoto)
                        scr_printf("No room '%s'.\n", towhere);
                        return;
                }
-               snprintf(aaa, sizeof aaa, "GOTO %s", bbb);
-               serv_puts(aaa);
-               serv_gets(aaa);
-               if (aaa[3] == '*')
-                       express_msgs = 1;
+               r = CtdlIPCGotoRoom(bbb, "", &roomrec, aaa);
        }
-       if (aaa[0] != '2') {
+       if (r / 100 != 2) {
                scr_printf("%s\n", aaa);
                return;
        }
-       extract(room_name, &aaa[4], 0);
-       room_flags = extract_int(&aaa[4], 4);
+       safestrncpy(room_name, roomrec->RRname, ROOMNAMELEN);
+       room_flags = roomrec->RRflags;
        from_floor = curr_floor;
-       curr_floor = extract_int(&aaa[4], 10);
+       curr_floor = roomrec->RRfloor;
 
        remove_march(room_name, 0);
        if (!strcasecmp(towhere, "_BASEROOM_"))
                remove_march(towhere, 0);
-       if (!extract_int(&aaa[4], 1))
+       if (!roomrec->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)
@@ -440,41 +426,36 @@ void dotgoto(char *towhere, int display_name, int fromungoto)
        }
        if (display_name != 2) {
                color(BRIGHT_YELLOW);
-               scr_printf("%d ", extract_int(&aaa[4], 1));
+               scr_printf("%d ", roomrec->RRunread);
                color(DIM_WHITE);
                scr_printf("new of ");
                color(BRIGHT_YELLOW);
-               scr_printf("%d ", extract_int(&aaa[4], 2));
+               scr_printf("%d ", roomrec->RRtotal);
                color(DIM_WHITE);
                scr_printf("messages.\n");
        }
-       highest_msg_read = extract_int(&aaa[4], 6);
-       maxmsgnum = extract_int(&aaa[4], 5);
-       is_mail = (char) extract_int(&aaa[4], 7);
-       is_room_aide = (char) extract_int(&aaa[4], 8);
-       ls = extract_long(&aaa[4], 6);
+       highest_msg_read = roomrec->RRlastread;
+       maxmsgnum = roomrec->RRhighest;
+       is_mail = roomrec->RRismailbox;
+       is_room_aide = roomrec->RRaide;
+       ls = roomrec->RRlastread;
 
        /* read info file if necessary */
-       if (extract_int(&aaa[4], 3) > 0)
+       if (roomrec->RRinfoupdated > 0)
                readinfo();
 
        /* check for newly arrived mail if we can */
-       if (num_parms(&aaa[4]) >= 10) {
-               newmailcount = extract_int(&aaa[4], 9);
-               if (newmailcount > 0) {
-                       color(BRIGHT_RED);
-                       scr_printf("*** You have %d new mail message%s\n",
-                                       newmailcount - oldmailcount,
-                                       (newmailcount - oldmailcount == 1) ?
-                                       "" : "s");
-                       color(DIM_WHITE);
-               }
-               status_line(serv_info.serv_humannode, serv_info.serv_bbs_city,
-                               room_name, secure, newmailcount);
-       } else {
-               status_line(serv_info.serv_humannode, serv_info.serv_bbs_city,
-                               room_name, secure, -1);
+       newmailcount = roomrec->RRnewmail;
+       if (newmailcount > 0) {
+               color(BRIGHT_RED);
+               scr_printf("*** You have %d new mail message%s\n",
+                               newmailcount - oldmailcount,
+                               (newmailcount - oldmailcount == 1) ?
+                               "" : "s");
+               color(DIM_WHITE);
        }
+       status_line(serv_info.serv_humannode, serv_info.serv_bbs_city,
+                       room_name, secure, newmailcount);
 }
 
 /* Goto next room having unread messages.
@@ -748,10 +729,8 @@ int set_password(void)
        strproc(pass1);
        strproc(pass2);
        if (!strcasecmp(pass1, pass2)) {
-               snprintf(buf, sizeof buf, "SETP %s", pass1);
-               serv_puts(buf);
-               serv_gets(buf);
-               scr_printf("%s\n", &buf[4]);
+               CtdlIPCChangePassword(pass1, buf);
+               scr_printf("%s\n", buf);
                offer_to_remember_password(hostbuf, portbuf, fullname, pass1);
                return (0);
        } else {
@@ -772,20 +751,10 @@ void get_serv_info(char *supplied_hostname)
        CtdlInternalGetServInfo(&serv_info);
 
        /* be nice and identify ourself to the server */
-       snprintf(buf, sizeof buf, "IDEN %d|%d|%d|%s|",
-                SERVER_TYPE, 0, REV_LEVEL,
-                (server_is_local ? "local" : CITADEL));
-
-       /* Append a hostname */
-       if (supplied_hostname != NULL) {
-               strcat(buf, supplied_hostname);
-       }
-       else {
-               locate_host(&buf[strlen(buf)]); /* append to the end */
-       }
-
-       serv_puts(buf);
-       serv_gets(buf);         /* we don't care about the result code */
+       CtdlIPCIdentifySoftware(SERVER_TYPE, 0, REV_LEVEL,
+                (server_is_local ? "local" : CITADEL),
+                (supplied_hostname) ? supplied_hostname : "", buf);
+                /* (locate_host(buf), buf)); */
 }
 
 
@@ -804,20 +773,14 @@ void who_is_online(int longlist)
        time_t timenow = 0;
        time_t idletime, idlehours, idlemins, idlesecs;
        int last_session = (-1);
-    int skipidle = 0;
+       int skipidle = 0;
     
-    if (longlist == 2)
-      {
-        longlist = 0;
-        skipidle = 1;
-      }
-
-       serv_puts("TIME");
-       serv_gets(tbuf);
-       if (tbuf[0] == '2') {
-               timenow = extract_long(&tbuf[4], 0);
+       if (longlist == 2) {
+               longlist = 0;
+               skipidle = 1;
        }
-       else {
+
+       if (!(timenow = CtdlIPCServerTime(tbuf))) {
                time(&timenow);
        }
 
@@ -942,6 +905,9 @@ int main(int argc, char **argv)
        char hexstring[MD5_HEXSTRING_SIZE];
        int stored_password = 0;
        char password[SIZ];
+       struct ctdlipcmisc chek;
+       struct usersupp *myself;
+       int r;                          /* IPC result code */
 
        setIPCDeathHook(screen_delete);
        setIPCErrorPrintf(err_printf);
@@ -1147,10 +1113,8 @@ GSTA:    /* See if we have a username and password on disk */
                goto TERMN8;
        }
        /* sign on to the server */
-       snprintf(aaa, sizeof aaa, "USER %s", fullname);
-       serv_puts(aaa);
-       serv_gets(aaa);
-       if (aaa[0] != '3')
+       r = CtdlIPCTryLogin(fullname, aaa);
+       if (r / 100 != 3)
                goto NEWUSR;
 
        /* password authentication */
@@ -1221,10 +1185,9 @@ PWOK:
        }
        scr_printf("\n");
 
-       serv_puts("CHEK");
-       serv_gets(aaa);
-       if (aaa[0] == '2') {
-               b = extract_int(&aaa[4], 0);
+       r = CtdlIPCMiscCheck(&chek, aaa);
+       if (r / 100 == 2) {
+               b = chek.newmail;
                if (b > 0) {
                        color(BRIGHT_RED);
                        if (b == 1)
@@ -1233,10 +1196,10 @@ PWOK:
                                scr_printf("*** You have %d new private messages in Mail>\n", b);
                        color(DIM_WHITE);
                }
-               if ((axlevel >= 6) && (extract_int(&aaa[4], 2) > 0)) {
+               if ((axlevel >= 6) && (chek.needvalid > 0)) {
                        scr_printf("*** Users need validation\n");
                }
-               if (extract_int(&aaa[4], 1) > 0) {
+               if (chek.needregis > 0) {
                        scr_printf("*** Please register.\n");
                        formout("register");
                        entregis();
@@ -1258,11 +1221,10 @@ PWOK:
         */
        screenwidth = 80;
        screenheight = 24;
-       serv_puts("GETU");
-       serv_gets(aaa);
-       if (aaa[0] == '2') {
-               screenwidth = extract_int(&aaa[4], 0);
-               screenheight = extract_int(&aaa[4], 1);
+       r = CtdlIPCGetConfig(&myself, aaa);
+       if (r == 2) {
+               screenwidth = myself->USscreenwidth;
+               screenheight = myself->USscreenheight;
        }
        if (getenv("TERM") != NULL)
                if (!strcmp(getenv("TERM"), "xterm")) {
@@ -1463,26 +1425,20 @@ PWOK:
 
                        case 75:
                                enternew("roomname", aaa, 20);
-                               snprintf(bbb, sizeof bbb, "RCHG %s", aaa);
-                               serv_puts(bbb);
-                               serv_gets(aaa);
-                               if (strncmp("200", aaa, 3))
+                               r = CtdlIPCChangeRoomname(aaa, bbb);
+                               if (r / 100 != 2)
                                        scr_printf("\n%s\n", aaa);
                                break;
                        case 76:
                                enternew("hostname", aaa, 25);
-                               snprintf(bbb, sizeof bbb, "HCHG %s", aaa);
-                               serv_puts(bbb);
-                               serv_gets(aaa);
-                               if (strncmp("200", aaa, 3))
+                               r = CtdlIPCChangeHostname(aaa, bbb);
+                               if (r / 100 != 2)
                                        scr_printf("\n%s\n", aaa);
                                break;
                        case 77:
                                enternew("username", aaa, 32);
-                               snprintf(bbb, sizeof bbb, "UCHG %s", aaa);
-                               serv_puts(bbb);
-                               serv_gets(aaa);
-                               if (strncmp("200", aaa, 3))
+                               r = CtdlIPCChangeUsername(aaa, bbb);
+                               if (r / 100 != 2)
                                        scr_printf("\n%s\n", aaa);
                                break;
 
@@ -1514,10 +1470,9 @@ PWOK:
                                scr_printf("All users will be disconnected!  "
                                        "Really terminate the server? ");
                                if (yesno() == 1) {
-                                       serv_puts("DOWN");
-                                       serv_gets(aaa);
-                                       scr_printf("%s\n", &aaa[4]);
-                                       if (aaa[0]=='2') {
+                                       r = CtdlIPCTerminateServerNow(aaa);
+                                       scr_printf("%s\n", aaa);
+                                       if (r / 100 == 2) {
                                                updatels();
                                                a = 0;
                                                termn8 = 1;
@@ -1529,15 +1484,13 @@ PWOK:
                                scr_printf("Do you really want to schedule a "
                                        "server shutdown? ");
                                if (yesno() == 1) {
-                                       serv_puts("SCDN 1");
-                                       serv_gets(aaa);
-                                       if (aaa[0]=='2') {
-                                               if (atoi(&aaa[4])) {
+                                       r = CtdlIPCTerminateServerScheduled(1, aaa);
+                                       if (r / 100 == 2) {
+                                               if (atoi(aaa)) {
                                                        scr_printf(
 "The Citadel server will terminate when all users are logged off.\n"
                                                                );
-                                               }
-                                               else {
+                                               } else {
                                                        scr_printf(
 "The Citadel server will not terminate.\n"
                                                                );
@@ -1720,9 +1673,7 @@ TERMN8:   scr_printf("%s logged out.\n", fullname);
        if (mcmd == 30) {
                sln_printf("\n\nType 'off' to disconnect, or next user...\n");
        }
-       snprintf(aaa, sizeof aaa, "LOUT");
-       serv_puts(aaa);
-       serv_gets(aaa);
+       CtdlIPCLogout();
        screen_delete();
        sttybbs(SB_RESTORE);
        if ((mcmd == 29) || (mcmd == 15)) {
index 75a1dbb3f55be5121ce26fb11bd0eca06dffee23..bd30e3029c94b11c029db5f98716ad395de4b4ff 100644 (file)
@@ -124,6 +124,13 @@ struct config {
        char c_aideroom[ROOMNAMELEN];   /* Name of aideroom (Aide)          */
 };
 
+struct march {
+       struct march *next;
+       char march_name[ROOMNAMELEN];
+       char march_floor;
+       char march_order;
+};
+
 #define NODENAME               config.c_nodename
 #define FQDN                   config.c_fqdn
 #define HUMANNODE              config.c_humannode
diff --git a/citadel/citadel_ipc.c b/citadel/citadel_ipc.c
new file mode 100644 (file)
index 0000000..8c311a5
--- /dev/null
@@ -0,0 +1,1974 @@
+/* $Id$ */
+
+#include "sysdep.h"
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+#  include <sys/time.h>
+# else
+#  include <time.h>
+# endif
+#endif
+#include <stdio.h>
+#include <sys/types.h>
+#include <string.h>
+#include <malloc.h>
+#include <stdlib.h>
+#ifdef THREADED_CLIENT
+#include <pthread.h>
+#endif
+#include "citadel.h"
+#include "citadel_ipc.h"
+#include "client_crypto.h"
+#include "tools.h"
+
+#ifdef THREADED_CLIENT
+pthread_mutex_t rwlock;
+#endif
+extern char express_msgs;
+
+static volatile int download_in_progress = 0;  /* download file open */
+static volatile int upload_in_progress = 0;    /* upload file open */
+/* static volatile int serv_sock;      /* Socket on which we talk to server */
+
+
+/*
+ * Does nothing.  The server should always return 200.
+ */
+int CtdlIPCNoop(void)
+{
+       char aaa[128];
+
+       register int ret;
+
+       netio_lock();
+       serv_puts("NOOP");
+       serv_gets(aaa);
+       ret = atoi(aaa);
+       netio_unlock();
+       return ret;
+}
+
+
+/*
+ * Does nothing interesting.  The server should always return 200
+ * along with your string.
+ */
+int CtdlIPCEcho(const char *arg, char *cret)
+{
+       register int ret;
+       char *aaa;
+       
+       if (!arg) return -2;
+       if (!cret) return -2;
+
+       aaa = (char *)malloc(strlen(arg) + 6);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "ECHO %s", arg);
+       ret = CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/*
+ * Asks the server to close the connecction.
+ * Should always return 200.
+ */
+int CtdlIPCQuit(void)
+{
+       register int ret;
+       char aaa[128];
+
+       netio_lock();
+       serv_puts("QUIT");
+       serv_gets(aaa);
+       ret = atoi(aaa);
+       netio_unlock();
+       return ret;
+}
+
+
+/*
+ * Asks the server to logout.  Should always return 200, even if no user
+ * was logged in.  The user will not be logged in after this!
+ */
+int CtdlIPCLogout(void)
+{
+       register int ret;
+       char aaa[128];
+
+       netio_lock();
+       serv_puts("LOUT");
+       serv_gets(aaa);
+       ret = atoi(aaa);
+       netio_unlock();
+       return ret;
+}
+
+
+/*
+ * First stage of authentication - pass the username.  Returns 300 if the
+ * username is able to log in, with the username correctly spelled in cret.
+ * Returns various 500 error codes if the user doesn't exist, etc.
+ */
+int CtdlIPCTryLogin(const char *username, char *cret)
+{
+       register int ret;
+       char *aaa;
+
+       if (!username) return -2;
+       if (!cret) return -2;
+
+       aaa = (char *)malloc(strlen(username) + 6);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "USER %s", username);
+       ret = CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/*
+ * Second stage of authentication - provide password.  The server returns
+ * 200 and several arguments in cret relating to the user's account.
+ */
+int CtdlIPCTryPassword(const char *passwd, char *cret)
+{
+       register int ret;
+       char *aaa;
+
+       if (!passwd) return -2;
+       if (!cret) return -2;
+
+       aaa = (char *)malloc(strlen(passwd) + 6);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "PASS %s", passwd);
+       ret = CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/*
+ * Create a new user.  This returns 200 plus the same arguments as TryPassword
+ * unless there was a problem creating the account.
+ */
+int CtdlIPCCreateUser(const char *username, char *cret)
+{
+       register int ret;
+       char *aaa;
+
+       if (!username) return -2;
+       if (!cret) return -2;
+
+       aaa = (char *)malloc(strlen(username) + 6);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "NEWU %s", username);
+       ret = CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/*
+ * Changes the user's password.  Returns 200 if changed, errors otherwise.
+ */
+int CtdlIPCChangePassword(const char *passwd, char *cret)
+{
+       register int ret;
+       char *aaa;
+
+       if (!passwd) return -2;
+       if (!cret) return -2;
+
+       aaa = (char *)malloc(strlen(passwd) + 6);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "SETP %s", passwd);
+       ret = CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* LKRN */
+/* Caller must free the march list */
+/* which is 0 = LRMS, 1 = LKRN, 2 = LKRO, 3 = LKRA, 4 = LZRM */
+/* floor is -1 for all, or floornum */
+int CtdlIPCKnownRooms(int which, int floor, char *cret, struct march **listing)
+{
+       register int ret;
+       struct march *march = NULL;
+       static char *proto[] = {"LRMS", "LKRN", "LKRO", "LKRA", "LZRM" };
+       char aaa[256];
+       char *bbb = NULL;
+       size_t bbbsize;
+
+       if (!listing) return -2;
+       if (*listing) return -2;        /* Free the listing first */
+       if (!cret) return -2;
+       if (which < 0 || which > 4) return -2;
+       if (floor < -1) return -2;      /* Can't validate upper bound, sorry */
+
+       sprintf(aaa, "%s %d", proto[which], floor);
+       ret = CtdlIPCGenericCommand(aaa, NULL, 0, &bbb, &bbbsize, cret);
+       if (ret / 100 == 1) {
+               struct march *mptr;
+
+               while (strlen(bbb)) {
+                       int a;
+
+                       extract_token(aaa, bbb, 0, '\n');
+                       a = strlen(aaa);
+                       memmove(aaa, bbb + a + 1, strlen(bbb) - a - 1);
+                       mptr = (struct march *) malloc(sizeof (struct march));
+                       if (mptr) {
+                               mptr->next = NULL;
+                               extract(mptr->march_name, aaa, 0);
+                               mptr->march_floor = (char) extract_int(aaa, 2);
+                               mptr->march_order = (char) extract_int(aaa, 3);
+                               if (march == NULL)
+                                       march = mptr;
+                               else {
+                                       struct march *mptr2;
+
+                                       mptr2 = march;
+                                       while (mptr2->next != NULL)
+                                               mptr2 = mptr2->next;
+                                       mptr2->next = mptr;
+                               }
+                       }
+               }
+       }
+       *listing = march;
+       return ret;
+}
+
+
+/* GETU */
+/* Caller must free the struct usersupp; caller may pass an existing one */
+int CtdlIPCGetConfig(struct usersupp **uret, char *cret)
+{
+       register int ret;
+
+       if (!cret) return -2;
+       if (!uret) return -2;
+       if (!*uret) *uret = (struct usersupp *)calloc(1, sizeof (struct usersupp));
+       if (!*uret) return -1;
+
+       ret = CtdlIPCGenericCommand("GETU", NULL, 0, NULL, NULL, cret);
+       if (ret / 100 == 2) {
+               uret[0]->USscreenwidth = extract_int(cret, 0);
+               uret[0]->USscreenheight = extract_int(cret, 1);
+               uret[0]->flags = extract_int(cret, 2);
+       }
+       return ret;
+}
+
+
+/* SETU */
+int CtdlIPCSetConfig(struct usersupp *uret, char *cret)
+{
+       char aaa[48];
+
+       if (!uret) return -2;
+       if (!cret) return -2;
+
+       sprintf(aaa, "SETU %d|%d|%d",
+                       uret->USscreenwidth, uret->USscreenheight,
+                       uret->flags);
+       return CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+}
+
+
+/* GOTO */
+int CtdlIPCGotoRoom(const char *room, const char *passwd,
+               struct ctdlipcroom **rret, char *cret)
+{
+       register int ret;
+       char *aaa;
+
+       if (!cret) return -2;
+       if (!rret) return -2;
+       if (!*rret) *rret = (struct ctdlipcroom *)calloc(1, sizeof (struct ctdlipcroom));
+       if (!*rret) return -1;
+
+       if (passwd) {
+               aaa = (char *)malloc(strlen(room) + strlen(passwd) + 7);
+               if (!aaa) {
+                       free(*rret);
+                       return -1;
+               }
+               sprintf(aaa, "GOTO %s|%s", room, passwd);
+       } else {
+               aaa = (char *)malloc(strlen(room) + 6);
+               if (!aaa) {
+                       free(*rret);
+                       return -1;
+               }
+               sprintf(aaa, "GOTO %s", room);
+       }
+       ret = CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+       if (ret / 100 == 2) {
+               extract(rret[0]->RRname, cret, 0);
+               rret[0]->RRunread = extract_long(cret, 1);
+               rret[0]->RRtotal = extract_long(cret, 2);
+               rret[0]->RRinfoupdated = extract_int(cret, 3);
+               rret[0]->RRflags = extract_int(cret, 4);
+               rret[0]->RRhighest = extract_long(cret, 5);
+               rret[0]->RRlastread = extract_long(cret, 6);
+               rret[0]->RRismailbox = extract_int(cret, 7);
+               rret[0]->RRaide = extract_int(cret, 8);
+               rret[0]->RRnewmail = extract_long(cret, 9);
+               rret[0]->RRfloor = extract_int(cret, 10);
+       } else {
+               free(*rret);
+       }
+       return ret;
+}
+
+
+/* MSGS */
+/* which is 0 = all, 1 = old, 2 = new, 3 = last, 4 = first, 5 = gt, 6 = lt */
+/* whicharg is number of messages, applies to last, first, gt, lt */
+int CtdlIPCGetMessages(int which, int whicharg, const char *template,
+               long **mret, char *cret)
+{
+       register int ret;
+       register long count = 0;
+       static char *proto[] =
+               { "ALL", "OLD", "NEW", "LAST", "FIRST", "GT", "LT" };
+       char aaa[33];
+       char *bbb;
+       size_t bbbsize;
+
+       if (!cret) return -2;
+       if (!mret) return -2;
+       if (*mret) return -2;
+       if (which < 0 || which > 6) return -2;
+
+       if (which <= 2)
+               sprintf(aaa, "MSGS %s||%d", proto[which],
+                               (template) ? 1 : 0);
+       else
+               sprintf(aaa, "MSGS %s|%d|%d", proto[which], whicharg,
+                               (template) ? 1 : 0);
+       if (template) count = strlen(template);
+       ret = CtdlIPCGenericCommand(aaa, template, count, &bbb, &bbbsize, cret);
+       count = 0;
+       while (strlen(bbb)) {
+               int a;
+
+               extract_token(aaa, bbb, 0, '\n');
+               a = strlen(aaa);
+               memmove(aaa, bbb + a + 1, strlen(bbb) - a - 1);
+               *mret = (long *)realloc(mret, (count + 1) * sizeof (long));
+               if (*mret)
+                       *mret[count++] = atol(aaa);
+               *mret[count] = 0L;
+       }
+       return ret;
+}
+
+
+/* MSG0, MSG2 */
+int CtdlIPCGetSingleMessage(long msgnum, int headers, int as_mime,
+               struct ctdlipcmessage **mret, char *cret)
+{
+       register int ret;
+       char aaa[27];
+       char *bbb;
+       size_t bbbsize;
+
+       if (!cret) return -1;
+       if (!mret) return -1;
+       if (!*mret) *mret = (struct ctdlipcmessage *)calloc(1, sizeof (struct ctdlipcmessage));
+       if (!*mret) return -1;
+       if (!msgnum) return -1;
+
+       sprintf(aaa, "MSG%c %ld|%d", as_mime ? '2' : '0', msgnum, headers);
+       ret = CtdlIPCGenericCommand(aaa, NULL, 0, &bbb, &bbbsize, cret);
+       if (ret / 100 == 1) {
+               if (!as_mime) {
+                       while (strlen(bbb) > 4 && bbb[4] == '=') {
+                               int a;
+
+                               extract_token(aaa, bbb, 0, '\n');
+                               a = strlen(aaa);
+                               memmove(aaa, bbb + a + 1, strlen(bbb) - a - 1);
+
+                               if (!strncasecmp(aaa, "nhdr=yes", 8))
+                                       mret[0]->nhdr = 1;
+                               else if (!strncasecmp(aaa, "from=", 5))
+                                       strcpy(mret[0]->author, &aaa[5]);
+                               else if (!strncasecmp(aaa, "type=", 5))
+                                       mret[0]->type = atoi(&aaa[5]);
+                               else if (!strncasecmp(aaa, "msgn=", 5))
+                                       strcpy(mret[0]->msgid, &aaa[5]);
+                               else if (!strncasecmp(aaa, "subj=", 5))
+                                       strcpy(mret[0]->subject, &aaa[5]);
+                               else if (!strncasecmp(aaa, "rfca=", 5))
+                                       strcpy(mret[0]->email, &aaa[5]);
+                               else if (!strncasecmp(aaa, "hnod=", 5))
+                                       strcpy(mret[0]->hnod, &aaa[5]);
+                               else if (!strncasecmp(aaa, "room=", 5))
+                                       strcpy(mret[0]->room, &aaa[5]);
+                               else if (!strncasecmp(aaa, "node=", 5))
+                                       strcpy(mret[0]->node, &aaa[5]);
+                               else if (!strncasecmp(aaa, "rcpt=", 5))
+                                       strcpy(mret[0]->recipient, &aaa[5]);
+                               else if (!strncasecmp(aaa, "time=", 5))
+                                       mret[0]->time = atol(&aaa[5]);
+                               else if (!strncasecmp(aaa, "part=", 5)) {
+                                       struct parts *ptr, *chain;
+       
+                                       ptr = (struct parts *)calloc(1, sizeof (struct parts));
+                                       if (ptr) {
+                                               extract(ptr->name, &aaa[5], 0);
+                                               extract(ptr->filename, &aaa[5], 1);
+                                               extract(ptr->number, &aaa[5], 2);
+                                               extract(ptr->disposition, &aaa[5], 3);
+                                               extract(ptr->mimetype, &aaa[5], 4);
+                                               ptr->length = extract_long(&aaa[5], 5);
+                                               if (!mret[0]->attachments)
+                                                       mret[0]->attachments = ptr;
+                                               else {
+                                                       chain = mret[0]->attachments;
+                                                       while (chain->next)
+                                                               chain = chain->next;
+                                                       chain->next = ptr;
+                                               }
+                                       }
+                               }
+                       }
+               }
+               if (strlen(bbb)) {
+                       bbb = (char *)realloc(bbb, strlen(bbb) + 1);
+                       mret[0]->text = bbb;
+               } else {
+                       free(bbb);
+               }
+       }
+       return ret;
+}
+
+
+/* WHOK */
+int CtdlIPCWhoKnowsRoom(char **listing, char *cret)
+{
+       register int ret;
+       size_t bytes;
+
+       if (!cret) return -2;
+       if (!listing) return -2;
+       if (*listing) return -2;
+
+       ret = CtdlIPCGenericCommand("WHOK", NULL, 0, listing, &bytes, cret);
+       return ret;
+}
+
+
+/* INFO */
+int CtdlIPCServerInfo(char **listing, char *cret)
+{
+       register int ret;
+       size_t bytes;
+
+       if (!cret) return -2;
+       if (!listing) return -2;
+       if (*listing) return -2;
+
+       ret = CtdlIPCGenericCommand("INFO", NULL, 0, listing, &bytes, cret);
+       return ret;
+}
+
+
+/* RDIR */
+int CtdlIPCReadDirectory(char **listing, char *cret)
+{
+       register int ret;
+       size_t bytes;
+
+       if (!cret) return -2;
+       if (!listing) return -2;
+       if (*listing) return -2;
+
+       ret = CtdlIPCGenericCommand("RDIR", NULL, 0, listing, &bytes, cret);
+       return ret;
+}
+
+
+/*
+ * Set last-read pointer in this room to msgnum, or 0 for HIGHEST.
+ */
+int CtdlIPCSetLastRead(long msgnum, char *cret)
+{
+       register int ret;
+       char aaa[16];
+
+       if (!cret) return -2;
+
+       if (msgnum)
+               sprintf(aaa, "SLRP %ld", msgnum);
+       else
+               sprintf(aaa, "SLRP HIGHEST");
+       ret = CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+       return ret;
+}
+
+
+/* INVT */
+int CtdlIPCInviteUserToRoom(const char *username, char *cret)
+{
+       register int ret;
+       char *aaa;
+
+       if (!cret) return -2;
+       if (!username) return -2;
+
+       aaa = (char *)malloc(strlen(username) + 6);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "INVT %s", username);
+       ret = CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* KICK */
+int CtdlIPCKickoutUserFromRoom(const char *username, char *cret)
+{
+       register int ret;
+       char *aaa;
+
+       if (!cret) return -1;
+       if (!username) return -1;
+
+       aaa = (char *)malloc(strlen(username) + 6);
+
+       sprintf(aaa, "KICK %s", username);
+       ret = CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* GETR */
+int CtdlIPCGetRoomAttributes(struct quickroom **qret, char *cret)
+{
+       register int ret;
+
+       if (!cret) return -2;
+       if (!qret) return -2;
+       if (!*qret) *qret = (struct quickroom *)calloc(1, sizeof (struct quickroom));
+       if (!*qret) return -1;
+
+       ret = CtdlIPCGenericCommand("GETR", NULL, 0, NULL, NULL, cret);
+       if (ret / 100 == 2) {
+               extract(qret[0]->QRname, cret, 0);
+               extract(qret[0]->QRpasswd, cret, 1);
+               extract(qret[0]->QRdirname, cret, 2);
+               qret[0]->QRflags = extract_int(cret, 3);
+               qret[0]->QRfloor = extract_int(cret, 4);
+               qret[0]->QRorder = extract_int(cret, 5);
+       }
+       return ret;
+}
+
+
+/* SETR */
+/* set forget to kick all users out of room */
+int CtdlIPCSetRoomAttributes(int forget, struct quickroom *qret, char *cret)
+{
+       register int ret;
+       char *aaa;
+
+       if (!cret) return -2;
+       if (!qret) return -2;
+
+       aaa = (char *)malloc(strlen(qret->QRname) + strlen(qret->QRpasswd) +
+                       strlen(qret->QRdirname) + 52);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "SETR %s|%s|%s|%d|%d|%d|%d",
+                       qret->QRname, qret->QRpasswd, qret->QRdirname,
+                       qret->QRflags, forget, qret->QRfloor, qret->QRorder);
+       ret = CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* GETA */
+int CtdlIPCGetRoomAide(char *cret)
+{
+       if (!cret) return -1;
+
+       return CtdlIPCGenericCommand("GETA", NULL, 0, NULL, NULL, cret);
+}
+
+
+/* SETA */
+int CtdlIPCSetRoomAide(const char *username, char *cret)
+{
+       register int ret;
+       char *aaa;
+
+       if (!cret) return -2;
+       if (!username) return -2;
+
+       aaa = (char *)malloc(strlen(username) + 6);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "SETA %s", username);
+       ret = CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* ENT0 */
+int CtdlIPCPostMessage(int flag, const struct ctdlipcmessage *mr, char *cret)
+{
+       register int ret;
+       char *aaa;
+
+       if (!cret) return -2;
+       if (!mr) return -2;
+
+       aaa = (char *)malloc(strlen(mr->recipient) + strlen(mr->author) + 40);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "ENT0 %d|%s|%d|%d|%s", flag, mr->recipient, mr->anonymous,
+                       mr->type, mr->author);
+       ret = CtdlIPCGenericCommand(aaa, mr->text, strlen(mr->text), NULL,
+                       NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* RINF */
+int CtdlIPCRoomInfo(char **iret, char *cret)
+{
+       size_t bytes;
+
+       if (!cret) return -2;
+       if (!iret) return -2;
+       if (*iret) return -2;
+
+       return CtdlIPCGenericCommand("RINF", NULL, 0, iret, &bytes, cret);
+}
+
+
+/* DELE */
+int CtdlIPCDeleteMessage(long msgnum, char *cret)
+{
+       char aaa[16];
+
+       if (!cret) return -2;
+       if (!msgnum) return -2;
+
+       sprintf(aaa, "DELE %ld", msgnum);
+       return CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+}
+
+
+/* MOVE */
+int CtdlIPCMoveMessage(int copy, long msgnum, const char *destroom, char *cret)
+{
+       register int ret;
+       char *aaa;
+
+       if (!cret) return -2;
+       if (!destroom) return -2;
+       if (!msgnum) return -2;
+
+       aaa = (char *)malloc(strlen(destroom) + 28);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "MOVE %ld|%s|%d", msgnum, destroom, copy);
+       ret = CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* KILL */
+int CtdlIPCDeleteRoom(int for_real, char *cret)
+{
+       char aaa[16];
+
+       if (!cret) return -2;
+
+       sprintf(aaa, "KILL %d", for_real);
+       return CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+}
+
+
+/* CRE8 */
+int CtdlIPCCreateRoom(int for_real, const char *roomname, int type,
+               const char *password, int floor, char *cret)
+{
+       register int ret;
+       char *aaa;
+
+       if (!cret) return -2;
+       if (!roomname) return -2;
+
+       if (password) {
+               aaa = (char *)malloc(strlen(roomname) + strlen(password) + 40);
+               if (!aaa) return -1;
+               sprintf(aaa, "CRE8 %d|%s|%d|%s|%d", for_real, roomname, type,
+                               password, floor);
+       } else {
+               aaa = (char *)malloc(strlen(roomname) + 40);
+               if (!aaa) return -1;
+               sprintf(aaa, "CRE8 %d|%s|%d||%d", for_real, roomname, type,
+                               floor);
+       }
+       ret = CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* FORG */
+int CtdlIPCForgetRoom(char *cret)
+{
+       if (!cret) return -2;
+
+       return CtdlIPCGenericCommand("FORG", NULL, 0, NULL, NULL, cret);
+}
+
+
+/* MESG */
+int CtdlIPCSystemMessage(const char *message, char **mret, char *cret)
+{
+       register int ret;
+       char *aaa;
+       size_t bytes;
+
+       if (!cret) return -2;
+       if (!mret) return -2;
+       if (*mret) return -2;
+       if (!message) return -2;
+
+       aaa = (char *)malloc(strlen(message) + 6);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "MESG %s", message);
+       ret = CtdlIPCGenericCommand(aaa, NULL, 0, mret, &bytes, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* GNUR */
+int CtdlIPCNextUnvalidatedUser(char *cret)
+{
+       if (!cret) return -2;
+
+       return CtdlIPCGenericCommand("GNUR", NULL, 0, NULL, NULL, cret);
+}
+
+
+/* GREG */
+int CtdlIPCGetUserRegistration(const char *username, char **rret, char *cret)
+{
+       register int ret;
+       char *aaa;
+       size_t bytes;
+
+       if (!cret) return -2;
+       if (!rret) return -2;
+       if (*rret) return -2;
+
+       if (username)
+               aaa = (char *)malloc(strlen(username) + 6);
+       else
+               aaa = (char *)malloc(12);
+       if (!aaa) return -1;
+
+       if (username)
+               sprintf(aaa, "GREG %s", username);
+       else
+               sprintf(aaa, "GREG _SELF_");
+       ret = CtdlIPCGenericCommand(aaa, NULL, 0, rret, &bytes, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* VALI */
+int CtdlIPCValidateUser(const char *username, int axlevel, char *cret)
+{
+       register int ret;
+       char *aaa;
+
+       if (!cret) return -2;
+       if (!username) return -2;
+       if (axlevel < 0 || axlevel > 7) return -2;
+
+       aaa = (char *)malloc(strlen(username) + 17);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "VALI %s|%d", username, axlevel);
+       ret = CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* EINF */
+int CtdlIPCSetRoomInfo(int for_real, const char *info, char *cret)
+{
+       char aaa[16];
+
+       if (!cret) return -1;
+       if (!info) return -1;
+
+       sprintf(aaa, "EINF %d", for_real);
+       return CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+}
+
+
+/* LIST */
+int CtdlIPCUserListing(char **listing, char *cret)
+{
+       size_t bytes;
+
+       if (!cret) return -1;
+       if (!listing) return -1;
+       if (*listing) return -1;
+
+       return CtdlIPCGenericCommand("LIST", NULL, 0, listing, &bytes, cret);
+}
+
+
+/* REGI */
+int CtdlIPCSetRegistration(const char *info, char *cret)
+{
+       if (!cret) return -1;
+       if (!info) return -1;
+
+       return CtdlIPCGenericCommand("REGI", info, strlen(info),
+                       NULL, NULL, cret);
+}
+
+
+/* CHEK */
+int CtdlIPCMiscCheck(struct ctdlipcmisc *chek, char *cret)
+{
+       register int ret;
+
+       if (!cret) return -1;
+       if (!chek) return -1;
+
+       ret = CtdlIPCGenericCommand("CHEK", NULL, 0, NULL, NULL, cret);
+       if (ret / 100 == 2) {
+               chek->newmail = extract_long(cret, 0);
+               chek->needregis = extract_int(cret, 1);
+               chek->needvalid = extract_int(cret, 2);
+       }
+       return ret;
+}
+
+
+/* DELF */
+int CtdlIPCDeleteFile(const char *filename, char *cret)
+{
+       register int ret;
+       char *aaa;
+
+       if (!cret) return -2;
+       if (!filename) return -2;
+       
+       aaa = (char *)malloc(strlen(filename) + 6);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "DELF %s", filename);
+       ret = CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* MOVF */
+int CtdlIPCMoveFile(const char *filename, const char *destroom, char *cret)
+{
+       register int ret;
+       char *aaa;
+
+       if (!cret) return -2;
+       if (!filename) return -2;
+       if (!destroom) return -2;
+
+       aaa = (char *)malloc(strlen(filename) + strlen(destroom) + 7);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "MOVF %s|%s", filename, destroom);
+       ret = CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* NETF */
+int CtdlIPCNetSendFile(const char *filename, const char *destnode, char *cret)
+{
+       register int ret;
+       char *aaa;
+
+       if (!cret) return -2;
+       if (!filename) return -2;
+       if (!destnode) return -2;
+
+       aaa = (char *)malloc(strlen(filename) + strlen(destnode) + 7);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "NETF %s|%s", filename, destnode);
+       ret = CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* RWHO */
+int CtdlIPCOnlineUsers(char **listing, time_t *stamp, char *cret)
+{
+       register int ret;
+       size_t bytes;
+
+       if (!cret) return -1;
+       if (!listing) return -1;
+       if (*listing) return -1;
+
+       *stamp = CtdlIPCServerTime(cret);
+       if (!*stamp)
+               *stamp = time(NULL);
+       ret = CtdlIPCGenericCommand("RWHO", NULL, 0, listing, &bytes, cret);
+       return ret;
+}
+
+
+/* OPEN */
+int CtdlIPCFileDownload(const char *filename, void **buf, char *cret)
+{
+       register int ret;
+       size_t bytes;
+       time_t last_mod;
+       char mimetype[256];
+       char *aaa;
+
+       if (!cret) return -2;
+       if (!filename) return -2;
+       if (!buf) return -2;
+       if (*buf) return -2;
+       if (download_in_progress) return -2;
+
+       aaa = (char *)malloc(strlen(filename) + 6);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "OPEN %s", filename);
+       ret = CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       /* FIXME: Possible race condition */
+       if (ret / 100 == 2) {
+               download_in_progress = 1;
+               bytes = extract_long(cret, 0);
+               last_mod = extract_int(cret, 1);
+               extract(mimetype, cret, 2);
+               ret = CtdlIPCReadDownload(buf, bytes, cret);
+               ret = CtdlIPCEndDownload(cret);
+               if (ret / 100 == 2)
+                       sprintf(cret, "%d|%ld|%s|%s", bytes, last_mod,
+                                       filename, mimetype);
+       }
+       return ret;
+}
+
+
+/* OPNA */
+int CtdlIPCAttachmentDownload(long msgnum, const char *part, void **buf,
+               char *cret)
+{
+       register int ret;
+       size_t bytes;
+       time_t last_mod;
+       char filename[256];
+       char mimetype[256];
+       char *aaa;
+
+       if (!cret) return -2;
+       if (!buf) return -2;
+       if (*buf) return -2;
+       if (!part) return -2;
+       if (!msgnum) return -2;
+       if (download_in_progress) return -2;
+
+       aaa = (char *)malloc(strlen(part) + 17);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "OPNA %ld|%s", msgnum, part);
+       ret = CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       /* FIXME: Possible race condition */
+       if (ret / 100 == 2) {
+               download_in_progress = 1;
+               bytes = extract_long(cret, 0);
+               last_mod = extract_int(cret, 1);
+               extract(mimetype, cret, 2);
+               ret = CtdlIPCReadDownload(buf, bytes, cret);
+               ret = CtdlIPCEndDownload(cret);
+               if (ret / 100 == 2)
+                       sprintf(cret, "%d|%ld|%s|%s", bytes, last_mod,
+                                       filename, mimetype);
+       }
+       return ret;
+}
+
+
+/* OIMG */
+int CtdlIPCImageDownload(const char *filename, void **buf, char *cret)
+{
+       register int ret;
+       size_t bytes;
+       time_t last_mod;
+       char mimetype[256];
+       char *aaa;
+
+       if (!cret) return -1;
+       if (!buf) return -1;
+       if (*buf) return -1;
+       if (!filename) return -1;
+       if (download_in_progress) return -1;
+
+       aaa = (char *)malloc(strlen(filename) + 6);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "OIMG %s", filename);
+       ret = CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       /* FIXME: Possible race condition */
+       if (ret / 100 == 2) {
+               download_in_progress = 1;
+               bytes = extract_long(cret, 0);
+               last_mod = extract_int(cret, 1);
+               extract(mimetype, cret, 2);
+               ret = CtdlIPCReadDownload(buf, bytes, cret);
+               ret = CtdlIPCEndDownload(cret);
+               if (ret / 100 == 2)
+                       sprintf(cret, "%d|%ld|%s|%s", bytes, last_mod,
+                                       filename, mimetype);
+       }
+       return ret;
+}
+
+
+/* UOPN */
+int CtdlIPCFileUpload(const char *filename, const char *comment, void *buf,
+               size_t bytes, char *cret)
+{
+       register int ret;
+       char *aaa;
+
+       if (!cret) return -1;
+       if (!filename) return -1;
+       if (!comment) return -1;
+       if (upload_in_progress) return -1;
+
+       aaa = (char *)malloc(strlen(filename) + strlen(comment) + 7);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "UOPN %s|%s", filename, comment);
+       ret = CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       /* FIXME: Possible race condition */
+       if (ret / 100 == 2)
+               upload_in_progress = 1;
+       ret = CtdlIPCWriteUpload(buf, bytes, cret);
+       ret = CtdlIPCEndUpload(cret);
+       return ret;
+}
+
+
+/* UIMG */
+int CtdlIPCImageUpload(int for_real, const char *filename, size_t bytes,
+               char *cret)
+{
+       register int ret;
+       char *aaa;
+
+       if (!cret) return -1;
+       if (!filename) return -1;
+       if (upload_in_progress) return -1;
+
+       aaa = (char *)malloc(strlen(filename) + 17);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "UIMG %d|%s", for_real, filename);
+       ret = CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       /* FIXME: Possible race condition */
+       if (ret / 100 == 2)
+               upload_in_progress = 1;
+       return ret;
+}
+
+
+/* QUSR */
+int CtdlIPCQueryUsername(const char *username, char *cret)
+{
+       register int ret;
+       char *aaa;
+
+       if (!cret) return -2;
+       if (!username) return -2;
+
+       aaa = (char *)malloc(strlen(username) + 6);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "QUSR %s", username);
+       ret = CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* LFLR */
+int CtdlIPCFloorListing(char **listing, char *cret)
+{
+       size_t bytes;
+
+       if (!cret) return -2;
+       if (!listing) return -2;
+       if (*listing) return -2;
+
+       return CtdlIPCGenericCommand("LFLR", NULL, 0, listing, &bytes, cret);
+}
+
+
+/* CFLR */
+int CtdlIPCCreateFloor(int for_real, const char *name, char *cret)
+{
+       register int ret;
+       char *aaa;
+
+       if (!cret) return -2;
+       if (!name) return -2;
+
+       aaa = (char *)malloc(strlen(name) + 17);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "CFLR %s|%d", name, for_real);
+       ret = CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* KFLR */
+int CtdlIPCDeleteFloor(int for_real, int floornum, char *cret)
+{
+       char aaa[27];
+
+       if (!cret) return -1;
+       if (floornum < 0) return -1;
+
+       sprintf(aaa, "KFLR %d|%d", floornum, for_real);
+       return CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+}
+
+
+/* EFLR */
+int CtdlIPCEditFloor(int floornum, const char *floorname, char *cret)
+{
+       register int ret;
+       char *aaa;
+
+       if (!cret) return -2;
+       if (!floorname) return -2;
+       if (floornum < 0) return -2;
+
+       aaa = (char *)malloc(strlen(floorname) + 17);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "EFLR %d|%s", floornum, floorname);
+       ret = CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* IDEN */
+int CtdlIPCIdentifySoftware(int developerid, int clientid, int revision,
+               const char *software_name, const char *hostname, char *cret)
+{
+       register int ret;
+       char *aaa;
+
+       if (developerid < 0) return -2;
+       if (clientid < 0) return -2;
+       if (revision < 0) return -2;
+       if (!software_name) return -2;
+       if (!hostname) return -2;
+
+       aaa = (char *)malloc(strlen(software_name) + strlen(hostname) + 29);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "IDEN %d|%d|%d|%s|%s", developerid, clientid,
+                       revision, software_name, hostname);
+       ret = CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* SEXP */
+int CtdlIPCSendInstantMessage(const char *username, const char *text,
+               char *cret)
+{
+       register int ret;
+       char *aaa;
+
+       if (!cret) return -2;
+       if (!username) return -2;
+
+       aaa = (char *)malloc(strlen(username) + 8);
+       if (!aaa) return -1;
+
+       if (text) {
+               sprintf(aaa, "SEXP %s|-", username);
+               ret = CtdlIPCGenericCommand(aaa, text, strlen(text),
+                               NULL, NULL, cret);
+       } else {
+               sprintf(aaa, "SEXP %s||", username);
+               ret = CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+       }
+       free(aaa);
+       return ret;
+}
+
+
+/* GEXP */
+int CtdlIPCGetInstantMessage(char **listing, char *cret)
+{
+       size_t bytes;
+
+       if (!cret) return -2;
+       if (!listing) return -2;
+       if (*listing) return -2;
+
+       return CtdlIPCGenericCommand("GEXP", NULL, 0, listing, &bytes, cret);
+}
+
+
+/* DEXP */
+/* mode is 0 = enable, 1 = disable, 2 = status */
+int CtdlIPCEnableInstantMessageReceipt(int mode, char *cret)
+{
+       char aaa[16];
+
+       if (!cret) return -2;
+
+       sprintf(aaa, "DEXP %d", mode);
+       return CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+}
+
+
+/* EBIO */
+int CtdlIPCSetBio(char *bio, char *cret)
+{
+       if (!cret) return -2;
+       if (!bio) return -2;
+
+       return CtdlIPCGenericCommand("EBIO", bio, strlen(bio),
+                       NULL, NULL, cret);
+}
+
+
+/* RBIO */
+int CtdlIPCGetBio(const char *username, char **listing, char *cret)
+{
+       register int ret;
+       size_t bytes;
+       char *aaa;
+
+       if (!cret) return -2;
+       if (!username) return -2;
+       if (!listing) return -2;
+       if (*listing) return -2;
+
+       aaa = (char *)malloc(strlen(username) + 6);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "RBIO %s", username);
+       ret = CtdlIPCGenericCommand(aaa, NULL, 0, listing, &bytes, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* LBIO */
+int CtdlIPCListUsersWithBios(char **listing, char *cret)
+{
+       size_t bytes;
+
+       if (!cret) return -2;
+       if (!listing) return -2;
+       if (*listing) return -2;
+
+       return CtdlIPCGenericCommand("LBIO", NULL, 0, listing, &bytes, cret);
+}
+
+
+/* STEL */
+int CtdlIPCStealthMode(int mode, char *cret)
+{
+       char aaa[16];
+
+       if (!cret) return -1;
+
+       sprintf(aaa, "STEL %d", mode ? 1 : 0);
+       return CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+}
+
+
+/* TERM */
+int CtdlIPCTerminateSession(int sid, char *cret)
+{
+       char aaa[16];
+
+       if (!cret) return -1;
+
+       sprintf(aaa, "TERM %d", sid);
+       return CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+}
+
+
+/* DOWN */
+int CtdlIPCTerminateServerNow(char *cret)
+{
+       if (!cret) return -1;
+
+       return CtdlIPCGenericCommand("DOWN", NULL, 0, NULL, NULL, cret);
+}
+
+
+/* SCDN */
+int CtdlIPCTerminateServerScheduled(int mode, char *cret)
+{
+       char aaa[16];
+
+       if (!cret) return -1;
+
+       sprintf(aaa, "SCDN %d", mode ? 1 : 0);
+       return CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+}
+
+
+/* EMSG */
+int CtdlIPCEnterSystemMessage(const char *filename, const char *text,
+               char *cret)
+{
+       register int ret;
+       char *aaa;
+
+       if (!cret) return -2;
+       if (!text) return -2;
+       if (!filename) return -2;
+
+       aaa = (char *)malloc(strlen(filename) + 6);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "EMSG %s", filename);
+       ret = CtdlIPCGenericCommand(aaa, text, strlen(text), NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* HCHG */
+int CtdlIPCChangeHostname(const char *hostname, char *cret)
+{
+       register int ret;
+       char *aaa;
+
+       if (!cret) return -2;
+       if (!hostname) return -2;
+
+       aaa = (char *)malloc(strlen(hostname) + 6);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "HCHG %s", hostname);
+       ret = CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* RCHG */
+int CtdlIPCChangeRoomname(const char *roomname, char *cret)
+{
+       register int ret;
+       char *aaa;
+
+       if (!cret) return -2;
+       if (!roomname) return -2;
+
+       aaa = (char *)malloc(strlen(roomname) + 6);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "RCHG %s", roomname);
+       ret = CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* UCHG */
+int CtdlIPCChangeUsername(const char *username, char *cret)
+{
+       register int ret;
+       char *aaa;
+
+       if (!cret) return -2;
+       if (!username) return -2;
+
+       aaa = (char *)malloc(strlen(username) + 6);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "UCHG %s", username);
+       ret = CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* TIME */
+/* This function returns the actual server time reported, or 0 if error */
+time_t CtdlIPCServerTime(char *cret)
+{
+       register time_t tret;
+       register int ret;
+
+       ret = CtdlIPCGenericCommand("TIME", NULL, 0, NULL, NULL, cret);
+       if (ret / 100 == 2) {
+               tret = extract_long(cret, 0);
+       } else {
+               tret = 0L;
+       }
+       return tret;
+}
+
+
+/* AGUP */
+int CtdlIPCAideGetUserParameters(struct usersupp **uret, char *cret)
+{
+       register int ret;
+       char *aaa;
+
+       if (!cret) return -2;
+       if (!uret) return -2;
+       if (!*uret) return -2;
+
+       aaa = (char *)malloc(strlen(uret[0]->fullname) + 6);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "AGUP %s", uret[0]->fullname);
+       ret = CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+       if (ret / 100 == 2) {
+               extract(uret[0]->fullname, cret, 0);
+               extract(uret[0]->password, cret, 1);
+               uret[0]->flags = extract_int(cret, 2);
+               uret[0]->timescalled = extract_long(cret, 3);
+               uret[0]->posted = extract_long(cret, 4);
+               uret[0]->axlevel = extract_int(cret, 5);
+               uret[0]->usernum = extract_long(cret, 6);
+               uret[0]->lastcall = extract_long(cret, 7);
+               uret[0]->USuserpurge = extract_int(cret, 8);
+       }
+       free(aaa);
+       return ret;
+}
+
+
+/* ASUP */
+int CtdlIPCAideSetUserParameters(const struct usersupp *uret, char *cret)
+{
+       register int ret;
+       char *aaa;
+
+       if (!cret) return -2;
+       if (!uret) return -2;
+
+       aaa = (char *)malloc(strlen(uret->fullname) + strlen(uret->password) + 84);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "ASUP %s|%s|%d|%ld|%ld|%d|%ld|%ld|%d",
+                       uret->fullname, uret->password, uret->flags,
+                       uret->timescalled, uret->posted, uret->axlevel,
+                       uret->usernum, uret->lastcall, uret->USuserpurge);
+       ret = CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* GPEX */
+/* which is 0 = room, 1 = floor, 2 = site */
+int CtdlIPCGetMessageExpirationPolicy(int which, char *cret)
+{
+       static char *proto[] = {"room", "floor", "site"};
+       char aaa[11];
+
+       if (!cret) return -2;
+       if (which < 0 || which > 2) return -2;
+       
+       sprintf(aaa, "GPEX %s", proto[which]);
+       return CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+}
+
+
+/* SPEX */
+/* which is 0 = room, 1 = floor, 2 = site */
+/* policy is 0 = inherit, 1 = no purge, 2 = by count, 3 = by age (days) */
+int CtdlIPCSetMessageExpirationPolicy(int which, int policy, int value,
+               char *cret)
+{
+       char aaa[38];
+
+       if (!cret) return -2;
+       if (which < 0 || which > 2) return -2;
+       if (policy < 0 || policy > 3) return -2;
+       if (policy >= 2 && value < 1) return -2;
+
+       sprintf(aaa, "SPEX %d|%d|%d", which, policy, value);
+       return CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+}
+
+
+/* CONF GET */
+int CtdlGetSystemConfig(char **listing, char *cret)
+{
+       size_t bytes;
+
+       if (!cret) return -2;
+       if (!listing) return -2;
+       if (*listing) return -2;
+
+       return CtdlIPCGenericCommand("CONF GET", NULL, 0,
+                       listing, &bytes, cret);
+}
+
+
+/* CONF SET */
+int CtdlSetSystemConfig(const char *listing, char *cret)
+{
+       if (!cret) return -2;
+       if (!listing) return -2;
+
+       return CtdlIPCGenericCommand("CONF SET", listing, strlen(listing),
+                       NULL, NULL, cret);
+}
+
+
+/* MMOD */
+int CtdlIPCModerateMessage(long msgnum, int level, char *cret)
+{
+       char aaa[27];
+
+       if (!cret) return -2;
+       if (!msgnum) return -2;
+
+       sprintf(aaa, "MMOD %ld|%d", msgnum, level);
+       return CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+}
+
+
+/* REQT */
+int CtdlIPCRequestClientLogout(int session, char *cret)
+{
+       char aaa[16];
+
+       if (!cret) return -2;
+       if (session < 0) return -2;
+
+       sprintf(aaa, "REQT %d", session);
+       return CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+}
+
+
+/* SEEN */
+int CtdlIPCSetMessageSeen(long msgnum, int seen, char *cret)
+{
+       char aaa[27];
+
+       if (!cret) return -2;
+       if (msgnum < 0) return -2;
+
+       sprintf(aaa, "SEEN %ld|%d", msgnum, seen ? 1 : 0);
+       return CtdlIPCGenericCommand(aaa, NULL, 0, NULL, NULL, cret);
+}
+
+
+/* STLS */
+int CtdlIPCStartEncryption(char *cret)
+{
+       return CtdlIPCGenericCommand("STLS", NULL, 0, NULL, NULL, cret);
+}
+
+
+/*
+ * Not implemented:
+ * 
+ * CHAT
+ * ETLS
+ * EXPI
+ * GTLS
+ * IGAB
+ * IPGM
+ * MSG3
+ * MSG4
+ * NDOP
+ * NETP
+ * NUOP
+ * SMTP
+ */
+
+
+/* ************************************************************************** */
+/*             Stuff below this line is not for public consumption            */
+/* ************************************************************************** */
+
+
+inline void netio_lock(void)
+{
+#ifdef THREADED_CLIENT
+       pthread_mutex_lock(&rwlock);
+#endif
+}
+
+
+inline void netio_unlock(void)
+{
+#ifdef THREADED_CLIENT
+       pthread_mutex_unlock(&rwlock);
+#endif
+}
+
+
+/* Read a listing from the server up to 000.  Append to dest if it exists */
+char *CtdlIPCReadListing(char *dest)
+{
+       long length = 0;
+       char *ret;
+       char aaa[256];
+
+       ret = dest;
+       if (ret) length = strlen(ret);
+       while (serv_gets(aaa), strcmp(aaa, "000")) {
+               ret = (char *)realloc(ret, length + strlen(aaa) + 1);
+               if (ret)
+                       strcpy(&ret[length], aaa);
+       }
+       return ret;
+}
+
+
+/* Send a listing to the server; generate the ending 000. */
+int CtdlIPCSendListing(const char *listing)
+{
+       char *text;
+
+       text = (char *)malloc(strlen(listing) + 5);
+       if (text) {
+               strcpy(text, listing);
+               if (text[strlen(text) - 1] == '\n')
+                       text[strlen(text) - 1] = '\0';
+               strcat(text, "000");
+               serv_puts(text);
+               free(text);
+               text = NULL;
+       } else {
+               /* Malloc failed but we are committed to send */
+               /* This may result in extra blanks at the bottom */
+               serv_puts(text);
+               serv_puts("000");
+       }
+       return 0;
+}
+
+
+/* Partial read of file from server */
+size_t CtdlIPCPartialRead(void **buf, size_t offset, size_t bytes, char *cret)
+{
+       register size_t len = 0;
+       char aaa[256];
+
+       if (!buf) return -1;
+       if (!cret) return -1;
+       if (bytes < 1) return -1;
+       if (offset < 0) return -1;
+
+       netio_lock();
+       sprintf(aaa, "READ %d|%d", offset, bytes);
+       serv_puts(aaa);
+       serv_gets(aaa);
+       if (aaa[0] != '6')
+               strcpy(cret, &aaa[4]);
+       else {
+               len = extract_long(&aaa[4], 0);
+               *buf = (void *)realloc(*buf, offset + len);
+               if (*buf) {
+                       /* I know what I'm doing */
+                       serv_read((char *)&buf[offset], len);
+               } else {
+                       /* We have to read regardless */
+                       serv_read(aaa, len);
+                       len = -1;
+               }
+       }
+       netio_unlock();
+       return len;
+}
+
+
+/* CLOS */
+int CtdlIPCEndDownload(char *cret)
+{
+       register int ret;
+
+       if (!cret) return -2;
+       if (!download_in_progress) return -2;
+
+       ret = CtdlIPCGenericCommand("CLOS", NULL, 0, NULL, NULL, cret);
+       if (ret / 100 == 2)
+               download_in_progress = 0;
+       return ret;
+}
+
+
+/* READ */
+int CtdlIPCReadDownload(void **buf, size_t bytes, char *cret)
+{
+       register size_t len;
+
+       if (!cret) return -1;
+       if (!buf) return -1;
+       if (*buf) return -1;
+       if (!download_in_progress) return -1;
+
+       len = 0;
+       while (len < bytes) {
+               len = CtdlIPCPartialRead(buf, len, 4096, cret);
+               if (len == -1) {
+                       free(*buf);
+                       return 0;
+               }
+       }
+       return len;
+}
+
+
+/* UCLS */
+int CtdlIPCEndUpload(char *cret)
+{
+       register int ret;
+
+       if (!cret) return -1;
+       if (!upload_in_progress) return -1;
+
+       ret = CtdlIPCGenericCommand("UCLS", NULL, 0, NULL, NULL, cret);
+       if (ret / 100 == 2)
+               upload_in_progress = 0;
+       return ret;
+}
+
+
+/* WRIT */
+int CtdlIPCWriteUpload(void *buf, size_t bytes, char *cret)
+{
+       register int ret = -1;
+       register size_t offset;
+       char aaa[256];
+
+       if (!cret) return -1;
+       if (!buf) return -1;
+       if (bytes < 1) return -1;
+
+       offset = 0;
+       while (offset < bytes) {
+               sprintf(aaa, "WRIT %d", bytes - offset);
+               serv_puts(aaa);
+               serv_gets(aaa);
+               strcpy(cret, &aaa[4]);
+               ret = atoi(aaa);
+               if (aaa[0] == '7') {
+                       register size_t to_write;
+
+                       to_write = extract_long(&aaa[4], 0);
+                       serv_write(buf + offset, to_write);
+                       offset += to_write;
+               } else {
+                       break;
+               }
+       }
+       return ret;
+}
+
+
+/*
+ * Generic command method.  This method should handle any server command
+ * except for CHAT.  It takes the following arguments:
+ *
+ * command             Preformatted command to send to server
+ * to_send             A text or binary file to send to server
+ *                     (only sent if server requests it)
+ * bytes_to_send       The number of bytes in to_send (required if
+ *                     sending binary, optional if sending listing)
+ * to_receive          Pointer to a NULL pointer, if the server
+ *                     sends text or binary we will allocate memory
+ *                     for the file and stuff it here
+ * bytes_to_receive    If a file is received, we will store its
+ *                     byte count here
+ * proto_response      The protocol response.  Caller must provide
+ *                     this buffer and ensure that it is at least
+ *                     128 bytes in length.
+ *
+ * This function returns a number equal to the protocol response number,
+ * -1 if an internal error occurred, -2 if caller provided bad values,
+ * or 0 - the protocol response number if bad values were found during
+ * the protocol exchange.
+ * It stores the protocol response string (minus the number) in 
+ * protocol_response as described above.  Some commands send additional
+ * data in this string.
+ */
+int CtdlIPCGenericCommand(const char *command, const char *to_send,
+               size_t bytes_to_send, char **to_receive, 
+               size_t *bytes_to_receive, char *proto_response)
+{
+       char buf[SIZ];
+       register int ret;
+
+       if (!command) return -2;
+       if (!proto_response) return -2;
+
+       netio_lock();
+       serv_puts((char *)command);
+       while (1) {
+               serv_gets(proto_response);
+               if (proto_response[3] == '*')
+                       express_msgs = 1;
+               ret = atoi(proto_response);
+               memmove(proto_response, &proto_response[4],
+                               strlen(proto_response) - 3);
+               switch (ret / 100) {
+               default:                        /* Unknown, punt */
+               case 2:                         /* OK */
+               case 3:                         /* MORE_DATA */
+               case 5:                         /* ERROR */
+                       /* Don't need to do anything */
+                       break;
+               case 1:                         /* LISTING_FOLLOWS */
+                       if (to_receive && !*to_receive && bytes_to_receive) {
+                               *to_receive = CtdlIPCReadListing(NULL);
+                       } else { /* Drain */
+                               while (serv_gets(buf), strcmp(buf, "000")) ;
+                               ret = -ret;
+                       }
+                       break;
+               case 4:                         /* SEND_LISTING */
+                       if (to_send) {
+                               CtdlIPCSendListing(to_send);
+                       } else {
+                               /* No listing given, fake it */
+                               serv_puts("000");
+                               ret = -ret;
+                       }
+                       break;
+               case 6:                         /* BINARY_FOLLOWS */
+                       if (to_receive && !*to_receive && bytes_to_receive) {
+                               *bytes_to_receive =
+                                       extract_long(proto_response, 0);
+                               *to_receive = (char *)malloc(*bytes_to_receive);
+                               if (!*to_receive) {
+                                       ret = -1;
+                               } else {
+                                       serv_read(*to_receive,
+                                                       *bytes_to_receive);
+                               }
+                       } else {
+                               /* Drain */
+                               size_t drain;
+
+                               drain = extract_long(proto_response, 0);
+                               while (drain > SIZ) {
+                                       serv_read(buf, SIZ);
+                                       drain -= SIZ;
+                               }
+                               serv_read(buf, drain);
+                               ret = -ret;
+                       }
+                       break;
+               case 7:                         /* SEND_BINARY */
+                       if (to_send && bytes_to_send) {
+                               serv_write((char *)to_send, bytes_to_send);
+                       } else if (bytes_to_send) {
+                               /* Fake it, send nulls */
+                               size_t fake;
+
+                               fake = bytes_to_send;
+                               memset(buf, '\0', SIZ);
+                               while (fake > SIZ) {
+                                       serv_write(buf, SIZ);
+                                       fake -= SIZ;
+                               }
+                               serv_write(buf, fake);
+                               ret = -ret;
+                       } /* else who knows?  DANGER WILL ROBINSON */
+                       break;
+               case 8:                         /* START_CHAT_MODE */
+                       if (!strncasecmp(command, "CHAT", 4)) {
+                               /* Don't call chatmode with generic! */
+                               serv_puts("/quit");
+                               ret = -ret;
+                       } else {
+                               /* In this mode we send then receive listing */
+                               if (to_send) {
+                                       CtdlIPCSendListing(to_send);
+                               } else {
+                                       /* No listing given, fake it */
+                                       serv_puts("000");
+                                       ret = -ret;
+                               }
+                               if (to_receive && !*to_receive
+                                               && bytes_to_receive) {
+                                       *to_receive = CtdlIPCReadListing(NULL);
+                               } else { /* Drain */
+                                       while (serv_gets(buf),
+                                                       strcmp(buf, "000")) ;
+                                       ret = -ret;
+                               }
+                       }
+                       break;
+               case 9:                         /* ASYNC_MSG */
+                       /* CtdlIPCDoAsync(ret, proto_response); */
+                       free(CtdlIPCReadListing(NULL)); /* STUB FIXME */
+                       break;
+               }
+               if (ret / 100 != 9)
+                       break;
+       }
+       netio_unlock();
+       return ret;
+}
diff --git a/citadel/citadel_ipc.h b/citadel/citadel_ipc.h
new file mode 100644 (file)
index 0000000..8c1c827
--- /dev/null
@@ -0,0 +1,169 @@
+/* $Id$ */
+
+struct ctdlipcroom {
+       char RRname[ROOMNAMELEN];       /* Name of room */
+       long RRunread;                  /* Number of unread messages */
+       long RRtotal;                   /* Total number of messages in room */
+       char RRinfoupdated;             /* Nonzero if info was updated */
+       unsigned RRflags;               /* Various flags (see LKRN) */
+       long RRhighest;                 /* Highest message number in room */
+       long RRlastread;                /* Highest message user has read */
+       char RRismailbox;               /* Is this room a mailbox room? */
+       char RRaide;                    /* User can do aide commands in room */
+       long RRnewmail;                 /* Number of new mail messages */
+       char RRfloor;                   /* Which floor this room is on */
+};
+
+
+struct parts {
+       struct parts *next;
+       char number[16];                /* part number */
+       char name[PATH_MAX];            /* Name */
+       char filename[PATH_MAX];        /* Suggested filename */
+       char mimetype[256];             /* MIME type */
+       char disposition[256];          /* Content disposition */
+       unsigned long length;           /* Content length */
+};
+
+
+struct ctdlipcmessage {
+       char msgid[256];                /* Original message ID */
+       char path[256];                 /* Return path to sender */
+       char zaps[256];                 /* Message ID that this supersedes */
+       char subject[256];              /* Message subject */
+       char email[256];                /* Email address of sender */
+       char author[USERNAME_SIZE];     /* Sender of message */
+       char recipient[USERNAME_SIZE];  /* Recipient of message */
+       char room[ROOMNAMELEN];         /* Originating room */
+       char node[16];                  /* Short nodename of origin system */
+       char hnod[21];                  /* Humannode of origin system */
+       struct parts *attachments;      /* Available attachments */
+       char *text;                     /* Message text */
+       int type;                       /* Message type */
+       time_t time;                    /* Time message was posted */
+       char nhdr;                      /* Suppress message header? */
+       char anonymous;                 /* An anonymous message */
+};
+
+
+struct ctdlipcmisc {
+       long newmail;                   /* Number of new Mail messages */
+       char needregis;                 /* Nonzero if user needs to register */
+       char needvalid;                 /* Nonzero if users need validation */
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int CtdlIPCNoop(void);
+int CtdlIPCEcho(const char *arg, char *cret);
+int CtdlIPCQuit(void);
+int CtdlIPCLogout(void);
+int CtdlIPCTryLogin(const char *username, char *cret);
+int CtdlIPCTryPassword(const char *passwd, char *cret);
+int CtdlIPCCreateUser(const char *username, char *cret);
+int CtdlIPCChangePassword(const char *passwd, char *cret);
+int CtdlIPCKnownRooms(int which, int floor, char *cret, struct march **listing);
+int CtdlIPCGetConfig(struct usersupp **uret, char *cret);
+int CtdlIPCSetConfig(struct usersupp *uret, char *cret);
+int CtdlIPCGotoRoom(const char *room, const char *passwd,
+               struct ctdlipcroom **rret, char *cret);
+int CtdlIPCGetMessages(int which, int whicharg, const char *template,
+               long **mret, char *cret);
+int CtdlIPCGetSingleMessage(long msgnum, int headers, int as_mime,
+               struct ctdlipcmessage **mret, char *cret);
+int CtdlIPCWhoKnowsRoom(char **listing, char *cret);
+int CtdlIPCServerInfo(char **listing, char *cret);
+int CtdlIPCReadDirectory(char **listing, char *cret);
+int CtdlIPCSetLastRead(long msgnum, char *cret);
+int CtdlIPCInviteUserToRoom(const char *username, char *cret);
+int CtdlIPCKickoutUserFromRoom(const char *username, char *cret);
+int CtdlIPCGetRoomAttributes(struct quickroom **qret, char *cret);
+int CtdlIPCSetRoomAttributes(int forget, struct quickroom *qret, char *cret);
+int CtdlIPCGetRoomAide(char *cret);
+int CtdlIPCSetRoomAide(const char *username, char *cret);
+int CtdlIPCPostMessage(int flag, const struct ctdlipcmessage *mr, char *cret);
+int CtdlIPCRoomInfo(char **iret, char *cret);
+int CtdlIPCDeleteMessage(long msgnum, char *cret);
+int CtdlIPCMoveMessage(int copy, long msgnum, const char *destroom, char *cret);
+int CtdlIPCDeleteRoom(int for_real, char *cret);
+int CtdlIPCCreateRoom(int for_real, const char *roomname, int type,
+               const char *password, int floor, char *cret);
+int CtdlIPCForgetRoom(char *cret);
+int CtdlIPCSystemMessage(const char *message, char **mret, char *cret);
+int CtdlIPCNextUnvalidatedUser(char *cret);
+int CtdlIPCGetUserRegistration(const char *username, char **rret, char *cret);
+int CtdlIPCValidateUser(const char *username, int axlevel, char *cret);
+int CtdlIPCSetRoomInfo(int for_real, const char *info, char *cret);
+int CtdlIPCUserListing(char **list, char *cret);
+int CtdlIPCSetRegistration(const char *info, char *cret);
+int CtdlIPCMiscCheck(struct ctdlipcmisc *chek, char *cret);
+int CtdlIPCDeleteFile(const char *filename, char *cret);
+int CtdlIPCMoveFile(const char *filename, const char *destroom, char *cret);
+int CtdlIPCNetSendFile(const char *filename, const char *destnode, char *cret);
+int CtdlIPCOnlineUsers(char **listing, time_t *stamp, char *cret);
+int CtdlIPCFileDownload(const char *filename, void **buf, char *cret);
+int CtdlIPCAttachmentDownload(long msgnum, const char *part, void **buf,
+               char *cret);
+int CtdlIPCImageDownload(const char *filename, void **buf, char *cret);
+int CtdlIPCFileUpload(const char *filename, const char *comment, void *buf,
+               size_t bytes, char *cret);
+int CtdlIPCImageUpload(int for_real, const char *filename, size_t bytes,
+               char *cret);
+int CtdlIPCQueryUsername(const char *username, char *cret);
+int CtdlIPCFloorListing(char **listing, char *cret);
+int CtdlIPCCreateFloor(int for_real, const char *name, char *cret);
+int CtdlIPCDeleteFloor(int for_real, int floornum, char *cret);
+int CtdlIPCEditFloor(int floornum, const char *floorname, char *cret);
+int CtdlIPCIdentifySoftware(int developerid, int clientid, int revision,
+               const char *software_name, const char *hostname, char *cret);
+int CtdlIPCSendInstantMessage(const char *username, const char *text,
+               char *cret);
+int CtdlIPCGetInstantMessage(char **listing, char *cret);
+int CtdlIPCEnableInstantMessageReceipt(int mode, char *cret);
+int CtdlIPCSetBio(char *bio, char *cret);
+int CtdlIPCGetBio(const char *username, char **listing, char *cret);
+int CtdlIPCListUsersWithBios(char **listing, char *cret);
+int CtdlIPCStealthMode(int mode, char *cret);
+int CtdlIPCTerminateSession(int sid, char *cret);
+int CtdlIPCTerminateServerNow(char *cret);
+int CtdlIPCTerminateServerScheduled(int mode, char *cret);
+int CtdlIPCEnterSystemMessage(const char *filename, const char *text,
+               char *cret);
+int CtdlIPCChangeHostname(const char *hostname, char *cret);
+int CtdlIPCChangeRoomname(const char *roomname, char *cret);
+int CtdlIPCChangeUsername(const char *username, char *cret);
+time_t CtdlIPCServerTime(char *crert);
+int CtdlIPCAideGetUserParameters(struct usersupp **uret, char *cret);
+int CtdlIPCAideSetUserParameters(const struct usersupp *uret, char *cret);
+int CtdlIPCGetMessageExpirationPolicy(int which, char *cret);
+int CtdlIPCSetMessageExpirationPolicy(int which, int policy, int value,
+               char *cret);
+int CtdlGetSystemConfig(char **listing, char *cret);
+int CtdlSetSystemConfig(const char *listing, char *cret);
+int CtdlIPCModerateMessage(long msgnum, int level, char *cret);
+int CtdlIPCRequestClientLogout(int session, char *cret);
+int CtdlIPCSetMessageSeen(long msgnum, int seen, char *cret);
+int CtdlIPCStartEncryption(char *cret);
+
+/* ************************************************************************** */
+/*             Stuff below this line is not for public consumption            */
+/* ************************************************************************** */
+
+inline void netio_lock(void);
+inline void netio_unlock(void);
+char *CtdlIPCReadListing(char *dest);
+int CtdlIPCSendListing(const char *listing);
+size_t CtdlIPCPartialRead(void **buf, size_t offset, size_t bytes, char *cret);
+int CtdlIPCEndUpload(char *cret);
+int CtdlIPCWriteUpload(void *buf, size_t bytes, char *cret);
+int CtdlIPCEndDownload(char *cret);
+int CtdlIPCReadDownload(void **buf, size_t bytes, char *cret);
+int CtdlIPCGenericCommand(const char *command, const char *to_send,
+               size_t bytes_to_send, char **to_receive,
+               size_t *bytes_to_receive, char *proto_response);
+
+#ifdef __cplusplus
+}
+#endif