#
# Copyright (c) 2001 SuSE GmbH Nuernberg, Germany.  All rights reserved.
# Copyright (c) 2002 SuSE Linux AG Nuernberg, Germany.  All rights reserved.
#
#
# $Id: change_passwd.pm,v 1.4 2003/10/17 14:27:13 varkoly Exp $
# 

$| = 1; # do not buffer stdout

package change_passwd;
use strict;

use CGI qw(-no_xhtml);
use CGI::Carp qw(fatalsToBrowser);
use Net::LDAP;
use Digest::MD5;
use MIME::Base64;
use LDAPUtils;
use Display;
use Time::Local;
use vars qw(@ISA);
use subs qw(exit);
# Select the correct exit function
*exit = $ENV{MOD_PERL} ? \&Apache::exit : sub { CORE::exit };
@ISA = qw(Display);


sub new {
    my $this = shift;
    my $self = new Display(@_);
    
    return bless $self, $this;
}

sub display {
    my $this = shift;
    
    my $cgi = $this->{"cgi"};
    
    my $doit = $cgi->param('doit');

    if(defined $doit && $doit eq "change") {
	$this->change_pass();
    } else {
	$this->startup();
    }
}

sub startup {
    my $this = shift;

    my $q = $this->{"cgi"};
    my $session = $this->{"session"};
    my $confParam = $session->{"confParam"};
    my $message = ($session->getLanguage($confParam->{LANG},"CHANGE_PASSWD"))->data();
    my $sessionID = $confParam->{"sessionID"};

    my $LOGINuid = $this->{"uid"};

    my $filter      = $q->param('filter');
    # C SCHULSERVER
    # my $uid         = ( $LOGINuid eq "cyrus" ? $q->param('USER') : $LOGINuid );
    my $uid         = "";
        if ( $LOGINuid eq "cyrus" || $confParam->{admin} ) {
           $uid = $q->param('USER') ;
        } else {
           $uid = $LOGINuid ;
        }
    if ( $uid eq '' ){
        $uid = $LOGINuid ;
    }
    # E SCHULSERVER
    my $old_passwd  = $q->param('old_passwd');
    my $new_passwd  = $q->param('new_passwd');
    my $new2_passwd = $q->param('new2_passwd');
    my $tab         = $q->param('tab');
    my $stab        = $q->param('stab');

    my $ldapbase    = $confParam->{baseDN};
    my $LOGINpasswd = $confParam->{passwd};
    my $ldaphost    = $confParam->{LDAPserver};
    my $ldapport    = $confParam->{LDAPport};
    
    my $inbetween = "";

    my $bind_dn = "";
    my $bind_pass = "";
    my $errs = "";

    # C SCHULSERVER
    #if($LOGINuid eq "cyrus") {
    if($LOGINuid eq "cyrus" || $confParam->{admin} ) {
    # E SCHULSERVER
	$bind_dn = "uid=".$LOGINuid.",".$ldapbase;
    } else {
	$bind_dn = "uid=".$uid.",".$ldapbase;
    }
    $bind_pass = $LOGINpasswd;


    my $ldap = Net::LDAP->new("$ldaphost", port => $ldapport, version => 3);
    if(!defined $ldap) {
	$this->display_error("can't contact LDAP Server",
			     "$confParam->{cgi_path}/menu.pl?sessionID=$sessionID");
	exit;
    }
    
    my $mesg = $ldap->bind (   # bind to a directory with dn and password
			       dn       => "$bind_dn",
			       password => "$bind_pass"
			       );
    
    if($mesg->code != 0) {
	$errs = "bind $message->{failed}\n\n";
	$errs .= &LDAPerror($mesg);
	$this->display_error($errs,
			     "$confParam->{cgi_path}/menu.pl?sessionID=$sessionID");
	exit;
    }

    $mesg = $ldap->search( base => $ldapbase,
			   scope => "one",
			   filter=> "uid=$uid",
			   attrs=> [ "userPassword" ] );
    if( $mesg->count <= 0 ) {
	$ldap->unbind;
	$errs = "search $message->{failed}\n\n";
	$errs .= &LDAPerror($mesg);
	$this->display_error($errs,
			     "$confParam->{cgi_path}/menu.pl?sessionID=$sessionID");
	exit;
    }

    my $res = $mesg->as_struct;
    my $mech = $res->{"uid=$uid,$ldapbase"}->{userpassword}->[0];
    $mech =~ s/^{([\w]+)}.*/$1/;
    $mech = uc($mech);
    $ldap->unbind;
    
    #print STDERR "mech=$mech\n";

    my $hidden = $q->hidden(-name=>"USER", -value=>$uid).
	$q->hidden(-name=>"doit", -value=>"change").
	    $q->hidden(-name=>"tab", -value=>$tab).
		$q->hidden(-name=>"stab", -value=>$stab).
		    $q->hidden(-name=>"sessionID", -value=>$sessionID).
			$q->hidden(-name=>"filter", -value=>$filter).
			    $q->end_form();	    
    
    # C SCHULSERVER
    # if($LOGINuid eq "cyrus") {
    if($LOGINuid ne $uid) {
    # E SCHULSERVER
	$inbetween = $q->Tr(
			    [
			     $this->help_th(2, $message->{change_passwd}, "CHANGE_PASSWD_ADMIN"),
			     $q->td({-colspan=>2, -class=>'AdminSubHeader'}, "$message->{for_user}: $uid"),
			     $q->td( ["$message->{new_pass}", $q->password_field(-name=>'new_passwd', -size=>20)]),
			     $q->td( ["$message->{new2_pass}", $q->password_field(-name=>'new2_passwd', -size=>20)]),
			     $q->td( ["$message->{pwmech}",
				     $q->popup_menu(-name=>'pw_mech',
						    -values=>["CRYPT","SMD5"],
						    -defaults=>$mech)]),
			     $q->td( ["$message->{must_change}",
				      $q->checkbox(-name=>'must_change',
						   -label=>"",
						   -checked=>"")]),
			     $q->td({-colspan=>2, -align=>"center"},
				        $q->submit(-class=>"button",-name=>"$message->{update}").
				        $q->hidden(-name=>"filter", -value=>$filter).
				        $q->hidden(-name=>"filtered", -value=>1).$hidden)
			    ]);
    } else {
	$inbetween = $q->Tr(
			    [
			     $this->help_th(2, $message->{change_passwd}, "CHANGE_PASSWD_USER"),
			     ( defined $confParam->{mustchangepasswd} ?
			       $q->td({-colspan=>2, -class=>'AdminSubHeaderRed'},
				      "$message->{password_expired_red}") :
			       $q->td({-colspan=>2, -class=>'AdminSubHeader'},
				      "$message->{for_user}: $uid")
			       ),
			     $q->td(["$message->{old_pass}", $q->password_field(-name=>'old_passwd', -size=>20)]),
			     $q->td(["$message->{new_pass}", $q->password_field(-name=>'new_passwd', -size=>20)]),
			     $q->td(["$message->{new2_pass}", $q->password_field(-name=>'new2_passwd', -size=>20)]),
			     $q->td(
				    ["$message->{pwmech}",
				     $q->popup_menu(-name=>'pw_mech',
						    -values=>["CRYPT","SMD5"],
						    -default=>$mech)
                    ]),
			     $q->td({-colspan=>2, -align=>"center"}, $q->submit(-class=>"button",-name=>"$message->{update}").$hidden)
			    ]);
    }

    my $html = "";
    $html .= $q->start_table({-class=>'AdminBorder', -cellspacing=>2, -cellpadding=>0, -width=>"100%"});
    $html .= $q->start_Tr();
    $html .= $q->start_td();
    $html .=    $q->start_table({-cellspacing=>0, -cellpadding=>2, -width=>"100%"});
    $html .=        $q->start_form(-action=>$confParam->{cgi_path}."/change_passwd.pl");
    $html .=            $inbetween;
    $html .=        $q->end_form();
    $html .=    $q->end_table();
    $html .= $q->end_td();
    $html .= $q->end_Tr();
    $html .= $q->end_table();

    # C SCHULSERVER
    #if($LOGINuid eq "cyrus") {
    if($LOGINuid eq "cyrus" || $confParam->{admin} ) {
    # E SCHULSERVER
        $this->back_url("$confParam->{cgi_path}/browse_user.pl?sessionID=$sessionID\&tab=$tab\&stab=$stab\&filter=$filter\&filtered=1");
    }

    $this->SUPER::display($html);
}

sub change_pass {
    my $this = shift;

    my $q = $this->{"cgi"};
    my $session = $this->{"session"};
    my $confParam = $session->{"confParam"};
    my $message = ($session->getLanguage($confParam->{LANG},"CHANGE_PASSWD"))->data();
    my $sessionID = $confParam->{"sessionID"};

    my $LOGINuid = $this->{"uid"};

    my $filter      = $q->param('filter');
    my $uid         = $q->param('USER');
    my $old_passwd  = $q->param('old_passwd');
    my $new_passwd  = $q->param('new_passwd');
    my $new2_passwd = $q->param('new2_passwd');
    my $tab         = $q->param('tab');
    my $stab        = $q->param('stab');
    my $pwmech      = $q->param('pw_mech');
    my $must_change = $q->param('must_change');

    my $LOGINpasswd   = $confParam->{passwd};

    my $ldapbase      = $confParam->{baseDN};
    my $ldaphost      = $confParam->{LDAPserver};
    my $ldapport      = $confParam->{LDAPport};

    my $errs = "";

    # C SCHULSERVER
    #if($uid eq "" || $new_passwd eq "" || $new2_passwd eq "" || ( $LOGINuid ne "cyrus" && $old_passwd eq "" )) {
    if($uid eq "" || $new_passwd eq "" || $new2_passwd eq "" || ( $LOGINuid ne "cyrus" && !$confParam->{admin} && $old_passwd eq "" )) {
    # E SCHULSERVER
	$this->display_error($message->{miss_some_values},
		      "$confParam->{cgi_path}/change_passwd.pl?sessionID=$sessionID".
		      "\&filter=$filter&filtered=1\&tab=$tab\&stab=$stab\&USER=$uid");
	exit;

    } elsif($new_passwd ne $new2_passwd) {
	$this->display_error($message->{new_passwds_not_equal},
		      "$confParam->{cgi_path}/change_passwd.pl?sessionID=$sessionID".
		      "\&filter=$filter&filtered=1\&tab=$tab\&stab=$stab\&USER=$uid");
	exit;

    } elsif($new_passwd eq $old_passwd) {
	$this->display_error($message->{old_equals_new},
		      "$confParam->{cgi_path}/change_passwd.pl?sessionID=$sessionID".
		      "\&filter=$filter&filtered=1\&tab=$tab\&stab=$stab\&USER=$uid");
	exit;

    } elsif(length($new_passwd) < 5 || ( $pwmech eq "SMD5" ? 0 : length($new_passwd) > 8 ) ) {
	$this->display_error($message->{incorrect_passwd_length},
		      "$confParam->{cgi_path}/change_passwd.pl?sessionID=$sessionID".
		      "\&filter=$filter&filtered=1\&tab=$tab\&stab=$stab\&USER=$uid");
	exit;

    } else {
	my $bind_dn = "";
	my $bind_pass = "";

	# C SCHULSERVER
        #if($LOGINuid eq "cyrus") {
        if($LOGINuid eq "cyrus" || $confParam->{admin} ) {
        # E SCHULSERVER
	    $bind_dn = "uid=".$LOGINuid.",".$ldapbase;
	    $bind_pass = $LOGINpasswd;
	} else {
	    $bind_dn = "uid=".$uid.",".$ldapbase;
	    $bind_pass = $old_passwd;
	}

	my $ldap = Net::LDAP->new("$ldaphost", port => $ldapport, version => 3);
	if(!defined $ldap) {
	    $this->display_error("can't contact LDAP Server",
			  "$confParam->{cgi_path}/menu.pl?sessionID=$sessionID");
	    exit;
	}
	
	my $mesg = $ldap->bind (   # bind to a directory with dn and password
				dn       => "$bind_dn",
				password => "$bind_pass"
			       );
	
	if($mesg->code != 0) {
	    $errs = "bind $message->{failed}\n\n";
	    $errs .= &LDAPerror($mesg);
	    $this->display_error($errs,
			  "$confParam->{cgi_path}/menu.pl?sessionID=$sessionID");
	    exit;
	}

	my $crypt_passwd = "*";
	if( $pwmech eq "CRYPT" ) {
	    my $salt =  pack("C2",(int(rand 26)+65),(int(rand 26)+65));
	    $crypt_passwd = crypt $new_passwd,$salt;
	    $crypt_passwd = "{crypt}".$crypt_passwd;
	} elsif( $pwmech eq "SMD5" ) {
	    my $salt =  pack("C5",(int(rand 26)+65),(int(rand 26)+65),(int(rand 26)+65),
			     (int(rand 26)+65), (int(rand 26)+65));
	    my $ctx = new Digest::MD5();
	    $ctx->add($new_passwd);
	    $ctx->add($salt);
	    $crypt_passwd = "{smd5}".encode_base64($ctx->digest.$salt, "");
	}

	my $days_since_1970 = int(timelocal(localtime()) / 3600 / 24);


	my @mod_op;
	push @mod_op, "replace", [ "userpassword", "$crypt_passwd" ];
	if( defined $must_change && $must_change ne "" ) {
	    push @mod_op, "replace", [ "shadowlastchange", "0" ];
	} else {
	    push @mod_op, "replace", [ "shadowlastchange", "$days_since_1970" ];
	}

	$mesg = $ldap->modify( "uid=$uid,$ldapbase",
			       changes => \@mod_op );
	if($mesg->code != 0) {
	    $errs = "$message->{change_passwd} $message->{failed}\n\n";
	    $errs .= &LDAPerror($mesg);
	    $ldap->unbind;   # take down session
	    $this->display_error($errs,
			  "$confParam->{cgi_path}/menu.pl?sessionID=$sessionID");
	    exit;
	}

	# C SCHULSERVER
        #if($LOGINuid ne "cyrus") {
        if($LOGINuid eq $uid ) {
        # E SCHULSERVER
	    $mesg = $session->modify({passwd => "$new_passwd"} );
	    if( $mesg->code() ne "OK" ) {
		$this->display_error($message->{write2session_failed},
				     "$confParam->{cgi_path}/menu.pl?sessionID=$sessionID");
		exit;
	    }
	    $session->load($sessionID);
	}

	    # look whether this user is smb enabled
	    my ($ret, $err) = LDAP_is_samba_user($confParam, $ldap, $uid);
	    
	    if( $ret == 1 ) {
	        #print STDERR "changing sambaAccount\n";
	        $mesg = $session->suad_change_smbpw($uid, $old_passwd, $new_passwd);
	        if( $mesg->code() ne "OK" ) {
		    $this->display_error("$message->{samba_pw_failed}: ".$mesg->code(),
					 "$confParam->{cgi_path}/menu.pl?sessionID=$sessionID");
		    exit;
	        }
	    }elsif( $ret == -1 ){
	    	$this->display_error("search failed: ".$mesg->code(),
	    			     "$confParam->{cgi_path}/menu.pl?sessionID=$sessionID");
	    	exit;
	    }

	$ldap->unbind;   # take down session

	my $url;
	if( defined $confParam->{mustchangepasswd} ) {
	    my %temp = ("mustchangepasswd" => undef);
	    $session->modify(\%temp);
	    $session->load($sessionID);
	    $message = ($session->getLanguage($session->{confParam}->{LANG},"CHANGE_PASSWD"))->data();
	    if( ( defined $::CONF_GLOBAL{'GENERAL'}{'DisableGroupwareLogin'} &&
		  $::CONF_GLOBAL{'GENERAL'}{'DisableGroupwareLogin'} eq "true" ) ||
		$confParam->{loginDestination} eq "config" ) {
		$url = "$confParam->{cgi_path}/menu.pl?sessionID=$sessionID";
	    } elsif( $confParam->{loginDestination} eq "groupware" ) {
		my $remotehost = $::CONF_GLOBAL{'GENERAL'}{'GroupwareHostname'};
		if( defined $remotehost && $remotehost ne "" ) {
		    $remotehost = &getPROTO().$remotehost;
		} else {
		    $remotehost = "";
		}
		$url = "$remotehost/servlet/intranet?SITE=beforeAuth&sessionID=$sessionID";
	    } elsif( $confParam->{loginDestination} eq "webmail" ) {
		my $remotehost = $::CONF_GLOBAL{'GENERAL'}{'GroupwareHostname'};
		if( defined $remotehost && $remotehost ne "" ) {
		    $remotehost = &getPROTO().$remotehost;
		} else {
		    $remotehost = "";
		}
		$url = "$remotehost/servlet/webmail?sessionID=$sessionID\&SITE=mauth";
	    }
	} else {
	    $url = "$confParam->{cgi_path}/change_passwd.pl?sessionID=$sessionID\&tab=$tab\&stab=$stab";
	}

	#
	# comment this out, if you want to also update /etc/sasldb
	# to use strong auth mechs
	#
	#if($LOGINuid eq "cyrus") {
	#    $mesg = $session->suad_saslsetpw($uid, $new_passwd);
	#    if( $mesg->code() ne "OK" ) {
	#	$this->display_error("Failed to set SASL password",
	#			     "$confParam->{cgi_path}/change_passwd.pl?sessionID=$sessionID\&tab=$tab\&stab=$stab");
	#    }
	#}

	if($LOGINuid eq "cyrus") {
	    $this->display_msg("$message->{change_passwd_success}",
			       "$confParam->{cgi_path}/browse_user.pl?sessionID=$sessionID".
			       "\&filter=$filter&filtered=1\&tab=$tab\&stab=$stab",
			       3);
	} else {
	    $this->display_msg("$message->{change_passwd_success}", $url, 3);
	}
    }
}

1;
