|
Lines 864-972
sub checkauth {
Link Here
|
| 864 |
elsif ( $emailaddress) { |
864 |
elsif ( $emailaddress) { |
| 865 |
# the Google OpenID Connect passes an email address |
865 |
# the Google OpenID Connect passes an email address |
| 866 |
} |
866 |
} |
| 867 |
elsif ( $sessionID = $query->cookie("CGISESSID") ) |
867 |
elsif ( $sessionID = $query->cookie("CGISESSID") ) { # assignment, not comparison |
| 868 |
{ # assignment, not comparison |
868 |
my ( $return, $more_info ); |
| 869 |
$session = get_session($sessionID); |
869 |
( $return, $session, $more_info ) = check_cookie_auth( $sessionID, $flags, |
| 870 |
C4::Context->_new_userenv($sessionID); |
870 |
{ remote_addr => $ENV{REMOTE_ADDR} } |
| 871 |
my ( $ip, $lasttime, $sessiontype ); |
871 |
); |
| 872 |
my $s_userid = ''; |
|
|
| 873 |
if ($session) { |
| 874 |
$s_userid = $session->param('id') // ''; |
| 875 |
C4::Context->set_userenv( |
| 876 |
$session->param('number'), $s_userid, |
| 877 |
$session->param('cardnumber'), $session->param('firstname'), |
| 878 |
$session->param('surname'), $session->param('branch'), |
| 879 |
$session->param('branchname'), $session->param('flags'), |
| 880 |
$session->param('emailaddress'), $session->param('shibboleth'), |
| 881 |
$session->param('desk_id'), $session->param('desk_name'), |
| 882 |
$session->param('register_id'), $session->param('register_name') |
| 883 |
); |
| 884 |
Koha::Logger->get->debug(sprintf "AUTH_SESSION: (%s)\t%s %s - %s", map { $session->param($_) || q{} } qw(cardnumber firstname surname branch)); |
| 885 |
$ip = $session->param('ip'); |
| 886 |
$lasttime = $session->param('lasttime'); |
| 887 |
$userid = $s_userid; |
| 888 |
$sessiontype = $session->param('sessiontype') || ''; |
| 889 |
} |
| 890 |
if ( ( $query->param('koha_login_context') && ( $q_userid ne $s_userid ) ) |
| 891 |
|| ( $cas && $query->param('ticket') && !C4::Context->userenv->{'id'} ) |
| 892 |
|| ( $shib && $shib_login && !$logout && !C4::Context->userenv->{'id'} ) |
| 893 |
) { |
| 894 |
|
| 895 |
#if a user enters an id ne to the id in the current session, we need to log them in... |
| 896 |
#first we need to clear the anonymous session... |
| 897 |
$anon_search_history = $session->param('search_history'); |
| 898 |
$session->delete(); |
| 899 |
$session->flush; |
| 900 |
C4::Context->_unset_userenv($sessionID); |
| 901 |
$sessionID = undef; |
| 902 |
$userid = undef; |
| 903 |
} |
| 904 |
elsif ($logout) { |
| 905 |
|
872 |
|
| 906 |
# voluntary logout the user |
873 |
if ( $return eq 'ok' ) { |
| 907 |
# check wether the user was using their shibboleth session or a local one |
874 |
Koha::Logger->get->debug(sprintf "AUTH_SESSION: (%s)\t%s %s - %s", map { $session->param($_) || q{} } qw(cardnumber firstname surname branch)); |
| 908 |
my $shibSuccess = C4::Context->userenv->{'shibboleth'}; |
|
|
| 909 |
$session->delete(); |
| 910 |
$session->flush; |
| 911 |
C4::Context->_unset_userenv($sessionID); |
| 912 |
|
875 |
|
| 913 |
$sessionID = undef; |
876 |
my $s_userid = $session->param('id'); |
| 914 |
$userid = undef; |
877 |
$userid = $s_userid; |
| 915 |
|
878 |
|
| 916 |
if ($cas and $caslogout) { |
879 |
if ( ( $query->param('koha_login_context') && ( $q_userid ne $s_userid ) ) |
| 917 |
logout_cas($query, $type); |
880 |
|| ( $cas && $query->param('ticket') && !C4::Context->userenv->{'id'} ) |
| 918 |
} |
881 |
|| ( $shib && $shib_login && !$logout && !C4::Context->userenv->{'id'} ) |
|
|
882 |
) { |
| 919 |
|
883 |
|
| 920 |
# If we are in a shibboleth session (shibboleth is enabled, a shibboleth match attribute is set and matches koha matchpoint) |
884 |
#if a user enters an id ne to the id in the current session, we need to log them in... |
| 921 |
if ( $shib and $shib_login and $shibSuccess) { |
885 |
#first we need to clear the anonymous session... |
| 922 |
logout_shib($query); |
886 |
$anon_search_history = $session->param('search_history'); |
|
|
887 |
$session->delete(); |
| 888 |
$session->flush; |
| 889 |
C4::Context->_unset_userenv($sessionID); |
| 923 |
} |
890 |
} |
| 924 |
} |
891 |
elsif ($logout) { |
| 925 |
elsif ( !$lasttime || ( $lasttime < time() - $timeout ) ) { |
|
|
| 926 |
|
892 |
|
| 927 |
# timed logout |
893 |
# voluntary logout the user |
| 928 |
$info{'timed_out'} = 1; |
894 |
# check wether the user was using their shibboleth session or a local one |
| 929 |
if ($session) { |
895 |
my $shibSuccess = C4::Context->userenv->{'shibboleth'}; |
| 930 |
$session->delete(); |
896 |
$session->delete(); |
| 931 |
$session->flush; |
897 |
$session->flush; |
| 932 |
} |
898 |
C4::Context->_unset_userenv($sessionID); |
| 933 |
C4::Context->_unset_userenv($sessionID); |
|
|
| 934 |
|
899 |
|
| 935 |
$userid = undef; |
900 |
if ($cas and $caslogout) { |
| 936 |
$sessionID = undef; |
901 |
logout_cas($query, $type); |
| 937 |
} |
902 |
} |
| 938 |
elsif ( C4::Context->preference('SessionRestrictionByIP') && $ip ne $ENV{'REMOTE_ADDR'} ) { |
|
|
| 939 |
|
903 |
|
| 940 |
# Different ip than originally logged in from |
904 |
# If we are in a shibboleth session (shibboleth is enabled, a shibboleth match attribute is set and matches koha matchpoint) |
| 941 |
$info{'oldip'} = $ip; |
905 |
if ( $shib and $shib_login and $shibSuccess) { |
| 942 |
$info{'newip'} = $ENV{'REMOTE_ADDR'}; |
906 |
logout_shib($query); |
| 943 |
$info{'different_ip'} = 1; |
907 |
} |
| 944 |
$session->delete(); |
908 |
} else { |
| 945 |
$session->flush; |
|
|
| 946 |
C4::Context->_unset_userenv($sessionID); |
| 947 |
|
909 |
|
| 948 |
$sessionID = undef; |
910 |
$cookie = $query->cookie( |
| 949 |
$userid = undef; |
911 |
-name => 'CGISESSID', |
| 950 |
} |
912 |
-value => $session->id, |
| 951 |
else { |
913 |
-HttpOnly => 1, |
| 952 |
$cookie = $query->cookie( |
914 |
-secure => ( C4::Context->https_enabled() ? 1 : 0 ), |
| 953 |
-name => 'CGISESSID', |
915 |
); |
| 954 |
-value => $session->id, |
916 |
|
| 955 |
-HttpOnly => 1, |
917 |
my $sessiontype = $session->param('sessiontype') || ''; |
| 956 |
-secure => ( C4::Context->https_enabled() ? 1 : 0 ), |
918 |
unless ( $sessiontype && $sessiontype eq 'anon' ) { #if this is an anonymous session, we want to update the session, but not behave as if they are logged in... |
| 957 |
); |
919 |
$flags = haspermission( $userid, $flagsrequired ); |
| 958 |
$session->param( 'lasttime', time() ); |
920 |
if ($flags) { |
| 959 |
unless ( $sessiontype && $sessiontype eq 'anon' ) { #if this is an anonymous session, we want to update the session, but not behave as if they are logged in... |
921 |
$loggedin = 1; |
| 960 |
$flags = haspermission( $userid, $flagsrequired ); |
922 |
} else { |
| 961 |
if ($flags) { |
923 |
$info{'nopermission'} = 1; |
| 962 |
$loggedin = 1; |
924 |
} |
| 963 |
} else { |
|
|
| 964 |
$info{'nopermission'} = 1; |
| 965 |
} |
925 |
} |
| 966 |
} |
926 |
} |
|
|
927 |
} elsif ( !$logout ) { |
| 928 |
if ( $return eq 'expired' ) { |
| 929 |
$info{timed_out} = 1; |
| 930 |
} elsif ( $return eq 'restricted' ) { |
| 931 |
$info{oldip} = $more_info->{old_ip}; |
| 932 |
$info{newip} = $more_info->{new_ip}; |
| 933 |
$info{different_ip} = 1; |
| 934 |
} |
| 967 |
} |
935 |
} |
| 968 |
} |
936 |
} |
| 969 |
unless ( $userid || $sessionID ) { |
937 |
|
|
|
938 |
unless ( $loggedin ) { |
| 939 |
$sessionID = undef; |
| 940 |
$userid = undef; |
| 941 |
} |
| 942 |
|
| 943 |
unless ( $userid ) { |
| 970 |
#we initiate a session prior to checking for a username to allow for anonymous sessions... |
944 |
#we initiate a session prior to checking for a username to allow for anonymous sessions... |
| 971 |
my $session = get_session("") or die "Auth ERROR: Cannot get_session()"; |
945 |
my $session = get_session("") or die "Auth ERROR: Cannot get_session()"; |
| 972 |
|
946 |
|
|
Lines 981-987
sub checkauth {
Link Here
|
| 981 |
C4::Context->_new_userenv($sessionID); |
955 |
C4::Context->_new_userenv($sessionID); |
| 982 |
$cookie = $query->cookie( |
956 |
$cookie = $query->cookie( |
| 983 |
-name => 'CGISESSID', |
957 |
-name => 'CGISESSID', |
| 984 |
-value => $session->id, |
958 |
-value => $sessionID, |
| 985 |
-HttpOnly => 1, |
959 |
-HttpOnly => 1, |
| 986 |
-secure => ( C4::Context->https_enabled() ? 1 : 0 ), |
960 |
-secure => ( C4::Context->https_enabled() ? 1 : 0 ), |
| 987 |
); |
961 |
); |
|
Lines 1313-1320
sub checkauth {
Link Here
|
| 1313 |
script_name => get_script_name(), |
1287 |
script_name => get_script_name(), |
| 1314 |
casAuthentication => C4::Context->preference("casAuthentication"), |
1288 |
casAuthentication => C4::Context->preference("casAuthentication"), |
| 1315 |
shibbolethAuthentication => $shib, |
1289 |
shibbolethAuthentication => $shib, |
| 1316 |
SessionRestrictionByIP => C4::Context->preference("SessionRestrictionByIP"), |
|
|
| 1317 |
suggestion => C4::Context->preference("suggestion"), |
1290 |
suggestion => C4::Context->preference("suggestion"), |
|
|
1291 |
SessionRestrictionByIP => C4::Context->preference("SessionRestrictionByIP"), |
| 1318 |
virtualshelves => C4::Context->preference("virtualshelves"), |
1292 |
virtualshelves => C4::Context->preference("virtualshelves"), |
| 1319 |
LibraryName => "" . C4::Context->preference("LibraryName"), |
1293 |
LibraryName => "" . C4::Context->preference("LibraryName"), |
| 1320 |
LibraryNameTitle => "" . $LibraryNameTitle, |
1294 |
LibraryNameTitle => "" . $LibraryNameTitle, |
|
Lines 1456-1461
Possible return values in C<$status> are:
Link Here
|
| 1456 |
|
1430 |
|
| 1457 |
=item "expired -- session cookie has expired; API user should resubmit userid and password |
1431 |
=item "expired -- session cookie has expired; API user should resubmit userid and password |
| 1458 |
|
1432 |
|
|
|
1433 |
=item "restricted" -- The IP has changed (if SessionRestrictionByIP) |
| 1434 |
|
| 1459 |
=back |
1435 |
=back |
| 1460 |
|
1436 |
|
| 1461 |
=cut |
1437 |
=cut |
|
Lines 1482-1561
sub check_api_auth {
Link Here
|
| 1482 |
return ( "maintenance", undef, undef ); |
1458 |
return ( "maintenance", undef, undef ); |
| 1483 |
} |
1459 |
} |
| 1484 |
|
1460 |
|
| 1485 |
# FIXME -- most of what follows is a copy-and-paste |
1461 |
my ( $sessionID, $session ); |
| 1486 |
# of code from checkauth. There is an obvious need |
|
|
| 1487 |
# for refactoring to separate the various parts of |
| 1488 |
# the authentication code, but as of 2007-11-19 this |
| 1489 |
# is deferred so as to not introduce bugs into the |
| 1490 |
# regular authentication code for Koha 3.0. |
| 1491 |
|
| 1492 |
# see if we have a valid session cookie already |
| 1493 |
# however, if a userid parameter is present (i.e., from |
| 1494 |
# a form submission, assume that any current cookie |
| 1495 |
# is to be ignored |
| 1496 |
my $sessionID = undef; |
| 1497 |
unless ( $query->param('userid') ) { |
1462 |
unless ( $query->param('userid') ) { |
| 1498 |
$sessionID = $query->cookie("CGISESSID"); |
1463 |
$sessionID = $query->cookie("CGISESSID"); |
| 1499 |
} |
1464 |
} |
| 1500 |
if ( $sessionID && not( $cas && $query->param('PT') ) ) { |
1465 |
if ( $sessionID && not( $cas && $query->param('PT') ) ) { |
| 1501 |
my $session = get_session($sessionID); |
|
|
| 1502 |
C4::Context->_new_userenv($sessionID); |
| 1503 |
if ($session) { |
| 1504 |
C4::Context->interface($session->param('interface')); |
| 1505 |
C4::Context->set_userenv( |
| 1506 |
$session->param('number'), $session->param('id'), |
| 1507 |
$session->param('cardnumber'), $session->param('firstname'), |
| 1508 |
$session->param('surname'), $session->param('branch'), |
| 1509 |
$session->param('branchname'), $session->param('flags'), |
| 1510 |
$session->param('emailaddress'), $session->param('shibboleth'), |
| 1511 |
$session->param('desk_id'), $session->param('desk_name'), |
| 1512 |
$session->param('register_id'), $session->param('register_name') |
| 1513 |
); |
| 1514 |
|
1466 |
|
| 1515 |
my $ip = $session->param('ip'); |
1467 |
my $return; |
| 1516 |
my $lasttime = $session->param('lasttime'); |
1468 |
( $return, $session, undef ) = check_cookie_auth( |
| 1517 |
my $userid = $session->param('id'); |
1469 |
$sessionID, $flagsrequired, { remote_addr => $ENV{REMOTE_ADDR} } ); |
| 1518 |
if ( $lasttime < time() - $timeout ) { |
|
|
| 1519 |
|
1470 |
|
| 1520 |
# time out |
1471 |
return ( $return, undef, undef ) # Cookie auth failed |
| 1521 |
$session->delete(); |
1472 |
if $return ne "ok"; |
| 1522 |
$session->flush; |
1473 |
|
| 1523 |
C4::Context->_unset_userenv($sessionID); |
1474 |
my $cookie = $query->cookie( |
| 1524 |
$userid = undef; |
1475 |
-name => 'CGISESSID', |
| 1525 |
$sessionID = undef; |
1476 |
-value => $session->id, |
| 1526 |
return ( "expired", undef, undef ); |
1477 |
-HttpOnly => 1, |
| 1527 |
} elsif ( C4::Context->preference('SessionRestrictionByIP') && $ip ne $ENV{'REMOTE_ADDR'} ) { |
1478 |
-secure => ( C4::Context->https_enabled() ? 1 : 0 ), |
|
|
1479 |
); |
| 1480 |
return ( $return, undef, $session ); |
| 1528 |
|
1481 |
|
| 1529 |
# IP address changed |
|
|
| 1530 |
$session->delete(); |
| 1531 |
$session->flush; |
| 1532 |
C4::Context->_unset_userenv($sessionID); |
| 1533 |
$userid = undef; |
| 1534 |
$sessionID = undef; |
| 1535 |
return ( "expired", undef, undef ); |
| 1536 |
} else { |
| 1537 |
my $cookie = $query->cookie( |
| 1538 |
-name => 'CGISESSID', |
| 1539 |
-value => $session->id, |
| 1540 |
-HttpOnly => 1, |
| 1541 |
-secure => ( C4::Context->https_enabled() ? 1 : 0 ), |
| 1542 |
); |
| 1543 |
$session->param( 'lasttime', time() ); |
| 1544 |
my $flags = haspermission( $userid, $flagsrequired ); |
| 1545 |
if ($flags) { |
| 1546 |
return ( "ok", $cookie, $sessionID ); |
| 1547 |
} else { |
| 1548 |
$session->delete(); |
| 1549 |
$session->flush; |
| 1550 |
C4::Context->_unset_userenv($sessionID); |
| 1551 |
$userid = undef; |
| 1552 |
$sessionID = undef; |
| 1553 |
return ( "failed", undef, undef ); |
| 1554 |
} |
| 1555 |
} |
| 1556 |
} else { |
| 1557 |
return ( "expired", undef, undef ); |
| 1558 |
} |
| 1559 |
} else { |
1482 |
} else { |
| 1560 |
|
1483 |
|
| 1561 |
# new login |
1484 |
# new login |
|
Lines 1708-1725
Possible return values in C<$status> are:
Link Here
|
| 1708 |
|
1631 |
|
| 1709 |
=item "expired -- session cookie has expired; API user should resubmit userid and password |
1632 |
=item "expired -- session cookie has expired; API user should resubmit userid and password |
| 1710 |
|
1633 |
|
|
|
1634 |
=item "restricted" -- The IP has changed (if SessionRestrictionByIP) |
| 1635 |
|
| 1711 |
=back |
1636 |
=back |
| 1712 |
|
1637 |
|
| 1713 |
=cut |
1638 |
=cut |
| 1714 |
|
1639 |
|
| 1715 |
sub check_cookie_auth { |
1640 |
sub check_cookie_auth { |
| 1716 |
my $cookie = shift; |
1641 |
my $sessionID = shift; |
| 1717 |
my $flagsrequired = shift; |
1642 |
my $flagsrequired = shift; |
| 1718 |
my $params = shift; |
1643 |
my $params = shift; |
| 1719 |
|
1644 |
|
| 1720 |
my $remote_addr = $params->{remote_addr} || $ENV{REMOTE_ADDR}; |
1645 |
my $remote_addr = $params->{remote_addr} || $ENV{REMOTE_ADDR}; |
| 1721 |
my $dbh = C4::Context->dbh; |
|
|
| 1722 |
my $timeout = _timeout_syspref(); |
| 1723 |
|
1646 |
|
| 1724 |
unless ( C4::Context->preference('Version') ) { |
1647 |
unless ( C4::Context->preference('Version') ) { |
| 1725 |
|
1648 |
|
|
Lines 1736-1762
sub check_cookie_auth {
Link Here
|
| 1736 |
return ( "maintenance", undef ); |
1659 |
return ( "maintenance", undef ); |
| 1737 |
} |
1660 |
} |
| 1738 |
|
1661 |
|
| 1739 |
# FIXME -- most of what follows is a copy-and-paste |
|
|
| 1740 |
# of code from checkauth. There is an obvious need |
| 1741 |
# for refactoring to separate the various parts of |
| 1742 |
# the authentication code, but as of 2007-11-23 this |
| 1743 |
# is deferred so as to not introduce bugs into the |
| 1744 |
# regular authentication code for Koha 3.0. |
| 1745 |
|
| 1746 |
# see if we have a valid session cookie already |
1662 |
# see if we have a valid session cookie already |
| 1747 |
# however, if a userid parameter is present (i.e., from |
1663 |
# however, if a userid parameter is present (i.e., from |
| 1748 |
# a form submission, assume that any current cookie |
1664 |
# a form submission, assume that any current cookie |
| 1749 |
# is to be ignored |
1665 |
# is to be ignored |
| 1750 |
unless ( defined $cookie and $cookie ) { |
1666 |
unless ( defined $sessionID and $sessionID ) { |
| 1751 |
return ( "failed", undef ); |
1667 |
return ( "failed", undef ); |
| 1752 |
} |
1668 |
} |
| 1753 |
my $sessionID = $cookie; |
|
|
| 1754 |
my $session = get_session($sessionID); |
1669 |
my $session = get_session($sessionID); |
| 1755 |
C4::Context->_new_userenv($sessionID); |
1670 |
C4::Context->_new_userenv($sessionID); |
| 1756 |
if ($session) { |
1671 |
if ($session) { |
| 1757 |
C4::Context->interface($session->param('interface')); |
1672 |
C4::Context->interface($session->param('interface')); |
| 1758 |
C4::Context->set_userenv( |
1673 |
C4::Context->set_userenv( |
| 1759 |
$session->param('number'), $session->param('id'), |
1674 |
$session->param('number'), $session->param('id') // '', |
| 1760 |
$session->param('cardnumber'), $session->param('firstname'), |
1675 |
$session->param('cardnumber'), $session->param('firstname'), |
| 1761 |
$session->param('surname'), $session->param('branch'), |
1676 |
$session->param('surname'), $session->param('branch'), |
| 1762 |
$session->param('branchname'), $session->param('flags'), |
1677 |
$session->param('branchname'), $session->param('flags'), |
|
Lines 1765-1781
sub check_cookie_auth {
Link Here
|
| 1765 |
$session->param('register_id'), $session->param('register_name') |
1680 |
$session->param('register_id'), $session->param('register_name') |
| 1766 |
); |
1681 |
); |
| 1767 |
|
1682 |
|
|
|
1683 |
my $userid = $session->param('id'); |
| 1768 |
my $ip = $session->param('ip'); |
1684 |
my $ip = $session->param('ip'); |
| 1769 |
my $lasttime = $session->param('lasttime'); |
1685 |
my $lasttime = $session->param('lasttime'); |
| 1770 |
my $userid = $session->param('id'); |
1686 |
my $timeout = _timeout_syspref(); |
| 1771 |
if ( $lasttime < time() - $timeout ) { |
1687 |
|
|
|
1688 |
if ( !$lasttime || ( $lasttime < time() - $timeout ) ) { |
| 1772 |
|
1689 |
|
| 1773 |
# time out |
1690 |
# time out |
| 1774 |
$session->delete(); |
1691 |
$session->delete(); |
| 1775 |
$session->flush; |
1692 |
$session->flush; |
| 1776 |
C4::Context->_unset_userenv($sessionID); |
1693 |
C4::Context->_unset_userenv($sessionID); |
| 1777 |
$userid = undef; |
|
|
| 1778 |
$sessionID = undef; |
| 1779 |
return ("expired", undef); |
1694 |
return ("expired", undef); |
| 1780 |
} elsif ( C4::Context->preference('SessionRestrictionByIP') && $ip ne $remote_addr ) { |
1695 |
} elsif ( C4::Context->preference('SessionRestrictionByIP') && $ip ne $remote_addr ) { |
| 1781 |
|
1696 |
|
|
Lines 1783-1802
sub check_cookie_auth {
Link Here
|
| 1783 |
$session->delete(); |
1698 |
$session->delete(); |
| 1784 |
$session->flush; |
1699 |
$session->flush; |
| 1785 |
C4::Context->_unset_userenv($sessionID); |
1700 |
C4::Context->_unset_userenv($sessionID); |
| 1786 |
$userid = undef; |
1701 |
return ( "restricted", undef, { old_ip => $ip, new_ip => $remote_addr}); |
| 1787 |
$sessionID = undef; |
|
|
| 1788 |
return ( "expired", undef ); |
| 1789 |
} else { |
1702 |
} else { |
| 1790 |
$session->param( 'lasttime', time() ); |
1703 |
$session->param( 'lasttime', time() ); |
| 1791 |
my $flags = defined($flagsrequired) ? haspermission( $userid, $flagsrequired ) : 1; |
1704 |
my $flags = defined($flagsrequired) ? haspermission( $userid, $flagsrequired ) : 1; |
| 1792 |
if ($flags) { |
1705 |
if ($flags) { |
| 1793 |
return ( "ok", $sessionID ); |
1706 |
return ( "ok", $session ); |
| 1794 |
} else { |
1707 |
} else { |
| 1795 |
$session->delete(); |
1708 |
$session->delete(); |
| 1796 |
$session->flush; |
1709 |
$session->flush; |
| 1797 |
C4::Context->_unset_userenv($sessionID); |
1710 |
C4::Context->_unset_userenv($sessionID); |
| 1798 |
$userid = undef; |
|
|
| 1799 |
$sessionID = undef; |
| 1800 |
return ( "failed", undef ); |
1711 |
return ( "failed", undef ); |
| 1801 |
} |
1712 |
} |
| 1802 |
} |
1713 |
} |
| 1803 |
- |
|
|