]> code.citadel.org Git - citadel.git/commitdiff
* Added multi-connection support. libCxClient can now handle
authorBrian <brian@uncensored.citadel.org>
Mon, 14 May 2001 14:01:23 +0000 (14:01 +0000)
committerBrian <brian@uncensored.citadel.org>
Mon, 14 May 2001 14:01:23 +0000 (14:01 +0000)
multiple concurrent connections to different Citadel servers.
This, unfortunately, required a change in the development API.
Please examine the source in src/newtest.c for an example
of how to use the new API.
* Fixed some bugs in the transport layer.
* No new features that I can think of, but there may be some...

libCxClient/ChangeLog
libCxClient/src/CxClient.h
libCxClient/src/files.c
libCxClient/src/libtransport.c
libCxClient/src/messages.c
libCxClient/src/misc.c
libCxClient/src/newtest.c [new file with mode: 0644]
libCxClient/src/rooms.c
libCxClient/src/testlib.c
libCxClient/src/testlib.cpp [new file with mode: 0644]
libCxClient/src/users.c

index 75a20df3bac3d290f3109ec4b34d7c4161a002ef..4c9b6d8da5c18dcfc199ed1a6ab888c9b4ea44c1 100644 (file)
@@ -1,4 +1,13 @@
 $Log$
+Revision 1.10  2001/05/14 14:01:22  brian
+* Added multi-connection support.  libCxClient can now handle
+multiple concurrent connections to different Citadel servers.
+This, unfortunately, required a change in the development API.
+Please examine the source in src/newtest.c for an example
+of how to use the new API.
+* Fixed some bugs in the transport layer.
+* No new features that I can think of, but there may be some...
+
 Revision 1.9  2001/03/13 22:45:53  brian
 * Changed the return interface for CxMiExpRecv().  It now returns a more
 intelligible struct representing an atomic Express Message.  UNfortunately,
@@ -29,4 +38,3 @@ Revision 1.3  2001/02/07 22:42:24  brian
 
 Revision 1.2  2001/02/07 22:41:51  brian
 * Updated ChangeLog to conform to Citadel/UX standards (kinda)  :)
-
index c05debae1faa5c029f547f1e210cbacf181fd06c..1232818c8b94d78062bf0629b2f9f895e743ed81 100644 (file)
@@ -203,24 +203,32 @@ void              CxSerialize(const char *, char **);
  ** Client/Server Communications
  **/
 void           CxClRegClient(const char *);
-int            CxClConnect(const char *);
-void           CxClDisconnect();
-int            CxClStat();
-void           CxClSend(const char *s);
-int            CxClRecv(char *s);
+int            CxClConnection( const char *, int, const char *, const char * );
+void           CxClSetHost( int, const char *);
+void           CxClSetUser( int, const char *);
+char           *CxClGetUser( int );
+void           CxClSetPass( int, const char *);
+char           *CxClGetPass( int );
+void           CxClDelete( int );
+int            CxClConnect( int );
+void           CxClDisconnect( int );
+int            CxClStat( int );
+void           CxClSend( int, const char *s );
+int            CxClRecv( int, char *s );
+
 int            CxClChatInit();
 void           CxClChatShutdown();
 int            CxClCbRegister(int, void *);
 void           CxClCbShutdown();
-void           CxClCbRemove(int);
-CXCBHNDL       CxClCbExists(int);
+void           CxClCbRemove( int);
+CXCBHNDL       CxClCbExists( int);
 
 /**
  ** File Input/Output
  **/
-CXLIST         CxFiIndex();
-int            CxFiPut(FILEINFO, int);
-char           *CxFiGet(const char *);
+CXLIST         CxFiIndex( int );
+int            CxFiPut( int, FILEINFO, int);
+char           *CxFiGet(int, const char *);
 
 /**
  ** Message Input/Output
@@ -229,30 +237,30 @@ char              *CxFiGet(const char *);
 #define                MSGS_NEW                1       // Retrieve only UNREAD messages in room.
 #define                MSGS_LAST               2       // *Unsupported* Retrieve the LAST X messages in room.
 #define                MSGS_SEARCH             3       // *Unsupported* Search room for ...? .
-CXLIST         CxMsInfo(CXLIST);
-CXLIST         CxMsList(int,int);
-int            CxMsLoad(const char *, int, MESGINFO *);
-int            CxMsSaveOk(const char *);
-int            CxMsSave(MESGINFO);
-void           CxMsMark( long unsigned int );
+CXLIST         CxMsInfo(int, CXLIST);
+CXLIST         CxMsList(int, int,int);
+int            CxMsLoad(int, const char *, int, MESGINFO *);
+int            CxMsSaveOk(int, const char *);
+int            CxMsSave(int, MESGINFO);
+void           CxMsMark(int, long unsigned int );
 
 /**
  ** Room/Floor Commands
  **/
-ROOMINFO       *CxRmGoto(const char *, int);
-CXLIST         CxRmList();
-CXLIST         CxFlList();
-int            CxRmCreate(ROOMINFO);
+ROOMINFO       *CxRmGoto(int, const char *, int);
+CXLIST         CxRmList(int);
+CXLIST         CxFlList(int);
+int            CxRmCreate(int, ROOMINFO);
 
 /**
  ** Miscellaneous Commands
  **/
-int            CxMiExpSend(const char *, const char *);
-EXPRMESG       *CxMiExpRecv();
-int            CxMiExpCheck();
+int            CxMiExpSend(int,const char *, const char *);
+EXPRMESG       *CxMiExpRecv(int);
+int            CxMiExpCheck(int);
 void           CxMiExpHook(void (*)(const char *, const char*));
-char           *CxMiMessage(const char *);
-char           *CxMiImage(const char *);
+char           *CxMiMessage(int,const char *);
+char           *CxMiImage(int,const char *);
 
 /**
  ** Linked-List Handlers
@@ -264,10 +272,10 @@ CXLIST            CxLlFlush(CXLIST);
 /**
  ** User Info Commands
  **/
-CXLIST          CxUsOnline(int);
-CXLIST          CxUsList();
-int            CxUsCreate(USERINFO);
-USERINFO       *CxUsAuth(const char *, const char *);
+CXLIST          CxUsOnline(int, int);
+CXLIST          CxUsList(int );
+int            CxUsCreate(int, USERINFO);
+USERINFO       *CxUsAuth(int, const char *, const char *);
 
 #ifdef         __cplusplus
 } // extern "C"
index 273be713fa987e17b5631deff3f57ee6031808fa..d85b51d8d97800c22fb509c66076b660af9c9b08 100644 (file)
@@ -28,7 +28,7 @@ static void   (*_CxFiFunc)(const char *, void *);
  **  Success, No Files: 1 blank entry
  **  Failure: NULL list.
  **/
-CXLIST         CxFiIndex() {
+CXLIST         CxFiIndex( int id ) {
 int            rc;
 char           buf[512];
 CXLIST         flist = 0;
@@ -39,8 +39,8 @@ CXLIST                flist = 0;
         ** Request directory listing from server.
         **/
        DPF((DFA,"Sending request..."));
-       CxClSend("RDIR");
-       rc = CxClRecv(buf);
+       CxClSend(id, "RDIR");
+       rc = CxClRecv(id, buf);
 
        /**
         ** If this room allows directory listings...
@@ -49,7 +49,7 @@ CXLIST                flist = 0;
                DPF((DFA,"LISTING_FOLLOWS..."));
 
                do {
-                       rc = CxClRecv(buf);
+                       rc = CxClRecv(id, buf);
                        DPF((DFA,"%s", buf));
                        if(rc<0) {
                                flist = CxLlInsert(flist, buf);
@@ -82,7 +82,7 @@ CXLIST                flist = 0;
  **  Failure; File Exists: 3
  **  Failure; Nonexistent FILE pointer.
  **/
-int            CxFiPut(FILEINFO f_info, int f_ptr) {
+int            CxFiPut(int id, FILEINFO f_info, int f_ptr) {
        return(0);
 }
 
@@ -96,7 +96,7 @@ int           CxFiPut(FILEINFO f_info, int f_ptr) {
  **  Success: Ptr to malloc()ed tmp filename containing file data.
  **  Failure: NULL
  **/
-char           *CxFiGet(const char *name) {
+char           *CxFiGet(int id, const char *name) {
 
 
        /**
index acc9f3ae285d572f6af7d6f75e40bf95d5602d2e..d763eee7e885aff47736773580256ce1a5fe3e56 100644 (file)
@@ -40,7 +40,338 @@ static CXCBHNDL     _CbHandles = 0;
 static char    g_CxClientName[32] = "";
 static int     _CxCallback(int cmd, void *data);
 static void    timeout() {}
+static void    _CxClSend( int, const char * );
+static int     _CxClRecv( int, int*, char * );
 
+/**
+ ** CXTBL: Connection handle table.  Use this to make libCxClient thread-safe, and allow
+ ** us to maintain multiple concurrent connections.
+ **/
+typedef struct _cx_tbl_entry {
+
+       int     cxId;           /* cxId: Connection ID */
+
+       char    host[255],      /* host: Citadel/UX hostname */
+               user[64],       /* user: Citadel/UX username */
+               pass[64];       /* pass: Citadel/UX password */
+       int     port,           /* port: Port number to connect to */
+               connected,      /* connected: (bool) Are we connected to our Citadel/UX host? */
+               asynMode,       /* asynMode: (bool) Are we actively in ASYN mode? */
+               semaphore;      /* semaphore: (bool) Prevent access to _sock at this time? */
+
+       /**
+        ** Internal
+        **/
+       int     _sock;          /* _sock: TCP/IP connection socket */
+
+       struct _cx_tbl_entry
+               *_next,         /* _next: Next CXTBL entry. */
+               *_prev;         /* _prev: Previous CXTBL entry. */
+
+}              CXTBLENT;
+typedef CXTBLENT* CXHNDL;
+
+/**
+ ** [GLOBAL CXTABLE] There should only exist one of these in memory at any
+ ** point in time, to ensure threadsafeness.
+ **/
+static CXHNDL          g_CxTbl = 0L;
+
+/**
+ ** _CxTbNewID(): Get the next cxId for the specified connection table.
+ **/
+static
+int            _CxTbNewID( CXHNDL tbl ) {
+CXHNDL         p;
+int            ret;
+
+       p = tbl;
+       ret = 1;
+       while( p ) {
+               if(p->cxId == ret) ret = (p->cxId)+1;
+               p = p->_next;
+       }
+
+       DPF((DFA, "Next cxId: %d", ret));
+       return(ret);
+}
+
+/**
+ ** _CxTbNew(): New CXTBL entry.
+ **/
+static
+CXHNDL         _CxTbNew( CXHNDL tbl ) {
+CXHNDL         ret = 0;
+
+       DPF((DFA, "Creating new CXTBL handle."));
+
+       ret = (CXHNDL) malloc( sizeof(CXTBLENT) );
+       if(ret<=0) return(NULL);
+
+       /**
+        ** Initialize these pointers to prevent confusion.
+        **/
+       ret->_next = NULL;
+       ret->_prev = NULL;
+
+       /**
+        ** Establish Default values
+        **/
+       ret->port = 504;
+       ret->connected = 0;
+       ret->asynMode = 0;
+       ret->semaphore = 0;
+       ret->_sock = 0;
+       ret->host[0] = 0;
+       ret->user[0] = 0;
+       ret->pass[0] = 0;
+
+       /**
+        ** Obtain the next cxId for this particular table.
+        **/
+       ret->cxId = _CxTbNewID( tbl );
+
+       DPF((DFA, "Returning hndl @0x%08x", ret ));
+       return(ret);
+}
+
+/**
+ ** _CxTbEntry(): Return a handle to a particular table entry.
+ **/
+static
+CXHNDL         _CxTbEntry( CXHNDL tbl, int id ) {
+CXHNDL         p;
+
+       DPF((DFA,"Resolve [tbl@0x%08x] id %d", tbl, id ));
+       p = tbl;
+       while( p ) {
+               DPF((DFA,"p->cxId: %d", p->cxId));
+               if( id == p->cxId ) {
+                       DPF((DFA," ->host: %s:%d", p->host, p->port));
+                       DPF((DFA," ->user: %s", p->user));
+                       DPF((DFA," ->pass: %s", p->pass));
+                       DPF((DFA," ->_sock: %d", p->_sock));
+                       return(p);
+               }
+               p = p->_next;
+       }
+       return((CXHNDL)NULL);
+}
+
+/**
+ ** _CxTbInsert(): Insert a new CxTbl entry into the table.  Return a handle
+ ** id for the new entry.  (Parameters here can be set at a later time.)
+ **/
+static
+int            _CxTbInsert( const char *host, int port, const char *user, const char *pass ) {
+CXHNDL         p,n;
+char           *tmp;
+
+       DPF((DFA,"Insert new table entry."));
+
+       DPF((DFA,"Allocating new CXTBL block."));
+       n = _CxTbNew( g_CxTbl );
+
+       DPF((DFA,"Copying host"));
+       if(host && *host) {
+               if(strlen(host) >= 254) {
+                       tmp = strdup(host);
+                       tmp[254] = 0;
+                       strcpy(n->host, tmp);
+                       free(tmp);
+               } else {
+                       strcpy(n->host, host);
+               }
+       }
+
+       DPF((DFA,"Copying user"));
+       if(user && *user) {
+               if(strlen(user) >= 64) {
+                       tmp = strdup(user);
+                       tmp[64] = 0;
+                       strcpy(n->user, tmp);
+                       free(tmp);
+               } else {
+                       strcpy(n->user, user);
+               }
+       }
+
+       DPF((DFA,"Copying pass"));
+       if(pass && *pass) {
+               if(strlen(pass) >= 64) {
+                       tmp = strdup(pass);
+                       tmp[64] = 0;
+                       strcpy(n->pass, tmp);
+                       free(tmp);
+               } else {
+                       strcpy(n->pass, pass);
+               }
+       }
+
+       DPF((DFA,"Copying port"));
+       if(port) n->port = port;
+
+       DPF((DFA,"Binding to g_CxTbl"));
+       if(!g_CxTbl) {
+               DPF((DFA,"new g_CxTbl"));
+               g_CxTbl = n;
+               DPF((DFA,"New table @0x%08x", g_CxTbl ));
+               return(n->cxId);
+
+       } else {
+               DPF((DFA,"existing g_CxTbl"));
+               p = g_CxTbl;
+               while( p && p->_next ) {
+                       p = p->_next;
+               }
+               if( p ) {
+                       p->_next = n;
+                       n->_prev = p;
+               }
+       }
+
+       return(n->cxId);
+}
+
+/**
+ ** _CxTbDelete(): Delete the specified id.
+ **/
+static
+void           _CxTbDelete( int id ) {
+CXHNDL         p;
+
+       if(!g_CxTbl || !id ) return;
+
+       DPF((DFA,"Delete id %d", id));
+       p = g_CxTbl;
+       while( p ) {
+               if( p->cxId == id ) break;
+               p = p->_next;
+       }
+
+       DPF((DFA,"p @0x%08x", p));
+
+       if( p ) {
+
+               DPF((DFA,"p->_next @0x%08x", p->_next));
+               DPF((DFA,"p->_prev @0x%08x", p->_prev));
+
+               /**
+                ** This was the only entry in the CxTbl.
+                **/
+               if( !p->_next && !p->_prev ) {
+                       free(p);
+                       g_CxTbl = NULL;
+
+               /**
+                ** Gymnastics time...
+                **/
+               } else {
+                       if( p->_next ) p->_next->_prev = p->_prev;
+                       if( p->_prev ) p->_prev->_next = p->_next;
+
+                       if( g_CxTbl == p ) g_CxTbl = p->_next;
+
+                       free( p );
+               }
+       }
+       DPF((DFA,"g_CxTbl @0x%08x", g_CxTbl));
+}
+
+/**
+ ** CxClConnection(): Obtain a Connection handle for a new host/username/password.  This _must_ be
+ ** performed before any other CxCl functions can be called.
+ **/
+int            CxClConnection( const char *host, int port, const char *user, const char *pass ) {
+
+       DPF((DFA,"New connection hndl %s:%s@%s:%d", user, "**", host, port));
+       return(_CxTbInsert( host, port, user, pass ) );
+}
+
+/**
+ ** CxClDelete(): Delete the specified connection handle.
+ **/
+void           CxClDelete( int id ) {
+
+       DPF((DFA,"Delete hndl %d", id ));
+       _CxTbDelete( id );
+}
+
+
+/**
+ ** CxClSetHost(): Set the username for a specific connection handle.
+ **/
+void           CxClSetHost( int id, const char *host ) {
+CXHNDL         e;
+
+       if(!host || !*host) return;
+
+       e = _CxTbEntry( g_CxTbl, id );
+       if(!e) return;
+
+       DPF((DFA,"Set tbl[%d].host = '%s'", id, host ));
+       memset( &(e->host), 0, 253 );
+       strcpy( e->host, host );
+}
+
+/**
+ ** CxClSetUser(): Set the username for a specific connection handle.
+ **/
+void           CxClSetUser( int id, const char *user ) {
+CXHNDL         e;
+
+       if(!user || !*user) return;
+
+       e = _CxTbEntry( g_CxTbl, id );
+       if(!e) return;
+
+       DPF((DFA,"Set tbl[%d].user = '%s'", id, user ));
+       strcpy( e->user, user );
+}
+
+/**
+ ** CxClGetUser(): Set the username for a specific connection handle.
+ **/
+char           *CxClGetUser( int id ) {
+CXHNDL         e;
+
+       e = _CxTbEntry( g_CxTbl, id );
+       if(!e) return(NULL);
+
+       if(e->user) return(strdup(e->user));
+       else return(NULL);
+}
+
+/**
+ ** CxClSetPass(): Set the username for a specific connection handle.
+ **/
+void           CxClSetPass( int id, const char *pass ) {
+CXHNDL         e;
+
+       if(!pass || !*pass) return;
+
+       e = _CxTbEntry( g_CxTbl, id );
+       if(!e) return;
+
+       DPF((DFA,"Set tbl[%d].pass = '%s'", id, pass ));
+       strcpy( e->pass, pass );
+}
+
+/**
+ ** CxClGetPass(): Set the username for a specific connection handle.
+ **/
+char           *CxClGetPass( int id ) {
+CXHNDL         e;
+
+       e = _CxTbEntry( g_CxTbl, id );
+       if(!e) return(NULL);
+
+       if(e->user) return(strdup(e->pass));
+       else return(NULL);
+}
+
+/**
+ ** CxClSetPass(): Set the username for a specific connection handle.
 /**
  ** CxClRegClient(): (For Developers) Register your client name with
  ** libCxClient.  This gets reported along with the IDEN information passed
@@ -67,8 +398,13 @@ void                CxClRegClient(const char *cl_name) {
 /**
  ** CxClConnect(): Establish a connection to the server via the Transport layer.
  ** [Much of this code was gleaned from the "citadel" client]
+ **
+ ** [Returns]
+ **  On Success: 0
+ **  On Failure: -1: Mis-configuration
+ **              -[errno]: use abs(errno) to retrieve error message.
  **/
-int            CxClConnect(const char *host) {
+int            CxClConnect( int id ) {
 char           buf[512];
 struct 
 hostent        *phe;
@@ -81,13 +417,27 @@ sockaddr_in        sin;
 int            s, type, rc;
 char           *service = "citadel";
 char           *protocol = "tcp";
+CXHNDL         e;
 
        DPF((DFA,"(Library was built with UNIX_SOCKET support)"));
-       DPF((DFA,"Establishing connection to host \"%s\"",host));
+
+       e = _CxTbEntry( g_CxTbl, id );
+
+       if(!e) {
+               DPF((DFA,"Did not call CxConnection(), huh?"));
+               return(-1);
+       }
+
+       if(!e->host[0]) {
+               DPF((DFA,"No hostname provided.  Use CxClSetHost() first!!"));
+               return(-1);
+       }
+       DPF((DFA,"Establishing connection to host \"%s\"",e->host));
 
         memset(&sin, 0, sizeof(sin));
         sin.sin_family = AF_INET;
 
+       DPF((DFA,"-> getservbyname()"));
         pse = getservbyname(service, protocol);
         if(pse) {
                 sin.sin_port = pse->s_port;
@@ -95,19 +445,21 @@ char               *protocol = "tcp";
         } else {
                sin.sin_port = htons((u_short)504);
        }
-        phe = gethostbyname(host);
+
+       DPF((DFA,"-> gethostbyname(): \"%s\"", e->host));
+        phe = gethostbyname(e->host);
+       DPF((DFA,"phe@0x%08x", phe));
         if (phe) {
                 memcpy(&sin.sin_addr, phe->h_addr, phe->h_length);
 
-        } else if ((sin.sin_addr.s_addr = inet_addr(host)) == INADDR_NONE) {
-               printf("\n* * *  Fatal Error  * * *\n");
-               printf("System Error: Can't get host entry for '%s'\n", host);
-               printf("Error Details: %s\n\n", strerror(errno));
-                return(-1);
+        } else if ((sin.sin_addr.s_addr = inet_addr(e->host)) == INADDR_NONE) {
+               DPF((DFA,"Unable to get host entry.  %s", strerror(errno)));
+                return(-1*(errno));
         }
+
+       DPF((DFA,"-> getprotobyname()"));
         if ((ppe = getprotobyname(protocol)) == 0) {
-                fprintf(stderr, "Can't get %s protocol entry: %s\n",
-                        protocol, strerror(errno));
+               DPF((DFA,"Unable to get protocol entry.  %s", strerror(errno)));
                 return(-1);
         }
         if (!strcmp(protocol, "udp")) {
@@ -118,44 +470,51 @@ char              *protocol = "tcp";
 
         s = socket(PF_INET, type, ppe->p_proto);
         if (s < 0) {
-               printf("\n* * *  Fatal Error  * * *\n");
-               printf("System Error: Can't create socket\n");
-               printf("Error Details: %s\n\n", strerror(errno));
+               DPF((DFA,"Unable to create socket.  %s", strerror(errno)));
                 return(-1);
         }
         signal(SIGALRM, timeout);
         alarm(30);
-         if (connect(s, (struct sockaddr *) &sin, sizeof(sin)) < 0) {
-               printf("\n* * *  Fatal Error  * * *\n");
-               printf("System Error: Can't connect to '%s' [%s]\n",host, service);
-               printf("Error Details: %s\n\n", strerror(errno));
-                return(-1);
+
+       DPF((DFA,"-> connect()"));
+        if (connect(s, (struct sockaddr *) &sin, sizeof(sin)) < 0) {
+/**            printf("\n* * *  Fatal Error  * * *\n");
+               printf("System Error: Can't connect to '%s' [%s]\n",e->host, service);
+               printf("Error Details: %s\n\n", strerror(errno)); 
+ **/
+                return(errno*(-1));
         }
         alarm(0);
         signal(SIGALRM, SIG_IGN);
 
-       g_CxSocket = s;
+       DPF((DFA,"Socket %d", s));
+       e->_sock = s;
+       e->connected = 1;
 
-       if(s) {
-               CxClRecv(buf);
+       if( s ) {
+
+               DPF((DFA,"-> recv"));
+               _CxClRecv( e->_sock, &(e->semaphore), buf );
                if(g_CxClientName[0]) {
                        sprintf(buf,"IDEN 1|1|100|CX/%s (%s)|",VERSION, g_CxClientName);
                } else {
                        sprintf(buf,"IDEN 1|1|100|CX/%s (unknown)|",VERSION);
                }
-               CxClSend(buf);
-               CxClRecv(buf);
-               CxClSend("ASYN 1");
-               rc = CxClRecv(buf);
+               _CxClSend(s, buf);
+               _CxClRecv(e->_sock, &(e->semaphore), buf);
+               _CxClSend(s, "ASYN 1");
+               rc = _CxClRecv(e->_sock, &(e->semaphore), buf);
 
                /**
                 ** If the server doesn't support Asnychronous mode, then
                 ** we shouldn't try to be asynchronous...
                 **/
                if(CHECKRC(rc, RC_OK)) {
-                       g_CxAsynMode = 1;
+                       e->asynMode = 1;
+//                     g_CxAsynMode = 1;
                } else {
-                       g_CxAsynMode = 0;
+                       e->asynMode = 0;
+//                     g_CxAsynMode = 0;
                }
 
                /**
@@ -167,21 +526,34 @@ char              *protocol = "tcp";
 }
 
 /**
- ** CxClDisconnect(): Disconnect the socket.
+ ** CxClDisconnect(): Disconnect the specified socket.
  **/
-void           CxClDisconnect() {
+void           CxClDisconnect( int id ) {
+CXHNDL         e;
+
+       DPF((DFA,"Caught orders to close socket %d.", id));
 
-       DPF((DFA,"Caught orders to close socket."));
+       e = _CxTbEntry( g_CxTbl, id );
+       if(!e) return;
 
-       shutdown(g_CxSocket, 0);
+       /**
+        ** Sleep until the semaphore is cleared.
+        **/
+       while(e->semaphore) ;
+
+       shutdown(e->_sock, 0);
 }
 
 /**
  ** CxClStat(): Return connection status.
  **/
-int            CxClStat() {
+int            CxClStat( int id ) {
+CXHNDL         e;
+
+       e = _CxTbEntry( g_CxTbl, id );
+       if(!e) return(0);
 
-       if(g_CxSocket) {
+       if( e->connected ) {
                return(1);
        } else {
                return(0);
@@ -189,19 +561,20 @@ int               CxClStat() {
 }
 
 /**
- ** CxClSend(): Send a string to the server.
+ ** _CxClSend(): REAL send.  Uses a socket instead of the ID.
  **/
-void           CxClSend(const char *s) {
+static
+void           _CxClSend( int sock, const char *s ) {
 int            bytes_written = 0;
 int            retval,nbytes;
 char           *ss;
 
        /**
-        ** Don't try to do anything if we are not connected.
+        ** If the socket is not open, there's no point in going here.
         **/
-       if(!CxClStat()) return;
+       if(!sock) return;
 
-       DPF((DFA,"SEND: \"%s\"", s));
+       DPF((DFA,"REALSEND: \"%s\"", s));
 
        ss = (char *)CxMalloc(strlen(s)+2);
        sprintf(ss,"%s\n",s);
@@ -212,29 +585,43 @@ char              *ss;
                return;
        }
 
-        while (bytes_written < nbytes) {
-                retval = write(g_CxSocket, &ss[bytes_written],
-                               nbytes - bytes_written);
-                if (retval < 1) {
-                       write (g_CxSocket, "\n", strlen("\n"));
-                        return;
-                }
-                bytes_written = bytes_written + retval;
-        }
+       while (bytes_written < nbytes) {
+               retval = write(sock, &ss[bytes_written],
+                               nbytes - bytes_written);
+               if (retval < 1) {
+                       write (sock, "\n", strlen("\n"));
+                       return;
+               }
+               bytes_written = bytes_written + retval;
+       }
        CxFree(ss);
 }
 
+/**
+ ** CxClSend(): Send a string to the server.
+ **/
+void           CxClSend(int id, const char *s) {
+CXHNDL         e;
+
+       e = _CxTbEntry( g_CxTbl, id );
+       if(!e) return;
+       if(!e->connected) return;
+
+       DPF((DFA,"SEND: \"%s\"", s));
+       _CxClSend( e->_sock, s );
+}
+
 /**
  ** ClRecvChr(): Retrieve the next message from the server.
  ** *********** SOURCE: citadel-source/citmail.c:serv_read()
  **/
 static
-void           ClRecvChar(char *buf, int bytes) {
+void           ClRecvChar(int socket, char *buf, int bytes) {
 int            len, rlen;
 
         len = 0;
         while (len < bytes) {
-                rlen = read(g_CxSocket, &buf[len], bytes - len);
+                rlen = read(socket, &buf[len], bytes - len);
                 if (rlen < 1) {
                         return;
                 }
@@ -246,56 +633,55 @@ int               len, rlen;
  ** _CxClWait(): Wait on the semaphore.
  **/
 static
-void           _CxClWait() {
+void           _CxClWait( int *e ) {
 
        DPF((DFA,"Waiting on Semaphore..."));
-       while(g_CxSemaphore) ;
+       while(*e) ;
 
        DPF((DFA,"*** LOCKING SESSION ***"));
-       g_CxSemaphore = 1;
+       (*e) = 1;
 }
 
 /**
  ** _CxClClear(): Clear the semaphore.
  **/
 static
-void           _CxClClear() {
+void           _CxClClear( int *e ) {
 
        DPF((DFA,"*** CLEARING SESSION ***"));
-       g_CxSemaphore = 0;
+       (*e) = 0;
 }
 
 /**
- ** CxClRecv(): Receive a string from the server.
+ ** _CxClRecv(): REAL receive.
  **/
-int            CxClRecv(char *s) {
+static
+int            _CxClRecv( int sock, int *semaphore, char *s ) {
 char           substr[4];
 int            i, tmp;
 
        /**
-        ** If we are not connected, do nothing.
+        ** If the socket is not open, there's no point in going here.
         **/
-       if(!CxClStat()) return(NULL);
-
-       /**
-        ** At this point, we should wait for the semaphore to be cleared.
-        ** This will prevent multi-threaded clients from pissing all over
-        ** themselves when 2 threads attempt to read at the same time...
-        **/
-       _CxClWait();
+       DPF((DFA,"Receive on %d", sock));
+       if(!sock) {
+               DPF((DFA,"No socket."));
+               return(0);
+       }
 
        /**
         ** RETRY_RECV when we have a callback and need to re-synch the protocol.
         **/
 RETRY_RECV:
 
+       _CxClWait( semaphore );
        DPF((DFA,"for(;message <= bottle;) ;"));
  
        /**
         ** Read one character at a time.
          **/
        for(i = 0; ; i++) {
-               ClRecvChar(&s[i], 1);
+               ClRecvChar(sock, &s[i], 1);
                if (s[i] == '\n' || i == 255)
                        break;
        }
@@ -305,8 +691,9 @@ RETRY_RECV:
         **/
        if (i == 255)
                while (s[i] != '\n')
-                       ClRecvChar(&s[i], 1);
+                       ClRecvChar(sock, &s[i], 1);
+
+       _CxClClear( semaphore );
 
        /**
         ** Strip all trailing nonprintables (crlf)
@@ -355,12 +742,6 @@ RETRY_RECV:
                i = -1;
        }
 
-       /**
-        ** We wish to clear the semaphore BEFORE executing any callbacks.
-        ** This will help to prevent nasty race conditions.  >:)
-        **/
-       _CxClClear();
-
        /**
         ** This is the only instance of Goto you'll find in
         ** libCxClient.  The point is: Once we're done handling
@@ -409,6 +790,29 @@ RETRY_RECV:
        return(i);
 }
 
+/**
+ ** CxClRecv(): Receive a string from the server.
+ **/
+int            CxClRecv(int id, char *s) {
+char           substr[4];
+int            i, tmp;
+CXHNDL         e;
+
+       DPF((DFA,"Receive on handle %d", id));
+       e = _CxTbEntry( g_CxTbl, id );
+       if(!e) {
+               DPF((DFA,"Handle %d unresolvable", id));
+               return(0);
+       }
+       if(!e->connected) {
+               DPF((DFA,"Handle %d not connected", id));
+               return(0);
+       }
+
+       DPF((DFA,"Preparing to receive on %d", e->_sock));
+       return(_CxClRecv( e->_sock, &(e->semaphore), s ));
+}
+
 /**
  ** CxClChatInit(): Initialize Chat mode
  **/
@@ -568,7 +972,8 @@ CXCBHNDL    new;
 void           CxClCbShutdown() {
 CXCBHNDL       x, y;
 
-       return(0);
+       return;
+
        DPF((DFA,"Shutting down callback subsystem"));
        x = _CbHandles;
        while( x ) {
index ecd989656a6165a78ba2e1ffb12eb59e5e69e912..ea3314b4ef5ccda40374a28b8edaa0a29be00731 100644 (file)
@@ -21,7 +21,7 @@
  ** CxMsInfo(): Retrieve message information for all of the message id's listed inside
  ** of a Message List.
  **/
-CXLIST         CxMsInfo(CXLIST msg_list) {
+CXLIST         CxMsInfo(int id, CXLIST msg_list) {
 CXLIST         mp, messages = NULL;
 char           buf[255], *from, *date, *subject;
 int            rc;
@@ -31,13 +31,13 @@ int         rc;
        mp = msg_list;
        while ( mp ) {
                sprintf(buf,"MSG0 %s|1",mp->data);
-               CxClSend(buf);
-               rc = CxClRecv(buf);
+               CxClSend(id, buf);
+               rc = CxClRecv(id, buf);
                if( CHECKRC(rc, RC_LISTING)) {
                        from = date = subject = 0;
                        do {
 
-                               rc = CxClRecv(buf);
+                               rc = CxClRecv(id, buf);
                                if(rc && strstr(buf,"from=")) {
                                        DPF((DFA, "from: %s",buf));
 
@@ -74,7 +74,7 @@ int           rc;
 /**
  ** CxMsList(): Retrieve a list of messages in the current room.
  **/
-CXLIST         CxMsList(int list_type, int number_messages) {
+CXLIST         CxMsList(int id, int list_type, int number_messages) {
 int            rc;
 char           buf[255], *malleable;
 CXLIST         msgs = NULL;
@@ -87,19 +87,19 @@ CXLIST              msgs = NULL;
                        break;
 
                case(1):
-                       CxClSend("MSGS NEW");
+                       CxClSend( id, "MSGS NEW");
                        break;
 
                default:
-                       CxClSend("MSGS");
+                       CxClSend( id, "MSGS");
                        break;
        }
-       rc = CxClRecv( buf );
+       rc = CxClRecv( id, buf );
 
        if( CHECKRC(rc, RC_LISTING) ) {
 
                do {
-                       rc = CxClRecv(buf);
+                       rc = CxClRecv(id, buf);
 
                        if(rc) {
                                malleable = (char *)CxMalloc(strlen(buf) + 1);
@@ -126,7 +126,7 @@ CXLIST              msgs = NULL;
  **
  ** CLIENT MUST free(toret.body) MANUALLY!!!!
  **/
-int            CxMsLoad(const char *mid, int preserve_newlines, MESGINFO *toret) {
+int            CxMsLoad(int id, const char *mid, int preserve_newlines, MESGINFO *toret) {
 char           buf[255], *newline="\n";
 int            rc, message_contents = 0, line_width;
 
@@ -138,12 +138,12 @@ int               rc, message_contents = 0, line_width;
        toret->subject[0] = 0;
 
        sprintf(buf,"MSG2 %s",mid);
-       CxClSend(buf);
-       rc = CxClRecv(buf);
+       CxClSend(id, buf);
+       rc = CxClRecv(id, buf);
        if(CHECKRC(rc, RC_LISTING) ) {
                DPF((DFA,"RC_LISTING"));
                do {
-                       rc = CxClRecv(buf);
+                       rc = CxClRecv(id, buf);
                        if( rc ) {
                                if(buf[strlen(buf)-1]=='\r') 
                                        buf[strlen(buf)-1]=0;
@@ -196,7 +196,7 @@ int         rc, message_contents = 0, line_width;
                                         **/
 
                                        do {
-                                               rc = CxClRecv(buf);
+                                               rc = CxClRecv(id, buf);
                                                if(rc) {
                                                        DPF((DFA,"%s",buf));
 
@@ -270,14 +270,14 @@ int               rc, message_contents = 0, line_width;
  ** CxMsSaveOk(): Verify that users can post to this room.  Returns 1 if posting is
  ** allowed, 0 if posting is not allowed.
  **/
-int            CxMsSaveOk(const char *username) {
+int            CxMsSaveOk(int id, const char *username) {
 int            rc;
 char           buf[255];
 
        DPF((DFA,"Checking room for post permissions..."));
        sprintf(buf,"ENT0 0|%s|0|0",username);
-       CxClSend(buf);
-       rc = CxClRecv(buf);
+       CxClSend(id, buf);
+       rc = CxClRecv(id, buf);
        if(CHECKRC(rc, RC_OK) ) {
                DPF((DFA,"Ok for posting"));
                return(1);
@@ -299,7 +299,7 @@ char                buf[255];
  **  3: Message rejected for unknown reasons.
  **  ... tba
  **/
-int            CxMsSave(MESGINFO msg) {
+int            CxMsSave(int id, MESGINFO msg) {
 int            rc;
 char           buf[255];
 
@@ -312,31 +312,31 @@ char              buf[255];
 
        DPF((DFA,"Checking for access..."));
        sprintf(buf,"ENT0 0|%s|0|0",msg.rcpt);
-       CxClSend(buf);
-       rc = CxClRecv(buf);
+       CxClSend(id, buf);
+       rc = CxClRecv(id, buf);
        DPF((DFA,"Server said [%s]",buf));
 
        if( CHECKRC(rc, RC_OK)) {
                DPF((DFA,"Permission to save"));
 
                sprintf(buf,"ENT0 1|%s|0|4|",msg.rcpt);
-               CxClSend(buf);
+               CxClSend(id, buf);
 
-               rc = CxClRecv(buf);
+               rc = CxClRecv(id, buf);
                if( CHECKRC(rc, RC_SENDLIST)) {
                        DPF((DFA,"Sending message to server..."));
                        sprintf(buf, "From: %s", msg.author);
-                       CxClSend(buf);
+                       CxClSend(id, buf);
                        sprintf(buf, "To: %s", msg.rcpt);
-                       CxClSend(buf);
+                       CxClSend(id, buf);
                        sprintf(buf, "X-Mailer: libCxClient %s", CXREVISION);
-                       CxClSend(buf);
+                       CxClSend(id, buf);
                        sprintf(buf, "Subject: %s", msg.subject);
-                       CxClSend(buf);
-                       CxClSend("");
-                       CxClSend(msg.body);
+                       CxClSend(id, buf);
+                       CxClSend(id, "");
+                       CxClSend(id, msg.body);
 
-                       CxClSend("000");
+                       CxClSend(id, "000");
                        DPF((DFA,"Done!"));
 
                        DPF((DFA,"Server accepted message"));
@@ -354,7 +354,7 @@ char                buf[255];
 /**
  ** CxMsMark(): Mark message(s) as read.
  **/
-void           CxMsMark( long unsigned int msgid ) {
+void           CxMsMark( int id, long unsigned int msgid ) {
 char           buf[1024];
 int            rc;
 
@@ -367,8 +367,8 @@ int         rc;
                sprintf( buf, "SLRP %ld", msgid );
        }
 
-       CxClSend( buf );
-       rc = CxClRecv( buf );
+       CxClSend( id, buf );
+       rc = CxClRecv( id, buf );
 
        if( rc == RC_OK ) {
                DPF((DFA, "Done."));
index 88306ffaa697c24435dd012a7c9fea49030acd19..e649ab0ffb350144077c24a3dc59550f676edb9e 100644 (file)
@@ -70,17 +70,17 @@ int             i;
  **  Success: 0
  **  Failure; Unknown Error: 1
  **/
-int            CxMiExpSend(const char *user, const char *msg) {
+int            CxMiExpSend(int id, const char *user, const char *msg) {
 char           *xmit, buf[255];
 int            rc;
 
        DPF((DFA,"Send Express Message"));
        xmit = (char *)CxMalloc(strlen(user)+strlen(msg) + 7);
        sprintf(xmit,"SEXP %s|%s",user, msg);
-       CxClSend(xmit);
+       CxClSend(id, xmit);
        CxFree(xmit);
 
-       rc = CxClRecv(buf);
+       rc = CxClRecv(id, buf);
        if( CHECKRC(rc, RC_OK) ) {
                return(0);
 
@@ -97,7 +97,7 @@ int           rc;
  **  Success: Ptr to malloc()ed EXPRMESG struct.  [*]
  **  Failure: NULL
  **/
-EXPRMESG       *CxMiExpRecv() {
+EXPRMESG       *CxMiExpRecv( int id ) {
 char           buf[255], *Ser[20];
 EXPRMESG       *toret;
 int            rc;
@@ -106,8 +106,8 @@ int         rc;
         ** Ask the server for the latest Express Message [GEXP].
         **/
        DPF((DFA,"Receive Express Message"));
-       CxClSend("GEXP");
-       rc = CxClRecv(buf);
+       CxClSend(id, "GEXP");
+       rc = CxClRecv(id, buf);
        DPF((DFA,"buf=%s\n",buf));
        toret = 0L;
 
@@ -130,7 +130,7 @@ int         rc;
                strcpy( toret->node, Ser[4] );
                toret->message = 0L;
                do {
-                       if((rc = CxClRecv(buf))) {
+                       if((rc = CxClRecv(id, buf))) {
                                DPF((DFA, "%s", buf));
                                toret->message = (char *) realloc(toret, strlen(toret->message)+strlen(buf)+1);
                                strcat(toret->message, buf);
@@ -141,7 +141,7 @@ int         rc;
                strcpy(toret,buf);
                strcat(toret,"|");
                do {
-                       if((rc = CxClRecv(buf))) {
+                       if((rc = CxClRecv(id, buf))) {
                                DPF((DFA,"%s",buf));
                                toret = (char *) realloc(toret, strlen(toret)+strlen(buf)+1);
                                strcat(toret,buf);
@@ -161,14 +161,14 @@ int               rc;
  **  Message Waiting: 1
  **  No Messages: 0
  **/
-int            CxMiExpCheck() {
+int            CxMiExpCheck( int id ) {
 int            rc;
 char           buf[255];
 
        DPF((DFA,"Sending NOOP"));
-       CxClSend("NOOP");
+       CxClSend(id, "NOOP");
        DPF((DFA,"Checking response"));
-       rc = CxClRecv(buf);
+       rc = CxClRecv(id, buf);
 
        /**
         ** CxClRecv() tacks on a RC_MESGWAIT flag to the result
@@ -192,14 +192,14 @@ char              buf[255];
  ** [Not Intended For External Use]
  **/
 static
-void           _CxMiExpHook(const void* data) {
+void           _CxMiExpHook(int id, const void* data) {
 char           buf[512], *user_buf, *data_buf;
 int            rc;
 
        DPF((DFA, "Received RC_901 message"));
        DPF((DFA, "Raw data: %s\n", (char *)data));
 
-       rc = CxClRecv(buf);
+       rc = CxClRecv(id, buf);
        user_buf = (char *)CxMalloc(strlen(buf)+1);
        strcpy(user_buf, buf);
 
@@ -211,7 +211,7 @@ int         rc;
         **/
        if(CHECKRC(rc, RC_LISTING)) {
                do {
-                       rc = CxClRecv(buf);
+                       rc = CxClRecv( id, buf);
                        if(rc<0) {
                                realloc(data_buf, strlen(data_buf)+strlen(buf));
                                strcat(data_buf, buf);
@@ -273,18 +273,18 @@ void              CxMiExpHook(void (*func)(const char*, const char *)) {
  **  Success: Ptr to malloc()ed file data.  [*]
  **  Failure; File not found: NULL
  **/
-char           *CxMiMessage(const char *file) {
+char           *CxMiMessage(int id, const char *file) {
 char           buf[255], *toret;
 int            rc;
 
        if((file!=NULL) && file[0]) {
                DPF((DFA,"Requesting %s from server.",file));
                sprintf(buf,"MESG %s", file);
-               CxClSend(buf);
-               rc = CxClRecv(buf);
+               CxClSend(id, buf);
+               rc = CxClRecv(id, buf);
                if(CHECKRC(rc, RC_LISTING)) {
                        DPF((DFA,"Retrieving line from file..."));
-                       rc = CxClRecv(buf);
+                       rc = CxClRecv(id, buf);
                        if(rc < 0) {
                                toret = (char *)CxMalloc(strlen(buf)+1);
                                strcpy(toret, buf);
@@ -299,7 +299,7 @@ int         rc;
        } else {
 
                DPF((DFA,"Retrieving line from file..."));
-               rc = CxClRecv(buf);
+               rc = CxClRecv( id, buf);
                if(rc < 0) {
                        toret = (char *)CxMalloc(strlen(buf)+1);
                        strcpy(toret,buf);
@@ -328,7 +328,7 @@ int         rc;
  **  Success: Ptr to malloc()ed image data.  [*]
  **  Failure; File not found: NULL
  **/
-char           *CxMiImage(const char *img) {
+char           *CxMiImage(int id, const char *img) {
 
        /**
         ** Hmm.. Not sure how similar this is to MESG...
diff --git a/libCxClient/src/newtest.c b/libCxClient/src/newtest.c
new file mode 100644 (file)
index 0000000..f73e771
--- /dev/null
@@ -0,0 +1,240 @@
+/**
+ ** This is a new test program for libCxClient, which should demonstrate
+ ** the new thread-safe code.  It should now be possible to develop
+ ** multithreaded, multiconnection Citadel/UX clients, using libCxClient 
+ ** (which takes most of the effort out of creating Cit/UX clients!).
+ **
+ ** If you wish to test this program, try something like:
+ **
+ **    $ gcc *.o newtest.c -pthread -o newtest
+ **     (adjust based on your platform, of course)
+ **
+ ** http://www.shadowcom.net/Software/libCxClient/
+ **/
+#include       <stdio.h>
+#include       <stdlib.h>
+#include       <string.h>
+#include       <pthread.h>
+#include       "CxClient.h"
+
+/*
+     int
+     pthread_create(pthread_t *thread, const pthread_attr_t *attr,
+             void *(*start_routine)(void *), void *arg)
+ */
+
+/**
+ ** 901 express message callback...
+ **/
+void           chathook(const char *user, const char *msg) {
+       printf("Chat Message Handler\n");
+
+       printf("[[[[[[ %s ]]]]]]\n", user);
+       printf("%s\n", msg);
+}
+
+/**
+ ** THREAD 1: Connect to Uncensored! BBS
+ **/
+void           *session1( void *args ) {
+int            cxhndl;
+USERINFO       *user_info = 0;
+CXLIST         fl=0, foo;
+
+
+       /**
+        ** The primary method of creating a new Connection Handle is to specify all
+        ** options as arguments to CxClConnection.
+        **/
+       printf("1: Requesting connection handle...\n");
+       cxhndl = CxClConnection( "uncensored.citadel.org", 504, "detsaoT", "Loudness" );
+
+       if(!cxhndl) {
+               printf("1: MEMORY ERROR!\n");
+               pthread_exit(0);
+       }
+
+       /**
+        ** The handles you receive are only descriptive numeric values for CXTBL (Connection Table)
+        ** entries.  You can't do anything with them.  Invalid cxhndl's passed to any of the
+        ** support functions are ignored.
+        **/
+       printf("1: handle: %d\n", cxhndl);
+
+       /**
+        ** At any point in time, you can issue the CONNECT command to your CXHNDL.  This will
+        ** instruct it to connect using the stored parameters.  (see above)
+        **/
+       printf("1: Connecting to ucg...\n");
+       if(CxClConnect( cxhndl )) {
+               printf("1: Connection to ucg failed!\n");
+               CxClDelete(cxhndl);
+               pthread_exit(NULL);
+       }
+       printf("1: Connected to ucg!\n");
+
+       printf("1: Authenticating..\n");
+       if(!(user_info = CxUsAuth( cxhndl, NULL, NULL ))) {
+               printf("1: Failed authenticating %s!\n", CxClGetUser( cxhndl ));
+               CxClDisconnect( cxhndl );
+               CxClDelete( cxhndl );
+               pthread_exit(0);
+       }
+       printf("1: Authenticated!\n");
+       free(user_info);
+
+       printf("1: Retrieving online user list...\n");
+       fl = CxUsOnline( cxhndl, 0 );
+       printf("1: Done!\n");
+       foo = fl;
+       printf("1: Users on uncensored:\n");
+       while( foo ) {
+               printf("1: %s\n", foo->data);
+               foo = foo->next;
+       }
+       fl = CxLlFlush( fl );
+
+       /**
+        ** Similarly, you can Disconnect() from these handles at any time.  Disconnecting the
+        ** handle does not destroy it, though!  This means that...
+        **/
+       printf("1: Disconnecting...\n");
+       CxClDisconnect( cxhndl );
+
+       /**
+        ** ... you can re-connect a handle without having to create it again.
+        **/
+       printf("1: Connecting to ucg...\n");
+       CxClConnect( cxhndl );
+       printf("1: Disconnecting...\n");
+       CxClDisconnect( cxhndl );
+
+       /**
+        ** When you are done with a CXHNDL, just delete it.
+        **/
+       printf("1: Destroying handle...\n");
+       CxClDelete( cxhndl );
+
+       printf("1: DONE\n");
+       return(NULL);
+}
+
+/**
+ ** THREAD 2: Connect to Pixel! BBS
+ **/
+void           *session2( void *args ) {
+int            cxhndl2;
+USERINFO       *user_info = 0;
+CXLIST         fl = 0, foo;
+
+
+       /**
+        ** However, if you don't know all of the information right away, that's ok.
+        ** You can create the connection, and adjust the values later.
+        **/
+       printf("2: Requesting connection handle...\n");
+       cxhndl2 = CxClConnection( NULL, 0, NULL, NULL );
+
+       /**
+        ** If CxClConnection() returns NULL, you should not try to use the
+        ** handle provided (you're outta memory, dude!)
+        **/
+       if(!cxhndl2) {
+               pthread_exit(0);
+       }
+
+       printf("2: handle: %d\n", cxhndl2);
+
+       /**
+        ** Adjust the values of a CXHNDL.
+        **/
+       printf("2: Setting up handle...\n");
+       CxClSetHost( cxhndl2, "pixel.citadel.org" );
+       CxClSetUser( cxhndl2, "detsaoT" );
+       CxClSetPass( cxhndl2, "Loudness" );
+
+       printf("2: Connecting to pixel...\n");
+       if(CxClConnect( cxhndl2 )) {
+               printf("2: Connection to pixel failed!\n");
+               CxClDelete(cxhndl2);
+               pthread_exit(NULL);
+       }
+       printf("2: Connected to pixel!\n");
+
+       printf("2: Requesting online user list\n");
+       fl = CxUsOnline( cxhndl2, 0 );
+       printf("2: Done!\n");
+       printf("2: Users on pixel:\n");
+       while( foo ) {
+               printf("2: %s\n", foo->data);
+               foo = foo->next;
+       }
+       fl = CxLlFlush( fl );
+
+       printf("2: Disconnecting...\n");
+       CxClDisconnect( cxhndl2 );
+       printf("2: Destroying handle...\n");
+       CxClDelete( cxhndl2 );
+
+       printf("2: DONE\n");
+       return(0);
+}
+
+/**
+ ** main() launches our test threads.
+ **/
+int            main(int argc, char *argv[]) {
+int            cxhndl;
+pthread_t      t1 = 0, thread2 = 0;
+
+       printf("libCxClient Multithreaded Test Program\n");
+       printf("Library Revision %0.2f\n\n", CxRevision());
+
+       /**
+        ** As a developer, you should start by registering your client name with
+        ** libCxClient.  This adjusts the IDEN string that is sent to the server
+        ** upon connection (CxClConnect()).
+        **/
+       CxClRegClient("mt test program");
+
+       /**
+        ** You can register callbacks for the ASYNchronous server mode.  These callbacks are
+        ** local functions which should handle whichever event was generated by the server.
+        **/
+       printf("Registering callbacks\n");
+       CxMiExpHook(chathook);
+
+       printf("Going Multithreaded...\n\n");
+
+       /**
+        ** Create threads to test the new multithread-safe library.
+        **/
+       printf("0: Creating thread 1...\n");
+       if(cxhndl = pthread_create( &t1, NULL, session1, NULL )) {
+               printf("Failed creating thread 1\n");
+               printf("Error %d: %s\n", cxhndl, strerror(cxhndl));
+       }
+
+       printf("0: Creating thread 2...\n");
+       if(cxhndl = pthread_create( &thread2, NULL, session2, NULL )) {
+               printf("0: Failed creating thread 2\n");
+               printf("0: Error %d: %s\n", cxhndl, strerror(cxhndl));
+       }
+
+       if(cxhndl = pthread_join( t1, NULL )) {
+               printf("0: Error #%d: %s\n", cxhndl, strerror(cxhndl));
+               printf("0: Thread1: %d\n", t1);
+       }
+       printf("0: Thread 1 completed.\n");
+
+       if(cxhndl = pthread_join( thread2, NULL )) {
+               printf("0: Error #%d: %s\n", cxhndl, strerror(cxhndl));
+               printf("0: Thread1: %d\n", thread2);
+       }
+
+       /**
+        ** libCxClient ignores invalid handle id's, of course!!
+        **/
+       CxClDelete( cxhndl );
+       printf("0: DONE\n");
+}
index 8c965a02830f1ed48d60b2968093678c813aa0aa..bfc6f1dfa721529cdfaa218cd0882ab984251b05 100644 (file)
@@ -31,7 +31,7 @@
  **  On Success: The room's full information structure [*]
  **  On Failure: NULL
  **/
-ROOMINFO       *CxRmGoto(const char *room, int operation) {
+ROOMINFO       *CxRmGoto(int id, const char *room, int operation) {
 ROOMINFO       *room_info;
 char           *xmit, buf[255], *g_Ser[20];
 int            rc, i;
@@ -39,10 +39,10 @@ int         rc, i;
        if((room && *room) && !operation) {
                xmit = (char *)CxMalloc(strlen(room)+6);
                sprintf(xmit, "GOTO %s", room);
-               CxClSend(xmit);
+               CxClSend(id, xmit);
                CxFree(xmit);
 
-               rc = CxClRecv(buf);
+               rc = CxClRecv(id, buf);
 
                /**
                 ** If we successfully went to this room, return the
@@ -82,18 +82,18 @@ int         rc, i;
                /**
                 ** Set last-read pointer for this room.
                 **/
-               CxClSend("SLRP highest");
-               CxClRecv(buf);
+               CxClSend(id, "SLRP highest");
+               CxClRecv(id, buf);
 
                /**
                 ** Retrieve a list of all rooms w/ new messages.
                 **/
-               CxClSend("LKRN");
-               rc = CxClRecv(buf);
+               CxClSend(id, "LKRN");
+               rc = CxClRecv(id, buf);
                i = (int) xmit = 0;
                if(CHECKRC(rc, RC_LISTING)) {
                        do {
-                               rc = CxClRecv(buf);
+                               rc = CxClRecv(id, buf);
                                if(rc) {
                                        if(!i) {
                                                xmit = (char *)CxMalloc(strlen(buf)+6);
@@ -104,10 +104,10 @@ int               rc, i;
                        } while(rc<0);
 
                        if(xmit) {
-                               CxClSend(xmit);
+                               CxClSend(id, xmit);
                                CxFree(xmit);
 
-                               rc = CxClRecv(buf);
+                               rc = CxClRecv(id, buf);
                                if(CHECKRC(rc, RC_OK)) {
                                        CxSerialize(buf, &g_Ser);
                 
@@ -157,7 +157,7 @@ int         rc, i;
  **             3: room exists.
  **             4: not here/not allowed.
  **/
-int            CxRmCreate(ROOMINFO rm) {
+int            CxRmCreate(int id, ROOMINFO rm) {
 char           buf[512];
 int            rc;
 
@@ -188,8 +188,8 @@ int         rc;
        }
 
        sprintf(buf, "CRE8 1|%s|%d||%d", rm.name, rm.mode, rm.floor_id );
-       CxClSend(buf);
-       rc = CxClRecv(buf);
+       CxClSend(id, buf);
+       rc = CxClRecv(id, buf);
        if( CHECKRC(rc, RC_OK)) {
                DPF((DFA,"Success!"));
                return(0);
@@ -204,21 +204,21 @@ int               rc;
  ** as a Character array.  THE CALLER IS RESPONSIBLE FOR DEALLOCATING THIS
  ** MEMORY!!
  **/
-CXLIST         CxRmList() {
+CXLIST         CxRmList(int id) {
 int            rc;
 char           buf[255];
 CXLIST         rooms = NULL;
 
        DPF((DFA,"Retrieving list of rooms from the server."));
 
-       CxClSend("LKRA");
-       rc = CxClRecv( buf );
+       CxClSend(id, "LKRA");
+       rc = CxClRecv( id, buf );
        DPF((DFA,"%s [%d]",buf,rc));
 
        if( CHECKRC(rc, RC_LISTING)) {
 
                do {
-                       rc = CxClRecv( buf );
+                       rc = CxClRecv( id, buf );
                        DPF((DFA,"%s [%d]",buf,rc));
 
                        if(rc) {
@@ -235,21 +235,21 @@ CXLIST            rooms = NULL;
 /**
  ** CxFlList(): Retrieve a list of floors.
  **/
-CXLIST         CxFlList() {
+CXLIST         CxFlList(int id) {
 int            rc;
 char           buf[255];
 CXLIST         floors = NULL;
 
        DPF((DFA,"Retrieving list of floors from the server."));
 
-       CxClSend("LFLR");
-       rc = CxClRecv( buf );
+       CxClSend(id, "LFLR");
+       rc = CxClRecv( id, buf );
        DPF((DFA,"%s [%d]",buf,rc));
 
        if( CHECKRC(rc, RC_LISTING)) {
 
                do {
-                       rc = CxClRecv( buf );
+                       rc = CxClRecv( id, buf );
                        DPF((DFA,"%s [%d]",buf,rc));
 
                        if(rc) {
index 91051acad252e40e8ea3d063b295fe2c02b84624..d3316fc0b4c7866bff4bc3ae3ea56f95475d3574 100644 (file)
@@ -1,5 +1,7 @@
 /**
  ** This is a test program for libCxClient.  It's not important.
+ **
+ ** $ gcc *.o testlib.c -o testlib
  **/
 #include       <stdio.h>
 #include       <stdlib.h>
@@ -21,6 +23,7 @@ CXLIST                fl = 0;
 USERINFO       *user_info = 0;
 ROOMINFO       *room_info = 0;
 char           buf[255],*s = 0;
+int            hndl;
 
        printf("libCxClient Test Program\n");
        printf("Library Revision %0.2f\n\n", CxRevision());
@@ -34,38 +37,43 @@ char                buf[255],*s = 0;
        printf("Registering callbacks\n");
        CxMiExpHook(chathook);
 
+       if(!(hndl = CxClConnection( argv[1], 504, argv[2], argv[3] ))) {
+               printf("Failed creating connection handle.  Dying.\n");
+               exit(-1);
+       }
+
        // I suggest 'tesseract.citadel.org'
        printf("Connecting to '%s'...\n",argv[1]);
-       if(!CxClConnect(argv[1])) {
+       if(!CxClConnect( hndl )) {
 
                printf("Logging in\n");
-               if(user_info = CxUsAuth(argv[2],argv[3])) {
+               if(user_info = CxUsAuth(hndl, NULL, NULL)) {
                        CxFree(user_info);
                        user_info = 0;
 
-                       room_info = CxRmGoto("_BASEROOM_",0);
+                       room_info = CxRmGoto(hndl, "_BASEROOM_",0);
                        CxFree(room_info);
                        room_info = 0;
 
                        fl = CxLlFlush(fl);
-                       fl = CxMsList(0, 0);
+                       fl = CxMsList(hndl, 0, 0);
 
                        fl = CxLlFlush(fl);
 
-                       CxMiExpSend("detsaoT","Hello, World");
-                       CxMiExpSend("detsaoT","How are you?");
-                       CxMiExpSend("detsaoT","Blah blah blah.");
+                       CxMiExpSend(hndl, "detsaoT","Hello, World");
+                       CxMiExpSend(hndl, "detsaoT","How are you?");
+                       CxMiExpSend(hndl, "detsaoT","Blah blah blah.");
 
 
-                       CxClSend("ECHO Hello");
-                       CxClRecv(buf);
+                       CxClSend(hndl, "ECHO Hello");
+                       CxClRecv(hndl, buf);
 
                }
 
                CxClCbShutdown();
 
        } else {
-               printf("Unable to connect to 'bbs.shadowcom.net'!\n");
+               printf("Unable to connect to '%s'!\n", argv[1]);
        }
 
 }
diff --git a/libCxClient/src/testlib.cpp b/libCxClient/src/testlib.cpp
new file mode 100644 (file)
index 0000000..d7d4da2
--- /dev/null
@@ -0,0 +1,82 @@
+/**
+ ** This is a test program for libCxClient.  It's not important.
+ **
+ ** $ gcc *.o testlib.c -o testlib
+ **/
+#include       <stdio.h>
+#include       <stdlib.h>
+#include       <string.h>
+#include       "CxClient.h"
+
+/**
+ ** 901 express message callback...
+ **/
+void           chathook(const char *user, const char *msg) {
+       printf("Chat Message Handler\n");
+
+       printf("[[[[[[ %s ]]]]]]\n", user);
+       printf("%s\n", msg);
+}
+
+int            main(int argc, char *argv[]) {
+CXLIST         fl = 0;
+USERINFO       *user_info = 0;
+ROOMINFO       *room_info = 0;
+char           buf[255],*s = 0;
+int            hndl;
+
+       printf("libCxClient Test Program\n");
+       printf("Library Revision %0.2f\n\n", CxRevision());
+
+       if(argc<3) {
+               printf("\nUsage:\n      %s system username password\n\n", argv[0]);
+               exit(0);
+       }
+
+       CxClRegClient("test program");
+       printf("Registering callbacks\n");
+       CxMiExpHook(chathook);
+
+       if(!(hndl = CxClConnection( NULL, 504, NULL, NULL ))) {
+               printf("Failed creating connection handle.  Dying.\n");
+               exit(-1);
+       }
+       CxClSetHost( hndl, argv[1] );
+       CxClSetUser( hndl, argv[2] );
+       CxClSetPass( hndl, argv[3] );
+
+       // I suggest 'tesseract.citadel.org'
+       printf("Connecting to '%s'...\n",argv[1]);
+       if(!CxClConnect( hndl )) {
+
+               printf("Logging in\n");
+               if(user_info = CxUsAuth(hndl, NULL, NULL)) {
+                       free(user_info);
+                       user_info = 0;
+
+                       room_info = CxRmGoto(hndl, "_BASEROOM_",0);
+                       free(room_info);
+                       room_info = 0;
+
+                       fl = CxLlFlush(fl);
+                       fl = CxMsList(hndl, 0, 0);
+
+                       fl = CxLlFlush(fl);
+
+                       CxMiExpSend(hndl, "detsaoT","Hello, World");
+                       CxMiExpSend(hndl, "detsaoT","How are you?");
+                       CxMiExpSend(hndl, "detsaoT","Blah blah blah.");
+
+
+                       CxClSend(hndl, "ECHO Hello");
+                       CxClRecv(hndl, buf);
+
+               }
+
+               CxClCbShutdown();
+
+       } else {
+               printf("Unable to connect to '%s'!\n", argv[1]);
+       }
+
+}
index 0c524baca64e01910ca98e50db0af69a69eeffcb..c14f087bd3e9b76a26f351f6a633e04bbc55ae09 100644 (file)
  ** [fmt]: Format of list you are expecting to receive:
  ** 0: (Default) Session ID|User|Room
  **/
-CXLIST         CxUsOnline(int fmt) {
+CXLIST         CxUsOnline(int id,int fmt) {
 CXLIST         toret = 0;
 int            rc;
 char           buf[255];
 
        DPF((DFA,"Retrieving online user list"));
-       CxClSend("RWHO");
-       rc = CxClRecv(buf);
+       CxClSend(id, "RWHO");
+       rc = CxClRecv(id, buf);
 
        /**
         ** The session protocol specs say that
@@ -39,7 +39,7 @@ char          buf[255];
        if( CHECKRC(rc, RC_LISTING) ) {
 
                do {
-                       rc = CxClRecv(buf);
+                       rc = CxClRecv(id, buf);
                        DPF((DFA,"[%d] %s",rc,buf));
 
                        if( rc ) {
@@ -65,17 +65,17 @@ char                buf[255];
 /**
  ** CxUsList(): Fetch the Global Address Book.
  **/
-CXLIST         CxUsList() {
+CXLIST         CxUsList( int id ) {
 CXLIST         toret = 0;
 int            rc;
 char           buf[512];
 
        DPF((DFA,"Requesting user list..."));
-       CxClSend("LIST");
-       rc = CxClRecv(buf);
+       CxClSend(id, "LIST");
+       rc = CxClRecv(id, buf);
        if( CHECKRC(rc, RC_LISTING) ) {
                do {
-                       rc = CxClRecv(buf);
+                       rc = CxClRecv(id, buf);
                        if(rc) {
                                toret = CxLlInsert(toret,buf);
                        }
@@ -98,28 +98,69 @@ char                buf[512];
  **  On Success: USERINFO: User Information structure. [*]
  **  On Failure: NULL
  **/
-USERINFO       *CxUsAuth(const char *uname, const char *passwd) {
+USERINFO       *CxUsAuth(int id, const char *uname, const char *passwd) {
 USERINFO       *user_info;
-char           *xmit, buf[512], *g_Ser[20];
+char           *xmit, *tmp = 0, buf[512], *g_Ser[20];
 int            rc;
 
-       DPF((DFA,"Authenticating '%s'",uname));
-       xmit = (char *)CxMalloc(strlen(uname)+6);
-       sprintf(xmit,"USER %s",uname);
-       CxClSend(xmit);
+       DPF((DFA,"Auth uname: %s; passwd: %s", uname, passwd));
+       if(uname && *uname) {
+               CxClSetUser( id, uname );
+               DPF((DFA,"Authenticating '%s'",uname));
+               xmit = (char *)CxMalloc(strlen(uname)+6);
+               sprintf(xmit,"USER %s",uname);
+
+       } else {
+               tmp = CxClGetUser( id );
+               if(!tmp) {
+                       DPF((DFA,"Authentication Failed (CxClGetUser failed?)"));                       
+                       DPF((DFA,"CxClGetUser returned %s",tmp));
+                       return(NULL);
+               }
+
+               DPF((DFA,"Authenticating '%s'", tmp));
+               xmit = (char *)CxMalloc(strlen(tmp)+6);
+               sprintf( xmit, "USER %s", tmp);
+       }
+       CxClSend(id, xmit);
        CxFree(xmit);
 
+       if(tmp) CxFree(tmp);
+
        DPF((DFA,"Validating username"));
-       rc = CxClRecv(buf);
+       rc = CxClRecv(id, buf);
+
+       /**
+        ** Error in communications layer.
+        **/
+       if(!rc) {
+               DPF((DFA,"Authentication Failed (invalid username?)"));
+               DPF((DFA,"rc = %d", rc));
+               DPF((DFA,"buf = %s", buf));
+               return(NULL);
+       }
+
        DPF((DFA,"%d", rc));
        if( CHECKRC(rc, RC_MOREDATA) ) {
-               xmit = (char *)CxMalloc(strlen(passwd)+6);
-               sprintf(xmit,"PASS %s",passwd);
-               CxClSend(xmit);
+               DPF((DFA,"Sending passwd"));
+
+               if(passwd && *passwd) {
+                       xmit = (char *)CxMalloc(strlen(passwd)+6);
+                       sprintf(xmit,"PASS %s",passwd);
+               } else {
+                       tmp = CxClGetPass( id );
+                       if(!tmp) tmp = strdup("");
+
+                       xmit = (char *)CxMalloc(strlen(tmp)+6);
+                       sprintf(xmit,"PASS %s",tmp);
+               }
+               CxClSend(id, xmit);
                CxFree(xmit);
 
+               if(tmp) free(tmp);
+
                DPF((DFA,"Validating password"));
-               rc = CxClRecv(buf);
+               rc = CxClRecv(id, buf);
 
                /**
                 ** RETURN: Authentication information O.K.
@@ -135,13 +176,15 @@ int               rc;
                        user_info->system.user_flags = atol(g_Ser[4]);
                        user_info->system.user_number = atol(g_Ser[5]);
                        DPF((DFA,"MEM/MDA:\t-1\t@0x%08x (Needs manual deallocation)", user_info));
-                       
+
+                       DPF((DFA,"Authentication Successful"));                 
                        return(user_info);
 
                /**
                 ** RETURN: Invalid password.
                 **/
                } else {
+                       DPF((DFA,"Authentication Failed (invalid password)"));                  
                        return(NULL);
                }
 
@@ -149,12 +192,14 @@ int               rc;
         ** RETURN: Invalid username
         **/
        } else {
+               DPF((DFA,"Authentication Failed (invalid username)"));                  
                return(NULL);
        }
 
        /**
         ** SHOULD be unreachable...
         **/
+       DPF((DFA,"Authentication Failed (freak of nature)"));                   
        return(NULL);
 }
 
@@ -169,7 +214,7 @@ int         rc;
  **  On Failure: 1 - Not enough information
  **              2 - USER ALREADY EXISTS
  **/
-int            CxUsCreate(USERINFO user) {
+int            CxUsCreate(int id, USERINFO user) {
 char           buf[512];
 int            rc;
 
@@ -184,8 +229,8 @@ int         rc;
        DPF((DFA,"Creating user account '%s'", user.username));
 
        sprintf(buf, "NEWU %s", user.username);
-       CxClSend(buf);
-       rc = CxClRecv(buf);
+       CxClSend(id, buf);
+       rc = CxClRecv(id, buf);
 
        /**
         ** RETURN: User already exists.
@@ -195,8 +240,8 @@ int         rc;
        }
 
        sprintf(buf, "SETP %s", user.password);
-       CxClSend(buf);
-       rc = CxClRecv(buf);
+       CxClSend(id, buf);
+       rc = CxClRecv(id, buf);
 
        if( CHECKRC(rc, RC_OK)) {
                return(0); /** Non-fatal error.  User just has a blank "" password. **/
@@ -205,47 +250,47 @@ int               rc;
        /**
         ** Phase 2: Populate registration structures on server.
         **/
-       CxClSend("REGI");
-       rc = CxClRecv(buf);
+       CxClSend(id, "REGI");
+       rc = CxClRecv(id, buf);
        if( CHECKRC(rc, RC_SENDLIST)) {
                sprintf(buf, "%s", user.fullname);
-               CxClSend(buf);
+               CxClSend(id, buf);
                sprintf(buf, "%s", user.addr.street);
-               CxClSend(buf);
+               CxClSend(id, buf);
                sprintf(buf, "%s", user.addr.city);
-               CxClSend(buf);
+               CxClSend(id, buf);
                sprintf(buf, "%s", user.addr.st);
-               CxClSend(buf);
+               CxClSend(id, buf);
                sprintf(buf, "%s", user.addr.zip);
-               CxClSend(buf);
+               CxClSend(id, buf);
                sprintf(buf, "%s", user.contact.telephone);
-               CxClSend(buf);
+               CxClSend(id, buf);
                sprintf(buf, "%s", user.contact.emailaddr);
-               CxClSend(buf);
-               CxClSend("000");
+               CxClSend(id, buf);
+               CxClSend(id, "000");
        }
 
        /**
         ** Phase 3: Create personal rooms expected by CxClient.  [Please note that this will only work if
         ** the server's default permissions allow new users to create rooms.  bbs.shadowcom.net will.]
         **/
-       CxClSend("CRE8 0");
-       rc = CxClRecv(buf);
+       CxClSend(id, "CRE8 0");
+       rc = CxClRecv(id, buf);
        if( CHECKRC(rc, RC_OK)) {
-               CxClSend("CRE8 1|My Schedule|4||");
-               rc = CxClRecv(buf);
+               CxClSend(id, "CRE8 1|My Schedule|4||");
+               rc = CxClRecv(id, buf);
                printf("My Schedule: rc = %d", rc);
-               CxClSend("CRE8 1|My Notes|4||");
-               rc = CxClRecv(buf);
+               CxClSend(id, "CRE8 1|My Notes|4||");
+               rc = CxClRecv(id, buf);
                printf("My Notes: rc = %d", rc);
-               CxClSend("CRE8 1|My Tasks|4||");
-               rc = CxClRecv(buf);
+               CxClSend(id, "CRE8 1|My Tasks|4||");
+               rc = CxClRecv(id, buf);
                printf("My Tasks: rc = %d", rc);
-               CxClSend("CRE8 1|My Journal|4||");
-               rc = CxClRecv(buf);
+               CxClSend(id, "CRE8 1|My Journal|4||");
+               rc = CxClRecv(id, buf);
                printf("My Journal: rc = %d", rc);
-               CxClSend("CRE8 1|My Contacts|4||");
-               rc = CxClRecv(buf);
+               CxClSend(id, "CRE8 1|My Contacts|4||");
+               rc = CxClRecv(id, buf);
                printf("My Contacts: rc = %d", rc);
        }