]> code.citadel.org Git - citadel.git/blob - libCxClient/src/libtransport.c
Initial revision
[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 cmd, void *data);
42 static void     timeout() {}
43
44 /**
45  ** CxClRegClient(): (For Developers) Register your client name with
46  ** libCxClient.  This gets reported along with the IDEN information passed
47  ** to the server.  It should be called before CxClConnect().
48  **/
49 void            CxClRegClient(const char *cl_name) {
50
51         DPF((DFA,"Developer registered this as \"%s\"", cl_name));
52
53         /**
54          ** If this will cause libCxClient to crash, then just die.
55          **/
56         if(strlen(cl_name)>31) {
57                 printf("* * *  Fatal Error  * * *\n");
58                 printf("Invalid use of CxClRegClient().  I expect cl_name to be less than 31 characters in length.\n");
59                 printf("cl_name = '%s'\n", cl_name);
60                 printf("\nI can't continue.  Please re-build your client.\n");
61                 exit(999);
62         }
63
64         strcpy(g_CxClientName, cl_name);
65 }
66
67 /**
68  ** CxClConnect(): Establish a connection to the server via the Transport layer.
69  ** [Much of this code was gleaned from the "citadel" client]
70  **/
71 int             CxClConnect(const char *host) {
72 char            buf[512];
73 struct 
74 hostent         *phe;
75 struct 
76 servent         *pse;
77 struct 
78 protoent        *ppe;
79 struct 
80 sockaddr_in     sin;
81 int             s, type, rc;
82 char            *service = "citadel";
83 char            *protocol = "tcp";
84
85         DPF((DFA,"(Library was built with UNIX_SOCKET support)"));
86         DPF((DFA,"Establishing connection to host \"%s\"",host));
87
88         memset(&sin, 0, sizeof(sin));
89         sin.sin_family = AF_INET;
90
91         pse = getservbyname(service, protocol);
92         if(pse) {
93                 sin.sin_port = pse->s_port;
94         } else if((sin.sin_port = htons((u_short) atoi(service))) != 0) {
95         } else {
96                 sin.sin_port = htons((u_short)504);
97         }
98         phe = gethostbyname(host);
99         if (phe) {
100                 memcpy(&sin.sin_addr, phe->h_addr, phe->h_length);
101
102         } else if ((sin.sin_addr.s_addr = inet_addr(host)) == INADDR_NONE) {
103                 printf("\n* * *  Fatal Error  * * *\n");
104                 printf("System Error: Can't get host entry for '%s'\n", host);
105                 printf("Error Details: %s\n\n", strerror(errno));
106                 return(-1);
107         }
108         if ((ppe = getprotobyname(protocol)) == 0) {
109                 fprintf(stderr, "Can't get %s protocol entry: %s\n",
110                         protocol, strerror(errno));
111                 return(-1);
112         }
113         if (!strcmp(protocol, "udp")) {
114                 type = SOCK_DGRAM;
115         } else {
116                 type = SOCK_STREAM;
117         }
118
119         s = socket(PF_INET, type, ppe->p_proto);
120         if (s < 0) {
121                 printf("\n* * *  Fatal Error  * * *\n");
122                 printf("System Error: Can't create socket\n");
123                 printf("Error Details: %s\n\n", strerror(errno));
124                 return(-1);
125         }
126         signal(SIGALRM, timeout);
127         alarm(30);
128          if (connect(s, (struct sockaddr *) &sin, sizeof(sin)) < 0) {
129                 printf("\n* * *  Fatal Error  * * *\n");
130                 printf("System Error: Can't connect to '%s' [%s]\n",host, service);
131                 printf("Error Details: %s\n\n", strerror(errno));
132                 return(-1);
133         }
134         alarm(0);
135         signal(SIGALRM, SIG_IGN);
136
137         g_CxSocket = s;
138
139         if(s) {
140                 CxClRecv(buf);
141                 if(g_CxClientName[0]) {
142                         sprintf(buf,"IDEN 1|1|100|libCxClient/%s (%s)|",VERSION, g_CxClientName);
143                 } else {
144                         sprintf(buf,"IDEN 1|1|100|libCxClient/%s (unknown)|",VERSION);
145                 }
146                 CxClSend(buf);
147                 CxClRecv(buf);
148                 CxClSend("ASYN 1");
149                 rc = CxClRecv(buf);
150
151                 /**
152                  ** If the server doesn't support Asnychronous mode, then
153                  ** we shouldn't try to be asynchronous...
154                  **/
155                 if(CHECKRC(rc, RC_OK)) {
156                         g_CxAsynMode = 1;
157                 } else {
158                         g_CxAsynMode = 0;
159                 }
160
161                 /**
162                  ** We don't return our socket anymore.
163                  **/
164                 return(0);
165         }
166         return(0);
167 }
168
169 /**
170  ** CxClDisconnect(): Disconnect the socket.
171  **/
172 void            CxClDisconnect() {
173
174         DPF((DFA,"Caught orders to close socket."));
175
176         shutdown(g_CxSocket, 0);
177 }
178
179 /**
180  ** CxClStat(): Send a string to the server.
181  **/
182 int             CxClStat() {
183
184         if(g_CxSocket) {
185                 return(1);
186         } else {
187                 return(0);
188         }
189 }
190
191 /**
192  ** CxClSend(): Send a string to the server.
193  **/
194 void            CxClSend(const char *s) {
195 int             bytes_written = 0;
196 int             retval,nbytes;
197 char            *ss;
198
199         DPF((DFA,"SEND: \"%s\"", s));
200
201         ss = (char *)CxMalloc(strlen(s)+2);
202         sprintf(ss,"%s\n",s);
203
204         nbytes = strlen(ss);
205         if(!nbytes) {
206                 CxFree(ss);
207                 return;
208         }
209
210         while (bytes_written < nbytes) {
211                 retval = write(g_CxSocket, &ss[bytes_written],
212                                nbytes - bytes_written);
213                 if (retval < 1) {
214                         write (g_CxSocket, "\n", strlen("\n"));
215                         return;
216                 }
217                 bytes_written = bytes_written + retval;
218         }
219         CxFree(ss);
220 }
221
222 /**
223  ** ClRecvChr(): Retrieve the next message from the server.
224  ** *********** SOURCE: citadel-source/citmail.c:serv_read()
225  **/
226 static
227 void            ClRecvChar(char *buf, int bytes) {
228 int             len, rlen;
229
230         len = 0;
231         while (len < bytes) {
232                 rlen = read(g_CxSocket, &buf[len], bytes - len);
233                 if (rlen < 1) {
234                         return;
235                 }
236                 len = len + rlen;
237         }
238 }
239
240 /**
241  ** _CxClWait(): Wait on the semaphore.
242  **/
243 static
244 void            _CxClWait() {
245
246         DPF((DFA,"Waiting on Semaphore..."));
247         while(g_CxSemaphore) ;
248
249         DPF((DFA,"*** LOCKING SESSION ***"));
250         g_CxSemaphore = 1;
251 }
252
253 /**
254  ** _CxClClear(): Clear the semaphore.
255  **/
256 static
257 void            _CxClClear() {
258
259         DPF((DFA,"*** CLEARING SESSION ***"));
260         g_CxSemaphore = 0;
261 }
262
263 /**
264  ** CxClRecv(): Receive a string from the server.
265  **/
266 int             CxClRecv(char *s) {
267 char            substr[4];
268 int             i, tmp;
269
270         if(!CxClStat()) {
271                 return(NULL);
272         }
273
274         /**
275          ** At this point, we should wait for the semaphore to be cleared.
276          ** This will prevent multi-threaded clients from pissing all over
277          ** themselves when 2 threads attempt to read at the same time...
278          **/
279         _CxClWait();
280
281         /**
282          ** RETRY_RECV when we have a callback and need to re-synch the protocol.
283          **/
284 RETRY_RECV:
285
286         DPF((DFA,"for(;message <= bottle;) ;"));
287  
288         /**
289          ** Read one character at a time.
290          **/
291         for(i = 0; ; i++) {
292                 ClRecvChar(&s[i], 1);
293                 if (s[i] == '\n' || i == 255)
294                         break;
295         }
296  
297         /**
298          ** If we got a long line, discard characters until the newline.
299          **/
300         if (i == 255)
301                 while (s[i] != '\n')
302                         ClRecvChar(&s[i], 1);
303  
304
305         /**
306          ** Strip all trailing nonprintables (crlf)
307          **/
308         s[i] = 0;
309
310         DPF((DFA,"I got \"%s\"",s));
311
312         strncpy(substr,s,4);
313
314         /**
315          ** Check to see if the message is prepended with a server result code.
316          **/
317         if(
318                 (!strcmp(substr,"000")) ||
319                 ((substr[0]>='0' && substr[0]<='9') &&
320                  (substr[1]>='0' && substr[1]<='9') &&
321                  (substr[2]>='0' && substr[2]<='9') &&
322                  ((substr[3]==' ') || (substr[3]=='*')))
323           ) {
324                 i = (int)strtol(s, (char **)NULL, 10);
325                 if(substr[3]=='*') i+=RC_MESGWAIT;
326
327                 /**
328                  ** This removes the result code & other data from the
329                  ** returned string.  This is _really_ going to mess with
330                  ** lots of code.  Ugh.
331                  **/
332                 DPF((DFA," s: \"%s\"", s));
333
334                 /**
335                  ** Shift the entire string left 4 places.
336                  **/
337                 for(tmp = 0; tmp < strlen(s); tmp++) {
338                         if(tmp+4 < strlen(s)) s[tmp] = s[tmp+4];
339                         else s[tmp] = 0;
340                 }
341                 s[tmp] = 0;
342
343                 DPF((DFA," s: \"%s\"", s));
344
345         /**
346          ** Otherwise, we can assume that this is an RC_LISTING entry.
347          **/
348         } else {
349                 i = -1;
350         }
351
352         /**
353          ** We wish to clear the semaphore BEFORE executing any callbacks.
354          ** This will help to prevent nasty race conditions.  >:)
355          **/
356         _CxClClear();
357
358         /**
359          ** This is the only instance of Goto you'll find in
360          ** libCxClient.  The point is: Once we're done handling
361          ** an asynchronous message, we need to go back & handle
362          ** other messages as normal.
363          **/
364         DPF((DFA,"rc = %d (%d)", i, CHECKRC(i, RC_ASYNCMSG)));
365         if(i>0) {
366
367                 /**
368                  ** If the server has told us a secret...
369                  **/
370                 if(CHECKRC(i, RC_ASYNCMSG)) {
371                         DPF((DFA,"Preparing to process async message"));
372
373                         /**
374                          ** Do we have ANY callbacks defined?
375                          **/
376                         if(_CbHandles) {
377
378                                 /**
379                                  ** Pass data to callback function, if appropriate.
380                                  **/
381                                 if(_CxCallback(i, s)) {
382
383                                         /**
384                                          ** ... Callback has failed.  We need to
385                                          ** proactively ignore this message now.
386                                          **/
387
388                                         /** INCOMPLETE **/
389
390                                 }
391
392                                 goto RETRY_RECV;
393
394                         } else {
395
396                                         /** INCOMPLETE **/
397
398                         }
399                 }
400         }
401         DPF((DFA,"Preparing to return rc: %d", i));
402
403         return(i);
404 }
405
406 /**
407  ** CxClChatInit(): Initialize Chat mode
408  **/
409 int             CxClChatInit() {
410
411         if(g_CxChatMode) {
412                 return(1);
413         }
414
415         if(g_CxChatMode) return(1);
416
417         DPF((DFA,"Entering CHAT mode.  Please prepare client for chat-mode\n"));
418         DPF((DFA,"communications..."));
419
420         g_CxChatMode = 1;
421
422         return(0);
423 }
424
425 /**
426  ** CxClChatShutdown(): Shut down CHAT mode.
427  **/
428 void            CxClChatShutdown() {
429
430         if(!g_CxChatMode) {
431                 return;
432         }
433         if(!g_CxChatMode) 
434                 return;
435         DPF((DFA,"Exiting CHAT mode."));
436
437         g_CxChatMode = 0;
438 }
439
440 /*******************************************************************************
441  ** Communications Layer Abstractions:  These functions are to be used by the
442  ** higher level functions to handle communications with the servers.  The goal
443  ** is to abstract client<->server communication as much as possible, in such a
444  ** way that changes to the underlying transports don't affect CxClient code.
445  **/
446
447 /**
448  ** struct _Cmd_Stack: These are the commands we are sending to the server.  It
449  ** is governed by the MAX_SERV_CMD size limit (which, with the current protocol,
450  ** will be "1").
451  **
452  ** This is an internal structure which is not useful anywhere else in this 
453  ** software.
454  **/
455 typedef
456 struct          _Cmd_Stack {
457    char         cmd[4];         /** Command. **/
458    CXLIST       arg_list;       /** Arguments. **/
459 } _CXCSOBJ;
460
461 /**
462  ** CxClReq(): Send a request message to the server.  On success, returns a numeric
463  ** handle to the request id (which, for now, is the command's offset in the 
464  ** CMDSTACK.
465  **/
466 int             CxClSrvReq() {
467 }
468
469 /**
470  ** CxClReqCancel(): Cancel a pending request.  If the request does not exist in
471  ** the CMDSTACK, don't do anything.
472  ** // Unimplemented in Citadel/UX 5.74
473  **/
474 int             CxClSrvCnclReq() {
475 }
476
477 /**
478  ** CxClCbExists(): Return whether or not a callback exists.  If callback
479  ** exists, a pointer is returned to the Callback's handle.  Otherwise,
480  ** NULL is returned.
481  **/
482 CXCBHNDL        CxClCbExists(int cmd) {
483 CXCBHNDL        x;
484
485         return(0);
486         DPF((DFA,"[_CbHandles] @0x%08x", _CbHandles));
487         x = _CbHandles;
488         while( x ) {
489                 DPF((DFA,"[x] @0x%08x %i", x, cmd));
490                 if(x->cmd == cmd) {
491                         DPF((DFA,"[*] Found"));
492                         return(x);
493                 }
494                 x = x->next;
495         }
496         DPF((DFA,"[X] Not Found"));
497         return(NULL);
498 }
499
500 /**
501  ** CxClCbRegister(): Register a Transport Protocol callback.
502  **/
503 int             CxClCbRegister(int cmd, void *func) {
504 CXCBHNDL        new;
505
506         return(0);
507
508         DPF((DFA, "Registering callback for '%d' (@0x%08x)", cmd, func));
509         new = 0;
510
511         /**
512          ** Check to see if callback is already in table.  If it is, we'll
513          ** assume that the user intended to REPLACE the existing pointer.
514          **/
515         new = CxClCbExists(cmd);
516
517         /**
518          ** If we already exist, we can substitute the existing callback pointer
519          ** with another one and return.  No additional effort is required.
520          **/
521         if(new) {
522                 DPF((DFA, "Replacing existing callback"));
523                 new->Function = func;
524                 return(0);
525
526         /**
527          ** Since it doesn't exist in the stack already, we need ta add it
528          ** into the stack.
529          **/
530         } else {
531                 DPF((DFA, "Registering new callback"));
532                 new = (CXCBHNDL)CxMalloc(sizeof(CXCSCALLBACK));
533
534                 new->cmd = cmd;
535                 new->Function = func;
536         
537                 /**
538                  ** If we haven't defined any callbacks yet, we need to define
539                  ** this as the 'head' of the Stack.
540                  **/
541                 if( ! _CbHandles ) {
542                         _CbHandles = new;
543                         new->next = NULL;
544
545                 /**
546                  ** ... Otherwise, we need to add the newest callback to the
547                  ** head of the stack.
548                  **/
549                 } else {
550                         new->next = _CbHandles;
551                         _CbHandles = new;
552                 }
553                 DPF((DFA,"[new] @0x%08x",new));
554                 DPF((DFA,"->next: @0x%08x",new->next));
555                 DPF((DFA,"[_CbHandles] @0x%08x",_CbHandles));
556         }
557 }
558
559 /**
560  ** CxClCbShutdown(): Shutdown the callback subsystem.
561  **/
562 void            CxClCbShutdown() {
563 CXCBHNDL        x, y;
564
565         return(0);
566         DPF((DFA,"Shutting down callback subsystem"));
567         x = _CbHandles;
568         while( x ) {
569                 y = x;
570                 x = x->next;
571                 CxFree(y);
572         }
573         _CbHandles = 0;
574 }
575
576 /**
577  ** _CxCallback(): Execute a callback.
578  **/
579 static
580 int             _CxCallback(int cmd, void *data) {
581 CXCBHNDL        cb;
582
583         return(0);
584
585         DPF((DFA, "Executing callback %d", cmd));
586         cb = CxClCbExists(cmd);
587
588         if(cb) cb->Function(data);
589         else return(1);
590
591         return(0);
592 }