<?_code
{ 
    use strict;
    use vars qw($body %GET %POST);

    my $crumb = $LJ::IS_SSL ? 'securelogin' : 'login';
    LJ::set_active_crumb($crumb);

    $body = "";

    my @errors = ();

    # ! after username overrides expire to never
    # < after username overrides ipfixed to yes
    if ($POST{'user'} =~ s/[!<]{1,2}$//) {
        $POST{'expire'} = 'never' if index($&, "!") >= 0;
        $POST{'bindip'} = 'yes' if index($&, "<") >= 0;
    }

    my $user = LJ::canonical_username($POST{'user'});
    my $password = $POST{'password'};

    my $remote = LJ::get_remote();
    my $cursess = $remote ? $remote->{'_session'} : undef;

    if ($remote && $remote->readonly) {
        $body = LJ::bad_input("The database is temporarily in read-only mode, so creating new login sessions is temporarily down.  Please try again later.");
        return;
    }

    my $require_tos = 0;

    my $login_html = sub {
        my $nojs = $GET{'nojs'};

        # can't do challenge/response with LDAP.
        $nojs = 1 if $LJ::LDAP_HOST;

        my $getextra = $nojs ? '?nojs=1' : '';

        $body .= "<form action='login.bml$getextra' method='post' id='login'>\n";

        my $chal = LJ::challenge_generate(300); # 5 minute auth token
        $body .= "<input type='hidden' name='chal' id='login_chal' value='$chal' />";
        $body .= "<input type='hidden' name='response' id='login_response' value='' />";

        my $referer = BML::get_client_header('Referer');
        if ($GET{'ret'} == 1 && $referer) {
            my $eh_ref = LJ::ehtml($referer);
            $body .= "<input type='hidden' name='ref' value='$eh_ref' />\n";
        }

        if ($require_tos) {
            $body .= "<div style='margin-left: 40px; margin-bottom: 20px;'>";
            my $tos_err = ref $require_tos eq 'HASH' ? $require_tos->{err} : undef;
            $body .= LJ::tosagree_html('login', $POST{agree_tos}, $tos_err);
            $body .= "</div>";
        }

        if ($remote) {
            $body .= "<?p " . BML::ml(".login.text3", 
                                      {'sitename' => $LJ::SITENAME,
                                       'username' => LJ::ljuser($remote)}) . " p?>\n";
        } else {
            $body .= "<?p " . BML::ml(".login.text1", {'sitename' => $LJ::SITENAME}) . " p?>\n";
        }

        $body .= "<?p $ML{'.login.text2'} p?>\n";

        $body .= "<?standout\n";
        $body .= "<table border=0 cellpadding='3'>\n";
        if ($remote) {
            $body .= "<tr><td>$ML{'.login.username'}</td><td valign='bottom'>" . LJ::ljuser($remote) . "&nbsp;&nbsp;<input type='submit' name='action:logout' value='$ML{'.logout.btn'}' /></td></tr>\n";
        } else {
            $body .= "<tr><td>$ML{'.login.username'}</td><td><input type='text' value='$user' name='user' size='15' maxlength='15' style='<?loginboxstyle?>' /></td></tr>\n";
            $body .= "<tr><td valign='top'>$ML{'.login.password'}</td><td><input type='password' name='password' id='xc_password' size='15' maxlength='30' />";
            $body .= "<br /><a href='/lostinfo.bml'><font size='1'>$ML{'.login.forget'}</font></a></td></tr>\n";
            if (@errors) {
                $body .= "<tr><td colspan='2'>";
                $body .= "<?soerror $_ soerror?>" foreach @errors;
                $body .="</td></tr>";
            }
        }

        my $checked;
        my $checkcond = sub {
            my $cond = shift;
            $checked = $cond ? " checked='checked'" : "";
        };

        # expiration
        my $curexp = $cursess ? $cursess->{'exptype'} : "short";
        $body .= "<tr valign='top'><td>$ML{'.login.expiration'}</td><td>\n";
        $checkcond->($curexp eq "short");
        $body .= "<input type='radio' name='expire' value='close' $checked id='close' /> <label for='close'>$ML{'.login.whenbrowsercloses'}</label><br />\n";
        $checkcond->($curexp eq "long");
        $body .= "<input type='radio' name='expire' value='never' $checked id='never' /> <label for='never'>$ML{'.login.never'}</label>\n";
        $body .= "</td></tr>\n";

        # bind?
        my $curbind = $cursess && $cursess->{'ipfixed'} ? "yes" : "no";
        $body .= "<tr valign='top'><td>$ML{'.bindip.label'}</td><td>\n";
        $checkcond->($curbind eq "yes");
        $body .= "<input type='radio' name='bindip' value='yes' $checked id='bindyes' /> <label for='bindyes'>$ML{'.bindip.yes'}</label><br />\n";
        $checkcond->($curbind eq "no");
        $body .= "<input type='radio' name='bindip' value='no' $checked id='bindipno' /> <label for='bindipno'>$ML{'.bindip.no'}</label>\n";
        $body .= "</td></tr>\n";

        $body .= "<tr><td></td><td align='left'>";
        if ($remote) {
            $body .= "<input name='action:change' type='submit' value='$ML{'.login.btn.changeopts'}' />";
        } else {

            # FIXME: needs translation 'standard'/'secure' and make the below ML strings.
            my $secure = "[ <a href='$LJ::SITEROOT/login.bml?nojs=1'>Standard</a> | <b>Secure</b> ]";

            if ($LJ::IS_SSL) {
                $body .= "<input name='action:login' type='submit' value='$ML{'.login.btn.login'}' /> ".
                    "<img src='$LJ::IMGPREFIX/icon_protected.gif' width='14' height='15' alt='secure login' align='middle' /><br />";
                $body .= $secure;
            } else {
                my $login_btn_text = LJ::ejs($ML{'.login.btn.login'});
                unless ($nojs) {
                    $body .= "<script type='text/javascript' language='Javascript'> \n <!-- \n
                      document.write(\"<input name='action:login' onclick='return sendForm()' type='submit' value='$login_btn_text' />\");";
                    $body .= "
                      if (document.getElementById && document.getElementById('login')) {
                        document.write(\"&nbsp; <img src='$LJ::IMGPREFIX/icon_protected.gif' width='14' height='15' alt='secure login' align='middle' />\");
                        document.write(\"<br />\");
                        document.write(\"$secure\")
                       }\n // -->\n ";
                    $body .= '</script>';
                    $body .= "<noscript>";
                }

                $body .= "<input name='action:login' type='submit' value='$ML{'.login.btn.login'}' />";
                if ($nojs) {
                    # insecure now, but because they choose to not use javascript.  link to
                    # javascript version of login if they seem to have javascript, otherwise
                    # noscript to SSL
                    $body .= "<script type='text/javascript' language='Javascript'>\n";
                    $body .= "<!-- \n document.write(\"<br />" . 
                        LJ::ejs("[ <b>Standard</b> | <a href='$LJ::SITEROOT/login.bml'>Secure</a> ]") .
                        "\"); \n // -->\n </script>\n";
                    if ($LJ::USE_SSL) {
                        $body .= "<noscript>";
                        $body .= "<br />" . "[ <b>Standard</b> | <a href='$LJ::SSLROOT/login.bml'>Secure</a> ]";
                        $body .= "</noscript>";
                    }
                } else {
                    # insecure now, and not because it was forced, so javascript doesn't work.
                    # only way to get to secure now is via SSL, so link there
                    $body .= "<br />" . "[ <b>Standard</b> | <a href='$LJ::SSLROOT/login.bml'>Secure</a> ]"
                        if $LJ::USE_SSL;

                }

                $body .= "</noscript>" unless $nojs;
            }
            $body .= LJ::help_icon('securelogin', '&nbsp;');

        }
        $body .= "</td></tr>\n";

        if (! $remote && LJ::are_hooks("login_formopts")) {
            $body .= "<tr><td>$ML{'.login.otheropts'}</td><td style='white-space: nowrap'>\n";
            LJ::run_hooks("login_formopts", { 'ret' => \$body });
            $body .= "</td></tr>\n";
        }
        $body .= "</table>\n";
        $body .= " standout?>\n";

        # Save offsite redirect uri between POSTs
        my $redir = $GET{'ret'} || $POST{'ret'};
        $body .= LJ::html_hidden('ret', $redir) if $redir && $redir != 1;
        
        $body .= "</form>\n";

        $body .= "<?h1 $ML{'.whylogin.head'} h1?>\n";
        $body .= "<?p $ML{'.whylogin.text'} p?>\n";
        $body .= "<ul>\n";
        $body .= "<li>$ML{'.whylogin.benefit1'}</li>\n";
        $body .= "<li>$ML{'.whylogin.benefit2'}</li>\n";
        $body .= "<li>$ML{'.whylogin.benefit3'}</li>\n";
        $body .= "</ul>\n";
        return;
    };

    my $logout_remote = sub {
        $remote->kill_session if $remote;
        foreach (qw(ljsession langpref)) {
            delete $COOKIE{$_};
        }
        $remote  = undef;
        $cursess = undef;
        LJ::set_remote(undef);
        LJ::run_hooks("post_logout");
    };

    if (LJ::did_post()) {
        my $do_change = $POST{'action:change'};
        my $do_login  = $POST{'action:login'};
        my $do_logout = $POST{'action:logout'};

        # default action is to login:
        if (! $do_change && ! $do_logout) {
            $do_login = 1;
        }

        # can only change if logged in
        if ($do_change && not defined $remote) {
            $do_logout = 1;
            $do_change = 0;
        }

        if ($do_change && ! $remote->tosagree_verify) {
            $do_change = 0;
            $do_logout = 1;
            $require_tos = 1;
        }

        if ($do_logout) {
            $logout_remote->();
        }

        if ($do_change) {
            my $bindip;
            if ($POST{'bindip'} eq "yes") {
                $bindip = BML::get_remote_ip();
            }

            my $len = "short";
            my $etime = 0;  # for cookie
            my $session_secs = 60*60*24*1.5; # for db.
            if ($POST{'expire'} eq "never") {
                $len = "long";
                $session_secs = 60*60*24*60;
                $etime = time()+$session_secs;
            }
            $cursess->{'ipfixed'} = $bindip;
            $cursess->{'exptype'} = $len;

            $COOKIE{'ljsession'} = [ $COOKIE{'ljsession'}, $etime, 1 ]; # 1 => HttpOnly
            $COOKIE{'langpref'} = [ $COOKIE{'langpref'}, $etime ];
            $COOKIE{'BMLschemepref'} = [ $COOKIE{'BMLschemepref'}, $etime ];

            my $sessid = $remote->{'_session'}->{'sessid'};
            $remote->do("UPDATE sessions SET timeexpire=UNIX_TIMESTAMP()+$session_secs, ".
                        "exptype=?, ipfixed=? WHERE " .
                        "userid=? AND sessid=?", undef,
                        $len, $bindip, $remote->{'userid'}, $sessid);
            my $memkey = [$remote->{'userid'},"sess:$remote->{'userid'}:$sessid"];
            LJ::MemCache::delete($memkey);
        }

        if ($do_login)
        {
            $logout_remote->();

            my $u = LJ::load_user($user);
            push @errors, "$ML{'error.username_notfound'}" unless $u;
            push @errors, "$ML{'error.purged.text'}" if $u->{'statusvis'} eq "X";
            push @errors, "$ML{'error.nocommlogin'}"
                if $u->{'journaltype'} eq 'C' && $LJ::DISABLED{'community-logins'};
            if (LJ::get_cap($u, "readonly")) {
                $body = LJ::bad_input("The database is temporarily in read-only mode, so creating new login sessions is temporarily down.  Please try again later.");
                return;
            }
            
            my ($banned, $ok);
            $banned = $ok = 0;
            my $chal_opts = {};

            if ($POST{response}) {
                $ok = LJ::challenge_check_login($u, $POST{chal}, $POST{response}, \$banned, $chal_opts);
            } else {  # js disabled, fallback to plaintext
                $ok = LJ::auth_okay($u, $password, undef, undef, \$banned);
            }

            if ($banned) {
                $body = LJ::bad_input("Your IP address is temporarily banned for exceeding the login failure rate.");
                return;
            }

            if ($u && ! $ok) {
                if ($chal_opts->{'expired'}) {
                    push @errors, $ML{'error.expiredchal'};
                } else {
                    push @errors, $ML{'error.badpassword'};
                }
            }

            push @errors, "This account is locked and cannot be logged in to at this time."
                if $u->{statusvis} eq 'L';

            if (@errors) {
                $login_html->();
                return;
            }

            LJ::load_user_props($u, "browselang", "schemepref", "legal_tosagree");

            unless ($u->tosagree_verify) {
                if ($POST{agree_tos}) {
                    my $err = "";
                    unless ($u->tosagree_set(\$err)) {
                        # failed to save userprop, why?
                        $require_tos = { err => $err };
                        return $login_html->();
                    }
                    # else, successfully set... log them in
                } else {
                    # didn't check agreement checkbox
                    $require_tos = { err => $ML{'tos.error'} };
                    return $login_html->();
                }
            }

            my $bl = LJ::Lang::get_lang($u->{'browselang'});

            my $bindip;
            if ($POST{'bindip'} eq "yes") {
                $bindip = BML::get_remote_ip();
            }

            my $sess_opts = {
                'exptype' => 'short',
                'ipfixed' => $bindip,
            };
            
            my $etime = 0;
            if ($POST{'expire'} eq "never") { 
                $etime = time()+60*60*24*60; 
                $sess_opts->{'exptype'} = "long";
            }

            my $sess = $u->generate_session($sess_opts);
            $cursess = $sess;

            my @sopts;
            LJ::run_hooks("login_add_opts", { 'u' => $u, 'form' => \%POST, 'opts' => \@sopts });
            my $sopts = @sopts ? ":" . join('', map { ".$_" } @sopts) : "";

            $COOKIE{'ljsession'} = [  "ws:$u->{'user'}:$sess->{'sessid'}:$sess->{'auth'}$sopts", $etime, 1 ]; # 1 => HttpOnly

            # restore default scheme
            $COOKIE{'BMLschemepref'} = [ $u->{'schemepref'}, $etime ];
            BML::set_scheme($u->{'schemepref'});
            
            # kill the old-style cookies:
            foreach (qw(ljuser ljhpass permlogin)) {
                delete $COOKIE{$_} if $COOKIE{$_};
            }
            
            eval { Apache->request->notes('ljuser' => $user); };
            if ($bl) {
                $COOKIE{'langpref'} = [ $bl->{'lncode'} . "/" . time(), $etime ];
                BML::set_language($bl->{'lncode'});
            }
            
            LJ::run_hooks("post_login", {
                "u" => $u,
                "form" => \%POST,
                "expiretime" => $etime,
            });
            
            # Redirect to offsite uri if allowed, and not an internal LJ redirect. ('ret' == 1)
            if ($POST{'ret'} && $POST{'ret'} != 1) {
                my $redir_host = $1 if $POST{'ret'} =~ m#^http://([\.:\w-]+)#i;

                # if the redirect is going to FotoBilder, and they don't have the fb_account cap,
                # then we don't redirect them, but keep them here and display an error message
                if ($redir_host =~ /$LJ::FB_DOMAIN/ && ! LJ::get_cap($u, 'fb_account')) {
                    $body = LJ::bad_input("Your account type does not allow logins to remote FotoBilder sites");
                    return;
                }

                return BML::redirect($POST{'ret'}) if $LJ::REDIRECT_ALLOWED{$redir_host};
            }

            if ($POST{'ref'} =~ /\Q$LJ::DOMAIN\E/ && $POST{'ref'} !~ m!/logout\.bml$! &&
                $POST{'ref'} !~ /[\n\r]/) 
            {
                return BML::redirect("$POST{'ref'}");
            }
            
            LJ::set_remote($u);
            $remote = $u;
        }
    }

    # otherwise if it's a get request, and the user is logged in
    # but has agreed to an old terms of service, log them out
    # and force a TOS validation
    if ($remote && ! LJ::did_post() && ! $remote->tosagree_verify) {
        $require_tos = 1;
        $logout_remote->();
    }

    $login_html->();
    return;
} 
 _code?><?_info
nocache=>1
_info?><?page
head<=
<?_code return (! LJ::get_remote() &&
                ! $LJ::IS_SSL &&
                ! $LJ::REQ_HEAD_HAS{'chalresp_js'}++) ?
                $LJ::COMMON_CODE{'chalresp_js'} : "";
_code?>
<=head
title=><?_ml .title _ml?>
body=><?_code return $body; _code?>
page?><?_c <LJDEP>
hook: post_login login_formopts
link: htdocs/login.bml, htdocs/todo/index.bml, htdocs/users, htdocs/create.bml, htdocs/lostinfo.bml
post: htdocs/login.bml
</LJDEP> _c?>
