/*****************************************************************************/ /* NetReject.c Accept or reject the client connection based on wildcard patterns of either IP address, CIDR patterns, IP range patterns (n.n.n.n-n.n.n.n) or host name (if resolved). To improve matching efficiency the string of accepted/rejected patterns are converted into null-terminated records by NetRejectSet(). Reports of patterns are copied to allocated memory and converted back into newline terminated records. The calling function must deallocate the memory. A leading character of '#' allows a comment record to be ignored. A leading character of '$' sets a specific runtime setting (e.g. "$STATUS"). Otherwise a simple wildcard pattern matches either the IP address or host name. Rejected connections are $LOG optionally recorded in the relevant access log with an entry similar to the process log entry, like the following: wasd.vsm.com.au - - [28/Jul/2023:13:34:21 +1030] "CONNECT \ /REJECT-129.252.17.1/24~129.252.17.10~wasd.vsm.com.au HTTP/1.0" 418 0 The accept/reject match results are buffered to allow two things; 1) multiple consecutive matches to be counted, and 2) only a accept/single rejection note to be output to the server $NOTE process and/or $OPCOM log. %HTTPD-I-REJECT, 28-JUL-2023 13:54:58, 2, *.yandex.com \ 95-108-213-154.spider.yandex.com 95.108.213.154 klaatu.lan:443 %HTTPD-I-REJECT, 28-JUL-2023 14:19:51, 5, 118.123.105.69 klaatu.lan:80 The server shutdown calls this function with the IP address string NULL to write out any final buffered rejection note. $ must occur before any other rules! ---------- $4nn $5nn ---- Allows a standard HTTP status (e.g. 418, 406, 505) to be added to a list of IP addresses where connections are immediately dropped. This allows a complex WASD_CONFIG_MAP rule to be mapped to the specified status and thereafter any connections from that IP address immediately dropped, until a purge period expires. The syntax is |# WASD_CONFIG_REJECT | |# $4nn |$418 250 60 $400 ---- When a HTTP/1.n request is significantly malformed the IP address is added to a list of IP addresses where connections are immediately dropped. Must follow an initial $4nn/$5nn directive. OpenSSL shell command test: % echo -e "GET / HTTP/1.1\nHost: the.host.name\nTest: \0zero\n\n" | \ openssl s_client -connect the.host.name:443 -ign_eof $403 ---- When a failed authorisation exceeds the failure limit the IP address is added to a list of IP addresses where connections are immediately dropped. Must follow an initial $4nn/$5nn directive. $DNS ---- Reject connection immediately if host resolution has not occured. That is, the client host name is the same as the client IP address. $LOG ---- Reject connection is access logged. $NOTE ----- The rejected or accepted connection is noted in the server log. $OFF ----- Disable accept or reject processing. $OPCOM ------ The rejected or accepted connection is reported via OPCOM. VERSION HISTORY --------------- 15-DEC-2023 MGD Server Admin / Access Control reporting and editing 21-JUL-2023 MGD $4nn... extend to NetRejectThisStatus() et.al. 06-JUL-2023 MGD adapted from ConfigAccept() and ConfigReject() et.al. */ /*****************************************************************************/ #ifdef WASD_VMS_V7 #undef _VMS__V6__SOURCE #define _VMS__V6__SOURCE #undef __VMS_VER #define __VMS_VER 70000000 #undef __CRTL_VER #define __CRTL_VER 70000000 #endif /* standard C header files */ #include #include #include #include #include #include /* VMS related header files */ #include #include #include #include /* application related header */ #include "wasd.h" #include "gzip.h" #define WASD_MODULE "NETREJECT" #define DEFAULT_HTTP_STATUS 418 #define STATUS_SIZE_DEF 250 #define STATUS_SIZE_MAX 1000 #define STATUS_SIZE_MIN 100 /******************/ /* global storage */ /******************/ BOOL NetAcceptEnabled, NetRejectEnabled; /* used by Request..() */ int NetRejectStatus400, NetRejectStatus403, NetRejectStatusCode; char NetRejectStatusString [16]; static BOOL NetAcceptDisabled, NetAcceptNote, NetAcceptOpcom, NetRejectDNS, NetRejectDisabled, NetRejectLog, NetRejectNote, NetRejectOpcom; static int NetAcceptHostsLength, NetAcceptHostsStart, NetRejectHostsLength, NetRejectHostsStart, NetRejectPurgeMinutes, NetRejectPurgeSeconds, NetRejectStatusActive, NetRejectStatusFull, NetRejectStatusHit, NetRejectStatusLimit, NetRejectStatusSize; static char *NetConfigAcceptPtr, *NetConfigRejectPtr, *NetAcceptHostsPtr, *NetRejectHostsPtr; static REJECT_STRUCT *NetRejectStatusArray; /********************/ /* external storage */ /********************/ extern int HttpdTickSecond, LoggingEnabled, OpcomMessages, OpcomTarget; extern int64 HttpdTime64; extern uint SysPrvMask[]; extern char SoftwareID[]; extern ACCOUNTING_STRUCT *AccountingPtr; extern CONFIG_STRUCT Config; extern HTTPD_PROCESS HttpdProcess; extern REQUEST_STRUCT RequestKludge; extern WATCH_STRUCT Watch; /*****************************************************************************/ /* Set the global accept/reject hosts data or file name and parse any associated paramaters. */ char* NetRejectInit (char *type) { static char ErrorBuf [32]; char *sptr, *zptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_NET)) WatchThis (WATCHALL, WATCH_MOD_NET, "NetRejectInit()"); if (type && *type && to_upper(*type) != 'A' && to_upper(*type) != 'R') { FaoToBuffer (ErrorBuf, sizeof(ErrorBuf), NULL, "NET=LOAD=!AZ?", type); return (ErrorBuf); } if (!type || to_upper(*type) == 'A') { /**********/ /* accept */ /**********/ NetConfigAcceptPtr = Config.cfServer.AcceptHostsPtr; if (sptr = NetRejectSet ("ACCEPT")) { MetaConReport (&Config, METACON_REPORT_ERROR, sptr); if (!type) return (sptr); } else if (NetAcceptHostsPtr) { NetAcceptEnabled = true; NetAcceptDisabled = false; zptr = (sptr = NetAcceptHostsPtr) + NetAcceptHostsLength; while (sptr < zptr) { while (!*sptr && sptr < zptr) sptr++; if (sptr >= zptr) break; if (*sptr == '#') { /* comment record, ignored */ while (*sptr) sptr++; continue; } if (*sptr == '$') { if (strsame (sptr, "$NOTE", 5)) NetAcceptNote = true; else if (strsame (sptr, "$OFF", 4)) NetAcceptDisabled = true; else if (strsame (sptr, "$OPCOM", 6)) NetAcceptOpcom = true; } else { /* note where the rules begin */ NetAcceptHostsStart = sptr - NetAcceptHostsPtr; break; } while (*sptr) sptr++; continue; } } else NetAcceptDisabled = true; FaoToStdout ("%HTTPD-I-ACCEPT, !20%D, !AZ $NOTE:!&B $OPCOM:!&B\n", 0, NetAcceptDisabled ? "DISABLED" : "ENABLED", NetRejectNote, NetRejectOpcom); } if (!type || to_upper(*type) == 'R') { /**********/ /* reject */ /**********/ NetConfigRejectPtr = Config.cfServer.RejectHostsPtr; if (sptr = NetRejectSet ("REJECT")) { MetaConReport (&Config, METACON_REPORT_ERROR, sptr); if (!type) return (sptr); } else if (NetRejectHostsPtr) { NetRejectEnabled = true; NetRejectDisabled = false; zptr = (sptr = NetRejectHostsPtr) + NetRejectHostsLength; while (sptr < zptr) { while (!*sptr && sptr < zptr) sptr++; if (sptr >= zptr) break; if (*sptr == '#') { /* comment record, ignored */ while (*sptr) sptr++; continue; } if (*sptr == '$') { if (strsame (sptr, "$4", 2)) NetRejectStatusInit (sptr); else if (strsame (sptr, "$5", 2)) NetRejectStatusInit (sptr); else if (strsame (sptr, "$DNS", 4)) NetRejectDNS = true; else if (strsame (sptr, "$LOG", 4)) NetRejectLog = true; else if (strsame (sptr, "$NOTE", 5)) NetRejectNote = true; else if (strsame (sptr, "$OFF", 4)) NetRejectDisabled = true; else if (strsame (sptr, "$OPCOM", 6)) NetRejectOpcom = true; } else { /* note where the rules begin */ NetRejectHostsStart = sptr - NetRejectHostsPtr; break; } while (*sptr) sptr++; continue; } } else NetRejectDisabled = true; FaoToStdout ("%HTTPD-I-REJECT, !20%D, \ !AZ $DNS:!&B $LOG:!&B $NOTE:!&B $OPCOM:!&B $400:!&B $403:!&B $!UL !UL !UL\n", 0, NetRejectDisabled ? "DISABLED" : "ENABLED", NetRejectDNS, NetRejectLog, NetRejectNote, NetRejectOpcom, NetRejectStatus400, NetRejectStatus403, NetRejectStatusCode, NetRejectStatusSize, NetRejectPurgeMinutes); } return (NULL); } /*****************************************************************************/ /* Set up the $nnn (e.g. $418) parameters. */ void NetRejectStatusInit (char *sptr) { /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_NET)) WatchThis (WATCHALL, WATCH_MOD_NET, "NetRejectStatusInit() !AZ", sptr); if (strsame (sptr, "$400", 4)) { /* also include significantly malformed requests */ NetRejectStatus400 = 400; return; } if (strsame (sptr, "$403", 4)) { /* also include authorisation limit exceeds */ NetRejectStatus403 = 403; return; } memset (&RequestKludge, 0, sizeof(RequestKludge)); /* initialise */ NetRejectStatus400 = NetRejectStatus403 = NetRejectStatusCode = NetRejectStatusFull = NetRejectStatusHit = NetRejectStatusSize = NetRejectPurgeMinutes = NetRejectPurgeSeconds = 0; NetRejectStatusString[0] = '\0'; if (NetRejectStatusArray) VmFree (NetRejectStatusArray, FI_LI); NetRejectStatusArray = NULL; /* e.g. $ */ NetRejectStatusCode = atoi (sptr+1); while (*sptr && !isspace(*sptr)) sptr++; while (*sptr && isspace(*sptr)) sptr++; NetRejectStatusSize = atoi (sptr); while (*sptr && !isspace(*sptr)) sptr++; while (*sptr && isspace(*sptr)) sptr++; NetRejectPurgeMinutes = atoi (sptr); if (NetRejectStatusCode / 100 == 4 || NetRejectStatusCode / 100 == 5) sprintf (NetRejectStatusString, "%d", NetRejectStatusCode); else ErrorNoticed (NULL, SS$_BADPARAM, "$!3UL code", FI_LI, NetRejectStatusCode); if (NetRejectStatusSize) { if (NetRejectStatusSize < STATUS_SIZE_MIN || NetRejectStatusSize > STATUS_SIZE_MAX) { ErrorNoticed (NULL, SS$_BADPARAM, "$!3UL size !UL (!SL to !SL)", FI_LI, NetRejectStatusCode, NetRejectStatusSize, STATUS_SIZE_MIN, STATUS_SIZE_MIN); NetRejectStatusSize = 0; } } if (NetRejectPurgeMinutes) { /* minimum one minute, maximum one hour */ if (NetRejectPurgeMinutes < 1 || NetRejectPurgeMinutes > 60) { ErrorNoticed (NULL, SS$_BADPARAM, "$!3UL purge !UL (!UL to !UL)", FI_LI, NetRejectPurgeMinutes, 1, 60); NetRejectPurgeMinutes = 0; } else NetRejectPurgeMinutes = 60; NetRejectPurgeSeconds = NetRejectPurgeMinutes * 60; } if (NetRejectStatusCode && NetRejectPurgeMinutes && NetRejectStatusSize) { /* an array 1..n+1 */ NetRejectStatusArray = VmGet (sizeof(REJECT_STRUCT) * (NetRejectStatusSize+1)); /* adjust static members of kludged request */ RequestKludge.rqHeader.HttpVersion = HTTP_VERSION_1_0; strzcpy (RequestKludge.rqHeader.MethodName, "CONNECT", 8); } else { NetRejectStatus400 = NetRejectStatus403 = NetRejectStatusCode = NetRejectStatusSize = NetRejectPurgeMinutes = NetRejectPurgeSeconds = 0; NetRejectStatusString[0] = '\0'; } } /*****************************************************************************/ /* Accept the specified connection request by returning false, or reject by true. */ BOOL NetRejectConnect ( CLIENT_STRUCT *clptr, SERVICE_STRUCT *svptr ) { static BOOL cidr, range; int idx; char *aptr, *cptr, *hnptr, *hsptr, *iaptr, *sptr, *zptr; char buf [256]; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_NET)) WatchThis (WATCHALL, WATCH_MOD_NET, "NetRejectConnect()"); /* if neither is defined then just accept */ if (!NetAcceptHostsPtr && !NetRejectHostsPtr) return (false); if (clptr) { iaptr = clptr->IpAddressString; hnptr = clptr->Lookup.HostName; } else { /* server shutdown or rule reload */ NetRejectServerLog (NULL, NULL, "ACCEPT", NULL); NetRejectServerLog (NULL, NULL, "REJECT", NULL); return (false); } /******************/ /* hosts accepted */ /******************/ if (NetAcceptHostsPtr && !NetAcceptDisabled) { aptr = NULL; zptr = (sptr = NetAcceptHostsPtr) + NetAcceptHostsLength; sptr += NetAcceptHostsStart; while (sptr < zptr) { // if (WATCH_MODULE(WATCH_MOD_NET)) // WatchThis (WATCHALL, WATCH_MOD_NET, "!&Z", sptr); aptr = NULL; while (!*sptr && sptr < zptr) sptr++; if (sptr >= zptr) break; if (*sptr == '#', *sptr == '$') { /* comment record, dollar directives, ignored */ while (*sptr) sptr++; continue; } aptr = sptr; /* look for IPv4 address/CIDR/range in pattern */ cidr = range = false; for (cptr = sptr; *cptr == '*' || *cptr == '.' || *cptr == '/' || *cptr == '-' || isdigit(*cptr); cptr++) { if (*cptr == '/') cidr = true; if (*cptr == '-') range = true; } if (!*cptr || cidr || range) cptr = iaptr; else { /* look for IPv6 address */ for (cptr = sptr; *cptr && *cptr != ':'; cptr++); if (*cptr) cptr = iaptr; else cptr = hnptr; } if (cidr) { /* if the IP address matches the CIDR mask */ if (NetRejectCIDR (cptr, sptr)) break; } else if (range) { /* if the IP address matches the IP range */ if (NetRejectRange (cptr, sptr)) break; } else { /* if the address/hostname matches the pattern */ if (StringMatchGreedy (NULL, cptr, sptr)) break; } while (*sptr) sptr++; } } else sptr = zptr = ""; /* if not at the end of the patterns then don't reject (accept) */ if (sptr < zptr) { /************/ /* accepted */ /************/ if (WATCH_CATEGORY (WATCH_CONNECT)) WatchThis (WATCHALL, WATCH_CONNECT, "ACCEPT !AZ !AZ", sptr, cptr); InstanceGblSecIncrLong (&AccountingPtr->ConnectAcceptedCount); if (NetAcceptNote) NetRejectServerLog (clptr, svptr, "ACCEPT", aptr); return (false); } /*******************/ /* status rejected */ /*******************/ if (NetRejectStatusSize && !NetRejectDisabled) { if (idx = NetRejectThisStatus (clptr)) { hsptr = NetRejectStatusArray[idx].HttpStatus; if (WATCH_CATEGORY (WATCH_CONNECT)) WatchThis (WATCHALL, WATCH_CONNECT, "$!3UL !UL !UL !UL (Active:!UL Limit:!UL Hits:!UL Full:!UL)", NetRejectStatusCode, atoi(hsptr), NetRejectStatusSize, NetRejectPurgeMinutes, NetRejectStatusActive, NetRejectStatusLimit, NetRejectStatusHit, NetRejectStatusFull); RequestKludge.rqResponse.HttpStatus = atoi(hsptr); RequestHttpStatusCode (&RequestKludge); InstanceGblSecIncrLong (&AccountingPtr->ConnectRejectedCount); if (NetRejectNote) NetRejectServerLog (clptr, svptr, "REJECT", hsptr); if (NetRejectLog) NetRejectAccessLog (clptr, svptr, hsptr); return (true); } } /******************/ /* hosts rejected */ /******************/ if (NetRejectHostsPtr && !NetRejectDisabled) { aptr = NULL; zptr = (sptr = NetRejectHostsPtr) + NetRejectHostsLength; sptr += NetRejectHostsStart; while (sptr < zptr) { // if (WATCH_MODULE(WATCH_MOD_NET)) // WatchThis (WATCHALL, WATCH_MOD_NET, "!&Z", sptr); aptr = NULL; while (!*sptr && sptr < zptr) sptr++; if (sptr >= zptr) break; if (*sptr == '#' || *sptr == '$') { /* comment record, dollar directives, ignored */ while (*sptr) sptr++; continue; } if (NetRejectDNS) { /* if the IP address was not resolved */ if (Config.cfMisc.DnsLookupClient) if (strsame (iaptr, hnptr, -1)) { /* sentinal for failed DNS lookup */ aptr = sptr = "?"; cptr = iaptr; break; } } aptr = sptr; /* look for IPv4 address/CIDR/range in pattern */ cidr = range = false; for (cptr = sptr; *cptr == '*' || *cptr == '.' || *cptr == '/' || *cptr == '-' || isdigit(*cptr); cptr++) { if (*cptr == '/') cidr = true; if (*cptr == '-') range = true; } if (!*cptr || cidr || range) cptr = iaptr; else { /* look for IPv6 address */ for (cptr = sptr; *cptr && *cptr != ':'; cptr++); if (*cptr) cptr = iaptr; else cptr = hnptr; } if (cidr) { /* if the IP address matches the CIDR mask */ if (NetRejectCIDR (cptr, sptr)) break; } else if (range) { /* if the IP address matches the IP range */ if (NetRejectRange (cptr, sptr)) break; } else { /* if the address/hostname matches the pattern */ if (StringMatchGreedy (NULL, cptr, sptr)) break; } while (*sptr) sptr++; } } else sptr = zptr = ""; /* if at the end of the patterns then accept */ if (sptr >= zptr) return (false); /************/ /* rejected */ /************/ if (WATCH_CATEGORY (WATCH_CONNECT)) WatchThis (WATCHALL, WATCH_CONNECT, "REJECT !AZ !AZ", sptr, cptr); RequestHttpStatusCode (&RequestKludge); InstanceGblSecIncrLong (&AccountingPtr->ConnectRejectedCount); if (NetRejectNote) NetRejectServerLog (clptr, svptr, "REJECT", aptr); if (NetRejectLog) NetRejectAccessLog (clptr, svptr, aptr); return (true); } /*****************************************************************************/ /* Provide a server process log entry for either ACCEPT or REJECT log. Also provide it as an OPCOM message. */ char* NetRejectServerLog ( CLIENT_STRUCT *clptr, SERVICE_STRUCT *svptr, char *type, char *rule ) { /* index zero is 'accept', index one is 'reject' */ static int count[2]; static int64 time64 [2]; static char buffer [3][256]; BOOL opcomit, noteit; int idx; char *cptr, *hnptr, *iaptr, *sptr, *zptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_NET)) WatchThis (WATCHALL, WATCH_MOD_NET, "NetRejectServerLog() !&Z !&Z", type, rule); if (clptr) { iaptr = clptr->IpAddressString; hnptr = clptr->Lookup.HostName; } if (to_upper(*type) == 'A') { idx = 0; noteit = NetAcceptNote; opcomit = NetAcceptOpcom; } else if (to_upper(*type) == 'R') { idx = 1; noteit = NetRejectNote; opcomit = NetRejectOpcom; } else idx = 2; if (cptr = rule) { sptr = buffer[idx]; if (*cptr == '?') { /* looking for unresolved IP address in buffer */ if (strsame (iaptr, sptr + 2, -1)) { /* hit unresolved IP address */ count[idx]++; return (NULL); } } else if (strsame (cptr, sptr, -1)) { /* same pattern */ count[idx]++; return (NULL); } } /**********/ /* report */ /**********/ /* when getting most recent reject entry */ if (idx == 2) { /* four null-terminated strings: "rule\0host-name\0ip-addr\0service\0" */ if (!buffer[1][0]) return (NULL); zptr = (sptr = buffer[2]) + sizeof(buffer[2])-4; FaoToBuffer (buffer[2], sizeof(buffer[2]), NULL, "!20%D, !UL, ", &time64[1], count[1]); while (*sptr) sptr++; for (cptr = buffer[1]; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr++ = ' '; for (cptr++; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr++ = ' '; for (cptr++; *cptr && sptr < zptr; *sptr++ = *cptr++); if (*cptr) { /* last string can be empty i.e. "rule\0ip-addr\0service\0\0" */ *sptr++ = ' '; for (cptr++; *cptr; *sptr++ = *cptr++); } *sptr++ = '\0'; return (buffer[2]); } /* four null-terminated strings: "rule\0host-name\0ip-addr\0service\0" */ cptr = sptr = buffer[idx]; /* convert nulls to spaces */ while (*sptr) sptr++; *sptr++ = ' '; while (*sptr) sptr++; *sptr++ = ' '; while (*sptr) sptr++; /* last string can be empty i.e. "rule\0ip-addr\0service\0\0" */ if (*(sptr+1)) *sptr++ = ' '; if (WATCH_MODULE(WATCH_MOD_NET)) WatchThis (WATCHALL, WATCH_MOD_NET, "!UL !&Z", count[idx], cptr); /* step over "? host-name\0\0" */ if (*cptr == '?') cptr += 2; if (noteit) if (time64[idx]) FaoToStdout ("%HTTPD-I-!AZ, !20%D, !UL, !AZ\n", type, &time64[idx], count[idx], cptr); if (opcomit) if (time64[idx]) if (OpcomMessages & OPCOM_HTTPD) FaoToOpcom ("%HTTPD-I-!AZ, !20%D, !UL, !AZ", type, &time64[idx], count[idx], cptr); /************************/ /* buffer the new entry */ /************************/ if (!rule) { /* shutdown/reload flush of buffer */ buffer[0][0] = buffer[1][0] = buffer[2][0] = '\0'; count[0] = count[1] = 0; time64[0] = time64[1] = 0; return (NULL); } /* both buffers are the same size */ zptr = (sptr = buffer[idx]) + sizeof(buffer[0])-6; /* copy the rule into the buffer and terminate */ for (cptr = rule; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr++ = '\0'; /* if the host name was not resolved */ if (*rule == '?') { /* copy the IP address string into the buffer and terminate */ for (cptr = iaptr; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr++ = '\0'; } else { /* copy the host name into the buffer and terminate */ for (cptr = hnptr; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr++ = '\0'; /* copy the IP address string into the buffer and terminate */ for (cptr = iaptr; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr++ = '\0'; } /* copy the service into the buffer */ for (cptr = svptr->ServerHostPort; *cptr && sptr < zptr; *sptr++ = *cptr++); *(USHORTPTR)sptr = '\0\0'; time64[idx] = HttpdTime64; count[idx] = 1; return (NULL); } /*****************************************************************************/ /* Provide an access log entry for a reject. */ void NetRejectAccessLog ( CLIENT_STRUCT *clptr, SERVICE_STRUCT *svptr, char *rule ) { char *cptr, *hnptr, *iaptr, *sptr, *zptr; char buf [256]; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_NET)) WatchThis (WATCHALL, WATCH_MOD_NET, "NetRejectAccessLog()"); if (!LoggingEnabled) return; iaptr = clptr->IpAddressString; hnptr = clptr->Lookup.HostName; /* adjust dynamic members of kludged request */ RequestKludge.ClientPtr = clptr; RequestKludge.ServicePtr = svptr; RequestKludge.rqTime.BeginTime64 = HttpdTime64; sys$numtim (&RequestKludge.rqTime.BeginTime7, &HttpdTime64); RequestKludge.rqHeader.RequestUriPtr = buf; if (WATCH_CATEGORY (WATCH_LOG)) RequestKludge.WatchItem = ++Watch.ItemCount; else RequestKludge.WatchItem = 0; zptr = (sptr = buf) + sizeof(buf)-1; for (cptr = "/REJECT~"; *cptr && sptr < zptr; *sptr++ = *cptr++); if (*rule == '?') { /* if failed host name resolution copy in the IP address */ for (cptr = iaptr; *cptr && sptr < zptr; *sptr++ = *cptr++); } else { /* copy in the matching pattern */ for (cptr = rule; *cptr && sptr < zptr; *sptr++ = *cptr++); if (sptr < zptr) *sptr++ = '~'; /* if the host name was not resolved */ if (strsame (iaptr, hnptr, -1)) { /* the pattern must have been for an IP address */ for (cptr = iaptr; *cptr && sptr < zptr; *sptr++ = *cptr++); } else { /* the pattern might have been for a host name */ for (cptr = hnptr; *cptr && sptr < zptr; *sptr++ = *cptr++); if (sptr < zptr) *sptr++ = '~'; for (cptr = iaptr; *cptr && sptr < zptr; *sptr++ = *cptr++); } } *sptr = '\0'; Logging (&RequestKludge, LOGGING_ENTRY); } /*****************************************************************************/ /* If the address pointed to by |cptr| matches the CIDR mask pointed to by |sptr| then return true, otherwise not matching returns false. */ BOOL NetRejectCIDR ( char *cptr, char *sptr ) { static char CidrMask [32]; static int mask, retval; static in_addr_t ip1, ip2; char *aptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_NET)) WatchThis (WATCHALL, WATCH_MOD_NET, "NetRejectCIDR() !AZ !AZ", sptr, cptr); retval = inet_aton (cptr, &ip1); if (!retval) return (true); if (!strsame (sptr, CidrMask, -1)) { for (aptr = sptr; *aptr && *aptr != '/'; aptr++); *aptr = '\0'; retval = inet_aton (sptr, &ip2); *aptr++ = '/'; mask = atoi (aptr); mask = ((1 << mask) - 1) << (32 - mask); mask = htonl (mask); if (retval && mask) strzcpy (CidrMask, sptr, sizeof(CidrMask)); else CidrMask[0] = '\0'; } if (WATCH_MODULE(WATCH_MOD_NET)) WatchThis (WATCHALL, WATCH_MOD_NET, "!UL !&Z ip:!8XL mask:!8XL !8XL !8XL !8XL", retval, cptr, ip1, mask, ip2, ip1 & mask, ip2 & mask); if (!retval || !mask) return (true); return ((ip1 & mask) == (ip2 & mask)); } /*****************************************************************************/ /* If the address pointed to by |cptr| matches the IP address range pointed to by |sptr| (e.g. "46.148.32.0-46.148.47.255" - i.e. low to high) then return true, otherwise not within the range returns false. */ BOOL NetRejectRange ( char *cptr, char *sptr ) { static char RangeOf [32]; static int retval; static in_addr_t ip1, ip2, ip3; char *aptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_NET)) WatchThis (WATCHALL, WATCH_MOD_NET, "NetRejectRange() !AZ !AZ", sptr, cptr); retval = inet_aton (cptr, &ip1); if (!retval) return (true); ip1 = ntohl (ip1); if (!strsame (sptr, RangeOf, -1)) { for (aptr = sptr; *aptr && *aptr != '-'; aptr++); *aptr = '\0'; retval = inet_aton (sptr, &ip2); *aptr++ = '-'; if (retval) { ip2 = ntohl (ip2); retval = inet_aton (aptr, &ip3); ip3 = ntohl (ip3); } if (retval) strzcpy (RangeOf, sptr, sizeof(RangeOf)); else RangeOf[0] = '\0'; } if (WATCH_MODULE(WATCH_MOD_NET)) WatchThis (WATCHALL, WATCH_MOD_NET, "!UL !&Z ip1:!8XL !UL ip2:!8XL !UL ip3:!8XL !UL result:!&B", retval, cptr, ip1, ip1, ip2, ip2, ip3, ip3, ip1 >= ip2 && ip1 <= ip3); if (!retval) return (true); return (ip1 >= ip2 && ip1 <= ip3); } /*****************************************************************************/ /* Return a pointer to a file name if the client access configuration contains a file specification. Otherwise return NULL. */ char* NetRejectFile (char *aptr) { char *cptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_NET)) WatchThis (WATCHALL, WATCH_MOD_NET, "NetRejectFile()"); if (!aptr || !*aptr) return (NULL); cptr = SysTrnLnm (aptr); if (!cptr) { for (cptr = aptr; *cptr && *cptr != '\n' && *cptr != ':'; cptr++); if (*cptr) cptr = aptr; else cptr = NULL; } return (cptr); } /*****************************************************************************/ /* Load the text file content of the file name provided by aptr. Return a leading null error message as indicated, or a pointer to a null-terminated string containing the file contents. Calling routine must accept responsibility for VmFree()ing content data. */ char* NetRejectLoad (char *aptr) { int length, status; char *cptr, *sptr; ODS_STRUCT LoadOds; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_NET)) WatchThis (WATCHALL, WATCH_MOD_NET, "NetRejectLoad() !AZ", aptr); OdsStructInit (&LoadOds, true); sys$setprv (1, &SysPrvMask, 0, 0); status = OdsLoadTextFile (&LoadOds, cptr = aptr); sys$setprv (0, &SysPrvMask, 0, 0); if (VMSnok (status)) { if (status == RMS$_FNF) return (NULL); sptr = VmGet (256); FaoToBuffer (sptr, 256, NULL, "\0!AZ !&S", aptr, status); return (sptr); } /* prefix a commented file name */ length = LoadOds.DataLength + strlen(aptr) + 3; sptr = aptr = VmGet (length); *sptr++ = '#'; *sptr++ = ' '; while (*cptr) *sptr++ = *cptr++; *sptr++ = '\n'; for (cptr = LoadOds.DataPtr; *cptr; *sptr++ = *cptr++); OdsFreeTextFile (&LoadOds); return (aptr); } /*****************************************************************************/ /* Using the accept/reject configuration directives load up each newline terminated record into null terminated. */ char* NetRejectSet (char *type) { char *cptr, *sptr, *zptr; ODS_STRUCT LoadOds; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_NET)) WatchThis (WATCHALL, WATCH_MOD_NET, "NetRejectSet() !AZ", type); if (!type || to_upper(*type) == 'A') { /**********/ /* accept */ /**********/ NetAcceptHostsStart = 0; NetAcceptNote = NetAcceptOpcom = false; if (NetAcceptHostsPtr) { /* free previous memory */ VmFree (NetAcceptHostsPtr, FI_LI); NetAcceptHostsPtr = NULL; NetAcceptHostsLength = 0; } if (NetConfigAcceptPtr) { /* flush any existing notification records */ NetRejectServerLog (NULL, NULL, "ACCEPT", NULL); if (cptr = NetRejectFile (NetConfigAcceptPtr)) { /* logical name or file specification */ if (cptr = NetRejectLoad (cptr)) { if (!*cptr) return (cptr+1); NetAcceptHostsPtr = cptr; NetAcceptHostsLength = strlen(cptr); } } if (!cptr) { /* use literal string */ NetAcceptHostsLength = strlen(NetConfigAcceptPtr); NetAcceptHostsPtr = VmGet (NetAcceptHostsLength+1); strzcpy (NetAcceptHostsPtr, Config.cfServer.AcceptHostsPtr, NetAcceptHostsLength+1); } if (WATCH_MODULE(WATCH_MOD_NET)) WatchThis (WATCHALL, WATCH_MOD_NET, "!AZ", NetAcceptHostsPtr); /* convert to null-terminated records */ for (sptr = NetAcceptHostsPtr; *sptr; sptr++) if (*sptr == '\n') *sptr = '\0'; } } if (!type || to_upper(*type) == 'R') { /**********/ /* reject */ /**********/ NetRejectHostsStart = 0; NetRejectNote = NetRejectOpcom = NetRejectLog = NetRejectDNS = false; if (NetRejectHostsPtr) { /* free previous memory */ VmFree (NetRejectHostsPtr, FI_LI); NetRejectHostsPtr = NULL; NetRejectHostsLength = 0; } if (NetConfigRejectPtr) { /* flush any existing notification records */ NetRejectServerLog (NULL, NULL, "REJECT", NULL); if (cptr = NetRejectFile (NetConfigRejectPtr)) { /* logical name or file specification */ if (cptr = NetRejectLoad (cptr)) { if (!*cptr) return (cptr+1); NetRejectHostsPtr = cptr; NetRejectHostsLength = strlen(cptr); } } if (!cptr) { /* use literal string */ NetRejectHostsLength = strlen(NetConfigRejectPtr); NetRejectHostsPtr = VmGet (NetRejectHostsLength+1); strzcpy (NetRejectHostsPtr, Config.cfServer.RejectHostsPtr, NetRejectHostsLength+1); } if (WATCH_MODULE(WATCH_MOD_NET)) WatchThis (WATCHALL, WATCH_MOD_NET, "!AZ", NetRejectHostsPtr); /* convert to null-terminated records */ for (sptr = NetRejectHostsPtr; *sptr; sptr++) if (*sptr == '\n') *sptr = '\0'; } } return (NULL); } /*****************************************************************************/ /* Check the array of IP addresses for a match. If found return the (non-zero) index number, otherwise zero. */ int NetRejectThisStatus (CLIENT_STRUCT *clptr) { int idx; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_NET)) WatchThis (WATCHALL, WATCH_MOD_NET, "NetRejectThisStatus() !AZ !&I", clptr->IpAddressString, &clptr->IpAddress); if (!NetRejectStatusSize) return (0); if (idx = NetRejectCheckStatus (clptr)) { NetRejectStatusHit++; NetRejectStatusArray[idx].HitCount++; NetRejectStatusArray[idx].TickSecond = HttpdTickSecond + NetRejectPurgeSeconds; } return (idx); } /*****************************************************************************/ /* Check the array for a match. If found return the (non-zero) index number. */ int NetRejectCheckStatus (CLIENT_STRUCT *clptr) { int idx; IPADDRESS *ia1ptr, *ia2ptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_NET)) WatchThis (WATCHALL, WATCH_MOD_NET, "NetRejectCheckStatus() !AZ !&I", clptr->IpAddressString, &clptr->IpAddress); if (!NetRejectStatusSize) return (0); ia1ptr = &clptr->IpAddress; for (idx = 1; idx <= NetRejectStatusLimit; idx++) { ia2ptr = &NetRejectStatusArray[idx].IpAddress; if (!ia2ptr->iadr_size) continue; if (WATCH_MODULE(WATCH_MOD_NET)) WatchThis (WATCHALL, WATCH_MOD_NET, "!UL !UL !&I !UL !&I !UL !UL", idx, ia1ptr->iadr_size, ia1ptr, ia2ptr->iadr_size, ia2ptr, NetRejectStatusArray[idx].HitCount, HttpdTickSecond - NetRejectStatusArray[idx].TickSecond); if (ia1ptr->iadr_size == 4) if (ia2ptr->iadr_size == 4) if (*(ULONGPTR)ia1ptr->address == *(ULONGPTR)ia2ptr->address) break; else continue; if (ia1ptr->iadr_size == 16) if (ia2ptr->iadr_size == 16) if (!memcmp (&ia1ptr->address, &ia2ptr->address, 16)) break; } if (WATCH_MODULE(WATCH_MOD_NET)) WatchThis (WATCHALL, WATCH_MOD_NET, "!UL !UL !UL", idx, NetRejectStatusLimit, NetRejectStatusSize); if (idx <= NetRejectStatusLimit) return (idx); return (0); } /*****************************************************************************/ /* First check the array for a match. If hit then return the index number. If not hit then if the returned index is non-zero add the IP address at that index, otherwise if zero the array is full and we'll drop the address and just wait for the next purge. */ int NetRejectSetStatus (REQUEST_STRUCT *rqptr) { int idx; IPADDRESS *iaptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_NET)) WatchThis (WATCHALL, WATCH_MOD_NET, "NetRejectSetStatus() !AZ", rqptr->ClientPtr->IpAddressString); if (!NetRejectStatusSize) return (0); /* if there is already an entry for this IP address */ if (idx = NetRejectCheckStatus (rqptr->ClientPtr)) { NetRejectStatusArray[idx].HitCount++; NetRejectStatusArray[idx].TickSecond = HttpdTickSecond + NetRejectPurgeSeconds; NetRejectStatusHit++; } else { /* look for a purged item */ for (idx = 1; idx <= NetRejectStatusLimit; idx++) if (!NetRejectStatusArray[idx].IpAddress.iadr_size) break; /* if no purged item found */ if (idx >= NetRejectStatusLimit) { /* if we've run out of space just forget it */ if (NetRejectStatusLimit + 1 >= NetRejectStatusSize) { NetRejectStatusFull++; return (0); } idx = ++NetRejectStatusLimit; NetRejectStatusActive++; } /* use the array entry to store this IP address */ iaptr = &rqptr->ClientPtr->IpAddress; memcpy (&NetRejectStatusArray[idx].IpAddress, iaptr, sizeof(IPADDRESS)); NetRejectStatusArray[idx].HitCount = 1; NetRejectStatusArray[idx].TickSecond = HttpdTickSecond + NetRejectPurgeSeconds; sprintf (NetRejectStatusArray[idx].HttpStatus, "%3.3u", rqptr->rqResponse.HttpStatus); } if (WATCH_MODULE(WATCH_MOD_NET)) WatchThis (WATCHALL, WATCH_MOD_NET, "!UL !&I !UL !UL", idx, &NetRejectStatusArray[idx].IpAddress, NetRejectStatusArray[idx].HitCount, NetRejectStatusArray[idx].TickSecond); if (WATCH_CATEGORY (WATCH_CONNECT)) WatchThis (WATCHALL, WATCH_CONNECT, "REJECT !AZ !AZ", NetRejectStatusArray[idx].HttpStatus, rqptr->ClientPtr->IpAddressString); NetRejectServerLog (rqptr->ClientPtr, rqptr->ServicePtr, "REJECT", NetRejectStatusArray[idx].HttpStatus); NetRejectAccessLog (rqptr->ClientPtr, rqptr->ServicePtr, NetRejectStatusArray[idx].HttpStatus); return (idx); } /*****************************************************************************/ /* Purge all entries older than 'minutes'. Return the current number of entries in the array. */ int NetRejectPurgeStatus (int minutes) { int idx, TickSecond; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_NET)) WatchThis (WATCHALL, WATCH_MOD_NET, "NetRejectPurgeStatus() !SL", minutes); if (!NetRejectStatusSize) return (0); if (!minutes) minutes = NetRejectPurgeSeconds; minutes *= 60; /* zero the IP address size for anything older than the specified seconds */ NetRejectStatusActive = 0; for (idx = 1; idx <= NetRejectStatusLimit; idx++) if (NetRejectStatusArray[idx].TickSecond - minutes <= HttpdTickSecond) NetRejectStatusArray[idx].IpAddress.iadr_size = 0; else NetRejectStatusActive++; /* reset the array count to the lowest free array element */ for (idx = NetRejectStatusLimit; idx > 0; idx--) if (NetRejectStatusArray[idx].IpAddress.iadr_size) break; NetRejectStatusLimit = idx; return (NetRejectStatusLimit); } /*****************************************************************************/ /* Return a pointer to an allocated string containing a string representing the accept or reject report. Calling routine must accept responsibility for VmFree()ing content data. */ char* NetRejectReport (char *type) { char *aptr, *cptr, *sptr, *zptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_NET)) WatchThis (WATCHALL, WATCH_MOD_NET, "NetRejectReport() !AZ", type); if (strsame (type, "ACCEPT", -1)) { /* the loaded accept list */ cptr = NetAcceptHostsPtr; sptr = aptr = VmGet (NetAcceptHostsLength); zptr = sptr + NetAcceptHostsLength; while (sptr < zptr) { while (*cptr && sptr < zptr) *sptr++ = *cptr++; if (sptr < zptr) *sptr++ = '\n'; cptr++; } } else if (strsame (type, "REJECT", -1)) { /* the loaded reject list */ cptr = NetRejectHostsPtr; sptr = aptr = VmGet (NetRejectHostsLength); zptr = sptr + NetRejectHostsLength; while (sptr < zptr) { while (*cptr && sptr < zptr) *sptr++ = *cptr++; if (sptr < zptr) *sptr++ = '\n'; cptr++; } } else { aptr = VmGet (32); strcpy (aptr, "(none)"); } return (aptr); } /*****************************************************************************/ /* From the Server Admin menu generate a report of the rules and (recent) history. */ void NetRejectReportAdmin ( REQUEST_STRUCT *rqptr, char *type ) { static char ReportRulesFao [] = "

\n\ \n\ \n\
Rules
\n\ \n\ \n\
!AZ
\n\
\n"; static char ReportIpFao [] = "

\n\ \n\ \ \n"; static char ReportEmptyFao [] = "\n"; int count, idx, minutes, status; ulong FaoVector [8]; ulong *vecptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_NET)) WatchThis (WATCHALL, WATCH_MOD_NET, "NetRejectReportIp()"); count = 0; for (idx = 1; idx <= NetRejectStatusLimit; idx++) { if (!NetRejectStatusArray[idx].IpAddress.iadr_size) continue; minutes = NetRejectStatusArray[idx].TickSecond - HttpdTickSecond; minutes = (minutes + 65) / 60; if (minutes <= 0) { /* if reached timeout might as well time it out */ NetRejectStatusArray[idx].IpAddress.iadr_size = 0; continue; } vecptr = FaoVector; *vecptr++ = ++count; *vecptr++ = NetRejectStatusArray[idx].HitCount; *vecptr++ = minutes; *vecptr++ = NetRejectStatusArray[idx].HttpStatus; *vecptr++ = &NetRejectStatusArray[idx].IpAddress; status = FaolToNet (rqptr, ReportIpFao, &FaoVector); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); } if (!count) FaoToNet (rqptr, ReportEmptyFao); } /*****************************************************************************/ /* Generate a (recent) history by opening and reading relevant records from the current operator log. Relies on the $NOTE rule being enabled. */ void NetRejectReportHistory (REQUEST_STRUCT *rqptr) { static char ReportEmptyFao [] = "empty\n"; int count, status; char *sptr; char buf [1024]; ODS_STRUCT HistoryOds; ODS_STRUCT *odsptr = &HistoryOds; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_NET)) WatchThis (WATCHALL, WATCH_MOD_NET, "NetRejectReportHistory()"); if (!HttpdProcess.SysOutputFile) { FaoToNet (rqptr, "!&S\n", SS$_NOSUCHFILE); return; } /* Q&D flush SYS$OUTPUT */ fsync (STDOUT_FILENO); OdsStructInit (odsptr, true); status = OdsOpenShrUpdReadOnly (odsptr, HttpdProcess.SysOutput, false, true); if (VMSnok (status)) { FaoToNet (rqptr, "!&S\n", status); return; } count = 0; for (;;) { odsptr->Rab.rab$l_ubf = buf; odsptr->Rab.rab$w_usz = sizeof(buf) - 1; status = sys$get (&odsptr->Rab, 0, 0); if (VMSnok (status)) break; buf[odsptr->Rab.rab$w_rsz++] = '\n'; if (!MATCH16 (buf, "%HTTPD-I-REJECT,")) continue; NetWriteBuffered (rqptr, NULL, buf + 17, odsptr->Rab.rab$w_rsz - 17); count++; } sys$close (&odsptr->Fab, 0, 0); if (status == RMS$_EOF) status = SS$_NORMAL; if (VMSnok (status)) { FaoToNet (rqptr, "!&S\n", status); return; } /* get anything lurking in the most recent reject entry */ sptr = NetRejectServerLog (NULL, NULL, "", NULL); if (sptr) { NetWriteBuffered (rqptr, NULL, sptr, -1); count++; } if (!count) FaoToNet (rqptr, ReportEmptyFao); } /*****************************************************************************/
Current IP
\n\ \n\ \ \ \ \ \ \n"; static char ReportHistoryFao [] = "

HitExpiresStatusIP
\n\ \n\ \n\
History
\n\ \n\
"; static char ReportEndFao [] = "
\n\
\n"; static char ReportNoneFao [] = "none defined\n"; char *sptr; char ScratchString [256]; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_NET)) WatchThis (WATCHALL, WATCH_MOD_NET, "NetRejectReportAdmin() !&AZ", type); if (type) *type = toupper(*type); if (!type || (*type != 'A' && *type != 'R')) { rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, "Unknown function.", FI_LI); AdminEnd (rqptr); return; } rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN; ResponseHeader200 (rqptr, "text/html", NULL); FaoToBuffer (ScratchString, sizeof(ScratchString), NULL, "Access Control  –  !AZ Report", type); AdminPageTitle (rqptr, ScratchString); if (sptr = NetRejectReport (type)) { FaoToNet (rqptr, ReportRulesFao, sptr); /* NetRejectReport() allocated memory needs to be freed */ VmFree (sptr, FI_LI); } else FaoToNet (rqptr, ReportRulesFao, ReportNoneFao); if (strsame (type, "REJECT", -1)) { if (NetRejectStatusSize) { FaoToNet (rqptr, ReportIpFao); NetRejectReportIp (rqptr); FaoToNet (rqptr, ReportEndFao); } FaoToNet (rqptr, ReportHistoryFao); NetRejectReportHistory (rqptr); FaoToNet (rqptr, ReportEndFao); } AdminEnd (rqptr); } /*****************************************************************************/ /* Generate a report showing IP addresses currently beinf rejected. These are subject to purging. */ void NetRejectReportIp (REQUEST_STRUCT *rqptr) { static char ReportIpFao [] = "
!UL.!UL!UL!AZ!&I
empty