Perform secured web or sockets login
$Sir_Login is a $function that can be used to perform user logins. It is an unusual $function in several respects:
- It can only be used during:
Any attempt to use $Sir_Login outside of these two instances will result in request cancellation.
- It can only be invoked by a non-logged in user. Once a user is logged in, a $Sir_Login call will result in request cancellation.
- While free to all Janus Web Server and Janus Sockets customers, it is implemented as a pseudo-product that requires its own authorization. This is because some sites might view the mere presence of $Sir_Login as a security risk, so they would not want it available on their systems.
Therefore, to make $Sir_Login available at a Janus Web Server or Janus Sockets site, the following are necessary:
- $Sir_Login must be requested by the site.
- A new authorization zap must be applied to the Online load modules, or a new Online load module must be linked from object decks authorized for $Sir_Login.
$Sir_Login accepts six arguments and returns a 0 to indicate a successful login or a 1 to indicate login failure.
%result = $Sir_Login(userid, [accountid], [passwd], [newpasswd], [options], [outlistid])
|%result||A numerical result: 0 indicates successful login; 1 indicates failure.|
|userid||The user ID for which to attempt a login. This argument is required and must be non-null. At most sites, all Model 204 user IDs must be all uppercase, so you might need to call $UPCASE to translate this argument to uppercase.|
|accountid||The account ID to be used for a successful login. This argument is optional and, if not specified, the account ID is set to the same value as the user ID. Again, it may be appropriate in your environment to call $UPCASE to translate this argument to uppercase.|
|passwd||The password to be used for user authentication. This is an optional argument; if it is not specified or null, a trusted login is performed: the user ID is verified for validity, and if it is valid, the user is logged in without a password check. Obviously, not specifying this argument or specifying it as null should only be done when the application can be certain that the end-user is authorized to access the user ID.
No automatic case translation is performed on this argument. If CUSTOM is not set to 11 (allow mixed-case passwords), you must call $UpCase to translate this argument to uppercase.
Model 204 version 7.6 and earlier: Passwords can be as many as 8 characters long.
|newpasswd||A new password for the user ID. This is an optional argument, and if it is not specified or null, no new password is set on a successful login. If the third argument is null, this argument is ignored. If you want to ensure that the Model 204 user password is all uppercase, call $UpCase to translate this argument to uppercase.
|options||A blank-delimited set of option words. These options can be:
|outlistid||The sixth argument is the $list identifier of a $list to receive any terminal messages issued during the login process. These could be error messages during a failed login or information messages issued for a successful login. This is an optional parameter; if it is not specified, messages get sent to the standard output drivers, which could:
Any errors in the arguments such as a null or too-long user ID, a too-long password, or an invalid $list identifier cause request cancellation.
Usage notes and examples
Note: It cannot be overemphasized that before $Sir_Login is called, the password should be checked for a null value, unless the intent is to do a trusted login. Without such a check, unauthorized users can log in to an ID without that ID's password.
Trusted logins should be limited to cases where you are certain that the correct userid can be ascertained without the use of a password. In general, this can only really be done with TCP/IP with the use of certificates, though there might be cases where network and operating system configuration allows you to be certain that a userid that is passed over a connection is valid and trustworthy without a password.
Another case where trusted logins might be reasonable is when a password is retrieved from a connection, and the password is validated against a non-CCASTAT, non-external authorizer database. This database could be a Model 204 file that contains one-way encrypted passwords (saving unencrypted passwords is a very bad idea), or perhaps a database on another machine, such as an LDAP server which is queried via a Janus Sockets client request.
Protected Access and External Authorizers
One simple way to use certificates for login validation is by having a local certifying authority that gives users certificates that contain their user IDs. A connection from a client that contains a certificate signed by that certifying authority can then simply do a trusted login for the user ID passed in the common name portion of the certificate, on the theory that only the user in the certificate would have access to the signed certificate.
The following is a fragment of code from a Janus Web Server NEWSESCMD request that illustrates this technique:
IF $WEB_CERT_INFO('CNAME', 1) EQ 'Trusted CA' THEN %USERID = $WEB_CERT_INFO('CNAME') %RC = $Sir_Login(%USERID) IF NOT %RC THEN STOP END IF ...
A Janus Sockets server NEWSESCMD request that accomplishes the same thing is virtually identical (whether using a $function, as below, or using a comparable Socket object method):
IF $SOCK_CERT_INFO(1, 'CNAME', 1) EQ 'Trusted CA' THEN %USERID = $SOCK_CERT_INFO(1, 'CNAME') %RC = $Sir_Login(%USERID) IF NOT %RC THEN STOP END IF ....
Both these examples depend on the following:
- A trusted certifying authority certificate with a common name of "Trusted CA" must be added to the appropriate port with the JANUS ADDCA command.
- Only a single trusted CA with the indicated common name must exist. While it's unlikely that one would have trusted CA certificates for multiple certifying authorities with the same common name, this issue should still be kept in mind.
More sophisticated and complex approaches to client-certificate based user validation can be implemented using the same few $functions or object methods. For example, rather than using the common name in a certificate directly as a user ID, the common name could be mapped to a user ID by a database lookup:
IF $WEB_CERT_INFO('CNAME', 1) EQ 'Trusted CA' THEN %CNAME = $WEB_CERT_INFO('CNAME') F: IN FILE USERS FD CNAME EQ %CNAME END FIND FOR 1 RECORD IN F %RC = $Sir_Login(USERID) IF NOT %RC THEN STOP END IF . . . .
An even more sophisticated NEWSESCMD application could itself maintain the USERS file that contains, for example, a common name, MD5 certificate hash and a Model 204 user ID associated with the certificate. When a certificate is received, the common name and MD5 hash can be looked up in the database:
%CNAME = $WEB_CERT_INFO('CNAME') %MD5HASH = $WEB_CERT_INFO('MD5HASH') F: IN FILE USERS FD CNAME EQ %CNAME MD5HASH EQ %MD5HASH END FIND FOR 1 RECORD IN F %RC = $Sir_Login(USERID) IF NOT %RC THEN STOP END IF END FOR . . . .
If the received certificate is not in the database, the user ID and password could be obtained from HTTP headers and could be used for a logon attempt with a password. If the login fails or no user ID and password were received a "401 Unauthorized" response can be sent to the client to get a user ID and password:
%USERID = $WEB_USER %PASSWORD = $WEB_PASSWORD IF %USERID NE '' AND %PASSWORD NE '' THEN %RC = $Sir_Login(%USERID, %PASSWORD) IF NOT %RC THEN JUMP TO SUCCESS END IF END IF %RC = $WEB_DONE(401, 'Unauthorized') STOP . . . .
$Web_Password, like $Sir_Login, causes request cancellation if used outside of NEWSESCMD processing.
Finally, a successful login for a specific user ID and password suggests that user ID is the holder of the indicated certificate, so the certificate information can be saved in the USERS database for subsequent trusted logins:
SUCCESS: IN FILE USERS STORE RECORD CNAME = %CNAME MD5HASH = %MD5HASH USERID = %USERID END STORE STOP
Note that in the preceding example, it does not matter which certifying authority signed the user's certificate, as long as it was a certifying authority added to the port via the JANUS ADDCA command.
A similar technique can be used with Janus Sockets, though, of course, when a certificate is not in the database, the user ID and password need to be extracted in some way other than via $Web_User and $Web_Pass. The appropriate technique would largely depend on the protocol being used over TCP/IP.
The following illustrates how a user ID and password can be obtained from a user when using the telnet protocol over a Janus Sockets connection:
REPEAT FOREVER %RC = $SOCK_SEND(1, 'Userid: ') %RC = $SOCK_RECVPRS(1, %DATA, 9999999) %USERID = $UPCASE(%DATA) IF %USERID NE '' THEN %RC = $SOCK_SEND(1, 'Password: ') %RC = $SOCK_RECVPRS(1, %DATA, 9999999) %PASSWORD = $UPCASE(%DATA) END IF IF %PASSWORD NE '' THEN %LIST = $LISTNEW %LOGIN = $Sir_Login(%USERID, ,%PASSWORD, , ,%LIST) FOR %I FROM 1 TO $LISTCNT(%LIST) %RC = $SOCK_SENDLN(1, $LISTINF(%LIST, %I)) END FOR IF NOT %LOGIN THEN LOOP END END IF END IF %RC = $SOCK_SENDLN(1, 'Login failed') END REPEAT
Again, this kind of approach can be combined with a certificate-based approach where, if a received certificate is in the database, no user ID and password prompting is done, and a trusted login is performed.
Another use of $Sir_Login might be a case where all user validation is performed by a proxy server at a well known IP address. The network and proxy server machine are securely configured in such a way that, if Janus Web Server receives a request from a specific IP address, it can be certain of these:
- The request is coming from the proxy server.
- The proxy server has performed appropriate user validation for the request.
Finally, the proxy server in question is known to place the validated user ID into a nonstandard HTTP header parameter called "Userid". The following illustrates how a trusted login can be performed in such a situation:
IF $WEB_IPADDR EQ %PROXY_IPADDR THEN %USERID = $UPCASE($WEB_HDR_PARM('USERID')) IF %USERID NE '' THEN %RC = $Sir_Login(%USERID) IF NOT %RC THEN STOP END IF . . . .
In this example, the $Sir_Login will still fail if the user ID received from the proxy server is defined neither with the external authorizer nor in CCASTAT. If, as is likely, it is undesirable to try to maintain the user lists in both the proxy server and in the external authorizer or CCASTAT, a guest login can be performed with $Sir_Login:
IF $WEB_IPADDR EQ %PROXY_IPADDR THEN %USERID = $UPCASE($WEB_HDR_PARM('USERID')) IF %USERID NE '' THEN %RC = $Sir_Login(%USERID, , , , 'GUEST') IF NOT %RC THEN STOP END IF . . . .
In this case, the user will pick up default user privileges for the Online but will run under the user ID passed by the proxy server, even when the user is not defined in CCASTAT or to the external authorizer. This can be very useful for user tracking and auditing and for web-rule-based or APSY-based user access control.
If there are web-based DBA or system manager applications out there that one would not expect to be accessed through the proxy server, it might be worth making sure that the proxy server did not pass the user ID of a DBA or system manager who might have access to these critical applications:
IF $WEB_IPADDR EQ %PROXY_IPADDR THEN %USERID = $UPCASE($WEB_HDR_PARM('USERID')) IF $WINDEX('DBA SUPERKLUGE ZEUS', %USERID) THEN AUDIT 'Received userid ' %USERID 'from proxy server' %RC = $WEB_DONE(403, 'Forbidden') STOP END IF . . . .
The combination of $Sir_Login and NEWSESCMD processing provides a very flexible set of facilities that allows a site to tailor Janus Web Server and Janus Sockets user authentication as needed. With that flexibility comes the responsibility to ensure that the chosen user authentication approach is, indeed, secure. Toward this end, it is strongly recommended that NEWSESCMD code be examined by as many eyes as is reasonable, and that Rocket Software be contacted to review NEWSESCMD code.
A sample APSY subsystem that can be used with the NEWSESCMD subcommand is provided by Rocket Software (it is the same sample subsystem provided for use with the VTLAPSY parameter for IODEV7 users). The code is in the SIRIUS file, beginning with UL/SPF Version 7.7. Programs are VTLN.LOGIN and VTLN.ERROR, and the comments in VTLN.LOGIN explain how to customize the programs for the local environment.