]> code.citadel.org Git - citadel.git/blob - libCxClient/src/libtransport.c
* Fixed handling of ASYN messages, I think.
[citadel.git] / libCxClient / src / libtransport.c
1 /**
2  ** libCxClient - Citadel/UX Extensible Client API
3  ** Copyright (c) 2000, Flaming Sword Productions
4  ** Copyright (c) 2001, The Citadel/UX Consortium
5  ** All Rights Reserved
6  **
7  ** Module: libtransport.o
8  ** Date: 2000-11-30
9  ** Last Revision: 2000-11-30
10  ** Description: Interface to Transport module.
11  ** CVS: $Id$
12  **/
13 #include        <stdio.h>
14 #include        <stdlib.h>
15 #include        <stdarg.h>
16 #include        <unistd.h>
17 #include        <string.h>
18 #include        <termcap.h>
19 #include        <dlfcn.h> 
20 #include        <signal.h>
21 #include        <string.h>
22 #include        <pwd.h>
23 #include        <errno.h>
24 #include        <sys/types.h>
25 #include        <sys/socket.h>
26 #include        <netinet/in.h>
27 #include        <arpa/inet.h>
28 #include        <sys/un.h>
29 #include        <netdb.h>
30 #include        <dlfcn.h>
31 #include        <CxClient.h>
32 #include        "uname.h"
33 #include        "autoconf.h"
34
35 static int      g_CxChatMode = 0,
36                 g_CxSocket = 0,
37                 g_CxAsynMode = 0,
38                 g_CxSemaphore = 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 );
45
46 /**
47  ** CXTBL: Connection handle table.  Use this to make libCxClient thread-safe, and allow
48  ** us to maintain multiple concurrent connections.
49  **/
50 typedef struct  _cx_tbl_entry {
51
52         int     cxId;           /* cxId: Connection ID */
53
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? */
61
62         /**
63          ** Internal
64          **/
65         int     _sock;          /* _sock: TCP/IP connection socket */
66
67         struct _cx_tbl_entry
68                 *_next,         /* _next: Next CXTBL entry. */
69                 *_prev;         /* _prev: Previous CXTBL entry. */
70
71 }               CXTBLENT;
72 typedef CXTBLENT* CXHNDL;
73
74 /**
75  ** [GLOBAL CXTABLE] There should only exist one of these in memory at any
76  ** point in time, to ensure threadsafeness.
77  **/
78 static CXHNDL           g_CxTbl = 0L;
79
80 /**
81  ** _CxTbNewID(): Get the next cxId for the specified connection table.
82  **/
83 static
84 int             _CxTbNewID( CXHNDL tbl ) {
85 CXHNDL          p;
86 int             ret;
87
88         p = tbl;
89         ret = 1;
90         while( p ) {
91                 if(p->cxId == ret) ret = (p->cxId)+1;
92                 p = p->_next;
93         }
94
95         DPF((DFA, "Next cxId: %d", ret));
96         return(ret);
97 }
98
99 /**
100  ** _CxTbNew(): New CXTBL entry.
101  **/
102 static
103 CXHNDL          _CxTbNew( CXHNDL tbl ) {
104 CXHNDL          ret = 0;
105
106         DPF((DFA, "Creating new CXTBL handle."));
107
108         ret = (CXHNDL) CxMalloc( sizeof(CXTBLENT) );
109         if(ret<=0) return(NULL);
110
111         /**
112          ** Initialize these pointers to prevent confusion.
113          **/
114         ret->_next = NULL;
115         ret->_prev = NULL;
116
117         /**
118          ** Establish Default values
119          **/
120         ret->port = 504;
121         ret->connected = 0;
122         ret->asynMode = 0;
123         ret->semaphore = 0;
124         ret->_sock = 0;
125         ret->host[0] = 0;
126         ret->user[0] = 0;
127         ret->pass[0] = 0;
128
129         /**
130          ** Obtain the next cxId for this particular table.
131          **/
132         ret->cxId = _CxTbNewID( tbl );
133
134         DPF((DFA, "Returning hndl @0x%08x", ret ));
135         return(ret);
136 }
137
138 /**
139  ** _CxTbEntry(): Return a handle to a particular table entry.
140  **/
141 static
142 CXHNDL          _CxTbEntry( CXHNDL tbl, int id ) {
143 CXHNDL          p;
144
145         DPF((DFA,"Resolve [tbl@0x%08x] id %d", tbl, id ));
146         p = tbl;
147         while( p ) {
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));
154                         return(p);
155                 }
156                 p = p->_next;
157         }
158         return((CXHNDL)NULL);
159 }
160
161 /**
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.)
164  **/
165 static
166 int             _CxTbInsert( const char *host, int port, const char *user, const char *pass ) {
167 CXHNDL          p,n;
168 char            *tmp;
169
170         DPF((DFA,"Insert new table entry."));
171
172         DPF((DFA,"Allocating new CXTBL block."));
173         n = _CxTbNew( g_CxTbl );
174
175         DPF((DFA,"Copying host"));
176         if(host && *host) {
177                 if(strlen(host) >= 254) {
178                         tmp = (char *)CxMalloc( 255 );
179                         strcpy( tmp, host );
180                         tmp[254] = 0;
181                         strcpy(n->host, tmp);
182                         CxFree(tmp);
183
184                 } else {
185                         strcpy(n->host, host);
186                 }
187         }
188
189         DPF((DFA,"Copying user"));
190         if(user && *user) {
191                 if(strlen(user) >= 64) {
192                         tmp = (char *)CxMalloc( 65 );
193                         strcpy( tmp, user );
194                         tmp[64] = 0;
195                         strcpy(n->user, tmp);
196                         CxFree(tmp);
197                 } else {
198                         strcpy(n->user, user);
199                 }
200         }
201
202         DPF((DFA,"Copying pass"));
203         if(pass && *pass) {
204                 if(strlen(pass) >= 64) {
205                         tmp = (char *)CxMalloc( 65 );
206                         strcpy( tmp, pass );
207                         tmp[64] = 0;
208                         strcpy(n->pass, tmp);
209                         CxFree(tmp);
210                 } else {
211                         strcpy(n->pass, pass);
212                 }
213         }
214
215         DPF((DFA,"Copying port"));
216         if(port) n->port = port;
217
218         DPF((DFA,"Binding to g_CxTbl"));
219         if(!g_CxTbl) {
220                 DPF((DFA,"new g_CxTbl"));
221                 g_CxTbl = n;
222                 DPF((DFA,"New table @0x%08x", g_CxTbl ));
223                 return(n->cxId);
224
225         } else {
226                 DPF((DFA,"existing g_CxTbl"));
227                 p = g_CxTbl;
228                 while( p && p->_next ) {
229                         p = p->_next;
230                 }
231                 if( p ) {
232                         p->_next = n;
233                         n->_prev = p;
234                 }
235         }
236
237         return(n->cxId);
238 }
239
240 /**
241  ** _CxTbDelete(): Delete the specified id.
242  **/
243 static
244 void            _CxTbDelete( int id ) {
245 CXHNDL          p;
246
247         if(!g_CxTbl || !id ) return;
248
249         DPF((DFA,"Delete id %d", id));
250         p = g_CxTbl;
251         while( p ) {
252                 if( p->cxId == id ) break;
253                 p = p->_next;
254         }
255
256         DPF((DFA,"p @0x%08x", p));
257
258         if( p ) {
259
260                 DPF((DFA,"p->_next @0x%08x", p->_next));
261                 DPF((DFA,"p->_prev @0x%08x", p->_prev));
262
263                 /**
264                  ** This was the only entry in the CxTbl.
265                  **/
266                 if( !p->_next && !p->_prev ) {
267                         CxFree(p);
268                         g_CxTbl = NULL;
269
270                 /**
271                  ** Gymnastics time...
272                  **/
273                 } else {
274                         if( p->_next ) p->_next->_prev = p->_prev;
275                         if( p->_prev ) p->_prev->_next = p->_next;
276
277                         if( g_CxTbl == p ) g_CxTbl = p->_next;
278
279                         CxFree(p);
280                 }
281         }
282         DPF((DFA,"g_CxTbl @0x%08x", g_CxTbl));
283 }
284
285 /**
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.
288  **/
289 int             CxClConnection( const char *host, int port, const char *user, const char *pass ) {
290
291         DPF((DFA,"New connection hndl %s:%s@%s:%d", user, "**", host, port));
292         return(_CxTbInsert( host, port, user, pass ) );
293 }
294
295 /**
296  ** CxClDelete(): Delete the specified connection handle.
297  **/
298 void            CxClDelete( int id ) {
299
300         DPF((DFA,"Delete hndl %d", id ));
301         _CxTbDelete( id );
302 }
303
304
305 /**
306  ** CxClSetHost(): Set the username for a specific connection handle.
307  **/
308 void            CxClSetHost( int id, const char *host ) {
309 CXHNDL          e;
310
311         if(!host || !*host) return;
312
313         e = _CxTbEntry( g_CxTbl, id );
314         if(!e) return;
315
316         DPF((DFA,"Set tbl[%d].host = '%s'", id, host ));
317         memset( &(e->host), 0, 253 );
318         strcpy( e->host, host );
319 }
320
321 /**
322  ** CxClSetUser(): Set the username for a specific connection handle.
323  **/
324 void            CxClSetUser( int id, const char *user ) {
325 CXHNDL          e;
326
327         if(!user || !*user) return;
328
329         e = _CxTbEntry( g_CxTbl, id );
330         if(!e) return;
331
332         DPF((DFA,"Set tbl[%d].user = '%s'", id, user ));
333         strcpy( e->user, user );
334 }
335
336 /**
337  ** CxClGetUser(): Set the username for a specific connection handle.
338  ** [*] FREE the results of this operation!!
339  **/
340 char            *CxClGetUser( int id ) {
341 CXHNDL          e;
342 char            *ret;
343
344         e = _CxTbEntry( g_CxTbl, id );
345         if(!e) return(NULL);
346
347         if(e->user[0]) {
348                 ret = (char *)CxMalloc( strlen( e->user ) + 1 );
349                 strcpy( ret, e->user );
350                 return( ret );
351
352         } else {
353                 return(NULL);
354         }
355 }
356
357 /**
358  ** CxClSetPass(): Set the username for a specific connection handle.
359  **/
360 void            CxClSetPass( int id, const char *pass ) {
361 CXHNDL          e;
362
363         if(!pass || !*pass) return;
364
365         e = _CxTbEntry( g_CxTbl, id );
366         if(!e) return;
367
368         DPF((DFA,"Set tbl[%d].pass = '%s'", id, pass ));
369         strcpy( e->pass, pass );
370 }
371
372 /**
373  ** CxClGetPass(): Set the username for a specific connection handle.
374  **/
375 char            *CxClGetPass( int id ) {
376 CXHNDL          e;
377 char            *ret;
378
379         e = _CxTbEntry( g_CxTbl, id );
380         if(!e) return(NULL);
381
382         if(e->pass) {
383                 ret = (char *)CxMalloc( strlen(e->pass) +1 );
384                 strcpy(ret, e->pass);
385                 return(ret);
386
387         } else {
388                 return(NULL);
389         }
390 }
391
392 /**
393  ** CxClSetPass(): Set the username for a specific connection handle.
394 /**
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().
398  **/
399 void            CxClRegClient(const char *cl_name) {
400
401         DPF((DFA,"Developer registered this as \"%s\"", cl_name));
402
403         /**
404          ** If this will cause libCxClient to crash, then just die.
405          **/
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");
411                 exit(999);
412         }
413
414         strcpy(g_CxClientName, cl_name);
415 }
416
417 /**
418  ** CxClConnect(): Establish a connection to the server via the Transport layer.
419  ** [Much of this code was gleaned from the "citadel" client]
420  **
421  ** [Returns]
422  **  On Success: 0
423  **  On Failure: -1: Mis-configuration
424  **              -[errno]: use abs(errno) to retrieve error message.
425  **/
426 int             CxClConnect( int id ) {
427 char            buf[512];
428 struct 
429 hostent         *phe;
430 struct 
431 servent         *pse;
432 struct 
433 protoent        *ppe;
434 struct 
435 sockaddr_in     sin;
436 int             s, type, rc;
437 char            *service = "citadel";
438 char            *protocol = "tcp";
439 CXHNDL          e;
440
441         DPF((DFA,"(Library was built with UNIX_SOCKET support)"));
442
443         e = _CxTbEntry( g_CxTbl, id );
444
445         if(!e) {
446                 DPF((DFA,"Did not call CxConnection(), huh?"));
447                 return(-1);
448         }
449
450         if(!e->host[0]) {
451                 DPF((DFA,"No hostname provided.  Use CxClSetHost() first!!"));
452                 return(-1);
453         }
454         DPF((DFA,"Establishing connection to host \"%s\"",e->host));
455
456         memset(&sin, 0, sizeof(sin));
457         sin.sin_family = AF_INET;
458
459         DPF((DFA,"-> getservbyname()"));
460         pse = getservbyname(service, protocol);
461         if(pse) {
462                 sin.sin_port = pse->s_port;
463         } else if((sin.sin_port = htons((u_short) atoi(service))) != 0) {
464         } else {
465                 sin.sin_port = htons((u_short)504);
466         }
467
468         DPF((DFA,"-> gethostbyname(): \"%s\"", e->host));
469         phe = gethostbyname(e->host);
470         DPF((DFA,"phe@0x%08x", phe));
471         if (phe) {
472                 memcpy(&sin.sin_addr, phe->h_addr, phe->h_length);
473
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)));
476                 return(-1*(errno));
477         }
478
479         DPF((DFA,"-> getprotobyname()"));
480         if ((ppe = getprotobyname(protocol)) == 0) {
481                 DPF((DFA,"Unable to get protocol entry.  %s", strerror(errno)));
482                 return(-1);
483         }
484         if (!strcmp(protocol, "udp")) {
485                 type = SOCK_DGRAM;
486         } else {
487                 type = SOCK_STREAM;
488         }
489
490         s = socket(PF_INET, type, ppe->p_proto);
491         if (s < 0) {
492                 DPF((DFA,"Unable to create socket.  %s", strerror(errno)));
493                 return(-1);
494         }
495         signal(SIGALRM, timeout);
496         alarm(30);
497
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)); 
503  **/
504                 return(errno*(-1));
505         }
506         alarm(0);
507         signal(SIGALRM, SIG_IGN);
508
509         DPF((DFA,"Socket %d", s));
510         e->_sock = s;
511         e->connected = 1;
512
513         if( s ) {
514
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);
519                 } else {
520                         sprintf(buf,"IDEN 1|1|100|CX/%s|",VERSION);
521                 }
522                 _CxClSend(s, buf);
523                 _CxClRecv(e->_sock, &(e->semaphore), buf, id);
524
525                 _CxClSend(s, "ASYN 1");
526                 rc = _CxClRecv(e->_sock, &(e->semaphore), buf, id);
527
528                 /**
529                  ** If the server doesn't support Asnychronous mode, then
530                  ** we shouldn't try to be asynchronous...
531                  **/
532                 if(CHECKRC(rc, RC_OK)) {
533                         DPF((DFA,":: Server in ASYNCHRONOUS mode."));
534                         e->asynMode = 1;
535                 } else {
536                         DPF((DFA,":: ASYNCHRONOUS mode not supported."));
537                         e->asynMode = 0;
538                 }
539
540                 /**
541                  ** We don't return our socket anymore.
542                  **/
543                 return(0);
544         }
545         return(0);
546 }
547
548 /**
549  ** CxClDisconnect(): Disconnect the specified socket.
550  **/
551 void            CxClDisconnect( int id ) {
552 CXHNDL          e;
553
554         DPF((DFA,"Caught orders to close socket %d.", id));
555
556         e = _CxTbEntry( g_CxTbl, id );
557         if(!e) return;
558
559         /**
560          ** Sleep until the semaphore is cleared.
561          **/
562         while(e->semaphore) ;
563
564         shutdown(e->_sock, 0);
565 }
566
567 /**
568  ** CxClStat(): Return connection status.
569  **/
570 int             CxClStat( int id ) {
571 CXHNDL          e;
572
573         e = _CxTbEntry( g_CxTbl, id );
574         if(!e) return(0);
575
576         if( e->connected ) {
577                 return(1);
578         } else {
579                 return(0);
580         }
581 }
582
583 /**
584  ** _CxClSend(): REAL send.  Uses a socket instead of the ID.
585  **/
586 static
587 void            _CxClSend( int sock, const char *s ) {
588 int             bytes_written = 0;
589 int             retval,nbytes;
590 char            *ss;
591
592         /**
593          ** If the socket is not open, there's no point in going here.
594          **/
595         if(!sock) return;
596
597         DPF((DFA,"PROT --> \"%s\"", s));
598
599         ss = (char *)CxMalloc(strlen(s)+2);
600         sprintf(ss,"%s\n",s);
601
602         nbytes = strlen(ss);
603         if(!nbytes) {
604                 CxFree(ss);
605                 return;
606         }
607
608         while (bytes_written < nbytes) {
609                 retval = write(sock, &ss[bytes_written],
610                                 nbytes - bytes_written);
611                 if (retval < 1) {
612                         write (sock, "\n", strlen("\n"));
613                         return;
614                 }
615                 bytes_written = bytes_written + retval;
616         }
617         CxFree(ss);
618 }
619
620 /**
621  ** CxClSend(): Send a string to the server.
622  **/
623 void            CxClSend(int id, const char *s) {
624 CXHNDL          e;
625
626         e = _CxTbEntry( g_CxTbl, id );
627         if(!e) return;
628         if(!e->connected) return;
629
630         DPF((DFA,"SEND: \"%s\"", s));
631         _CxClSend( e->_sock, s );
632 }
633
634 /**
635  ** ClRecvChr(): Retrieve the next message from the server.
636  ** *********** SOURCE: citadel-source/citmail.c:serv_read()
637  **/
638 static
639 void            ClRecvChar(int socket, char *buf, int bytes) {
640 int             len, rlen;
641
642         len = 0;
643         while (len < bytes) {
644                 rlen = read(socket, &buf[len], bytes - len);
645                 if (rlen < 1) {
646                         return;
647                 }
648                 len = len + rlen;
649         }
650 }
651
652 /**
653  ** _CxClWait(): Wait on the semaphore.
654  **/
655 static
656 void            _CxClWait( int *e ) {
657
658         DPF((DFA,"Waiting on Semaphore..."));
659         while(*e) ;
660
661         DPF((DFA,"*** LOCKING SESSION ***"));
662         (*e) = 1;
663 }
664
665 /**
666  ** _CxClClear(): Clear the semaphore.
667  **/
668 static
669 void            _CxClClear( int *e ) {
670
671         DPF((DFA,"*** CLEARING SESSION ***"));
672         (*e) = 0;
673 }
674
675 /**
676  ** _CxClRecv(): REAL receive.
677  **/
678 static
679 int             _CxClRecv( int sock, int *semaphore, char *s, int cxid ) {
680 char            substr[4];
681 int             i, tmp;
682 char            *tmpstr;
683
684         /**
685          ** If the socket is not open, there's no point in going here.
686          **/
687         DPF((DFA,"RECV on %d", sock));
688         if(!sock) {
689                 DPF((DFA,"No socket."));
690                 return( 0 );
691         }
692
693         /**
694          ** RETRY_RECV when we have a callback and need to re-synch the protocol.
695          **/
696 RETRY_RECV:
697
698         _CxClWait( semaphore );
699         DPF((DFA,"for(;message <= bottle;) ;"));
700  
701         /**
702          ** Read one character at a time.
703          **/
704         for(i = 0; ; i++) {
705                 ClRecvChar(sock, &s[i], 1);
706                 if (s[i] == '\n' || i == 255)
707                         break;
708         }
709  
710         /**
711          ** If we got a long line, discard characters until the newline.
712          **/
713         if (i == 255)
714                 while (s[i] != '\n')
715                         ClRecvChar(sock, &s[i], 1);
716
717         _CxClClear( semaphore );
718
719         /**
720          ** Strip all trailing nonprintables (crlf)
721          **/
722         s[i] = 0;
723
724         DPF((DFA,"PROT <-- \"%s\"", s));
725
726         strncpy(substr,s,4);
727
728         /**
729          ** Check to see if the message is prepended with a server result code.
730          **/
731         if(
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]=='*')))
737           ) {
738                 i = (int)strtol(s, (char **)NULL, 10);
739                 if(substr[3]=='*') i+=RC_MESGWAIT;
740
741                 /**
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.
745                  **
746                  ** (Shift the entire string left 4 places.)
747                  **/
748                 for(tmp = 0; tmp < strlen(s); tmp++) {
749                         if(tmp+4 < strlen(s)) s[tmp] = s[tmp+4];
750                         else s[tmp] = 0;
751                 }
752                 s[tmp] = 0;
753
754                 DPF((DFA," s: \"%s\"", s));
755
756         /**
757          ** Otherwise, we can assume that this is an RC_LISTING entry.
758          **/
759         } else {
760                 i = -1;
761         }
762
763         /**
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.
768          **/
769         DPF((DFA,"rc = %d (%d)", i, CHECKRC(i, RC_ASYNCMSG)));
770         if(i>0) {
771
772                 /**
773                  ** If the server has told us a secret...
774                  **/
775                 if(CHECKRC(i, RC_ASYNCMSG)) {
776                         DPF((DFA,"Preparing to process async message on CXID"));
777
778                         /**
779                          ** Do we have ANY callbacks defined?
780                          **/
781                         if(_CbHandles) {
782
783                                 /**
784                                  ** Pass data to callback function, if appropriate.
785                                  **/
786                                 if(_CxCallback(i, cxid, s)) {
787
788                                         /**
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
793                                          ** MESSAGES!
794                                          **/
795
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 );
800                                         do {
801
802                                                 i = _CxClRecv( sock, semaphore, tmpstr, cxid );
803                                                 DPF(( DFA,"PROT: ROLL: i: %d", i ));
804
805                                         } while( i<0 );
806                                         free( tmpstr );
807                                         DPF((DFA,"PROT: ROLL: Cleared OOB data."));
808
809                                         goto RETRY_RECV;
810
811                                 /**
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.
818                                  **/
819                                 } else {
820
821                                 
822                                         goto RETRY_RECV;
823                                 }
824
825                         /**
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!
829                          **/
830                         } else {
831
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 );
836                                         do {
837
838                                                 i = _CxClRecv( sock, semaphore, tmpstr, cxid );
839                                                 DPF(( DFA,"PROT: ROLL: i: %d", i ));
840
841                                         } while( i<0 );
842                                         free( tmpstr );
843                                         DPF((DFA,"PROT: ROLL: Cleared OOB data."));
844                                         goto RETRY_RECV;
845
846                         }
847                 }
848         }
849         DPF((DFA,"Preparing to return rc: %d", i));
850
851         return(i);
852 }
853
854 /**
855  ** CxClRecv(): Receive a string from the server.
856  **/
857 int             CxClRecv(int id, char *s) {
858 char            substr[4];
859 int             i, tmp;
860 CXHNDL          e;
861
862         DPF((DFA,"Receive on handle %d", id));
863         e = _CxTbEntry( g_CxTbl, id );
864         if(!e) {
865                 DPF((DFA,"Handle %d unresolvable", id));
866                 return(0);
867         }
868         if(!e->connected) {
869                 DPF((DFA,"Handle %d not connected", id));
870                 return(0);
871         }
872
873         DPF((DFA,"Preparing to receive on %d", e->_sock));
874         return(_CxClRecv( e->_sock, &(e->semaphore), s, id ));
875 }
876
877 /**
878  ** CxClChatInit(): Initialize Chat mode
879  **/
880 int             CxClChatInit() {
881
882         if(g_CxChatMode) {
883                 return(1);
884         }
885
886         if(g_CxChatMode) return(1);
887
888         DPF((DFA,"Entering CHAT mode.  Please prepare client for chat-mode\n"));
889         DPF((DFA,"communications..."));
890
891         g_CxChatMode = 1;
892
893         return(0);
894 }
895
896 /**
897  ** CxClChatShutdown(): Shut down CHAT mode.
898  **/
899 void            CxClChatShutdown() {
900
901         if(!g_CxChatMode) {
902                 return;
903         }
904         if(!g_CxChatMode) 
905                 return;
906         DPF((DFA,"Exiting CHAT mode."));
907
908         g_CxChatMode = 0;
909 }
910
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.
916  **/
917
918 /**
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,
921  ** will be "1").
922  **
923  ** This is an internal structure which is not useful anywhere else in this 
924  ** software.
925  **/
926 typedef
927 struct          _Cmd_Stack {
928    char         cmd[4];         /** Command. **/
929    CXLIST       arg_list;       /** Arguments. **/
930 } _CXCSOBJ;
931
932 /**
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 
935  ** CMDSTACK.
936  **/
937 int             CxClSrvReq() {
938 }
939
940 /**
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
944  **/
945 int             CxClSrvCnclReq() {
946 }
947
948 /**
949  ** CxClCbExists(): Return whether or not a callback exists.  If callback
950  ** exists, a pointer is returned to the Callback's handle.  Otherwise,
951  ** NULL is returned.
952  **/
953 CXCBHNDL        CxClCbExists(int cmd) {
954 CXCBHNDL        x;
955
956         DPF((DFA,"[_CbHandles] @0x%08x", _CbHandles));
957         x = _CbHandles;
958         while( x ) {
959                 DPF((DFA,"[x] @0x%08x %i", x, cmd));
960                 if(x->cmd == cmd) {
961                         DPF((DFA,"[*] Found"));
962                         return(x);
963                 }
964                 x = x->next;
965         }
966         DPF((DFA,"[X] Not Found"));
967         return(NULL);
968 }
969
970 /**
971  ** CxClCbRegister(): Register a Transport Protocol callback.
972  **/
973 int             CxClCbRegister(int cmd, void *func) {
974 CXCBHNDL        new;
975
976         DPF((DFA, "Registering callback for '%d' (@0x%08x)", cmd, func));
977         new = 0;
978
979         /**
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.
982          **/
983         new = CxClCbExists(cmd);
984
985         /**
986          ** If we already exist, we can substitute the existing callback pointer
987          ** with another one and return.  No additional effort is required.
988          **/
989         if(new) {
990                 DPF((DFA, "Replacing existing callback"));
991                 new->Function = func;
992                 return(0);
993
994         /**
995          ** Since it doesn't exist in the stack already, we need ta add it
996          ** into the stack.
997          **/
998         } else {
999                 DPF((DFA, "Registering new callback"));
1000                 new = (CXCBHNDL)CxMalloc(sizeof(CXCSCALLBACK));
1001
1002                 new->cmd = cmd;
1003                 new->Function = func;
1004         
1005                 /**
1006                  ** If we haven't defined any callbacks yet, we need to define
1007                  ** this as the 'head' of the Stack.
1008                  **/
1009                 if( ! _CbHandles ) {
1010                         _CbHandles = new;
1011                         new->next = NULL;
1012
1013                 /**
1014                  ** ... Otherwise, we need to add the newest callback to the
1015                  ** head of the stack.
1016                  **/
1017                 } else {
1018                         new->next = _CbHandles;
1019                         _CbHandles = new;
1020                 }
1021                 DPF((DFA,"[new] @0x%08x",new));
1022                 DPF((DFA,"->next: @0x%08x",new->next));
1023                 DPF((DFA,"[_CbHandles] @0x%08x",_CbHandles));
1024         }
1025 }
1026
1027 /**
1028  ** CxClCbShutdown(): Shutdown the callback subsystem.
1029  **/
1030 void            CxClCbShutdown() {
1031 CXCBHNDL        x, y;
1032
1033         DPF((DFA,"Shutting down callback subsystem"));
1034         x = _CbHandles;
1035         while( x ) {
1036                 y = x;
1037                 x = x->next;
1038                 CxFree(y);
1039         }
1040         _CbHandles = 0;
1041 }
1042
1043 /**
1044  ** _CxCallback(): Execute a callback.
1045  **/
1046 static
1047 int             _CxCallback(int cmd, int cxid, void *data) {
1048 CXCBHNDL        cb;
1049
1050         DPF((DFA, "Executing callback %d", cmd));
1051         cb = CxClCbExists(cmd);
1052
1053         if(cb) cb->Function(cxid, data);
1054         else return(1);
1055
1056         return(0);
1057 }