#include #include #include #include "common.h" #include #include const char *USAGE="client [-v | -V logfile ] [ -k keylog ] [-c context_list] [-s server] [-m middlebox_list | -M middlebox_list_file]\n" "\tcontext list must be comma-separated list of context names, defaults to \"header,body\"\n" "\tserver must be url including port, default 127.0.0.1:" xstr(DEFAULT_SERVER_PORT) "\n" "\tmiddlebox list must semi-colon delimited urls including port," "\tmiddlebox list_file must be a file with middlebox urls on separate lines," "\t\tdefault \"127.0.0.1:" xstr(DEFAULT_MBOX_PORT) "\n" "Input to send to the server is taken from stdin in lines of the form context:command\n"; const char *ARG_TYPES="v:V:k:c:s:m:M"; typedef struct ClientConfigST { FILE *vlog; FILE *fIn; FILE *fOut; SSL *pSSL; int numContexts; const char *contextDescs[50]; int numMiddleboxes; const char *MiddleboxURLs[50]; const char *serverURL; char myKeyFile[1024]; char myPassword[1024]; char caFile[1024]; } ClientConfig; static int client_ParseArgs(int iArgC, char *papsArgV[], ClientConfig *ptCConf); void client_SetDefaults(ClientConfig *ptC) { memset(ptC,0,sizeof(*ptC)); ptC->fIn=stdin; ptC->fOut=stdout; strcpy(ptC->caFile,"ca.pem"); strcpy(ptC->myKeyFile,"client.pem"); strcpy(ptC->myPassword,"password"); ptC->serverURL="127.0.0.1:" xstr(DEFAULT_SERVER_PORT); ptC->numMiddleboxes=1; ptC->MiddleboxURLs[0]="127.0.0.1:" xstr(DEFAULT_MBOX_PORT); } extern char *optarg; //the next two are available but currently ununsed, //commented out to prevent compiler complaining //extern int optind; //extern int optopt; static int client_ParseArgs(int iArgC, char *papsArgV[], ClientConfig *ptCConf) { int iStatus=0; char carg=0; while( iStatus==0 && (carg=getopt(iArgC,papsArgV,ARG_TYPES)) != -1) { switch(carg) { case 'v': case 'V': if(ptCConf->vlog!=NULL) { COMMON_LogErrorAndExit(iStatus=INVALID_COMMAND_LINE_ARGS,stderr,"verbose logging specified multiple times\n%s",USAGE); } if(carg=='v') { ptCConf->vlog=stdout; } else { /*carg=='V'*/ ptCConf->vlog=fopen(optarg,"w"); if(ptCConf->vlog==NULL) { COMMON_LogErrorAndExit(iStatus=FILE_NOT_WRITABLE,stderr,"Unable to open log file %s",optarg); } } break; case 's': /*todo*/ assert(0); break; case 'k': /*todo*/ assert(0); break; case 'c': /*todo*/ assert(0); break; case 'm': /*todo*/ assert(0); break; case 'M': /*todo*/ assert(0); break; default: COMMON_LogErrorAndExit(iStatus=INVALID_COMMAND_LINE_ARGS,stderr,"Unrecognised command line option -%c",carg); break; /*never reached - keeps compiler happy*/ } } return iStatus; } ERROR_STATUS client_CreateContext(SSL_CTX **pptSSLctx,const ClientConfig *ptConf) { ERROR_STATUS iStatus; iStatus=COMMON_InitializeSSLCtx(pptSSLctx, ptConf->myKeyFile, ptConf->myPassword, ptConf->caFile, ID_CLIENT); return iStatus; } ERROR_STATUS client_Connect(SSL_CTX *ptSSLctx, ClientConfig *ptConf) { ERROR_STATUS iStatus=SUCCESS; int i,j,p; BIO *ptSBio; /*step 0 - create SSL structure*/ /*the SSL_CTX already has out keys set up*/ ptConf->pSSL=SSL_new(ptSSLctx); /*todo - check for NULL return*/ assert(ptConf->pSSL!=NULL); /*step 1 - create the contexts*/ iStatus=COMMON_InitMulticontextSet(ptConf->pSSL); assert(iStatus==SUCCESS); if(ptConf->numContexts==0) { iStatus=COMMON_AppendContext(ptConf->pSSL,"default"); } else { /*Iterate through the context descriptions and create each one*/ for(i=0;i!=ptConf->numContexts && iStatus==SUCCESS;i++) { iStatus=COMMON_AppendContext(ptConf->pSSL,ptConf->contextDescs[i]); } } if(iStatus!=SUCCESS) { /*TODO*/ assert(0); } /*step 2 - create the middlebox list*/ iStatus=COMMON_InitProxySet(ptConf->pSSL); assert(iStatus==SUCCESS); /*step 2.0? - do we need to insert client as middlebox 0? - TODO confirm*/ /*step 2.1 - iterate through middlebox list */ for(i=0;i!=ptConf->numMiddleboxes;i++) { iStatus=COMMON_AppendProxy(ptConf->pSSL,ptConf->MiddleboxURLs[i]); } /*step 2.2 - add server url*/ iStatus=COMMON_AppendClientServer(ptConf->pSSL,ptConf->serverURL,1); /*step 3 - set the middlebox access permissions*/ /*for demonstration purposed, we will set by the following rule: * Middleboxes with an even port number get read-only access * Middleboxes with an odd port number get write access * This is for demonstration only - there is no sensible * reason why this rule would ever be real-world implemented! */ for(j=0;j!=ptConf->numContexts;j++) { /*grant client if included*/ /*grant server*/ iStatus=COMMON_SetProxyAccessPermissionByID(ptConf->pSSL,ptConf->pSSL->slices[j]->slice_id, ptConf->numMiddleboxes-1, 1,1); for(i=0;i!=ptConf->numMiddleboxes-1;i++) { /*change to i=1 if client included*/ char *sMBoxUrl=ptConf->pSSL->proxies[i]->address; char *sPort=strchr(sMBoxUrl,':'); assert(sPort); sPort++; int iPort=atoi(sPort); iStatus=COMMON_SetProxyAccessPermissionByID(ptConf->pSSL,ptConf->pSSL->slices[j]->slice_id, i, 1,iPort%2); } } /*step 4? - do we need to create a socket to first middlebox?*/ // TCP Connect char* sAddress = (char*)malloc(strlen(ptConf->pSSL->proxies[0]->address) + 1); // Large enough for string+\0 memcpy(sAddress, ptConf->pSSL->proxies[0]->address, strlen(ptConf->pSSL->proxies[0]->address) + 1); char *sHost = strtok(sAddress, ":"); int iPort = atoi(strtok(NULL, ":")); int iSock; COMMON_TcpConnect(&iSock, sHost, iPort); // Connect TCP socket to SSL socket ptSBio = BIO_new_socket(iSock, BIO_NOCLOSE); SSL_set_bio(ptConf->pSSL, ptSBio, ptSBio); /*step 5 - call the connect method*/ /*SPP connect calls this method but also adds some checks, * in particular it checks that there is at least one context (fine) * but also checks that there is at least one proxy (which seems * unnecassary but the server is included so there must be one) */ if (SSL_connect(ptConf->pSSL) <= 0) { iStatus=NETWORK_CONNECT_FAIL; } return SUCCESS; } int giSignal=0; int giDone=0; static pthread_t gtParent_thread; static pthread_t gtSending_thread; static pthread_t gtReceiving_thread; static void client_sighandler(int sig) { if(giSignal==0) giSignal=sig; giDone=1; /*note: pthread_kill is a signal sending method - not a * "Die now" method. Unfortunate naming */ pthread_kill(gtSending_thread,sig); pthread_kill(gtReceiving_thread,sig); } struct iobuffer { int iUsed; union { unsigned char aucBuffer[1024]; char stext[1024]; }; char z;/*force null termination of string*/ }; static void * client_sendThread(void *pvArg) { int iNewLine=0; int iCurrentSlice=0; ERROR_STATUS iStatus=SUCCESS; int iBytesRead; int iBytesToSend; int iBytesSent; struct iobuffer tInBuff; const ClientConfig *ptCConf=(const ClientConfig *)pvArg; char *sCurrent,*sEnd,sChunk; char sContext[MAX_CONTEXT_LEN]; int iFIn=fileno(ptCConf->fIn); memset(&tInBuff,0,sizeof(tInBuff)); while(!giDone && iStatus==SUCCESS) { /*wait until we have data to read*/ /*we should always have an empty buffer at this point*/ tInBuff.iUsed=0; memset(tInBuff.aucBuffer,0,sizeof(tInBuff.aucBuffer)); iStatus=COMMON_ReadWaitFD(iFIn); /*Read as much data as possible*/ iBytesRead=read(iFIn,tInBuff.aucBuffer,sizeof(tInBuff.aucBuffer)); assert(iBytesRead>0); /*should be as we waited for it*/ iBytesToSend=iBytesRead; iNewLine=(tInBuff.aucBuffer[0]=='\n'); while(iStatus==SUCCESS && iBytesToSend>0) { if(iNewLine) { /*we hit a new line - advance to next context*/ iCurrentSlice=(iCurrentSlice+1)%ptCConf->pSSL->slices_len; ptCConf->pSSL->write_slice=SPP_get_slice_by_id(ptCConf->pSSL,ptCConf->pSSL->slices[iCurrentSlice]->slice_id); tInBuff.iUsed++; iBytesToSend--; iNewLine=(tInBuff.aucBuffer[tInBuff.iUsed]=='\n'); /*should only happen on \n\n*/ /*note - line above may overflow if \n happens to be last byte read * this is guaranteed safe due to the additional null terminator char * and the next iteration of the top loop will reset correctly*/ } else if((sEnd=strchr(tInBuff.stext+tInBuff.iUsed,'\n'))==NULL) { /*entire buffer to output in this context*/ iBytesSent=SSL_write(ptCConf->pSSL,tInBuff.stext+tInBuff.iUsed,iBytesToSend); /*todo - handle error sending*/ assert(iBytesSent>0); tInBuff.iUsed+=iBytesSent; iBytesToSend-=iBytesSent; } else { /*send entire buffer up to and including new line*/ /*advance buffer up to new line only - context change on next iteration * of this loop*/ iBytesSent=SSL_write(ptCConf->pSSL,tInBuff.stext+tInBuff.iUsed,sEnd-(tInBuff.stext+tInBuff.iUsed)+1); /*todo - handle error sending*/ tInBuff.iUsed=sEnd-tInBuff.stext; iBytesToSend-=sEnd-(tInBuff.stext+tInBuff.iUsed); iNewLine=1; assert(iBytesSent==(sEnd-(tInBuff.stext+tInBuff.iUsed))+1); } } } /*signal to the parent so all other threads are woken*/ pthread_kill(gtParent_thread,SIGINT); pthread_exit(NULL); return NULL; } static void * client_receiveThread(void *pvArg) { ERROR_STATUS iStatus=SUCCESS; struct iobuffer tInBuff; int iBytesRead=1; const ClientConfig *ptCConf=(const ClientConfig *)pvArg; BIO *sock_bio=ptCConf->pSSL->rbio; int rfd; SPP_SLICE *pSlice; SPP_CTX *pCtx; BIO_get_fd(sock_bio,&rfd); memset(&tInBuff,0,sizeof(tInBuff)); while(iBytesRead>0 && iStatus==SUCCESS) { /*wait until we have data to read*/ iStatus=COMMON_ReadWaitFD(rfd); iBytesRead = SSL_read(ptCConf->pSSL,tInBuff.aucBuffer,sizeof(tInBuff.aucBuffer)); pSlice = ptCConf->pSSL->read_slice; pCtx = ptCConf->pSSL->spp_read_ctx; ptCConf->pSSL->read_slice = NULL; ptCConf->pSSL->spp_read_ctx = NULL; if(iBytesRead>0) { /*print out what we got and from where*/ fprintf(ptCConf->fOut,"<%s(%d)>:%s",pSlice->purpose,pSlice->slice_id,tInBuff.stext); } else { fprintf(ptCConf->fOut,"zero bytes read - closing"); } } /*signal to the parent so all other threads are woken*/ pthread_kill(gtParent_thread,SIGINT); pthread_exit(NULL); return NULL; } ERROR_STATUS client_RunClient(const ClientConfig *ptConf) { void *pvUnused; ERROR_STATUS iStatus=SUCCESS; /*set an interrupt signal handler to kill the process*/ signal(SIGINT,client_sighandler); signal(SIGTERM,client_sighandler); gtParent_thread=pthread_self(); /*create the send and recioeve threads*/ pthread_create(>Receiving_thread, NULL, client_receiveThread, (void *)ptConf); pthread_create(>Sending_thread, NULL, client_sendThread, (void *)ptConf); /*wait for both to finish*/ pthread_join(gtReceiving_thread,&pvUnused); pthread_join(gtSending_thread,&pvUnused); return SUCCESS; } int main(const int iArgC, char *papsArgV[]) { ClientConfig tConfig; SSL_CTX *ptSSLctx=NULL; int iStatus; client_SetDefaults(&tConfig); iStatus=client_ParseArgs(iArgC,papsArgV, &tConfig); COMMON_CheckLogErrorAndExit(iStatus,stderr,"%s\n",USAGE); iStatus=client_CreateContext(&ptSSLctx,&tConfig); iStatus=client_Connect(ptSSLctx,&tConfig); iStatus=client_RunClient(&tConfig); return iStatus; }