/*****************************************************************************/ #ifdef COMMENTS_WITH_COMMENTS /* wsLIBcl.c An additional component of the wsLIB WebSocket library for WASD persistent WebSocket scripts. This module provides functionality to connect to a server host and port and undertake a WebSocket upgrade handshake. This establishes a TCP/IP socket that WSLIB.C functions recognise and can use to support client WebSocket actions. It is less intended to be a real-world client library than a vehicle for bench-testing the WASD WebSocket and WSLIB.C functionality. See WS_BENCHC.C utility for a usage example. FUNCTIONS --------- int WsLibClConnect ( struct WsLibStruct *wsptr, char *ServerHost, int ServerPort, char *RequestUri, void *AstFunction ) Connects via clear-text TCP/IP to the specified server host and port and performs a WebSocket upgrade handshake. int WsLibClSocketStatus (struct WsLibStruct *wsptr) Returns the the current (most recent) TCP/IP socket status. This is essentially the VMS status of the HTTP WebSocket upgrade request. COPYRIGHT --------- Copyright (C) 2011-2021 Mark G.Daniel Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. VERSION HISTORY --------------- 06-DEC-2014 MGD WsLibcl__Init() 06-FEB-2011 MGD WsLibCl__ConnAst() port 8080 kludge for proxy development 13-JUN-2011 MGD initial */ #endif /* COMMENTS_WITH_COMMENTS */ /*****************************************************************************/ #include #include #include #include #include #include #include #include #include #include /* IP-related header files */ #include #include #include #include #include "../httpd/base64.h" #include "../httpd/sha1.h" #include "wslib.h" #ifndef EFN$C_ENF # define EFN$C_ENF 128 /* Event No Flag (no stored state) */ #endif #define VMSok(x) ((x) & STS$M_SUCCESS) #define VMSnok(x) !(((x) & STS$M_SUCCESS)) #define FI_LI "WSLIBCL", __LINE__ #if 1 #define WATCH_WSLIB if(wsptr->WatchScript)WsLibWatchScript #else #define WATCH_WSLIB if(0)WsLibWatchScript #endif /* can be set non-zero by external module, see WsLibClBreakNow() */ int WsLibClBreakEvery = 0, WsLibClBreakCount; static $DESCRIPTOR (InetDeviceDsc, "UCX$DEVICE"); static int OptionEnabled = 1, EfnWait, EfnNoWait; static struct { unsigned short Length; unsigned short Parameter; char *Address; } ReuseAddr = { sizeof(OptionEnabled), TCPIP$C_REUSEADDR, (void*)&OptionEnabled }, NoDelaysAtAll [] = { { sizeof(OptionEnabled), TCPIP$C_TCP_NODELAY, (void*)&OptionEnabled }, { sizeof(OptionEnabled), TCPIP$C_TCP_NODELACK, (void*)&OptionEnabled }, }, NoDelAck = { sizeof(OptionEnabled), TCPIP$C_TCP_NODELACK, (void*)&OptionEnabled }, NoDelay = { sizeof(OptionEnabled), TCPIP$C_TCP_NODELAY, (void*)&OptionEnabled }, ReuseAddressSocketOption = { sizeof(ReuseAddr), TCPIP$C_SOCKOPT, (void*)&ReuseAddr }, NoDelAckTcpOption = { sizeof(NoDelAck), TCPIP$C_TCPOPT, (void*)&NoDelAck }, NoDelayTcpOption = { sizeof(NoDelay), TCPIP$C_TCPOPT, (void*)&NoDelay }, NoDelaysAtAllTcpOption = { sizeof(NoDelaysAtAll), TCPIP$C_TCPOPT, (void*)&NoDelaysAtAll }; static struct { unsigned short Protocol; unsigned char Type; unsigned char Family; } TcpSocket = { TCPIP$C_TCP, INET_PROTYP$C_STREAM, TCPIP$C_AF_INET }; /*** #define __VAX #undef __ALPHA ***/ /*****************************************************************************/ /* Make a TCP/IP connection to the specified HTTP server name and port. When network connected send an HTTP request to upgrade the connection to WebSocket. If an AST function is supplied then this function completes asynchronously. */ int WsLibClConnect ( struct WsLibStruct *wsptr, char *ServerHost, int ServerPort, char *RequestUri, void *AstFunction ) { int status; char *cptr, *sptr, *zptr; struct hostent *HostEntryPtr; /*********/ /* begin */ /*********/ if (getenv("WATCH_SCRIPT")) wsptr->WatchScript = 1; WsLibCl__Init (); if (!ServerPort) ServerPort = 80; HostEntryPtr = gethostbyname (ServerHost); if (!HostEntryPtr) return (vaxc$errno); /* allocate required storage */ wsptr->ClientAcceptSize = 64; wsptr->ClientAcceptPtr = calloc (1, wsptr->ClientAcceptSize); if (!wsptr->ClientAcceptPtr) return (vaxc$errno); wsptr->ClientHeaderSize = 512; wsptr->ClientHeaderPtr = calloc (1, wsptr->ClientHeaderSize); if (!wsptr->ClientHeaderPtr) return (vaxc$errno); wsptr->ClientKeySize = 32; wsptr->ClientKeyPtr = calloc (1, wsptr->ClientKeySize); if (!wsptr->ClientKeyPtr) return (vaxc$errno); wsptr->ClientServerSize = 128; wsptr->ClientServerPtr = calloc (1, wsptr->ClientServerSize); if (!wsptr->ClientServerPtr) return (vaxc$errno); wsptr->ClientUriSize = 256; wsptr->ClientUriPtr = calloc (1, wsptr->ClientUriSize); if (!wsptr->ClientUriPtr) return (vaxc$errno); wsptr->SocketName.sin_family = HostEntryPtr->h_addrtype; wsptr->SocketName.sin_port = htons (ServerPort); wsptr->SocketName.sin_addr = *((struct in_addr *)HostEntryPtr->h_addr); wsptr->SocketNameItem[0] = sizeof(wsptr->SocketName); wsptr->SocketNameItem[1] = (int)&wsptr->SocketName; if (strchr (HostEntryPtr->h_name, '.')) cptr = HostEntryPtr->h_name; else cptr = ServerHost; zptr = (sptr = wsptr->ClientServerPtr) + wsptr->ClientServerSize-1; while (*cptr && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; wsptr->ClientServerPort = ServerPort; zptr = (sptr = wsptr->ClientUriPtr) + wsptr->ClientUriSize-1; for (cptr = RequestUri; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; /* assign a channel to the internet template device */ status = sys$assign (&InetDeviceDsc, &wsptr->SocketChannel, 0, 0); if (VMSnok (status)) return (status); /* make the channel a TCP, connection-oriented socket */ status = sys$qiow (EfnWait, wsptr->SocketChannel, IO$_SETMODE, &wsptr->SocketIOsb, 0, 0, &TcpSocket, 0, 0, 0, &ReuseAddressSocketOption, 0); if (VMSok (status)) status = wsptr->SocketIOsb.iosb$w_status; if (VMSnok (status)) { sys$dassgn (wsptr->SocketChannel); wsptr->SocketChannel = 0; return (status); } /* turn off Nagle algorithm and acknowlegement delay (the default) */ status = sys$qiow (EfnWait, wsptr->SocketChannel, IO$_SETMODE, &wsptr->SocketIOsb, 0, 0, 0, 0, 0, 0, &NoDelaysAtAllTcpOption, 0); if (VMSok (status)) status = wsptr->SocketIOsb.iosb$w_status; if (VMSnok (status)) { sys$dassgn (wsptr->SocketChannel); wsptr->SocketChannel = 0; return (status); } if (wsptr->ConnectAstFunction = AstFunction) status = sys$qio (EfnNoWait, wsptr->SocketChannel, IO$_ACCESS, &wsptr->SocketIOsb, &WsLibCl__ConnAst, wsptr, 0, 0, &wsptr->SocketNameItem, 0, 0, 0); else { status = sys$qiow (EfnWait, wsptr->SocketChannel, IO$_ACCESS, &wsptr->SocketIOsb, &WsLibCl__ConnAst, wsptr, 0, 0, &wsptr->SocketNameItem, 0, 0, 0); if (VMSok (status)) status = wsptr->SocketIOsb.iosb$w_status; } return (status); } /*****************************************************************************/ /* Connection to server has completed (either successfully or unsuccessfully). */ static void WsLibCl__ConnAst (struct WsLibStruct *wsptr) { static $DESCRIPTOR (HeaderFaoDsc, "GET !AZ HTTP/1.1\r\n\ Host: !AZ!AZ\r\n\ Upgrade: websocket\r\n\ Connection: Upgrade\r\n\ Sec-WebSocket-Key: !AZ\r\n\ Sec-WebSocket-Version: !UL\r\n\ \r\n\0"); static $DESCRIPTOR (HeaderDsc, ""); static unsigned long RandomNumber, RandomFiller; int cnt, status, Base64Len; unsigned short HeaderLength; unsigned long Key16 [4]; char KeyGuid [96], HostPort [16]; unsigned char KeyGuidHash [20]; SHA1Context Sha1Ctx; /*********/ /* begin */ /*********/ status = wsptr->SocketIOsb.iosb$w_status; if (VMSnok (wsptr->SocketIOsb.iosb$w_status)) { /*****************/ /* connect error */ /*****************/ sys$dassgn (wsptr->SocketChannel); wsptr->SocketChannel = 0; if (wsptr->ConnectAstFunction) { sys$dclast (wsptr->ConnectAstFunction, wsptr, 0, 0); wsptr->ConnectAstFunction = NULL; } return; } /**********************/ /* build HTTP upgrade */ /**********************/ /* pseudo-random 16 bytes */ if (!(RandomNumber & 0xff)) sys$gettim (&RandomNumber); for (cnt = 0; cnt < 4; cnt++) Key16[cnt] = RandomNumber = RandomNumber * 69069 + 1; /* this will be the Sec-WebSocket-Key: field */ Base64Len = wsptr->ClientKeySize; base64_encode ((unsigned char*)wsptr->ClientKeyPtr, &Base64Len, (unsigned char*)Key16, 16); /* also generate the expected server Sec-WebSocket-Accept: value */ strcpy (KeyGuid, wsptr->ClientKeyPtr); strcat (KeyGuid, WSLIB_GUID); /* which will be used to ... */ SHA1Reset (&Sha1Ctx); SHA1Input (&Sha1Ctx, (unsigned char*)KeyGuid, strlen(KeyGuid)); if (!SHA1Result (&Sha1Ctx)) WsLibExit (NULL, FI_LI, SS$_BUGCHECK); /* copy into the hash buffer (converting from big to little endian) */ SHA1LitEnd (&Sha1Ctx, KeyGuidHash); /* ... compare to the server response Sec-WebSocket-Accept: */ Base64Len = wsptr->ClientAcceptSize; base64_encode ((unsigned char*)wsptr->ClientAcceptPtr, &Base64Len, (unsigned char*)KeyGuidHash, sizeof(KeyGuidHash)); if (wsptr->ClientServerPort == 80 || wsptr->ClientServerPort == 8080) HostPort[0] = '\0'; else sprintf (HostPort, ":%d", wsptr->ClientServerPort); HeaderDsc.dsc$a_pointer = wsptr->ClientHeaderPtr; HeaderDsc.dsc$w_length = wsptr->ClientHeaderSize; status = sys$fao (&HeaderFaoDsc, &HeaderLength, &HeaderDsc, wsptr->ClientUriPtr, wsptr->ClientServerPtr, HostPort, wsptr->ClientKeyPtr, wsptr->WebSocketVersion); if (VMSnok (status)) WsLibExit (NULL, FI_LI, status); /* not including the null terminator */ HeaderLength--; /****************/ /* send request */ /****************/ WATCH_WSLIB (wsptr, FI_LI, "!#AZ", HeaderLength, wsptr->ClientHeaderPtr); if (wsptr->ConnectAstFunction) status = sys$qio (EfnNoWait, wsptr->SocketChannel, IO$_WRITEVBLK, &wsptr->SocketIOsb, WsLibCl__ConnRequAst, wsptr, wsptr->ClientHeaderPtr, HeaderLength, 0, 0, 0, 0); else status = sys$qiow (EfnWait, wsptr->SocketChannel, IO$_WRITEVBLK, &wsptr->SocketIOsb, WsLibCl__ConnRequAst, wsptr, wsptr->ClientHeaderPtr, HeaderLength, 0, 0, 0, 0); WsLibClBreakNow (wsptr); } /*****************************************************************************/ /* Request header write has completed (either successfully or unsuccessfully). */ static void WsLibCl__ConnRequAst (struct WsLibStruct *wsptr) { int status; /*********/ /* begin */ /*********/ if (VMSnok (wsptr->SocketIOsb.iosb$w_status)) { /* write error */ sys$dassgn (wsptr->SocketChannel); wsptr->SocketChannel = 0; if (wsptr->ConnectAstFunction) { sys$dclast (wsptr->ConnectAstFunction, wsptr, 0, 0); wsptr->ConnectAstFunction = NULL; } return; } /* peek at response header */ if (wsptr->ConnectAstFunction) status = sys$qio (EfnNoWait, wsptr->SocketChannel, IO$_READLBLK, &wsptr->SocketIOsb, WsLibCl__ConnResp1Ast, wsptr, wsptr->ClientHeaderPtr, wsptr->ClientHeaderSize-1, 0, TCPIP$C_MSG_PEEK, 0, 0); else status = sys$qiow (EfnWait, wsptr->SocketChannel, IO$_READLBLK, &wsptr->SocketIOsb, WsLibCl__ConnResp1Ast, wsptr, wsptr->ClientHeaderPtr, wsptr->ClientHeaderSize-1, 0, TCPIP$C_MSG_PEEK, 0, 0); } /*****************************************************************************/ /* Assume the response header is complete in the single peek! Calculate the length of the response header and then read just that many bytes. This appears necessary because at least the Autobahn test suite sends the first frame at the same time as the response header. Orchestrating reads with $QIO means this peek+read is the most practical way to negotiate past the response header. */ void WsLibCl__ConnResp1Ast (struct WsLibStruct *wsptr) { int status; char *cptr; /*********/ /* begin */ /*********/ WATCH_WSLIB (wsptr, FI_LI, "PEEK %X!8XL", wsptr->SocketIOsb.iosb$w_status); if (VMSnok (wsptr->SocketIOsb.iosb$w_status)) { /**************/ /* read error */ /**************/ sys$dassgn (wsptr->SocketChannel); wsptr->SocketChannel = 0; if (wsptr->ConnectAstFunction) { sys$dclast (wsptr->ConnectAstFunction, wsptr, 0, 0); wsptr->ConnectAstFunction = NULL; } return; } /* scan down to the header-terminating empty line */ wsptr->ClientHeaderPtr[wsptr->SocketIOsb.iosb$w_bcnt] = '\0'; cptr = wsptr->ClientHeaderPtr; while (*cptr) { while (*cptr && *cptr != '\r' && *cptr != '\n') cptr++; if (*cptr == '\r') cptr++; if (*cptr == '\n') cptr++; if (*cptr != '\r') continue; cptr++; if (*cptr != '\n') continue; cptr++; break; } /* read response header */ if (wsptr->ConnectAstFunction) status = sys$qio (EfnNoWait, wsptr->SocketChannel, IO$_READLBLK, &wsptr->SocketIOsb, WsLibCl__ConnResp2Ast, wsptr, wsptr->ClientHeaderPtr, cptr - wsptr->ClientHeaderPtr, 0, 0, 0, 0); else status = sys$qiow (EfnWait, wsptr->SocketChannel, IO$_READLBLK, &wsptr->SocketIOsb, WsLibCl__ConnResp2Ast, wsptr, wsptr->ClientHeaderPtr, cptr - wsptr->ClientHeaderPtr, 0, 0, 0, 0); } /*****************************************************************************/ /* Assume the response header is complete in the single read! */ void WsLibCl__ConnResp2Ast (struct WsLibStruct *wsptr) { int ConUpgrade, EndOfHeader, Http11, HttpStatus, SecAccept, UpgradeWebSocket; char *cptr, *lptr, *sptr, *zptr, *HttpStatusPtr; /*********/ /* begin */ /*********/ WATCH_WSLIB (wsptr, FI_LI, "READ %X!8XL", wsptr->SocketIOsb.iosb$w_status); if (VMSnok (wsptr->SocketIOsb.iosb$w_status)) { /**************/ /* read error */ /**************/ sys$dassgn (wsptr->SocketChannel); wsptr->SocketChannel = 0; if (wsptr->ConnectAstFunction) { sys$dclast (wsptr->ConnectAstFunction, wsptr, 0, 0); wsptr->ConnectAstFunction = NULL; } return; } ConUpgrade = EndOfHeader = Http11 = SecAccept = UpgradeWebSocket = 0; wsptr->ClientHeaderPtr[wsptr->SocketIOsb.iosb$w_bcnt] = '\0'; cptr = wsptr->ClientHeaderPtr; WATCH_WSLIB (wsptr, FI_LI, "!AZ", cptr); /* response header line */ if (!memcmp (cptr, "HTTP/1.1", 8)) Http11 = 1; while (*cptr && *cptr != ' ' && *cptr != '\r' && *cptr != '\n') cptr++; while (*cptr == ' ' && *cptr != '\r' && *cptr != '\n') cptr++; HttpStatus = atoi(cptr); HttpStatusPtr = cptr; while (*cptr && *cptr != '\r' && *cptr != '\n') cptr++; if (*cptr) *cptr++ = '\0'; while (*cptr) { lptr = cptr; if (toupper(*cptr) == 'C' && !strncmp (cptr, "Connection:", 11)) { cptr += 11; while (isspace(*cptr) && *cptr != '\r' && *cptr != '\n') cptr++; wsptr->ServerConnectionPtr = cptr; while (*cptr && *cptr != '\r' && *cptr != '\n') *cptr++; if (*cptr) *cptr++ = '\0'; if (strstr (wsptr->ServerConnectionPtr, "Upgrade") || strstr (wsptr->ServerConnectionPtr, "upgrade")) ConUpgrade = 1; } else if (toupper(*cptr) == 'S' && !strncmp (cptr, "Sec-WebSocket-Accept:", 21)) { cptr += 21; while (isspace(*cptr) && *cptr != '\r' && *cptr != '\n') cptr++; wsptr->ServerAcceptPtr = cptr; while (*cptr && *cptr != '\r' && *cptr != '\n') *cptr++; if (*cptr) *cptr++ = '\0'; if (!strcmp (wsptr->ServerAcceptPtr, wsptr->ClientAcceptPtr)) SecAccept = 1; } else if (toupper(*cptr) == 'S' && !strncmp (cptr, "Server:", 7)) { cptr += 7; while (isspace(*cptr) && *cptr != '\r' && *cptr != '\n') cptr++; wsptr->ServerSoftwarePtr = cptr; while (*cptr && *cptr != '\r' && *cptr != '\n') *cptr++; if (*cptr) *cptr++ = '\0'; } else if (toupper(*cptr) == 'U' && !strncmp (cptr, "Upgrade:", 8)) { cptr += 8; while (isspace(*cptr) && *cptr != '\r' && *cptr != '\n') cptr++; wsptr->ServerUpgradePtr = cptr; while (*cptr && *cptr != '\r' && *cptr != '\n') *cptr++; if (*cptr) *cptr++ = '\0'; if (strstr (wsptr->ServerUpgradePtr, "websocket")) UpgradeWebSocket = 1; } while (*cptr && *cptr != '\r' && *cptr != '\n') cptr++; if (*cptr == '\r') cptr++; if (*cptr == '\n') cptr++; if (!*cptr) break; if (*(USHORTPTR)cptr == '\r\n' || *cptr == '\n' ) { EndOfHeader = 1; break; } } if (!Http11 || HttpStatus != 101 || !EndOfHeader) wsptr->SocketIOsb.iosb$w_status = SS$_PROTOCOL; else if (!ConUpgrade || !SecAccept || !UpgradeWebSocket) wsptr->SocketIOsb.iosb$w_status = SS$_PROTOCOL; else wsptr->SocketIOsb.iosb$w_status = SS$_NORMAL; if (VMSok (wsptr->SocketIOsb.iosb$w_status)) { /* once connected channels become synonymous */ wsptr->InputChannel = wsptr->OutputChannel = wsptr->SocketChannel; } else { sys$dassgn (wsptr->SocketChannel); wsptr->SocketChannel = 0; } if (wsptr->ConnectAstFunction) { sys$dclast (wsptr->ConnectAstFunction, wsptr, 0, 0); wsptr->ConnectAstFunction = NULL; } WsLibClBreakNow (wsptr); } /*****************************************************************************/ /* Return the socket status value (essentially connect status). */ int WsLibClSocketStatus (struct WsLibStruct *wsptr) { /*********/ /* begin */ /*********/ return (wsptr->SocketIOsb.iosb$w_status); } /*****************************************************************************/ /* Set the maxiumum record szie for the WebSocket. Return the previous value. */ int WsLibClSetSocketMrs ( struct WsLibStruct *wsptr, int MaxRecSize ) { int PrevMaxRecSize; /*********/ /* begin */ /*********/ PrevMaxRecSize = wsptr->OutputMrs; if ((wsptr->InputMrs = wsptr->OutputMrs = MaxRecSize) > 65535) wsptr->InputMrs = wsptr->OutputMrs = 65535; return (PrevMaxRecSize); } /****************************************************************************/ /* At the specified proportion randomly sever the connection abruptly and return true, else return false if connection not broken. This function really only exists to support the exercisor or WS_BENCHC.C test-bench utility. */ int WsLibClBreakNow (struct WsLibStruct *wsptr) { static int RandomNumber, RandomFiller; /*********/ /* begin */ /*********/ if (!WsLibClBreakEvery) return (FALSE); if (!RandomNumber) sys$gettim (&RandomNumber); RandomNumber = RandomNumber * 69069 + 1; if (RandomNumber % WsLibClBreakEvery) return (FALSE); Shut (wsptr); WsLibClBreakCount++; return (TRUE); } /*****************************************************************************/ /* Initialise the client library. */ void WsLibCl__Init () { static char GetSyiVer [8]; static struct { unsigned short buf_len; unsigned short item; void *buf_addr; void *ret_len; } SyiItem [] = { { sizeof(GetSyiVer)-1, SYI$_VERSION, &GetSyiVer, 0 }, { 0,0,0,0 } }; int status, VersionInteger; /*********/ /* begin */ /*********/ /* just the once! */ if (EfnWait) return; status = sys$getsyiw (0, 0, 0, &SyiItem, 0, 0, 0); if (VMSnok (status)) WsLibExit (NULL, FI_LI, status); VersionInteger = ((GetSyiVer[1]-48) * 100) + ((GetSyiVer[3]-48) * 10); if (GetSyiVer[4] == '-') VersionInteger += GetSyiVer[5]-48; if (VersionInteger >= 700) EfnWait = EfnNoWait = EFN$C_ENF; else { if (VMSnok (status = lib$get_ef (&EfnWait))) WsLibExit (NULL, FI_LI, status);; if (VMSnok (status = lib$get_ef (&EfnNoWait))) WsLibExit (NULL, FI_LI, status);; } } /*****************************************************************************/