2 ** libCxClient - Citadel/UX Extensible Client API
3 ** Copyright (c) 2000, Flaming Sword Productions
4 ** Copyright (c) 2001, The Citadel/UX Consortium
7 ** Module: libtransport.o
9 ** Last Revision: 2000-11-30
10 ** Description: Interface to Transport module.
24 #include <sys/types.h>
25 #include <sys/socket.h>
26 #include <netinet/in.h>
27 #include <arpa/inet.h>
35 static int g_CxChatMode = 0,
39 static CXCBHNDL _CbHandles = 0;
40 static char g_CxClientName[32] = "";
41 static int _CxCallback(int, int, void *);
42 static void timeout() {}
43 static void _CxClSend( int, const char * );
44 static int _CxClRecv( int, int*, char *, int );
47 ** CXTBL: Connection handle table. Use this to make libCxClient thread-safe, and allow
48 ** us to maintain multiple concurrent connections.
50 typedef struct _cx_tbl_entry {
52 int cxId; /* cxId: Connection ID */
54 char host[255], /* host: Citadel/UX hostname */
55 user[64], /* user: Citadel/UX username */
56 pass[64]; /* pass: Citadel/UX password */
57 int port, /* port: Port number to connect to */
58 connected, /* connected: (bool) Are we connected to our Citadel/UX host? */
59 asynMode, /* asynMode: (bool) Are we actively in ASYN mode? */
60 semaphore; /* semaphore: (bool) Prevent access to _sock at this time? */
65 int _sock; /* _sock: TCP/IP connection socket */
68 *_next, /* _next: Next CXTBL entry. */
69 *_prev; /* _prev: Previous CXTBL entry. */
72 typedef CXTBLENT* CXHNDL;
75 ** [GLOBAL CXTABLE] There should only exist one of these in memory at any
76 ** point in time, to ensure threadsafeness.
78 static CXHNDL g_CxTbl = 0L;
81 ** _CxTbNewID(): Get the next cxId for the specified connection table.
84 int _CxTbNewID( CXHNDL tbl ) {
91 if(p->cxId == ret) ret = (p->cxId)+1;
95 DPF((DFA, "Next cxId: %d", ret));
100 ** _CxTbNew(): New CXTBL entry.
103 CXHNDL _CxTbNew( CXHNDL tbl ) {
106 DPF((DFA, "Creating new CXTBL handle."));
108 ret = (CXHNDL) CxMalloc( sizeof(CXTBLENT) );
109 if(ret<=0) return(NULL);
112 ** Initialize these pointers to prevent confusion.
118 ** Establish Default values
130 ** Obtain the next cxId for this particular table.
132 ret->cxId = _CxTbNewID( tbl );
134 DPF((DFA, "Returning hndl @0x%08x", ret ));
139 ** _CxTbEntry(): Return a handle to a particular table entry.
142 CXHNDL _CxTbEntry( CXHNDL tbl, int id ) {
145 DPF((DFA,"Resolve [tbl@0x%08x] id %d", tbl, id ));
148 DPF((DFA,"p->cxId: %d", p->cxId));
149 if( id == p->cxId ) {
150 DPF((DFA," ->host: %s:%d", p->host, p->port));
151 DPF((DFA," ->user: %s", p->user));
152 DPF((DFA," ->pass: %s", p->pass));
153 DPF((DFA," ->_sock: %d", p->_sock));
158 return((CXHNDL)NULL);
162 ** _CxTbInsert(): Insert a new CxTbl entry into the table. Return a handle
163 ** id for the new entry. (Parameters here can be set at a later time.)
166 int _CxTbInsert( const char *host, int port, const char *user, const char *pass ) {
170 DPF((DFA,"Insert new table entry."));
172 DPF((DFA,"Allocating new CXTBL block."));
173 n = _CxTbNew( g_CxTbl );
175 DPF((DFA,"Copying host"));
177 if(strlen(host) >= 254) {
178 tmp = (char *)CxMalloc( 255 );
181 strcpy(n->host, tmp);
185 strcpy(n->host, host);
189 DPF((DFA,"Copying user"));
191 if(strlen(user) >= 64) {
192 tmp = (char *)CxMalloc( 65 );
195 strcpy(n->user, tmp);
198 strcpy(n->user, user);
202 DPF((DFA,"Copying pass"));
204 if(strlen(pass) >= 64) {
205 tmp = (char *)CxMalloc( 65 );
208 strcpy(n->pass, tmp);
211 strcpy(n->pass, pass);
215 DPF((DFA,"Copying port"));
216 if(port) n->port = port;
218 DPF((DFA,"Binding to g_CxTbl"));
220 DPF((DFA,"new g_CxTbl"));
222 DPF((DFA,"New table @0x%08x", g_CxTbl ));
226 DPF((DFA,"existing g_CxTbl"));
228 while( p && p->_next ) {
241 ** _CxTbDelete(): Delete the specified id.
244 void _CxTbDelete( int id ) {
247 if(!g_CxTbl || !id ) return;
249 DPF((DFA,"Delete id %d", id));
252 if( p->cxId == id ) break;
256 DPF((DFA,"p @0x%08x", p));
260 DPF((DFA,"p->_next @0x%08x", p->_next));
261 DPF((DFA,"p->_prev @0x%08x", p->_prev));
264 ** This was the only entry in the CxTbl.
266 if( !p->_next && !p->_prev ) {
271 ** Gymnastics time...
274 if( p->_next ) p->_next->_prev = p->_prev;
275 if( p->_prev ) p->_prev->_next = p->_next;
277 if( g_CxTbl == p ) g_CxTbl = p->_next;
282 DPF((DFA,"g_CxTbl @0x%08x", g_CxTbl));
286 ** CxClConnection(): Obtain a Connection handle for a new host/username/password. This _must_ be
287 ** performed before any other CxCl functions can be called.
289 int CxClConnection( const char *host, int port, const char *user, const char *pass ) {
291 DPF((DFA,"New connection hndl %s:%s@%s:%d", user, "**", host, port));
292 return(_CxTbInsert( host, port, user, pass ) );
296 ** CxClDelete(): Delete the specified connection handle.
298 void CxClDelete( int id ) {
300 DPF((DFA,"Delete hndl %d", id ));
306 ** CxClSetHost(): Set the username for a specific connection handle.
308 void CxClSetHost( int id, const char *host ) {
311 if(!host || !*host) return;
313 e = _CxTbEntry( g_CxTbl, id );
316 DPF((DFA,"Set tbl[%d].host = '%s'", id, host ));
317 memset( &(e->host), 0, 253 );
318 strcpy( e->host, host );
322 ** CxClSetUser(): Set the username for a specific connection handle.
324 void CxClSetUser( int id, const char *user ) {
327 if(!user || !*user) return;
329 e = _CxTbEntry( g_CxTbl, id );
332 DPF((DFA,"Set tbl[%d].user = '%s'", id, user ));
333 strcpy( e->user, user );
337 ** CxClGetUser(): Set the username for a specific connection handle.
338 ** [*] FREE the results of this operation!!
340 char *CxClGetUser( int id ) {
344 e = _CxTbEntry( g_CxTbl, id );
348 ret = (char *)CxMalloc( strlen( e->user ) + 1 );
349 strcpy( ret, e->user );
358 ** CxClSetPass(): Set the username for a specific connection handle.
360 void CxClSetPass( int id, const char *pass ) {
363 if(!pass || !*pass) return;
365 e = _CxTbEntry( g_CxTbl, id );
368 DPF((DFA,"Set tbl[%d].pass = '%s'", id, pass ));
369 strcpy( e->pass, pass );
373 ** CxClGetPass(): Set the username for a specific connection handle.
375 char *CxClGetPass( int id ) {
379 e = _CxTbEntry( g_CxTbl, id );
383 ret = (char *)CxMalloc( strlen(e->pass) +1 );
384 strcpy(ret, e->pass);
393 ** CxClSetPass(): Set the username for a specific connection handle.
395 ** CxClRegClient(): (For Developers) Register your client name with
396 ** libCxClient. This gets reported along with the IDEN information passed
397 ** to the server. It should be called before CxClConnect().
399 void CxClRegClient(const char *cl_name) {
401 DPF((DFA,"Developer registered this as \"%s\"", cl_name));
404 ** If this will cause libCxClient to crash, then just die.
406 if(strlen(cl_name)>31) {
407 printf("* * * Fatal Error * * *\n");
408 printf("Invalid use of CxClRegClient(). I expect cl_name to be less than 31 characters in length.\n");
409 printf("cl_name = '%s'\n", cl_name);
410 printf("\nI can't continue. Please re-build your client.\n");
414 strcpy(g_CxClientName, cl_name);
418 ** CxClConnect(): Establish a connection to the server via the Transport layer.
419 ** [Much of this code was gleaned from the "citadel" client]
423 ** On Failure: -1: Mis-configuration
424 ** -[errno]: use abs(errno) to retrieve error message.
426 int CxClConnect( int id ) {
437 char *service = "citadel";
438 char *protocol = "tcp";
441 DPF((DFA,"(Library was built with UNIX_SOCKET support)"));
443 e = _CxTbEntry( g_CxTbl, id );
446 DPF((DFA,"Did not call CxConnection(), huh?"));
451 DPF((DFA,"No hostname provided. Use CxClSetHost() first!!"));
454 DPF((DFA,"Establishing connection to host \"%s\"",e->host));
456 memset(&sin, 0, sizeof(sin));
457 sin.sin_family = AF_INET;
459 DPF((DFA,"-> getservbyname()"));
460 pse = getservbyname(service, protocol);
462 sin.sin_port = pse->s_port;
463 } else if((sin.sin_port = htons((u_short) atoi(service))) != 0) {
465 sin.sin_port = htons((u_short)504);
468 DPF((DFA,"-> gethostbyname(): \"%s\"", e->host));
469 phe = gethostbyname(e->host);
470 DPF((DFA,"phe@0x%08x", phe));
472 memcpy(&sin.sin_addr, phe->h_addr, phe->h_length);
474 } else if ((sin.sin_addr.s_addr = inet_addr(e->host)) == INADDR_NONE) {
475 DPF((DFA,"Unable to get host entry. %s", strerror(errno)));
479 DPF((DFA,"-> getprotobyname()"));
480 if ((ppe = getprotobyname(protocol)) == 0) {
481 DPF((DFA,"Unable to get protocol entry. %s", strerror(errno)));
484 if (!strcmp(protocol, "udp")) {
490 s = socket(PF_INET, type, ppe->p_proto);
492 DPF((DFA,"Unable to create socket. %s", strerror(errno)));
495 signal(SIGALRM, timeout);
498 DPF((DFA,"-> connect()"));
499 if (connect(s, (struct sockaddr *) &sin, sizeof(sin)) < 0) {
500 /** printf("\n* * * Fatal Error * * *\n");
501 printf("System Error: Can't connect to '%s' [%s]\n",e->host, service);
502 printf("Error Details: %s\n\n", strerror(errno));
507 signal(SIGALRM, SIG_IGN);
509 DPF((DFA,"Socket %d", s));
515 DPF((DFA,"-> recv"));
516 _CxClRecv( e->_sock, &(e->semaphore), buf, id );
517 if(g_CxClientName[0]) {
518 sprintf(buf,"IDEN 1|1|100|CX/%s (%s)|",VERSION, g_CxClientName);
520 sprintf(buf,"IDEN 1|1|100|CX/%s|",VERSION);
523 _CxClRecv(e->_sock, &(e->semaphore), buf, id);
525 _CxClSend(s, "ASYN 1");
526 rc = _CxClRecv(e->_sock, &(e->semaphore), buf, id);
529 ** If the server doesn't support Asnychronous mode, then
530 ** we shouldn't try to be asynchronous...
532 if(CHECKRC(rc, RC_OK)) {
533 DPF((DFA,":: Server in ASYNCHRONOUS mode."));
536 DPF((DFA,":: ASYNCHRONOUS mode not supported."));
541 ** We don't return our socket anymore.
549 ** CxClDisconnect(): Disconnect the specified socket.
551 void CxClDisconnect( int id ) {
554 DPF((DFA,"Caught orders to close socket %d.", id));
556 e = _CxTbEntry( g_CxTbl, id );
560 ** Sleep until the semaphore is cleared.
562 while(e->semaphore) ;
564 shutdown(e->_sock, 0);
568 ** CxClStat(): Return connection status.
570 int CxClStat( int id ) {
573 e = _CxTbEntry( g_CxTbl, id );
584 ** _CxClSend(): REAL send. Uses a socket instead of the ID.
587 void _CxClSend( int sock, const char *s ) {
588 int bytes_written = 0;
593 ** If the socket is not open, there's no point in going here.
597 DPF((DFA,"PROT --> \"%s\"", s));
599 ss = (char *)CxMalloc(strlen(s)+2);
600 sprintf(ss,"%s\n",s);
608 while (bytes_written < nbytes) {
609 retval = write(sock, &ss[bytes_written],
610 nbytes - bytes_written);
612 write (sock, "\n", strlen("\n"));
615 bytes_written = bytes_written + retval;
621 ** CxClSend(): Send a string to the server.
623 void CxClSend(int id, const char *s) {
626 e = _CxTbEntry( g_CxTbl, id );
628 if(!e->connected) return;
630 DPF((DFA,"SEND: \"%s\"", s));
631 _CxClSend( e->_sock, s );
635 ** ClRecvChr(): Retrieve the next message from the server.
636 ** *********** SOURCE: citadel-source/citmail.c:serv_read()
639 void ClRecvChar(int socket, char *buf, int bytes) {
643 while (len < bytes) {
644 rlen = read(socket, &buf[len], bytes - len);
653 ** _CxClWait(): Wait on the semaphore.
656 void _CxClWait( int *e ) {
658 DPF((DFA,"Waiting on Semaphore..."));
661 DPF((DFA,"*** LOCKING SESSION ***"));
666 ** _CxClClear(): Clear the semaphore.
669 void _CxClClear( int *e ) {
671 DPF((DFA,"*** CLEARING SESSION ***"));
676 ** _CxClRecv(): REAL receive.
679 int _CxClRecv( int sock, int *semaphore, char *s, int cxid ) {
685 ** If the socket is not open, there's no point in going here.
687 DPF((DFA,"RECV on %d", sock));
689 DPF((DFA,"No socket."));
694 ** RETRY_RECV when we have a callback and need to re-synch the protocol.
698 _CxClWait( semaphore );
699 DPF((DFA,"for(;message <= bottle;) ;"));
702 ** Read one character at a time.
705 ClRecvChar(sock, &s[i], 1);
706 if (s[i] == '\n' || i == 255)
711 ** If we got a long line, discard characters until the newline.
715 ClRecvChar(sock, &s[i], 1);
717 _CxClClear( semaphore );
720 ** Strip all trailing nonprintables (crlf)
724 DPF((DFA,"PROT <-- \"%s\"", s));
729 ** Check to see if the message is prepended with a server result code.
732 (!strcmp(substr,"000")) ||
733 ((substr[0]>='0' && substr[0]<='9') &&
734 (substr[1]>='0' && substr[1]<='9') &&
735 (substr[2]>='0' && substr[2]<='9') &&
736 ((substr[3]==' ') || (substr[3]=='*')))
738 i = (int)strtol(s, (char **)NULL, 10);
739 if(substr[3]=='*') i+=RC_MESGWAIT;
742 ** This removes the result code & other data from the
743 ** returned string. This is _really_ going to mess with
744 ** lots of code. Ugh.
746 ** (Shift the entire string left 4 places.)
748 for(tmp = 0; tmp < strlen(s); tmp++) {
749 if(tmp+4 < strlen(s)) s[tmp] = s[tmp+4];
754 DPF((DFA," s: \"%s\"", s));
757 ** Otherwise, we can assume that this is an RC_LISTING entry.
764 ** This is the only instance of Goto you'll find in
765 ** libCxClient. The point is: Once we're done handling
766 ** an asynchronous message, we need to go back & handle
767 ** other messages as normal.
769 DPF((DFA,"rc = %d (%d)", i, CHECKRC(i, RC_ASYNCMSG)));
773 ** If the server has told us a secret...
775 if(CHECKRC(i, RC_ASYNCMSG)) {
776 DPF((DFA,"Preparing to process async message on CXID"));
779 ** Do we have ANY callbacks defined?
784 ** Pass data to callback function, if appropriate.
786 if(_CxCallback(i, cxid, s)) {
789 ** ... Callback has failed. We need to
790 ** proactively ignore this message now.
791 ** NOTE: WE MAY NEED TO ROLL THE SOCKET
792 ** FORWARD TO SKIP ALL OUT-OF-BAND
796 DPF((DFA,"PROT: ROLL: Rolling socket forward (CALLBACK FAILURE)"));
797 tmpstr = (char *)CxMalloc( 255 );
798 bzero( tmpstr, 254 );
799 i = _CxClRecv( sock, semaphore, tmpstr, cxid );
802 i = _CxClRecv( sock, semaphore, tmpstr, cxid );
803 DPF(( DFA,"PROT: ROLL: i: %d", i ));
807 DPF((DFA,"PROT: ROLL: Cleared OOB data."));
812 ** Previously, I returned 000 upon receiving an
813 ** ASYN message. This was the incorrect behaviour,
814 ** as the expected RECV operation has _not_ been
815 ** completed! At this point, our Callback should've
816 ** executed appropriately, and we can resume reading
817 ** from the Socket as previously planned.
826 ** If there are no callback handles, we need to ignore
827 ** what we just saw. NOTE: WE MAY NEED TO ROLL THE
828 ** SOCKET FORWARD TO SKIP ALL OUT-OF-BAND MESSAGES!
832 DPF((DFA,"PROT: ROLL: Rolling socket forward (NO CALLBACK)"));
833 tmpstr = (char *)CxMalloc( 255 );
834 bzero( tmpstr, 254 );
835 i = _CxClRecv( sock, semaphore, tmpstr, cxid );
838 i = _CxClRecv( sock, semaphore, tmpstr, cxid );
839 DPF(( DFA,"PROT: ROLL: i: %d", i ));
843 DPF((DFA,"PROT: ROLL: Cleared OOB data."));
849 DPF((DFA,"Preparing to return rc: %d", i));
855 ** CxClRecv(): Receive a string from the server.
857 int CxClRecv(int id, char *s) {
862 DPF((DFA,"Receive on handle %d", id));
863 e = _CxTbEntry( g_CxTbl, id );
865 DPF((DFA,"Handle %d unresolvable", id));
869 DPF((DFA,"Handle %d not connected", id));
873 DPF((DFA,"Preparing to receive on %d", e->_sock));
874 return(_CxClRecv( e->_sock, &(e->semaphore), s, id ));
878 ** CxClChatInit(): Initialize Chat mode
886 if(g_CxChatMode) return(1);
888 DPF((DFA,"Entering CHAT mode. Please prepare client for chat-mode\n"));
889 DPF((DFA,"communications..."));
897 ** CxClChatShutdown(): Shut down CHAT mode.
899 void CxClChatShutdown() {
906 DPF((DFA,"Exiting CHAT mode."));
911 /*******************************************************************************
912 ** Communications Layer Abstractions: These functions are to be used by the
913 ** higher level functions to handle communications with the servers. The goal
914 ** is to abstract client<->server communication as much as possible, in such a
915 ** way that changes to the underlying transports don't affect CxClient code.
919 ** struct _Cmd_Stack: These are the commands we are sending to the server. It
920 ** is governed by the MAX_SERV_CMD size limit (which, with the current protocol,
923 ** This is an internal structure which is not useful anywhere else in this
928 char cmd[4]; /** Command. **/
929 CXLIST arg_list; /** Arguments. **/
933 ** CxClReq(): Send a request message to the server. On success, returns a numeric
934 ** handle to the request id (which, for now, is the command's offset in the
941 ** CxClReqCancel(): Cancel a pending request. If the request does not exist in
942 ** the CMDSTACK, don't do anything.
943 ** // Unimplemented in Citadel/UX 5.74
945 int CxClSrvCnclReq() {
949 ** CxClCbExists(): Return whether or not a callback exists. If callback
950 ** exists, a pointer is returned to the Callback's handle. Otherwise,
953 CXCBHNDL CxClCbExists(int cmd) {
956 DPF((DFA,"[_CbHandles] @0x%08x", _CbHandles));
959 DPF((DFA,"[x] @0x%08x %i", x, cmd));
961 DPF((DFA,"[*] Found"));
966 DPF((DFA,"[X] Not Found"));
971 ** CxClCbRegister(): Register a Transport Protocol callback.
973 int CxClCbRegister(int cmd, void *func) {
976 DPF((DFA, "Registering callback for '%d' (@0x%08x)", cmd, func));
980 ** Check to see if callback is already in table. If it is, we'll
981 ** assume that the user intended to REPLACE the existing pointer.
983 new = CxClCbExists(cmd);
986 ** If we already exist, we can substitute the existing callback pointer
987 ** with another one and return. No additional effort is required.
990 DPF((DFA, "Replacing existing callback"));
991 new->Function = func;
995 ** Since it doesn't exist in the stack already, we need ta add it
999 DPF((DFA, "Registering new callback"));
1000 new = (CXCBHNDL)CxMalloc(sizeof(CXCSCALLBACK));
1003 new->Function = func;
1006 ** If we haven't defined any callbacks yet, we need to define
1007 ** this as the 'head' of the Stack.
1009 if( ! _CbHandles ) {
1014 ** ... Otherwise, we need to add the newest callback to the
1015 ** head of the stack.
1018 new->next = _CbHandles;
1021 DPF((DFA,"[new] @0x%08x",new));
1022 DPF((DFA,"->next: @0x%08x",new->next));
1023 DPF((DFA,"[_CbHandles] @0x%08x",_CbHandles));
1028 ** CxClCbShutdown(): Shutdown the callback subsystem.
1030 void CxClCbShutdown() {
1033 DPF((DFA,"Shutting down callback subsystem"));
1044 ** _CxCallback(): Execute a callback.
1047 int _CxCallback(int cmd, int cxid, void *data) {
1050 DPF((DFA, "Executing callback %d", cmd));
1051 cb = CxClCbExists(cmd);
1053 if(cb) cb->Function(cxid, data);