#!/usr/local/bin/perl5.004 # $Id: dm_check_password.pl,v 1.10 1998/06/29 19:59:20 briansp Exp briansp $ use Krb4; use Socket; use Sys::Hostname; require 'getopts.pl'; # Define some constants. $service = 'documentum'; $srvtab = '/etc/krb-srvtab'; $user_config = '/etc/dm_userconfig.conf'; $program_name = 'dm_check_password.pl'; # Debugging help. #$debug = 1; if ($debug) { $debug_file = "/tmp/dm_check_password.out"; open DEBUG, ">>$debug_file"; } ########################################################################## # Documentum API Result Codes ########################################################################## $DM_EXT_APP_SUCCESS = 0; $DM_CHKPASS_BAD_LOGIN = 245; $DM_CHKPASS_PASSWORD_STALE = 246; $DM_CHKPASS_PASSWORD_EXPIRED = 247; $DM_CHKPASS_ACCOUNT_EXPIRED = 248; $DM_CHKPASS_ACCOUNT_DROPPED = 249; $DM_CHGPASS_PASSWORD_CRITERIA = 251; $DM_ASSUME_USER_SYSTEM_ERROR = 252; $DM_EXT_APP_OS_ERROR = 253; $DM_EXT_APP_NOT_IMPLEMENTED = 254; $DM_EXT_APP_UNEXPECTED_ERROR = 255; ########################################################################## # Kerberos 4 Result Codes ########################################################################## # Values returned from KDC operations. $KDC_OK = 0; # Request OK $KDC_NAME_EXP = 1; # Principal expired $KDC_SERVICE_EXP = 2; # Service expired $KDC_AUTH_EXP = 3; # Auth expired $KDC_PKT_VER = 4; # Protocol version unknown $KDC_P_MKEY_VER = 5; # Wrong master key version $KDC_S_MKEY_VER = 6; # Wrong master key version $KDC_BYTE_ORDER = 7; # Byte order unknown $KDC_PR_UNKNOWN = 8; # Principal unknown $KDC_PR_N_UNIQUE = 9; # Principal not unique $KDC_NULL_KEY = 10; # Principal has null key $KDC_GEN_ERR = 20; # Generic error from KDC # Values returned by mk_ap_req $MK_AP_OK = 0; # Success $MK_AP_TGTEXP = 26; # TGT Expired # Values returned by rd_ap_req $RD_AP_OK = 0; # Request authentic $RD_AP_UNDEC = 31; # Can't decode authenticator $RD_AP_EXP = 32; # Ticket expired $RD_AP_NYV = 33; # Ticket not yet valid $RD_AP_REPEAT = 34; # Repeated request $RD_AP_NOT_US = 35; # The ticket isn't for us $RD_AP_INCON = 36; # Request is inconsistent $RD_AP_TIME = 37; # delta_t too big $RD_AP_BADD = 38; # Incorrect net address $RD_AP_VERSION = 39; # protocol version mismatch $RD_AP_MSG_TYPE = 40; # invalid msg type $RD_AP_MODIFIED = 41; # message stream modified $RD_AP_ORDER = 42; # message out of order $RD_AP_UNAUTHOR = 43; # unauthorized request # Values returned by get_pw_tkt $GT_PW_OK = 0; # Got password changing tkt $GT_PW_NULL = 51; # Current PW is null $GT_PW_BADPW = 52; # Incorrect current password $GT_PW_PROT = 53; # Protocol Error $GT_PW_KDCERR = 54; # Error returned by KDC $GT_PW_NULLTKT = 55; # Null tkt returned by KDC # Values returned by send_to_kdc $SKDC_OK = 0; # Response received $SKDC_RETRY = 56; # Retry count exceeded $SKDC_CANT = 57; # Can't send request $INTK_OK = 0; # Ticket obtained $INTK_W_NOTALL = 61; # Not ALL tickets returned $INTK_BADPW = 62; # Incorrect password $INTK_PROT = 63; # Protocol Error $INTK_ERR = 70; # Other error # Error codes returned by ticket file utilities $NO_TKT_FIL = 76; # No ticket file found $TKT_FIL_ACC = 77; # Couldn't access tkt file $TKT_FIL_LCK = 78; # Couldn't lock ticket file $TKT_FIL_FMT = 79; # Bad ticket file format $TKT_FIL_INI = 80; # tf_init not called first # Error code returned by kparse_name $KNAME_FMT = 81; # Bad Kerberos name format ########################################################################## # Simple getspnam routine. sub getspnam ($) { my($username) = @_; my(%data); open(SHADOW,"/etc/shadow") || return; while() { chomp; my($name,$junk) = split(':',$_); my(@list) = split(':',$_); $data{$name} = [ @list]; } close(SHADOW); @{ $data{$username} }; } # Local Auth (/etc/{passwd,shadow}). sub dm_Local_Auth ($$) { my($username,$user_passwd) = @_; $sys_passwd_enc = (&getspnam($username))[1]; # Its important to check that we got something back from getspnam(), # since crypt() returns NULL with NULL salt. if (! $sys_passwd_enc) { # The user doesn't exist locally. Return and we'll try another method. return(0); } else { $user_passwd_enc = crypt $password, $sys_passwd_enc; if ($user_passwd_enc eq $sys_passwd_enc) { return(1); } else { $RESULT = $DM_CHKPASS_BAD_LOGIN; return(0); } } } sub dm_Krb_Passwd_Auth ($$) { my($username,$password) = @_; my($instance) = ""; my($lifetime) = 1; my($tkt_file) = '/tmp/dm_krb_auth.tkt'; my($service) = 'krbtgt'; $ENV{'KRBTKFILE'} = $tkt_file; my($realm) = Krb4::realmofhost($hostname); if (! $realm) { $RESULT = $DM_EXT_APP_OS_ERROR; return(0); } my($rc) = Krb4::get_pw_in_tkt($username,$instance,$realm,$service, $realm,$lifetime,$password); # Clean up after ourselves. if (-f $tkt_file) { unlink $tkt_file; } if ($rc == $GT_PW_OK) { return(1); } elsif ($rc == $INTK_BADPW) { $RESULT = $DM_CHKPASS_BAD_LOGIN; return(0); } elsif ($rc == $KDC_PR_UNKNOWN) { print DEBUG "Kerberos returned ", Krb4::get_err_txt($rc), "\n" if ($debug); $RESULT = $DM_CHKPASS_ACCOUNT_DROPPED; return(0); } else { print DEBUG "Kerberos returned ", Krb4::get_err_txt($rc), "\n" if ($debug); $RESULT = $DM_EXT_APP_OS_ERROR; return(0); } return(0); } sub dm_Krb_Tkt_Auth ($$$) { my($username,$ticket_data_encoded,$nonce_encoded) = @_; print DEBUG "in dm_krb_tkt_auth: $nonce_encoded\n"; my($nonce_prefix) = "KERBEROS_V4_NONCE__"; my($time) = time(); # First, get rid of the encoding on the password/ticket. # uuencode encrypted ticket data, then encode it with URI-style encoding $ticket_data_encoded =~ tr/+/ /; $ticket_data_encoded =~ s/%([0-9a-fA-F]{2})/pack("c",hex($1))/ge; $ticket_data = unpack "u", $ticket_data_encoded; # Now turn the data into a real live ticket. $ticket = Krb4::Ticket->new($ticket_data); # Same thing with nonce. $nonce_encoded =~ tr/+/ /; $nonce_encoded =~ s/%([0-9a-fA-F]{2})/pack("c",hex($1))/ge; $nonce_encrypted = unpack "u", $nonce_encoded; #print DEBUG "Got this far (1).\n"; my($hostname) = hostname(); if (! $hostname) { $RESULT = $DM_EXT_APP_OS_ERROR; return(0); } my($inaddr) = inet_aton($hostname); if (! $inaddr) { $RESULT = $DM_EXT_APP_OS_ERROR; return(0); } #print DEBUG "Got this far (2).\n"; my($realm) = Krb4::realmofhost($hostname); if (! $realm) { $RESULT = $DM_EXT_APP_OS_ERROR; return(0); } my($phost) = Krb4::get_phost($hostname,$realm,$service); if (! $phost) { $RESULT = $DM_EXT_APP_OS_ERROR; return(0); } #print DEBUG "Got this far (3).\n"; my($auth_data) = Krb4::rd_req($ticket,$service,$phost,$srvtab); if (! $auth_data) { if ($debug) { print DEBUG "Didn't get auth_data.\n"; print DEBUG Krb4::get_err_txt($Krb4::error), "\n"; } $RESULT = $DM_EXT_APP_OS_ERROR; return(0); } #print DEBUG "Got this far (4).\n"; my($principal) = $auth_data->pname; my($instance) = $auth_data->pinst; $auth_username = "$principal"; if ($instance) { $auth_username .= ".$instance"; } # If we don't find a config file entry, make sure the user in the # ticket matches the user in the auth request. if (! $config_data{$username}) { if ($auth_username ne $username) { $RESULT = $DM_CHKPASS_BAD_LOGIN; #print DEBUG "$auth_username <> $username\n"; return(0); } } else { # Otherwise see if the config data permits the user to log in. my($expr) = $config_data{$username}; if ($auth_username !~ m#$expr#) { #print DEBUG "$auth_username !~ $expr\n"; $RESULT = $DM_CHKPASS_BAD_LOGIN; #$RESULT = DM_EXT_APP_OS_ERROR; return(0); } } #print DEBUG "Got this far (5).\n"; # Check to make sure the ticket hasn't expired. my($tkt_lifetime) = $auth_data->life; my($tkt_issuetime) = $auth_data->time_sec; my($tkt_expiretime) = $tkt_issuetime + ($tkt_lifetime * 5 * 60); my $tkt_issuedate = localtime $tkt_issuetime; my $tkt_expiredate = localtime $tkt_expiretime; my $date = localtime $time; if ( $tkt_expiretime <= $time ) { $RESULT = $DM_CHKPASS_BAD_LOGIN; return(0); } # Decrypt the nonce and make sure that this isn't a replay # attack. my($session_key) = $auth_data->session; if (! $session_key) { print DEBUG "Didn't get session_key.\n" if ($debug); $RESULT = $DM_EXT_APP_OS_ERROR; return(0); } else { print DEBUG "session_key: $session_key\n" if ($debug); } # Okay, now I realize this really sucks, since I'm using the # address in the ticket without comparing it against the address # of the client, but since I can't do a getpeername() here, there's # no reliable way to determine the address of the client unless # the documentum server puts something in the environment. my($client_inaddr) = inet_aton($auth_data->address); print DEBUG "client_inaddr: $client_inaddr\n"; print DEBUG "inaddr: $inaddr\n"; my($key_schedule) = Krb4::get_key_sched($session_key); if (! $key_schedule) { $RESULT = $DM_EXT_APP_OS_ERROR; return(0); } my($nonce) = Krb4::rd_priv($nonce_encrypted,$key_schedule,$session_key,$client_inaddr,$inaddr); $nonce =~ s#$nonce_prefix##; # Remove nonce prefix. The nonce needs # to be at least as long as the cipher # block size or you get no data. # 300 sec = five minutes, standard for K4. This allows for reasonable # clock skew. if ( ($time - $nonce) > 300 ) { print DEBUG "Nonce expired. Replay problem?\n" if ($debug); $RESULT = $DM_CHKPASS_BAD_LOGIN; return(0); } return(1); } sub Parse_Config ($) { my($config_file) = @_; my(%config_data); if (! -f $config_file) { return(0); } if (! open(CONFIG, "$config_file")) { print STDERR "$program_name: Unable to open config_file '$config_file': $!\n"; return(0); } while () { next if ($_ =~ m/^#+/); next if ($_ =~ m/^\s+/); chomp; my($dm_user,$krb_expr) = split ':',$_,2; $config_data{$dm_user} = $krb_expr; } close CONFIG; return %config_data; } ############################################################################## #print DEBUG `/usr/local/gnu/bin/date`; # Grab our data from stdin. Isn't perl great? $username = ; chomp $username; $password = ; chomp $password; $auth_data_1 = ; chomp $auth_data_1; $auth_data_2 = ; chop $auth_data_2; $nonce_encoded = $auth_data_2; # RightSite login sets a funky additional param for some reason. $nonce_encoded = '' unless (length $nonce_encoded > 8); if ($debug) { print DEBUG "$username : $password : $nonce_encoded\n"; } %config_data = &Parse_Config($user_config); # First try to validate the user locally. if (! $nonce_encoded) { if (&dm_Local_Auth($username,$password)) { print DEBUG "dm_Local_Auth succeeded.\n" if ($debug); exit $DM_EXT_APP_SUCCESS; } else { # If we set RESULT in dm_Local_Auth then exit with that value. if ($RESULT) { print DEBUG "Got $RESULT from dm_Local_Auth. Exiting.\n" if ($debug); exit($RESULT); } else { print DEBUG "dm_Local_Auth failed.\n" if ($debug); } } } # If no match (for whatever reason), try Kerberos. # If the user supplied a client_hostname, they must want ticket-based # authentication. if ($nonce_encoded) { print DEBUG "Got nonce. Trying dm_Krb_Tkt_Auth.\n" if ($debug); if (&dm_Krb_Tkt_Auth($username,$password,$nonce_encoded)) { print DEBUG "dm_Krb_Tkt_Auth succeeded.\n" if ($debug); exit($DM_EXT_APP_SUCCESS); } else { $RESULT = $DM_CHKPASS_BAD_LOGIN; if ($RESULT) { print DEBUG "dm_Krb_Tkt_Auth set RESULT($RESULT). Exiting.\n" if ($debug); exit($RESULT); } else { print DEBUG "dm_Krb_Tkt_Auth did not set RESULT. Exiting with DM_EXT_APP_UNEXPECTED_ERROR.\n" if ($debug); exit($DM_EXT_APP_UNEXPECTED_ERROR); } } } else { # Otherwise try password-based auth to the KDC. # Set our privilege level back to what it used to be. $real_user_id = $<; $> = $real_user_id; if (&dm_Krb_Passwd_Auth($username,$password,$default_realm)) { print DEBUG "Did not get nonce. Trying dm_Krb_Passwd_Auth.\n" if ($debug); exit($DM_EXT_APP_SUCCESS); } else { if ($RESULT) { print DEBUG "dm_Krb_Passwd_Auth set RESULT($RESULT). Exiting.\n" if ($debug); exit($RESULT); } else { print DEBUG "dm_Krb_Passwd_Auth did not set RESULT. Exiting.\n" if ($debug); exit($DM_EXT_APP_UNEXPECTED_ERROR); } } } # If we got this far, exit with a failure. print DEBUG "Passed through loop. Exiting with DM_CHKPASS_BAD_LOGIN.\n" if ($debug); exit($DM_CHKPASS_BAD_LOGIN);