/*****************************************************************************/ #ifdef COMMENTS_WITH_COMMENTS /* raw_chat.c (What must be a "classic") demonstrator for CGIplus RawSocket scripts. It's pretty simple-minded, and at the command-line allows lots of confusion when another party's chat appears during local line editing. However the purpose is not to create a chat environment, only to demonstrate (and exercise) the RawSocket environment. Also see rawLIB.c source. Suppress host name lookup by defining the logical name WS_CHAT_NO_LOOKUP. Experiment with the RawLibOnNextRequest() approach to request synchronisation by defining the logical name WS_CHAT_ON_NEXT_REQUEST. Example configuration: # WASD_CONFIG_SERVICE [[http:*:1234]] [ServiceRawSocket] enabled # WASD_CONFIG_MAP [[*:1234]] map * /cgiplus-bin/raw_chat Then to test: telnet server-host-name 1234 As the telnet client is the obvious end-user application RAW_CHAT.C implements a constrained set of telnet commands and associated options. COPYRIGHT --------- Copyright (C) 2016-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 --------------- 10-SEP-2016 MGD v1.0.0, initial development */ #endif /* COMMENTS_WITH_COMMENTS */ /*****************************************************************************/ #define SOFTWAREVN "1.0.0" #define SOFTWARENM "WS_CHAT" #ifdef __ALPHA # define SOFTWAREID SOFTWARENM " AXP-" SOFTWAREVN #endif #ifdef __ia64 # define SOFTWAREID SOFTWARENM " IA64-" SOFTWAREVN #endif #ifdef __VAX # define SOFTWAREID SOFTWARENM " VAX-" SOFTWAREVN #endif #ifdef __x86_64 # define SOFTWAREID SOFTWARENM " X86-" SOFTWAREVN #endif #include #include #include #include #include #include #include #include #include #include #include #include "rawlib.h" #define VMSok(x) ((x) & STS$M_SUCCESS) #define VMSnok(x) !(((x) & STS$M_SUCCESS)) #define Debug 0 #define FI_LI "WS_CHAT", __LINE__ #define EXIT_FI_LI(status) { printf ("[%s:%d]", FI_LI); exit(status); } #define TELNET_SE 240 /* F0 */ #define TELNET_NOP 241 /* F1 */ #define TELNET_DM 242 /* F2 */ #define TELNET_BRK 243 /* F3 */ #define TELNET_IP 244 /* F4 */ #define TELNET_AO 245 /* F5 */ #define TELNET_AYT 246 /* F6 */ #define TELNET_EC 247 /* F7 */ #define TELNET_EL 248 /* F8 */ #define TELNET_GA 249 /* F9 */ #define TELNET_SB 250 /* FA */ #define TELNET_WILL 251 /* FB */ #define TELNET_WONT 252 /* FC */ #define TELNET_DO 253 /* FD */ #define TELNET_DONT 254 /* FE */ #define TELNET_IAC 255 /* FF */ #define DEFAULT_UNUSED_TIMEOUT 300 /* seconds */ int ConnectedCount, UnusedTimeout = DEFAULT_UNUSED_TIMEOUT, UsageCount; unsigned int CurrentTime; unsigned long CurrentBinTime [2]; char ServerName [128]; struct ChatClient { int UpdateCount, YouIdent; unsigned int UnusedTime; char ChatBuffer [4096], ConnectTime [32], Handle [64], InputBuffer [1024], LineBuffer [1024], RemoteHost [128]; struct RawLibStruct *RawLibPtr; }; /* function prototypes */ void AddClient (); void EchoChat (struct ChatClient*); void NextRequest (); void RemoteHostName (struct ChatClient*); void RemoveClient (struct RawLibStruct *rawptr); void UpdateClient (struct RawLibStruct*); uchar* TelnetCommand (struct RawLibStruct*, uchar*, int); void TelnetLineMode (struct RawLibStruct*); /*****************************************************************************/ /* AST delivery is disabled during client acceptance and the add-client function is deferred using an AST to help minimise the client setup window with a potentially busy RawSocket application. */ main () { /*********/ /* begin */ /*********/ /* don't want the C-RTL fiddling with the carriage control */ stdout = freopen ("SYS$OUTPUT", "w", stdout, "ctx=bin"); /* no clients is two minutes in seconds */ RawLibSetLifeSecs (2*60); if (!RawLibIsCgiPlus()) { fprintf (stdout, "Status: 500\r\n\r\n\ RawSocket needs to be CGIplus!\n"); } else if (getenv("WS_CHAT_ON_NEXT_REQUEST") != NULL) { RawLibOnNextRequest (NextRequest); /* asynchronous from here-on-in */ for (;;) sys$hiber(); } else { for (;;) { RawLibCgiVar (""); sys$setast (0); UsageCount++; AddClient (0); RawLibCgiPlusEof (); sys$setast (1); } } exit (SS$_NORMAL); } /*****************************************************************************/ /* Asynchronous notification the next request is available. This function is called at AST-delivery level and so no need to disable ASTs. Add the client and then notify the server we're ready for another. */ void NextRequest () { /*********/ /* begin */ /*********/ UsageCount++; AddClient (1); RawLibCgiPlusEof (); } /*****************************************************************************/ /* Allocate a client structure and add it to the head of the list. */ void AddClient (int OnNext) { int cnt, len, status; char *sptr, *zptr; char ConnectTime [32], Greeting [2048], YouString [32]; $DESCRIPTOR (TimeFaoDsc, "!20%D\0"); $DESCRIPTOR (TimeDsc, ""); struct RawLibStruct *rawptr; struct ChatClient *clptr, *cl2ptr; /*********/ /* begin */ /*********/ if (!ServerName[0]) strcpy (ServerName, RawLibCgiVar ("SERVER_NAME")); clptr = calloc (1, sizeof(struct ChatClient)); if (!clptr) EXIT_FI_LI (vaxc$errno); /* won't have this until we have a request */ RemoteHostName (clptr); TimeDsc.dsc$a_pointer = clptr->ConnectTime; TimeDsc.dsc$w_length = sizeof(clptr->ConnectTime); sys$fao (&TimeFaoDsc, 0, &TimeDsc, 0); clptr->YouIdent = UsageCount; clptr->UnusedTime = CurrentTime + UnusedTimeout; /* create a RawSocket library structure for the client */ if (!(clptr->RawLibPtr = rawptr = RawLibCreate (clptr, RemoveClient))) { /* failed, commonly on some RawSocket protocol issue */ free (clptr); return; } /* no user interaction is two minutes in seconds */ RawLibSetIdleSecs (clptr->RawLibPtr, 2*60); /* open the IPC to the RawSocket (mailboxes) */ status = RawLibOpen (clptr->RawLibPtr); if (VMSnok(status)) EXIT_FI_LI(status); TelnetLineMode (rawptr); /* if asynchronous next-request convey this to the JavaScript client */ if (OnNext) RawLibWrite (clptr->RawLibPtr, "OnNext", 6, RAWLIB_ASYNCH); /* greet and inform of (any) other participants */ for (rawptr = NULL, cnt = 0; RawLibNext(&rawptr); cnt++); zptr = (sptr = Greeting) + sizeof(Greeting)-256; sptr += sprintf (sptr, "\r\n<-| WASD@%s\r\n\ <-| %s; %d other participant%s%s", ServerName, SOFTWAREID, cnt-1, cnt-1 == 1 ? "" : "s", cnt-1 ? "; " : "."); for (rawptr = NULL; RawLibNext(&rawptr);) if ((cl2ptr = RawLibGetUserData(rawptr)) != clptr) sptr += sprintf (sptr, "%s%s@%s", cnt++ > 3 ? ", " : "", cl2ptr->Handle, cl2ptr->RemoteHost); sptr += sprintf (sptr, "\r\n<-| Please enter your nick-name...\r\n->| "); if (sptr > zptr) EXIT_FI_LI(SS$_BUGCHECK); RawLibWrite (clptr->RawLibPtr, Greeting, sptr-Greeting, RAWLIB_ASYNCH); /* queue an asynchronous read from the client */ RawLibRead (clptr->RawLibPtr, clptr->InputBuffer, sizeof(clptr->InputBuffer), UpdateClient); ConnectedCount++; } /*****************************************************************************/ /* Remove the client structure from the list and free the memory. */ void RemoveClient (struct RawLibStruct *rawptr) { struct ChatClient *clptr; /*********/ /* begin */ /*********/ clptr = RawLibGetUserData (rawptr); clptr->RawLibPtr = NULL; /* advise other clients of the disconnection */ EchoChat (clptr); free (clptr); if (ConnectedCount) ConnectedCount--; } /*****************************************************************************/ /* Echo the chat to all connected clients. */ void UpdateClient (struct RawLibStruct *rawptr) { int cnt; uchar *cptr, *czptr, *sptr, *zptr; struct ChatClient *clptr; /*********/ /* begin */ /*********/ if (VMSnok (RawLibReadStatus(rawptr))) { /* WEBSOCKET_INPUT read error (can be EOF) */ RawLibClose (rawptr); return; } clptr = RawLibGetUserData(rawptr); cnt = RawLibReadCount(rawptr); czptr = (cptr = (uchar*)clptr->InputBuffer) + cnt; zptr = (sptr = (uchar*)clptr->LineBuffer) + sizeof(clptr->LineBuffer); while (cptr < czptr && sptr < zptr) { if (*cptr == TELNET_IAC) { cptr = TelnetCommand (rawptr, cptr, czptr-cptr); if (*cptr == TELNET_IAC) *sptr++ = *cptr++; } else *sptr++ = *cptr++; } *sptr = '\0'; if (clptr->LineBuffer[0]) { if (!clptr->Handle[0]) { /* first message should always be the handle */ zptr = (sptr = (uchar*)clptr->Handle) + sizeof(clptr->Handle)-1; for (cptr = (uchar*)clptr->LineBuffer; *cptr && *cptr != '\r' && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; clptr->LineBuffer[0] = '\0'; } EchoChat (clptr); } /* queue the next asynchronous read from the client */ RawLibRead (clptr->RawLibPtr, clptr->InputBuffer, sizeof(clptr->InputBuffer), UpdateClient); } /*****************************************************************************/ /* Build a RawSocket buffer with the details of the chat. The buffer contains four newline-delimited data that are split() by the JavaScript client and displayed. Write this buffer to all connected clients simultaneously updating all with the new chat. */ void EchoChat (struct ChatClient *clptr) { int len; struct RawLibStruct *rawptr; /*********/ /* begin */ /*********/ if (clptr->RawLibPtr) { if (clptr->LineBuffer[0]) len = sprintf (clptr->ChatBuffer, "\r\n<-| %u {%s@%s} %s->| ", clptr->YouIdent, clptr->Handle, clptr->RemoteHost, clptr->LineBuffer); else len = sprintf (clptr->ChatBuffer, "\r\n<-| %u {%s@%s} [joining]\r\n->| ", clptr->YouIdent, clptr->Handle, clptr->RemoteHost); } else len = sprintf (clptr->ChatBuffer, "\r\n<-| %u {%s@%s} [disconnected]\r\n->| ", clptr->YouIdent, clptr->Handle, clptr->RemoteHost); for (rawptr = NULL; RawLibNext(&rawptr);) RawLibWrite (rawptr, clptr->ChatBuffer, len, RAWLIB_ASYNCH); } /*****************************************************************************/ /* Get the remote host name/address (blocking, IPv4 only). */ void RemoteHostName (struct ChatClient *clptr) { int IpAddr; struct hostent *HostEntryPtr; /*********/ /* begin */ /*********/ /* if the server has host name resolution enabled */ strcpy (clptr->RemoteHost, RawLibCgiVar("REMOTE_HOST")); /* otherwise resolve it ourselves */ if (!strcmp (clptr->RemoteHost, RawLibCgiVar("REMOTE_ADDR"))) if (!getenv ("WS_CHAT_NO_LOOKUP")) if ((IpAddr = inet_addr (clptr->RemoteHost)) != -1) if (HostEntryPtr = gethostbyaddr (&IpAddr, 4, AF_INET)) strcpy (clptr->RemoteHost, HostEntryPtr->h_name); } /*****************************************************************************/ /* Simple-minded example assumes the command and options are all in-buffer. Just ignore all that can be gotten away with. Returns a pointer to the next available character (if any). */ uchar* TelnetCommand ( struct RawLibStruct *rawptr, uchar *DataPtr, int DataLength ) { uchar cmd; uchar *cptr, *czptr; /*********/ /* begin */ /*********/ /* IAC is *always* followed by a command (so we always need two) */ czptr = (cptr = DataPtr) + DataLength - 1; while (cptr < czptr && *cptr == TELNET_IAC) { cptr++; cmd = *cptr; if (cmd == TELNET_IAC) return (cptr); cptr++; if (cmd == TELNET_AYT) { RawLibWrite (rawptr, "\a", 1, RAWLIB_ASYNCH); continue; } if (cmd == TELNET_SB) { while (cptr < czptr) { if (*cptr == TELNET_IAC && *(cptr+1) == TELNET_SE) { cptr += 2; break; } cptr++; } } else { /* step over the option */ if (cmd == TELNET_DO || cmd == TELNET_DONT || cmd == TELNET_WILL || cmd == TELNET_WONT) cptr++; } } return (cptr); } /*****************************************************************************/ /* Request the client enters per-line mode. */ void TelnetLineMode (struct RawLibStruct *rawptr) { #define OPT_ECHO 1 #define OPT_SGA 3 static char DoCharBinary [] = { TELNET_IAC, TELNET_DO, OPT_ECHO, TELNET_IAC, TELNET_DO, OPT_SGA }; /*********/ /* begin */ /*********/ RawLibWrite (rawptr, DoCharBinary, sizeof(DoCharBinary), RAWLIB_ASYNCH); } /*****************************************************************************/