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...
$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,
Revision 1.2 2001/02/07 22:41:51 brian
* Updated ChangeLog to conform to Citadel/UX standards (kinda) :)
-
** 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
#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
/**
** 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"
** Success, No Files: 1 blank entry
** Failure: NULL list.
**/
-CXLIST CxFiIndex() {
+CXLIST CxFiIndex( int id ) {
int rc;
char buf[512];
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...
DPF((DFA,"LISTING_FOLLOWS..."));
do {
- rc = CxClRecv(buf);
+ rc = CxClRecv(id, buf);
DPF((DFA,"%s", buf));
if(rc<0) {
flist = CxLlInsert(flist, buf);
** 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);
}
** Success: Ptr to malloc()ed tmp filename containing file data.
** Failure: NULL
**/
-char *CxFiGet(const char *name) {
+char *CxFiGet(int id, const char *name) {
/**
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
/**
** 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;
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;
} 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")) {
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;
}
/**
}
/**
- ** 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);
}
/**
- ** 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);
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;
}
** _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;
}
**/
if (i == 255)
while (s[i] != '\n')
- ClRecvChar(&s[i], 1);
-
+ ClRecvChar(sock, &s[i], 1);
+
+ _CxClClear( semaphore );
/**
** Strip all trailing nonprintables (crlf)
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
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
**/
void CxClCbShutdown() {
CXCBHNDL x, y;
- return(0);
+ return;
+
DPF((DFA,"Shutting down callback subsystem"));
x = _CbHandles;
while( x ) {
** 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;
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));
/**
** 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;
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);
**
** 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;
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;
**/
do {
- rc = CxClRecv(buf);
+ rc = CxClRecv(id, buf);
if(rc) {
DPF((DFA,"%s",buf));
** 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);
** 3: Message rejected for unknown reasons.
** ... tba
**/
-int CxMsSave(MESGINFO msg) {
+int CxMsSave(int id, MESGINFO msg) {
int rc;
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"));
/**
** 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;
sprintf( buf, "SLRP %ld", msgid );
}
- CxClSend( buf );
- rc = CxClRecv( buf );
+ CxClSend( id, buf );
+ rc = CxClRecv( id, buf );
if( rc == RC_OK ) {
DPF((DFA, "Done."));
** 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);
** Success: Ptr to malloc()ed EXPRMESG struct. [*]
** Failure: NULL
**/
-EXPRMESG *CxMiExpRecv() {
+EXPRMESG *CxMiExpRecv( int id ) {
char buf[255], *Ser[20];
EXPRMESG *toret;
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;
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);
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);
** 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
** [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);
**/
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);
** 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);
} 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);
** 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...
--- /dev/null
+/**
+ ** 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");
+}
** 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;
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
/**
** 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);
} 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);
** 3: room exists.
** 4: not here/not allowed.
**/
-int CxRmCreate(ROOMINFO rm) {
+int CxRmCreate(int id, ROOMINFO rm) {
char buf[512];
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);
** 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) {
/**
** 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) {
/**
** This is a test program for libCxClient. It's not important.
+ **
+ ** $ gcc *.o testlib.c -o testlib
**/
#include <stdio.h>
#include <stdlib.h>
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());
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]);
}
}
--- /dev/null
+/**
+ ** 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]);
+ }
+
+}
** [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
if( CHECKRC(rc, RC_LISTING) ) {
do {
- rc = CxClRecv(buf);
+ rc = CxClRecv(id, buf);
DPF((DFA,"[%d] %s",rc,buf));
if( rc ) {
/**
** 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);
}
** 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.
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);
}
** RETURN: Invalid username
**/
} else {
+ DPF((DFA,"Authentication Failed (invalid username)"));
return(NULL);
}
/**
** SHOULD be unreachable...
**/
+ DPF((DFA,"Authentication Failed (freak of nature)"));
return(NULL);
}
** 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;
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.
}
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. **/
/**
** 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);
}