minor
[charm.git] / src / conv-ccs / ccs-server.c
1 /*
2 Converse Client/Server: Server-side interface
3 Orion Sky Lawlor, 9/11/2000, olawlor@acm.org
4
5 This file describes the under-the-hood implementation
6 of the CCS Server.  Here's where Ccs requests from the
7 network are actually received.
8 */
9 #include <stdio.h>
10 #include <ctype.h>
11 #include "conv-ccs.h"
12 #include "ccs-server.h"
13 #include "ccs-auth.h"
14
15 #if CMK_CCS_AVAILABLE
16
17 /****************** Security and Server Utilities ***************/
18 /*Security guard for a CCS server*/
19 struct CcsSecMan;
20 typedef int (*CcsSecMan_allowFn) 
21      (struct CcsSecMan *self,CcsSecAttr *attr);
22 typedef CcsSec_secretKey * (*CcsSecMan_getKeyFn) 
23      (struct CcsSecMan *self,CcsSecAttr *attr);
24
25 typedef struct CcsSecMan {
26   CcsSecMan_allowFn allowRequest;
27   CcsSecMan_getKeyFn getKey;
28   CcsSec_secretKey * keys[256];
29 } CcsSecMan;
30
31
32 /*A table of connected clients*/
33 typedef struct {
34   int nClients;
35   int *clients; /*Gives current salt of corresponding client.*/
36   CCS_RAND_state rand;
37 } CCS_AUTH_clients;
38
39 static void CCS_AUTH_new(CCS_AUTH_clients *cl)
40 {
41   cl->nClients=0;
42   cl->clients=NULL;
43   CCS_RAND_new(&cl->rand);
44 }
45 static int CCS_AUTH_numClients(CCS_AUTH_clients *cl) {
46   return cl->nClients;
47 }
48 static int CCS_AUTH_addClient(CCS_AUTH_clients *cl) {
49   int clientNo=cl->nClients++;
50   if ((clientNo%64)==0)
51     cl->clients=realloc(cl->clients,sizeof(int)*(cl->nClients+63));
52   cl->clients[clientNo]=CCS_RAND_next(&cl->rand);
53   return clientNo;
54 }
55 static int CCS_AUTH_clientSalt(CCS_AUTH_clients *cl,int clientNo) {
56   return cl->clients[clientNo];
57 }
58 static void CCS_AUTH_advanceSalt(CCS_AUTH_clients *cl,int clientNo) {
59   cl->clients[clientNo]++;
60 }
61
62 /***********************************************
63 Send the given data as a CCS reply on the given socket.
64 Goes to some effort to minimize the number of "sends"
65 (and hence outgoing TCP packets) by assembling the 
66 header and data in-place.
67 */
68 static void CcsServer_writeReply(SOCKET fd,
69                          CcsSecMan *security,
70                          CcsSecAttr *attr,
71                          int replyLen,char *reply)
72 {
73   const void *bufs[3]; int lens[3]; int nBuffers=0;
74   struct { /*Authentication header*/
75     SHA1_hash_t hash;
76   } aheader;
77   struct { /*Reply header*/
78     ChMessageInt_t len;
79   } header;
80   if (attr->auth==1) 
81   { /*Compose a reply SHA-1 hash header*/
82     CCS_AUTH_hash(security->getKey(security,attr),
83                   ChMessageInt(attr->replySalt),NULL,&aheader.hash);
84     bufs[nBuffers]=&aheader; lens[nBuffers]=sizeof(aheader); nBuffers++;
85   }
86   /*Compose a simple reply header*/
87   header.len=ChMessageInt_new(replyLen);
88   bufs[nBuffers]=&header; lens[nBuffers]=sizeof(header); nBuffers++;
89   bufs[nBuffers]=reply; lens[nBuffers]=replyLen; nBuffers++;
90   if (-1==skt_sendV(fd,nBuffers,bufs,lens)) return;
91   skt_close(fd);
92 #undef n
93 }
94
95 /********************************************************
96 Authenticate incoming request for a client salt value.
97 Exchange looks like:
98
99 1.) Client sends request code 0x80 (SHA-1), 0x00 (version 0), 
100 0x01 (create salt), 0xNN (security level); followed by 
101 client challenge (4 bytes, s1)
102
103 2.) Server replies with server challenge (4 bytes, s2)
104
105 3.) Client replies with hashed key & server challenge (20 bytes, s2hash)
106
107 4.) Server replies with hashed key & client challenge (20 bytes, s1hash),
108 as well as client identifier and initial client salt. (8 bytes total).
109 */
110 static const char *CcsServer_createSalt(SOCKET fd,CCS_AUTH_clients *cl,
111                                         CcsSecMan *security,CcsSecAttr *attr)
112 {
113   ChMessageInt_t s1;
114   ChMessageInt_t s2=ChMessageInt_new(CCS_RAND_next(&cl->rand));
115   SHA1_hash_t s2hash;
116   int clientId;
117   struct {
118     SHA1_hash_t s1hash;
119     ChMessageInt_t clientId;
120     ChMessageInt_t clientSalt;
121   } reply;
122   if (-1==skt_recvN(fd,&s1,sizeof(s1))) return "ERROR> CreateSalt challenge recv";
123   if (-1==skt_sendN(fd,&s2,sizeof(s2))) return "ERROR> CreateSalt challenge send";
124   if (-1==skt_recvN(fd,&s2hash,sizeof(s2hash))) return "ERROR> CreateSalt reply recv";
125   if (CCS_AUTH_differ(security->getKey(security,attr),ChMessageInt(s2),
126                       NULL,&s2hash))
127     return "ERROR> CreateSalt client hash mismatch! (bad password?)";
128   CCS_AUTH_hash(security->getKey(security,attr),ChMessageInt(s1),
129                 NULL,&reply.s1hash);
130   clientId=CCS_AUTH_addClient(cl);
131   reply.clientId=ChMessageInt_new(clientId);
132   reply.clientSalt=ChMessageInt_new(CCS_AUTH_clientSalt(cl,clientId));
133   if (-1==skt_sendN(fd,&reply,sizeof(reply))) return "ERROR> CreateSalt reply send";
134   /*HACK: this isn't an error return, and returning an error code
135    here is wrong; but all we want is to close the socket (not process
136    a CCS request), and printing out this text isn't a bad idea, so... 
137   */
138   return "Created new client";
139 }
140
141 /*******************
142 Grab an ordinary authenticated message off this socket.
143 The format is:
144 -4 byte client ID number (returned by createSalt earlier)
145 -4 byte client challenge (used to by client to authenticate reply)
146 -20 byte authentication hash code
147 -Regular CcsMessageHeader
148 */
149 static const char *CcsServer_SHA1_message(SOCKET fd,CCS_AUTH_clients *cl,
150                                         CcsSecMan *security,CcsSecAttr *attr,
151                                         CcsMessageHeader *hdr)
152 {
153   ChMessageInt_t clientNo_net;
154   int clientNo;
155   unsigned int salt;
156   SHA1_hash_t hash;
157
158   /* An ordinary authenticated message */      
159   if (-1==skt_recvN(fd,&clientNo_net,sizeof(clientNo_net)))
160     return "ERROR> During recv. client number";
161   if (-1==skt_recvN(fd,&attr->replySalt,sizeof(attr->replySalt)))
162     return "ERROR> During recv. reply salt";
163   if (-1==skt_recvN(fd,&hash,sizeof(hash)))
164     return "ERROR> During recv. authentication hash";
165   if (-1==skt_recvN(fd,hdr,sizeof(CcsMessageHeader)))
166     return "ERROR> During recv. message header";
167   clientNo=ChMessageInt(clientNo_net);
168   
169   if (clientNo<0 || clientNo>=CCS_AUTH_numClients(cl))
170     return "ERROR> Bad client number in SHA-1 request!";
171   salt=CCS_AUTH_clientSalt(cl,clientNo);
172   
173   /*Check the client's hash*/
174   if (CCS_AUTH_differ(security->getKey(security,attr),salt,
175                       hdr,&hash))
176     return "ERROR> Authentication hash code MISMATCH-- bad or faked key";
177
178   CCS_AUTH_advanceSalt(cl,clientNo);
179   return NULL; /*It's a good message*/
180 }
181
182 /*********************
183 Grab a message header from this socket.
184  */
185 static const char *CcsServer_readHeader(SOCKET fd,CCS_AUTH_clients *cl,
186                         CcsSecMan *security,
187                         CcsSecAttr *attr,CcsMessageHeader *hdr) 
188 {
189   /*Read the first bytes*/
190   unsigned char len[4];
191   if (-1==skt_recvN(fd,&len[0],sizeof(len)))
192     return "ERROR> During recv. length";
193   
194   /*
195     Decide what kind of message it is by the high byte of the length field.
196   */
197   if (len[0]<0x20) 
198   { /*Unauthenticated message-- do a security check*/
199       attr->auth=0;
200       attr->level=0;
201       attr->replySalt=ChMessageInt_new(0);
202       if (!security->allowRequest(security,attr))
203         return "ERROR> Unauthenticated request denied at security check";
204     /*Request is authorized-- grab the rest of the header*/
205       hdr->len=*(ChMessageInt_t *)len;
206       if (-1==skt_recvN(fd,&hdr->pe,sizeof(hdr->pe))) 
207         return "ERROR> During recv. PE";
208       if (-1==skt_recvN(fd,&hdr->handler[0],sizeof(hdr->handler))) 
209         return "ERROR> During recv. handler name"; 
210       return NULL; /*it's a good message*/
211   }
212   else if (len[0]==0x80)
213   { /*SHA-1 Authenticated request*/
214       if (len[1]!=0x00)
215         return "ERROR> Bad SHA-1 version field!";
216       attr->auth=1;
217       attr->level=len[3];/*Requested security level.*/
218       if (!security->allowRequest(security,attr))
219         return "ERROR> Authenticated request denied at security check";
220       
221       switch(len[2]) {
222       case 0x00: /*Regular message*/
223         return CcsServer_SHA1_message(fd,cl,security,attr,hdr); 
224       case 0x01: /*Request for salt*/
225         return CcsServer_createSalt(fd,cl,security,attr);
226       default: 
227         return "ERROR> Bad SHA-1 request field!";
228       };
229   }
230   else
231     return "ERROR> Unknown authentication protocol";
232 }
233
234 /*******************************************************************
235 Default security manager-- without any files, allow 
236 unauthenticated calls at level 0.  If securityString is
237 non-NULL, get keys for higher levels by reading this 
238 text file.
239 */
240 static int allowRequest_default
241      (struct CcsSecMan *self,CcsSecAttr *attr)
242 {
243   if (attr->auth==0) 
244   { /*Non-authenticated request-- allow only if *no* password for level zero*/
245     return NULL==self->keys[0];
246   } else {
247     /*Authenticated request-- allow only if we *have* a password*/
248     return NULL!=self->keys[attr->level];
249   }
250 }
251
252 static CcsSec_secretKey *getKey_default
253      (struct CcsSecMan *self,CcsSecAttr *attr)
254 {
255   return self->keys[attr->level];
256 }
257
258 static void CcsSecMan_make_otp(const char *str,CcsSec_secretKey *key)
259 {
260   int i;
261   CCS_RAND_state state;
262   CCS_RAND_new(&state);
263   i=0;
264   while (str[i]!=0 && i<sizeof(state)) {
265     state.state[i] ^= str[i];
266     i++;
267   }
268   for (i=0;i<sizeof(key->data)/sizeof(int);i++) {
269     unsigned int cur=CCS_RAND_next(&state);
270     key->data[4*i+0]=(unsigned char)(cur>>24);
271     key->data[4*i+1]=(unsigned char)(cur>>16);
272     key->data[4*i+2]=(unsigned char)(cur>> 8);
273     key->data[4*i+3]=(unsigned char)(cur>> 0);
274   }
275 }
276
277 static void CcsSecMan_printkey(FILE *out,int level,CcsSec_secretKey *k)
278 {
279   int i;
280   fprintf(out,"CCS_OTP_KEY> Level %d key: ",level);
281   for (i=0;i<sizeof(k->data);i++) 
282     fprintf(out,"%02X",k->data[i]);
283   fprintf(out,"\n");
284 }
285
286 static CcsSecMan *CcsSecMan_default(const char *authFile)
287 {
288   int i;
289   FILE *secFile;
290   char line[200];
291   CcsSecMan *ret=(CcsSecMan *)malloc(sizeof(CcsSecMan));
292   ret->allowRequest=allowRequest_default;
293   ret->getKey=getKey_default;
294   for (i=0;i<256;i++) {
295     ret->keys[i]=NULL; /*Null means no password set-- disallow unless level 0*/
296   }
297   if (authFile==NULL) return ret;
298   secFile=fopen(authFile,"r");
299   if (secFile==NULL) {
300     fprintf(stderr,"CCS ERROR> Cannot open CCS authentication file '%s'!\n",
301                   authFile);
302     exit(1);
303   }
304   while (NULL!=fgets(line,200,secFile)) {
305     int level;
306     char key[200]; /*Secret key, in ASCII hex*/
307     int nItems=sscanf(line,"%d%s",&level,key);
308     if (nItems==2 && level>=0 && level<255) {
309         /*Parse out the secret key*/
310         CcsSec_secretKey *k=(CcsSec_secretKey *)malloc(sizeof(CcsSec_secretKey));
311         memset(k->data,0,sizeof(CcsSec_secretKey));
312         if (isxdigit(key[0]) && isxdigit(key[1]))
313           CCS_AUTH_makeSecretKey(key,k);
314         else if (0==strncmp("OTP",key,3)) {
315           FILE *keyDest=stdout;
316           CcsSecMan_make_otp(&key[3],k);
317           CcsSecMan_printkey(keyDest,level,k);
318         }
319         else {
320           fprintf(stderr,"CCS ERROR> Cannot parse key '%s' for level %d from CCS security file '%s'!\n",
321                   key,level,authFile);
322           exit(1);
323         }
324         ret->keys[level]=k;
325     }
326   }
327   fclose(secFile);
328   return ret;
329 }
330
331 /*********************************************************/
332 #define CCSDBG(x) /*printf x*/
333
334 /*CCS Server state is all stored in global variables.
335 Since there's only one server, this is ugly but OK.
336 */
337 static SOCKET ccs_server_fd=SOCKET_ERROR;/*CCS request socket*/
338 static CCS_AUTH_clients ccs_clientlist;
339 static CcsSecMan *security;
340
341 /*Make a new Ccs Server socket, on the given port.
342 Returns the actual port and IP address.
343 */
344 void CcsServer_new(skt_ip_t *ret_ip,int *use_port,const char *authFile)
345 {
346   char ip_str[200];
347   skt_ip_t ip;
348   unsigned int port=0;if (use_port!=NULL) port=*use_port;
349   
350   CCS_AUTH_new(&ccs_clientlist);
351   security=CcsSecMan_default(authFile);
352   skt_init();
353   ip=skt_my_ip();
354   ccs_server_fd=skt_server(&port);
355   printf("ccs: %s\nccs: Server IP = %s, Server port = %u $\n", 
356            CMK_CCS_VERSION, skt_print_ip(ip_str,ip), port);
357   fflush(stdout);
358   if (ret_ip!=NULL) *ret_ip=ip;
359   if (use_port!=NULL) *use_port=port;
360 }
361
362 /*Get the Ccs Server socket.  This socket can
363 be added to the rdfs list for calling select().
364 */
365 SOCKET CcsServer_fd(void) {return ccs_server_fd;}
366
367 /*Connect to the Ccs Server socket, and 
368 receive a ccs request from the network.
369 Returns 1 if a request was successfully received.
370 reqData is allocated with malloc(hdr->len).
371 */
372 static int req_abortFn(int code,const char *msg) {
373         /*Just ignore bad requests-- indicates a client is messed up*/
374         fprintf(stderr,"CCS ERROR> Socket abort during request-- ignoring\n");
375         return -1;
376 }
377
378 static int CcsServer_recvRequestData(SOCKET fd,
379                                      CcsImplHeader *hdr,void **reqData)
380 {
381   CcsMessageHeader req;/*CCS header, from requestor*/
382   int reqBytes, numPes, destPE;
383   const char *err;
384   if (NULL!=(err=CcsServer_readHeader(fd,&ccs_clientlist,security,
385                                       &hdr->attr,&req))) 
386   { /*Not a regular message-- write error message and return error.*/
387     fprintf(stdout,"CCS %s\n",err);
388     return 0;
389   }
390
391   /*Fill out the internal CCS header*/
392   strncpy(hdr->handler,req.handler,CCS_MAXHANDLER);  
393   hdr->pe=req.pe;
394   hdr->len=req.len;
395   hdr->replyFd=ChMessageInt_new(fd);
396
397   /*Is it a multicast?*/
398   numPes = 0;
399   destPE = ChMessageInt(hdr->pe);
400   if (destPE < -1) numPes = -destPE;
401   
402   /*Grab the user data portion of the message*/
403   reqBytes=ChMessageInt(req.len) + numPes*sizeof(ChMessageInt_t);
404   *reqData=(char *)malloc(reqBytes);
405   if (-1==skt_recvN(fd,*reqData,reqBytes)) {
406     fprintf(stdout,"CCS ERROR> Retrieving %d message bytes\n",reqBytes);
407     free(*reqData);
408     return 0;
409   }
410   return 1;
411 }
412
413 int CcsServer_recvRequest(CcsImplHeader *hdr,void **reqData) 
414 {
415   char ip_str[200];
416   skt_ip_t ip;
417   unsigned int port,ret=1;
418   SOCKET fd;
419   skt_abortFn old=skt_set_abort(req_abortFn);
420
421   CCSDBG(("CCS Receiving connection...\n"));
422   fd=skt_accept(ccs_server_fd,&ip,&port);
423
424   CCSDBG(("CCS   Connected to IP=%s, port=%d...\n",skt_print_ip(ip_str,ip),port));
425   hdr->attr.ip=ip;
426   hdr->attr.port=ChMessageInt_new(port);
427
428   if (0==CcsServer_recvRequestData(fd,hdr,reqData))
429   {
430     fprintf(stdout,"During CCS Client IP:port (%s:%d) processing.\n",
431             skt_print_ip(ip_str,ip),
432             port);
433     skt_close(fd);
434     ret=0;
435   }
436
437   CCSDBG(("CCS   Got all %d data bytes for request.\n",reqBytes));
438   skt_set_abort(old);
439
440   return ret;
441 }
442
443 static int reply_abortFn(int code,const char *msg) {
444         /*Just ignore bad replies-- just indicates a client has died*/
445         fprintf(stderr,"CCS ERROR> Socket abort during reply-- ignoring\n");
446         return -1;
447 }
448
449 /*Send a Ccs reply down the given socket.
450 Closes the socket afterwards.
451 A CcsImplHeader len field equal to 0 means do not send any reply.
452 */
453 void CcsServer_sendReply(CcsImplHeader *hdr,int repBytes,const void *repData)
454 {
455   int fd=ChMessageInt(hdr->replyFd);
456   skt_abortFn old;
457   if (ChMessageInt(hdr->len)==0) {
458     CCSDBG(("CCS Closing reply socket without a reply.\n"));
459     skt_close(fd);
460     return;
461   }
462   old=skt_set_abort(reply_abortFn);
463   CCSDBG(("CCS   Sending %d bytes of reply data\n",repBytes));
464   CcsServer_writeReply(fd,security,&hdr->attr,repBytes,(char *)repData);
465   skt_close(fd);
466   CCSDBG(("CCS Reply socket closed.\n"));
467   skt_set_abort(old);
468 }
469
470 /***************************************************************************
471  * Routines to handle standard out/err forwarding from application to remote
472  * program connecting through CCS (used by CharmDebug)
473  ***************************************************************************/
474
475 #define REDIRECT_STDIO  "redirect stdio"
476 #define FETCH_STDIO "fetch stdio"
477 char *stdio_buffer = NULL;
478 int stdio_size = 0;
479 int stdio_alloc = 0;
480 int stdio_waiting = 0;
481 CcsImplHeader stdio_waiting_hdr;
482
483 void write_stdio_duplicate(char* data) {
484   if (stdio_alloc > 0) {
485     int size = strlen(data);
486     
487     if (stdio_waiting) {
488       stdio_waiting = 0;
489       CcsServer_sendReply(&stdio_waiting_hdr,size+1,data);
490     }
491     else {
492       if (size+stdio_size >= stdio_alloc) {
493         char *newbuf;
494         stdio_alloc += (size>4096 ? size : 4096);
495         newbuf = malloc(stdio_alloc);
496         memcpy(newbuf, stdio_buffer, stdio_size);
497         free(stdio_buffer);
498         stdio_buffer = newbuf;
499       }
500       strcpy(&stdio_buffer[stdio_size], data);
501       stdio_size += size;
502     }
503   }
504 }
505
506 int check_stdio_header(CcsImplHeader *hdr) {
507   if (strncmp(REDIRECT_STDIO, hdr->handler, strlen(REDIRECT_STDIO))==0) {
508     /*This is a request to make a duplicate to stdio*/
509     if (stdio_alloc == 0) {
510       stdio_alloc = 4096;
511       stdio_buffer = malloc(stdio_alloc);
512     }
513     CcsServer_sendReply(hdr,0,0);
514   }
515   else if (strncmp(FETCH_STDIO, hdr->handler, strlen(FETCH_STDIO))==0) {
516     /*Reply with the data loaded until now*/
517     if (stdio_size > 0) {
518       hdr->len = ChMessageInt_new(1); /* fake len to prevent socket closed without reply! */
519       CcsServer_sendReply(hdr,stdio_size,stdio_buffer);
520       stdio_size = 0;
521     } else {
522       if (stdio_waiting) {
523         CcsServer_sendReply(&stdio_waiting_hdr,0,0);
524       }
525       stdio_waiting = 1;
526       stdio_waiting_hdr = *hdr;
527       stdio_waiting_hdr.len = ChMessageInt_new(1); /* fake len to prevent socket closed without reply! */
528     }
529   } else {
530     return 0;
531   }
532   return 1;
533 }
534
535 #if ! CMK_CMIPRINTF_IS_A_BUILTIN
536 #if CMK_BIGSIM_CHARM
537 #define MAX_PRINT_BUF_SIZE 1024
538 #else
539 #define MAX_PRINT_BUF_SIZE 8192
540 #endif
541 int print_fw_handler_idx;
542
543 /* Receives messages passed to processor 0 by all other processors as a
544  * consequence of prints in debug mode.
545  */
546 void print_fw_handler(char *msg) {
547   write_stdio_duplicate(msg+CmiReservedHeaderSize);
548 }
549
550 /* Forward prints to node0 to be buffered and delivered through CCS */
551 void print_node0(const char *format, va_list args) {
552   char buffer[MAX_PRINT_BUF_SIZE];
553   int len;
554   if ((len=vsnprintf(buffer, MAX_PRINT_BUF_SIZE, format, args)) >= MAX_PRINT_BUF_SIZE) CmiAbort("CmiPrintf: printing buffer too long\n");
555   if (CmiMyPe() == 0) {
556     /* We are the print server, just concatenate the printed string */
557     write_stdio_duplicate(buffer);
558   } else {
559     /* Need to forward the string to processor 0 */
560     char* msg = CmiAlloc(CmiReservedHeaderSize+len+1);
561     memcpy(msg+CmiReservedHeaderSize, buffer, len+1);
562     CmiSetHandler(msg,print_fw_handler_idx);
563     CmiSyncSendAndFree(0,CmiReservedHeaderSize+len+1,msg);
564   }
565 }
566 #endif
567
568 #endif /*CMK_CCS_AVAILABLE*/
569
570