/*****************************************************************************/ /* AuthIdent.c Licensed under the Apache License, Version 2.0 (the License); you may not use this software 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. This module provides functions related to authorization via the RFC1413 'Identification Protocol'. Although used for authentication RFC1413 does not describe itself as such, just as an identification protocol. The client (this module) waits a maximum of thirty seconds for a response from the ident server. It always closes the connection immediately after processing the response (mainly because the three ident daemons this module was tested against always closed the server connection proactively, even though the RFC allows for multiple requests via the one TCP/IP connect). The 'RemoteUser' generated by this module comprises the RCF1413 name returned in the response plus the remote system IP dotted-decimal address, in the following format: "name@nnn.nnn.nnn.nnn" (which allows approximately 30 characters for the name, which should be ample). See AUTH.C for overall detail on the WASD authorization environment. VERSION HISTORY --------------- 27-APR-2021 MGD BSD 4.4 sockaddr.. IO$M_EXTEND to $QIO 10-JUN-2005 MGD make EXQUOTA (ASTLM) a little more obvious 10-APR-2004 MGD modifications to support IPv6 04-AUG-2002 MGD with DNS lookup use host name to construct remote user 04-AUG-2001 MGD support module WATCHing 23-APR-2001 MGD more flexibility in response parsing 20-APR-2001 MGD bind service address to connecting socket 13-FEB-2001 MGD initial */ /*****************************************************************************/ #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 /* VMS related header files */ #include #include #include #include /* application related header files */ #include "wasd.h" #define WASD_MODULE "AUTHIDENT" #if WATCH_MOD #define FI_NOLI WASD_MODULE, __LINE__ #else /* in production let's keep the exact line to ourselves! */ #define FI_NOLI WASD_MODULE, 0 #endif #define AUTH_IDENT_PORT 113 /********************/ /* external storage */ /********************/ extern int EfnWait, EfnNoWait; extern const int64 Delta30Sec; extern char ErrorSanityCheck []; extern struct dsc$descriptor TcpIpDeviceDsc; extern ACCOUNTING_STRUCT *AccountingPtr; extern TCP_SOCKET_ITEM TcpIpSocket4, TcpIpSocket6; extern VMS_ITEM_LIST2 TcpIpFullDuplexCloseOption; extern WATCH_STRUCT Watch; /****************************************************************************/ /* Begin RFC1413 identification. Authorization's all asynchronous from here on in. */ void AuthIdentBegin ( REQUEST_STRUCT *rqptr, REQUEST_AST AstFunction ) { static BOOL UseFullDuplexClose = true; int cnt, qiofun, status; AUTH_IDENT *idptr; CLIENT_STRUCT *clptr; SOCKADDRESS SocketName; SOCKADDRIN *sin4ptr; SOCKADDRIN6 *sin6ptr; TCP_SOCKET_ITEM *TcpSocketPtr; VMS_ITEM_LIST2 SocketNameItem; VMS_ITEM_LIST2 *il2ptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH, "AuthIdentBegin() !&A !&I", AstFunction, &rqptr->ClientPtr->IpAddress); clptr = rqptr->ClientPtr; /* after calling this function authorization completes asynchronously! */ rqptr->rqAuth.AstFunction = rqptr->rqAuth.AstFunctionBuffer; rqptr->rqAuth.FinalStatus = AUTH_PENDING; /* no need to free this as it will be disposed of at request run-down */ idptr = VmGetHeap (rqptr, sizeof(AUTH_IDENT)); idptr->RequestPtr = rqptr; idptr->AuthAstFunction = AstFunction; if (WATCHING (rqptr, WATCH_AUTH)) WatchThis (idptr->RequestPtr, WATCH_AUTH, "RFC1413 connect !AZ to !AZ,!UL", rqptr->ServicePtr->ServerIpAddressString, &clptr->IpAddressString, AUTH_IDENT_PORT); /* assign a channel to the internet template device */ if (VMSnok (status = sys$assign (&TcpIpDeviceDsc, &idptr->IdentChannel, 0, 0))) { /* leave it to the AST function to report! */ idptr->ConnectIOsb.Status = status; SysDclAst (AuthIdentConnectAst, idptr); return; } /* bind to the IP address of the service (for multi-homed hosts) */ if (IPADDRESS_IS_V4 (&rqptr->ServicePtr->ServerIpAddress)) { SOCKADDRESS_ZERO4 (&SocketName); sin4ptr = &SocketName.sa.v4; sin4ptr->SIN$B_FAMILY = TCPIP$C_AF_INET; sin4ptr->SIN$W_PORT = 0; /* driver allocates port */ IPADDRESS_SET4 (&sin4ptr->SIN$L_ADDR, &rqptr->ServicePtr->ServerIpAddress) il2ptr = &SocketNameItem; il2ptr->buf_len = sizeof(SOCKADDRIN); il2ptr->item = TCPIP$C_SOCK_NAME; il2ptr->buf_addr = &SocketName.sa.v4; } else if (IPADDRESS_IS_V6 (&rqptr->ServicePtr->ServerIpAddress)) { SOCKADDRESS_ZERO6 (&SocketName); sin6ptr = &SocketName.sa.v6; memset (sin6ptr, 0, sizeof(SOCKADDRIN6)); sin6ptr->SIN6$B_FAMILY = TCPIP$C_AF_INET6; sin6ptr->SIN6$W_PORT = 0; /* driver allocates port */ IPADDRESS_SET6 (sin6ptr->SIN6$R_ADDR_OVERLAY.SIN6$T_ADDR, &rqptr->ServicePtr->ServerIpAddress) il2ptr = &SocketNameItem; il2ptr->buf_len = sizeof(SOCKADDRIN6); il2ptr->item = TCPIP$C_SOCK_NAME; il2ptr->buf_addr = &SocketName.sa.v6; } else ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); if (IPADDRESS_IS_V4 (&clptr->IpAddress)) { TcpSocketPtr = &TcpIpSocket4; qiofun = IO$_SETMODE; } else if (IPADDRESS_IS_V6 (&clptr->IpAddress)) { TcpSocketPtr = &TcpIpSocket6; qiofun = IO$_SETMODE | IO$M_EXTEND; } else ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); /* make the channel a TCP, connection-oriented, address-bound socket */ if (UseFullDuplexClose) { status = sys$qiow (EfnWait, idptr->IdentChannel, qiofun, &idptr->ConnectIOsb, 0, 0, TcpSocketPtr, 0, &SocketNameItem, 0, &TcpIpFullDuplexCloseOption, 0); /* Multinet 3.2 UCX driver barfs on FULL_DUPLEX_CLOSE, try without */ if (VMSok (status) && VMSnok (idptr->ConnectIOsb.Status)) { /* deassign existing channel, assign a new channel, before retrying */ sys$dassgn (idptr->IdentChannel); if (VMSnok (status = sys$assign (&TcpIpDeviceDsc, &idptr->IdentChannel, 0, 0))) { /* leave it to the AST function to report! */ idptr->ConnectIOsb.Status = status; SysDclAst (AuthIdentConnectAst, idptr); return; } } } if (!UseFullDuplexClose || VMSok (status) && VMSnok (idptr->ConnectIOsb.Status)) { /* Multinet 3.2 UCX driver barfs on FULL_DUPLEX_CLOSE, use without */ status = sys$qiow (EfnWait, idptr->IdentChannel, qiofun, &idptr->ConnectIOsb, 0, 0, TcpSocketPtr, 0, &SocketNameItem, 0, 0, 0); /* seeing this worked OK let's not bother with the first one again */ if (UseFullDuplexClose && VMSok (status) && VMSok (idptr->ConnectIOsb.Status)) UseFullDuplexClose = false; } if (VMSok (status) && VMSnok (idptr->ConnectIOsb.Status)) status = idptr->ConnectIOsb.Status; if (VMSnok (status)) { /* leave it to the AST function to report! */ idptr->ConnectIOsb.Status = status; SysDclAst (AuthIdentConnectAst, idptr); return; } /* now the destination address and port details */ if (IPADDRESS_IS_V4 (&clptr->IpAddress)) { SOCKADDRESS_ZERO4 (&SocketName); sin4ptr = &SocketName.sa.v4; sin4ptr->SIN$B_FAMILY = TCPIP$C_AF_INET; sin4ptr->SIN$W_PORT = htons (AUTH_IDENT_PORT); IPADDRESS_SET4 (&sin4ptr->SIN$L_ADDR, &clptr->IpAddress) il2ptr = &SocketNameItem; il2ptr->buf_len = sizeof(SOCKADDRIN); il2ptr->item = TCPIP$C_SOCK_NAME; il2ptr->buf_addr = sin4ptr; qiofun = IO$_ACCESS; } else if (IPADDRESS_IS_V6 (&clptr->IpAddress)) { SOCKADDRESS_ZERO6 (&SocketName); sin6ptr = &SocketName.sa.v6; sin6ptr->SIN6$B_FAMILY = TCPIP$C_AF_INET6; sin6ptr->SIN6$W_PORT = htons (AUTH_IDENT_PORT); IPADDRESS_SET6 (sin6ptr->SIN6$R_ADDR_OVERLAY.SIN6$T_ADDR, &clptr->IpAddress) il2ptr = &SocketNameItem; il2ptr->buf_len = sizeof(SOCKADDRIN); il2ptr->item = TCPIP$C_SOCK_NAME; il2ptr->buf_addr = sin6ptr; qiofun = IO$_ACCESS | IO$M_EXTEND; } else ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); status = sys$qio (EfnNoWait, idptr->IdentChannel, qiofun, &idptr->ConnectIOsb, &AuthIdentConnectAst, idptr, 0, 0, &SocketNameItem, 0, 0, 0); /* if OK return waiting for the connect AST to be delivered */ if (VMSok (status)) return; /* if resource wait enabled the only quota not waited for is ASTLM */ if (status == SS$_EXQUOTA) { /* no ASTs means not much of anything else can happen so just exit! */ sys$canexh(0); /* make the message a little more meaningful */ sys$exit (SS$_EXASTLM); } /* connect failed, call AST explicitly, status in the IOsb */ idptr->ConnectIOsb.Status = status; SysDclAst (AuthIdentConnectAst, idptr); } /****************************************************************************/ /* Connection to ident daemon has completed (successfully or not). */ AuthIdentConnectAst (AUTH_IDENT *idptr) { int status; unsigned short Length; char *cptr; CLIENT_STRUCT *clptr; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ rqptr = idptr->RequestPtr; clptr = rqptr->ClientPtr; if (WATCHMOD (rqptr, WATCH_MOD_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH, "AuthIdentConnectAst() !&F !&S", &AuthIdentConnectAst, idptr->ConnectIOsb.Status); if (VMSnok (idptr->ConnectIOsb.Status)) { /*****************/ /* connect error */ /*****************/ if (WATCHING (rqptr, WATCH_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_AUTH, "RFC1413 connect !&S", idptr->ConnectIOsb.Status); /* dispose of the non-connected channel */ AuthIdentCloseSocket (idptr); /* continue to process the authorization */ rqptr->rqAuth.FinalStatus = idptr->ConnectIOsb.Status; /* let's renegotiate with the client, trying to get a certificate */ SysDclAst (idptr->AuthAstFunction, rqptr); return; } /***********************/ /* write ident request */ /***********************/ FaoToBuffer (idptr->RequestString, sizeof(idptr->RequestString), &Length, "!UL, !UL\r\n", clptr->IpPort, rqptr->ServicePtr->ServerPort); if (WATCHING (rqptr, WATCH_AUTH)) { WatchThis (WATCHITM(rqptr), WATCH_AUTH, "RFC1413 request !UL bytes", Length); WatchDataDump (idptr->RequestString, Length); } status = sys$qiow (EfnWait, idptr->IdentChannel, IO$_WRITEVBLK, &idptr->WriteIOsb, 0, 0, idptr->RequestString, Length, 0, 0, 0, 0); if (VMSok (status)) status = idptr->WriteIOsb.Status; if (VMSnok (status)) { /* not OK, becomes the final status, continue processing */ rqptr->rqAuth.FinalStatus = idptr->WriteIOsb.Status; SysDclAst (idptr->AuthAstFunction, rqptr); return; } /***********************/ /* read ident response */ /***********************/ status = sys$qio (EfnNoWait, idptr->IdentChannel, IO$_READVBLK, &idptr->ReadIOsb, &AuthIdentReadAst, idptr, idptr->ResponseString, sizeof(idptr->ResponseString)-1, 0, 0, 0, 0); if (WATCHMOD (rqptr, WATCH_MOD_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH, "RFC1413 $qio() read"); if (VMSok (status)) { if (VMSnok (status = sys$setimr (0, &Delta30Sec, &AuthIdentReadTimeoutAst, idptr, 0))) ErrorExitVmsStatus (status, NULL, FI_LI); if (WATCHMOD (rqptr, WATCH_MOD_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH, "RFC1413 $setimr() 30 seconds"); return; } /* if resource wait enabled the only quota not waited for is ASTLM */ if (status == SS$_EXQUOTA) { /* no ASTs means not much of anything else can happen so just exit! */ sys$canexh(0); /* make the message a little more meaningful */ sys$exit (SS$_EXASTLM); } /* read failed, call AST explicitly, status in the IOsb */ idptr->ReadIOsb.Status = status; idptr->ReadIOsb.Count = 0; SysDclAst (AuthIdentReadAst, idptr); } /*****************************************************************************/ /* Ident daemon has not responded within 30 seconds. */ AuthIdentReadTimeoutAst (AUTH_IDENT *idptr) { REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ rqptr = idptr->RequestPtr; if (WATCHMOD (rqptr, WATCH_MOD_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH, "AuthIdentReadTimeoutAst() !&F", &AuthIdentReadTimeoutAst); sys$cancel (idptr->IdentChannel); } /*****************************************************************************/ /* The ident daemon has responded. */ AuthIdentReadAst (AUTH_IDENT *idptr) { int status, LocalPort, RemotePort, UserDetailsLength; char *cptr, *sptr, *zptr; char UserDetails [64]; CLIENT_STRUCT *clptr; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ rqptr = idptr->RequestPtr; clptr = rqptr->ClientPtr; if (WATCHMOD (rqptr, WATCH_MOD_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH, "AuthIdentReadAst() !&F !&X !UL", &AuthIdentReadAst, idptr->ReadIOsb.Status, idptr->ReadIOsb.Count); /* cancel the time and close the socket */ sys$cantim (idptr, 0); if (VMSok (idptr->ReadIOsb.Status)) { /* zero bytes with a normal status is a definite no-no (TGV-Multinet) */ if (!idptr->ReadIOsb.Count) idptr->ReadIOsb.Status = SS$_ABORT; } if (VMSnok (idptr->ReadIOsb.Status)) { /* read error */ if (WATCHING (rqptr, WATCH_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_AUTH, "RFC1413 read !AZ,!UL !&S", &clptr->IpAddressString, AUTH_IDENT_PORT, idptr->ReadIOsb.Status); AuthIdentCloseSocket (idptr); /* continue to process the authorization */ rqptr->rqAuth.FinalStatus = idptr->ReadIOsb.Status; SysDclAst (idptr->AuthAstFunction, rqptr); return; } idptr->ResponseString[idptr->ReadIOsb.Count] = '\0'; if (WATCHING (rqptr, WATCH_AUTH)) { WatchThis (WATCHITM(rqptr), WATCH_AUTH, "RFC1413 response !UL bytes", idptr->ReadIOsb.Count); WatchDataDump (idptr->ResponseString, idptr->ReadIOsb.Count); } AuthIdentCloseSocket (idptr); /**********************/ /* parse the response */ /**********************/ /* format: "[ ]rem-port[ ],[ ]local-port[ ]:[ ]USERID[ ]:[ ]name[ ]" */ RemotePort = LocalPort = 0; for (cptr = idptr->ResponseString; *cptr && isspace(*cptr); cptr++); if (isdigit(*cptr)) RemotePort = atoi(cptr); while (*cptr && isdigit(*cptr)) cptr++; while (*cptr && (isspace(*cptr) || *cptr == ',')) cptr++; if (isdigit(*cptr)) LocalPort = atoi(cptr); while (*cptr && isdigit(*cptr)) cptr++; while (*cptr && (isspace(*cptr) || *cptr == ':')) cptr++; if (strsame (cptr, "USERID", 6)) { while (*cptr && *cptr != ':') cptr++; if (*cptr) cptr++; /* "user details" - operating system */ while (*cptr && isspace(*cptr)) cptr++; zptr = (sptr = UserDetails) + sizeof(UserDetails)-1; while (*cptr && !isspace(*cptr) && *cptr != ':' && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; UserDetailsLength = sptr - UserDetails; while (*cptr && (isspace(*cptr) || *cptr == ':')) cptr++; /* identifier */ zptr = (sptr = rqptr->RemoteUser) + sizeof(rqptr->RemoteUser); while (*cptr && !isspace(*cptr) && sptr < zptr) *sptr++ = *cptr++; if (sptr < zptr) *sptr++ = '@'; if (!*(cptr = clptr->Lookup.HostName)) cptr = &clptr->IpAddressString; while (*cptr && sptr < zptr) *sptr++ = *cptr++; if (sptr >= zptr) { sptr = rqptr->RemoteUser; ErrorGeneralOverflow (rqptr, FI_LI); } *sptr = '\0'; rqptr->RemoteUserLength = sptr - rqptr->RemoteUser; if (rqptr->RemoteUserLength && (RemotePort != clptr->IpPort || LocalPort != rqptr->ServicePtr->ServerPort)) { if (WATCHING (rqptr, WATCH_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_AUTH, "RFC1413 response ports?"); rqptr->RemoteUser[0] = '\0'; rqptr->RemoteUserLength = 0; } } if (rqptr->RemoteUserLength) { /* authenticated ... user can do anything (the path allows!) */ rqptr->rqAuth.UserCan = AUTH_READWRITE_ACCESS; rqptr->rqAuth.FinalStatus = SS$_NORMAL; rqptr->rqAuth.UserDetailsLength = UserDetailsLength; rqptr->rqAuth.UserDetailsPtr = VmGetHeap (rqptr, UserDetailsLength+1); strzcpy (rqptr->rqAuth.UserDetailsPtr, UserDetails, UserDetailsLength+1); strzcpy (rqptr->RemoteUserPassword, "anystringwilldo", sizeof(rqptr->RemoteUserPassword)); } else rqptr->rqAuth.FinalStatus = AUTH_DENIED_BY_FAIL; /* continue to process the authorization */ SysDclAst (idptr->AuthAstFunction, rqptr); } /****************************************************************************/ /* Just shut the socket down, bang! */ AuthIdentCloseSocket (AUTH_IDENT *idptr) { int status; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ rqptr = idptr->RequestPtr; if (WATCHMOD (rqptr, WATCH_MOD_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH, "AuthIdentCloseSocket() !&F", &AuthIdentCloseSocket); status = sys$dassgn (idptr->IdentChannel); if (WATCHING (rqptr, WATCH_AUTH)) { if (VMSok(status)) WatchThis (WATCHITM(rqptr), WATCH_AUTH, "RFC1413 close !AZ,!UL", rqptr->ClientPtr->IpAddressString, AUTH_IDENT_PORT); else WatchThis (WATCHITM(rqptr), WATCH_AUTH, "RFC1413 close !AZ,!UL !&S", rqptr->ClientPtr->IpAddressString, AUTH_IDENT_PORT, status); } } /****************************************************************************/