/*****************************************************************************/ /* authagent_CEL.c WASD authentication agent with behaviour compatible with the OSU (DECthreads) CEL authenticator (by Charles Lane). Program contains no OSU-derived code. Functional description from OSU [BASE_CODE]CEL_AUTHENTICATOR.C * The setup file simply defines a list of usernames/hosts and passwords. * If a password of '*' is specified, the corresponding username is checked * against the SYSUAF file. If the username specification includes * a host address (e.g. SMITH@128.146.235.*), the password applies to only * the matching hosts. A null username with a host address will accept * any connect from that host with no password check. * * Summary: * tag-args... -> Special configuration info: * string * @host * -> Host address must match * username[@host] password -> must match both * *[@host] password -> any username, must match password * username[@host] * -> must match username, password from SYSUAF * *[@host] * -> any username, password from SYSUAF * * If host restrictions would deny access regardless of username/password * given, the authentication fails with a 403 status so client doesn't waste * it's time prompting user for username/password. DIFFERENCES WITH OSU CEL ------------------------ The OSU CEL authenticator is more rigid in it's processing of wildcards. This WASD implementation uses decc$match_wild() and so allows regular expressions in the setup file strings. This should not give rise to major differences in behaviour. The code also enforces a case-insensitive match. For SYSUAF authentication, accounts that are disusered, are captive or restricted, or have SYSPRV authorized are always rejected. This behaviour can be modified. See the "GLOBAL STORAGE" section below. Comment-out any elements wished to be allowed and recompile. Caching is not needed by AUTHAGENT_CEL, as this is all handled internally by WASD. AUTHAGENT_CEL will only be accessed the first time authorization is required, or after the user-revalidation period has expired. HTTPD$AUTH CONFIGURATION ------------------------ CEL authenticator configuration. Note that each path can have it's own setup file. ["CEL Authenticator"=AUTHAGENT_CEL=agent"] /some/path/or/other/* r+w,param=WASD_ROOT:[SRC.AGENT]AUTHAGENT_CEL.LIS /another/path/* r+w,param=WASD_ROOT:[LOCAL]AUTHAGENT_CEL.LIS INSTALLED IMAGE --------------- To access the SYSUAF and possibly protected setup files this program needs to be installed with SYSPRV. $ INSTALL ADD WASD_EXE:AUTHAGENT_CEL /OPEN /HEADER /SHARED /PRIV=SYSPRV and have an ACL attached to prevent unexpected access: $ SET SECURITY WASD_EXE:AUTHAGENT_CEL.EXE - /ACL=((IDENT=HTTP$SERVER,ACCESS=READ+EXEC),(IDENT=*,ACCESS=NONE)) If to be configured as part of a site's environment these two requirements would need to be incorporated into the site Web service startup procedures. As this image must be installed privileged it will require a DCL procedure wrapper to activate it (all such image-installed privileged scripts require this arrangement), WASD_ROOT:[SCRIPT]AUTHAGENT_CEL.COM containing: $!(JUST ALLOWS IMAGE-INSTALLED PRIVILEGES TO BE ACTIVIATED) $ RUN WASD_EXE:AUTHAGENT_CEL.EXE LOGICAL NAMES ------------- AUTHAGENT_CEL$DBUG turns on all "if (Debug)" statements AUTHAGENT_CEL$WATCH turns on agent "000 message" WATCH statements Debug statements do not work very well inside authentication agent scripts. Use the WATCH statements using the server WATCH facility to observe script processing (for debug purposes). BUILD DETAILS ------------- Compile then link: $ @BUILD_AUTHAGENT_CEL To just link: $ @BUILD_AUTHAGENT_CEL LINK COPYRIGHT --------- Copyright (C) 1999-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 (update SOFTWAREVN as well) --------------- 10-JUN-2021 MGD v2.0.0, minimal changes for WASD v12... agent requirements 11-MAY-2007 MGD v1.1.2, belt-and-braces 23-DEC-2003 MGD v1.1.1, minor conditional mods to support IA64 28-OCT-2000 MGD v1.1.0, use CGILIB object module 31-OCT-1999 MGD v1.0.0, initial development */ /*****************************************************************************/ #define SOFTWAREVN "2.0.0" #define SOFTWARENM "AUTHAGENT_CEL" #ifdef __ALPHA # define SOFTWAREID SOFTWARENM " AXP-" SOFTWAREVN #endif #ifdef __ia64 # define SOFTWAREID SOFTWARENM " IA64-" SOFTWAREVN #endif #ifdef __x86_64 # define SOFTWAREID SOFTWARENM " X86-" SOFTWAREVN #endif /* standard C header files */ #include #include #include #include #include #include /* VMS related header files */ #include #include #include #include #include /* not defined in VAX C 3.2 */ #define UAI$M_RESTRICTED 0x8 #define UAI$C_PURDY_S 3 /* Internet-related header files */ #include #include #include #include /* application header files */ #include #define VMSok(x) ((x) & STS$M_SUCCESS) #define VMSnok(x) !(((x) & STS$M_SUCCESS)) #define boolean int #define true 1 #define false 0 #define ISLWS(c) (c == ' ' || c == '\t') #define ISEOL(c) (c == '\0' || c == '\n') /******************/ /* global storage */ /******************/ /* UAI flags that disallow SYSUAF authentication */ unsigned long DisallowVmsFlags = UAI$M_DISACNT | UAI$M_PWD_EXPIRED | UAI$M_PWD2_EXPIRED | UAI$M_CAPTIVE | UAI$M_RESTRICTED | 0; /* authorized privileges that disallow SYSUAF authentication */ unsigned long DisallowVmsPrivileges = PRV$M_SYSPRV | 0; int CgiPlusUsageCount; char Utility [] = "AUTHAGENT_CEL"; boolean Debug, DebugWatch; char SoftwareId [48]; unsigned long SysPrvMask [2] = { PRV$M_SYSPRV, 0 }; /*****************************************************************************/ /* */ main () { int status; char *cptr; /*********/ /* begin */ /*********/ Debug = (getenv ("AUTHAGENT_CEL$DBUG") != NULL); DebugWatch = (getenv ("AUTHAGENT_CEL$WATCH") != NULL); CgiLibEnvironmentSetDebug (Debug); CgiLibEnvironmentInit (0, NULL, false); /* MUST only be executed in a CGIplus environment! */ if (!CgiLibEnvironmentIsCgiPlus ()) { CgiLibResponseHeader (502, "text/plain"); fputs ("CGIplus!\n", stdout); exit (SS$_NORMAL); } for (;;) { /* block waiting for the next request */ CgiLibVar (""); CgiPlusUsageCount++; if (cptr = CgiLibVarNull("REQUEST_METHOD")) if (!*cptr) { /* proctored into existance */ CgiLibResponseHeader (204, "application/proctor"); CgiLibCgiPlusEOF (); continue; } CgiLibCgiPlusCallout ("!AGENT-BEGIN: %s (%s) usage:%d", SoftwareId, CgiLibEnvironmentVersion(), CgiPlusUsageCount); ProcessRequest (); CgiLibCgiPlusCallout ("!AGENT-END:"); CgiLibCgiPlusEOF (); } } /*****************************************************************************/ /* Main authentication request processing function. */ ProcessRequest () { int status; char *AuthAgentPtr, *PasswordPtr, *UserNamePtr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "ProcessRequest()\n"); /* provide the server attention "escape" sequence record */ if (!Debug) CgiLibCgiPlusESC (); /* ensure this is being invoked by the server */ if ((AuthAgentPtr = CgiLibVarNull ("WWW_AUTH_AGENT")) == NULL) exit (SS$_ABORT); /* belt and braces */ fprintf (stdout, "100 AUTHAGENT-CALLOUT\n"); fflush (stdout); if (DebugWatch) { /* just a comment that can be WATCHed */ fprintf (stdout, "000 [%d] %s\n", __LINE__, SoftwareId); fflush (stdout); } UserNamePtr = CgiLibVar ("WWW_REMOTE_USER"); PasswordPtr = CgiLibVar ("WWW_AUTH_PASSWORD"); AuthorizeUser (AuthAgentPtr, UserNamePtr, PasswordPtr); /* provide the "escape" end-of-text sequence record */ if (!Debug) CgiLibCgiPlusEOT (); } /*****************************************************************************/ /* Open the CEL setup file. Read each line parsing the authorization components out of those that contain setup data. Then process the username, password and host restriction components to return true if access is allowed, or false otherwise. 'AuthAgentPtr' will contain the "param=" specified setup file name. */ AuthorizeUser ( char *AuthAgentPtr, char *UserNamePtr, char *PasswordPtr ) { register char *cptr, *sptr, *zptr; boolean AccessOk; int status, LineCount; char Line [256], CelRealm [64], CelHost [128], CelPassword [64], CelUserName [64]; FILE *CelFile; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "AuthorizeUser() |%s|%s|%s|\n", AuthAgentPtr, UserNamePtr, PasswordPtr); if (DebugWatch) { fprintf (stdout, "000 [%d] authorizing \"%s\" from \"%s\"\n", __LINE__, UserNamePtr, AuthAgentPtr); fflush (stdout); } /* turn on SYSPRV to allow access to a possibly protected file */ status = sys$setprv (1, &SysPrvMask, 0, 0); if (VMSnok(status) || status == SS$_NOTALLPRIV) { if (DebugWatch) { fprintf (stdout, "000 [%d] sys$setprv() %%X%08.08X\n", __LINE__, status); fflush (stdout); } fprintf (stdout, "500 Error %%X%08.08X\n", status & 0xfffffffe); fflush (stdout); return; } CelFile = fopen (AuthAgentPtr, "r", "shr=get"); /* turn off SYSPRV */ if (VMSnok (status = sys$setprv (0, &SysPrvMask, 0, 0))) exit (status); if (CelFile == NULL) { status = vaxc$errno; if (DebugWatch) { fprintf (stdout, "000 [%d] error %%X%08.08X opening file \"%s\"\n", __LINE__, status, AuthAgentPtr); fflush (stdout); } fprintf (stdout, "500 Error %%X%08.08X opening file.\n", status); fflush (stdout); return; } CelRealm[0] = '\0'; LineCount = 0; /*************/ /* read file */ /*************/ while (fgets (Line, sizeof(Line), CelFile) != NULL) { LineCount++; if (DebugWatch) { for (cptr = Line; !ISEOL(*cptr); cptr++); *cptr = '\0'; fprintf (stdout, "000 [%d] line %d \"%s\"\n", __LINE__, LineCount, Line); fflush (stdout); } for (cptr = Line; !ISEOL(*cptr) && ISLWS(*cptr); cptr++); if (ISEOL(*cptr)) continue; if (*cptr == '#') continue; CelHost[0] = CelPassword[0] = CelUserName[0] = '\0'; if (strsame (cptr, "", 7)) { /*************/ /* realm tag */ /*************/ for (cptr += 7; *cptr && ISLWS(*cptr); cptr++); zptr = (sptr = CelRealm) + sizeof(CelRealm)-1; while (!ISEOL(*cptr) && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; if (Debug) fprintf (stdout, "CelRealm |%s|\n", CelRealm); continue; } /************/ /* username */ /************/ if (*cptr == '*') { *(unsigned short*)CelUserName = '*\0'; while (*cptr == '*') cptr++; while (*cptr && ISLWS(*cptr)) cptr++; } if (*cptr != '@') { /* not a restriction host, must be username */ zptr = (sptr = CelUserName) + sizeof(CelUserName)-1; while (!ISEOL(*cptr) && !ISLWS(*cptr) && *cptr != '@' && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; if (Debug) fprintf (stdout, "CelUserName |%s|\n", CelUserName); while (*cptr && ISLWS(*cptr)) cptr++; } /*********************/ /* host name/address */ /*********************/ if (*cptr == '@') { /* restriction host name/address */ cptr++; zptr = (sptr = CelHost) + sizeof(CelHost)-1; /* force to lower case (for case-less comparison) */ while (!ISEOL(*cptr) && !ISLWS(*cptr) && sptr < zptr) *sptr++ = tolower(*cptr++); *sptr = '\0'; if (Debug) fprintf (stdout, "CelHost |%s|\n", CelHost); while (*cptr && ISLWS(*cptr)) cptr++; } /******************/ /* authentication */ /******************/ /* now the authentication source (or password) */ zptr = (sptr = CelPassword) + sizeof(CelPassword)-1; while (!ISEOL(*cptr) && !ISLWS(*cptr) && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; if (Debug) fprintf (stdout, "CelPassword |%s|\n", CelPassword); /***********/ /* process */ /***********/ if (DebugWatch) { fprintf (stdout, "000 [%d] authorize \"%s\"@\"%s\" \"%s\"\n", __LINE__, CelUserName, CelHost, CelPassword); fflush (stdout); } if (!CelPassword[0] || (!CelUserName[0] && !CelHost[0])) { /* must have a password, cannot have no username and no host */ if (DebugWatch) { fprintf (stdout, "000 [%d] configuration problem at line %d of \"%s\"\n", __LINE__, LineCount, AuthAgentPtr); fflush (stdout); } continue; } /* if any literal username doesn't match then process next line */ if (CelUserName[0] && CelUserName[0] != '*' && !strsame (UserNamePtr, CelUserName, -1)) continue; if (CelHost[0] && !MatchHost(CelHost)) { /* does not match the host requirement, immediate 403 */ if (DebugWatch) { fprintf (stdout, "000 [%d] restricted by host at line %d of \"%s\"\n", __LINE__, LineCount, AuthAgentPtr); fflush (stdout); } fprintf (stdout, "403 Forbidden.\n"); fflush (stdout); fclose (CelFile); return; } if (!CelUserName[0] || (CelPassword[0] != '*' && strsame (PasswordPtr, CelPassword, -1)) || (CelPassword[0] == '*' && MatchSysUaf (UserNamePtr, PasswordPtr))) { /* no username, or literal password or SYSUAF password match */ fprintf (stdout, "200 READ+WRITE\n"); fflush (stdout); fclose (CelFile); return; } /* host OK, but not authenticated, return a 401 status */ break; } fclose (CelFile); /*******************/ /* nothing matched */ /*******************/ if (CelRealm[0]) fprintf (stdout, "401 \"%s\"\n", CelRealm); else fprintf (stdout, "401 Not authenticated\n"); fflush (stdout); } /*****************************************************************************/ /* Wildcard or literal match of client host address against that specified in the CEL setup file. If the host specified is an alpha-numeric then compare it to the server-supplied host name, and if this is numeric because DNS lookup is disabled then lookup the address and use the results. Return true if match is OK, false otherwise. */ boolean MatchHost (char *CelHost) { register char *cptr; int status, IpAddress; char *RemoteAddrPtr, *RemoteHostPtr; struct hostent *HostEntryPtr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "RestrictOnHost() |%s|\n", CelHost); if (DebugWatch) { fprintf (stdout, "000 [%d] match host \"%s\"\n", __LINE__, CelHost); fflush (stdout); } for (cptr = CelHost; *cptr && !isalpha(*cptr); cptr++); if (*cptr) { /* alpha-numeric host specification */ RemoteHostPtr = CgiLibVar ("WWW_REMOTE_HOST"); for (cptr = RemoteHostPtr; *cptr && !isalpha(*cptr); cptr++); if (*cptr) { /* server DNS resolution turned on, just use host name */ if (DebugWatch) { fprintf (stdout, "000 [%d] match \"%s\" by \"%s\"\n", __LINE__, RemoteHostPtr, CelHost); fflush (stdout); } return (decc$match_wild (RemoteHostPtr, CelHost)); } /* need to convert the address into a host name */ if (DebugWatch) { fprintf (stdout, "000 [%d] resolve host address \"%s\"\n", __LINE__, RemoteHostPtr); fflush (stdout); } IpAddress = inet_addr (RemoteHostPtr); if (Debug) fprintf (stdout, "IpAddress: %08.08X\n", IpAddress); if (IpAddress == -1) return (false); if ((HostEntryPtr = gethostbyaddr (&IpAddress, sizeof(IpAddress), AF_INET)) == NULL) { if (DebugWatch) { fprintf (stdout, "000 [%d] host address \"%s\" not resolved\n", __LINE__, RemoteHostPtr); fflush (stdout); } return (false); } if (DebugWatch) { fprintf (stdout, "000 [%d] match \"%s\" by \"%s\"\n", __LINE__, HostEntryPtr->h_name, CelHost); fflush (stdout); } return (decc$match_wild (HostEntryPtr->h_name, CelHost)); } else { /* no alphas, presume dotted-decimal address */ RemoteAddrPtr = CgiLibVar ("WWW_REMOTE_ADDR"); if (DebugWatch) { fprintf (stdout, "000 [%d] match \"%s\" by \"%s\"\n", __LINE__, RemoteAddrPtr, CelHost); fflush (stdout); } return (decc$match_wild (RemoteAddrPtr, CelHost)); } } /*****************************************************************************/ /* Verify the request username/password from the on-disk SYSUAF database. Get the specified user's flags, authorized privileges, quadword password, hash salt and encryption algorithm from SYSUAF. If any of specified bits in the flags are set (e.g. "disusered") then fail the password authentication. Using the salt and encryption algorithm hash the supplied password and compare it to the UAF hashed password. A SYSPRV privileged account is always failed. Return true if the password is validated and there are no account restrictions, false otherwise. */ boolean MatchSysUaf ( char *UserNamePtr, char *PasswordPtr ) { static unsigned long Context = -1; static unsigned long UaiFlags, UaiUic; static unsigned long UaiPriv [2], HashedPwd [2], UaiPwd [2]; static unsigned short UaiSalt; static unsigned char UaiEncrypt; static char UserName [15+1], Password [39+1]; static $DESCRIPTOR (UserNameDsc, UserName); static $DESCRIPTOR (PasswordDsc, Password); static struct { short BufferLength; short ItemCode; void *BufferPtr; void *LengthPtr; } UaiItems [] = { { sizeof(UaiUic), UAI$_UIC, &UaiUic, 0 }, { sizeof(UaiFlags), UAI$_FLAGS, &UaiFlags, 0 }, { sizeof(UaiPriv), UAI$_PRIV, &UaiPriv, 0 }, { sizeof(UaiPwd), UAI$_PWD, &UaiPwd, 0 }, { sizeof(UaiEncrypt), UAI$_ENCRYPT, &UaiEncrypt, 0 }, { sizeof(UaiSalt), UAI$_SALT, &UaiSalt, 0 }, { 0, 0, 0, 0 } }; register char *cptr, *sptr, *zptr; int status, setprvStatus; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "MatchSysUaf()\n"); /* to upper case! (just truncate if too long) */ zptr = (sptr = UserName) + sizeof(UserName)-1; for (cptr = UserNamePtr; *cptr && sptr < zptr; *sptr++ = toupper(*cptr++)); *sptr = '\0'; UserNameDsc.dsc$w_length = sptr - UserName; if (DebugWatch) { fprintf (stdout, "000 [%d] authenticate \"%s\" from SYSUAF\n", __LINE__, UserName); fflush (stdout); } /* turn on SYSPRV to allow access to SYSUAF records */ setprvStatus = sys$setprv (1, &SysPrvMask, 0, 0); if (VMSnok(setprvStatus) || setprvStatus == SS$_NOTALLPRIV) { if (DebugWatch) { fprintf (stdout, "000 [%d] sys$setprv() %%X%08.08X\n", __LINE__, setprvStatus); fflush (stdout); } fprintf (stdout, "500 Error %%X%08.08X\n", setprvStatus & 0xfffffffe); fflush (stdout); return (false); } status = sys$getuai (0, &Context, &UserNameDsc, &UaiItems, 0, 0, 0); if (Debug) fprintf (stdout, "sys$getuai() %%X%08.08X\n", status); /* turn off SYSPRV */ if (VMSnok (setprvStatus = sys$setprv (0, &SysPrvMask, 0, 0))) exit (setprvStatus); if (VMSnok (status)) { if (DebugWatch) { fprintf (stdout, "000 [%d] sys$getuai() %%X%08.08X\n", __LINE__, status); fflush (stdout); } return (false); } /* automatically disallow if any of these flags are set! */ if (UaiFlags & DisallowVmsFlags) { if (DebugWatch) { fprintf (stdout, "000 [%d] failed on SYSUAF flags\n", __LINE__); fflush (stdout); } return (false); } if (UaiPriv[0] & DisallowVmsPrivileges) { /* exclude accounts with extended privileges */ if (DebugWatch) { fprintf (stdout, "000 [%d] failed on SYSUAF privileges\n", __LINE__); fflush (stdout); } return (false); } /* to upper case! (just truncate if too long) */ zptr = (sptr = Password) + sizeof(Password)-1; for (cptr = PasswordPtr; *cptr && sptr < zptr; *sptr++ = toupper(*cptr++)); *sptr = '\0'; PasswordDsc.dsc$w_length = sptr - Password; status = sys$hash_password (&PasswordDsc, UaiEncrypt, UaiSalt, &UserNameDsc, &HashedPwd); if (Debug) fprintf (stdout, "sys$hash_password() %%X%08.08X\n", status); if (VMSnok (status)) { if (DebugWatch) { fprintf (stdout, "000 [%d] sys$hash_password() %%X%08.08X\n", __LINE__, status); fflush (stdout); } return (false); } if (HashedPwd[0] != UaiPwd[0] || HashedPwd[1] != UaiPwd[1]) { if (DebugWatch) { fprintf (stdout, "000 [%d] failed on SYSUAF password\n", __LINE__); fflush (stdout); } return (false); } return (true); } /****************************************************************************/ /* Does a case-insensitive, character-by-character string compare and returns true if two strings are the same, or false if not. If a maximum number of characters are specified only those will be compared, if the entire strings should be compared then specify the number of characters as 0. */ boolean strsame ( register char *sptr1, register char *sptr2, register int count ) { while (*sptr1 && *sptr2) { if (toupper (*sptr1++) != toupper (*sptr2++)) return (false); if (count) if (!--count) return (true); } if (*sptr1 || *sptr2) return (false); else return (true); } /*****************************************************************************/