summaryrefslogtreecommitdiff
path: root/doc/plugins/contrib/unixauth.mdwn
blob: 76a8477446f0d9d43b7db33e203604139a795c4c (plain)

[[!template id=plugin name=unixauth core=0 author="[[schmonz]]"]] [[!tag type/auth]]

This plugin authenticates users against the Unix user database. It presents a similar UI to [[plugins/passwordauth]], but simpler, as there's no need to be able to register or change one's password.

To authenticate, either checkpassword or pwauth must be installed and configured. checkpassword is strongly preferred. If your web server runs as an unprivileged user -- as it darn well should! -- then checkpassword needs to be setuid root. (Or your ikiwiki CGI wrapper, I guess, but don't do that.) Other checkpassword implementations are available, notably checkpassword-pam.

Config variables that affect the behavior of unixauth:

  • unixauth_type: defaults to unset, can be "checkpassword" or "pwauth"
  • unixauth_command: defaults to unset, should contain the full path and any arguments
  • unixauth_requiressl: defaults to 1, can be 0
  • sslcookie: needs to be 1 if unixauth_requiressl is 1 (perhaps this should be done automatically?)

Security: As with passwordauth, be wary of sending usernames and passwords in cleartext. Unlike passwordauth, sniffing unixauth credentials can get an attacker much further than mere wiki access. Therefore, this plugin defaults to not even displaying the login form fields unless we're running under SSL. Nobody should be able to do anything remotely dumb until the admin has done at least a little thinking. After that, dumb things are always possible. ;-)

unixauth tests for the presence of the HTTPS environment variable. Wrapper.pm needs to be tweaked to pass it through; without that, the plugin fails closed.

[[!toggle id="diff" text="Wrapper.pm.diff"]]

[[!toggleable id="diff" text="""

--- Wrapper.pm.orig 2008-07-29 00:09:10.000000000 -0400
+++ Wrapper.pm
@@ -28,7 +28,7 @@ sub gen_wrapper () {
    my @envsave;
    push @envsave, qw{REMOTE_ADDR QUERY_STRING REQUEST_METHOD REQUEST_URI
                   CONTENT_TYPE CONTENT_LENGTH GATEWAY_INTERFACE
-                  HTTP_COOKIE REMOTE_USER} if $config{cgi};
+                  HTTP_COOKIE REMOTE_USER HTTPS} if $config{cgi};
    my $envsave="";
    foreach my $var (@envsave) {
            $envsave.=<<"EOF"

"""]]

[[!toggle id="code" text="unixauth.pm"]]

[[!toggleable id="code" text="""

#!/usr/bin/perl
# Ikiwiki unixauth authentication.
package IkiWiki::Plugin::unixauth;

use warnings;
use strict;
use IkiWiki 2.00;

sub import {
    hook(type => "getsetup", id => "unixauth", call => \&getsetup);
        hook(type => "formbuilder_setup", id => "unixauth",
            call => \&formbuilder_setup);
        hook(type => "formbuilder", id => "unixauth",
            call => \&formbuilder);
    hook(type => "sessioncgi", id => "unixauth", call => \&sessioncgi);
}

sub getsetup () {
    return
    unixauth_type => {
            type => "string",
            example => "checkpassword",
            description => "type of authenticator; can be 'checkpassword' or 'pwauth'",
            safe => 0,
            rebuild => 1,
    },
    unixauth_command => {
            type => "string",
            example => "/path/to/checkpassword",
            description => "full path and any arguments",
            safe => 0,
            rebuild => 1,
    },
    unixauth_requiressl => {
            type => "boolean",
            example => "1",
            description => "require SSL? strongly recommended",
            safe => 0,
            rebuild => 1,
    },
    plugin => {
            description => "Unix user authentication",
            safe => 0,
            rebuild => 1,
    },
}

# Checks if a string matches a user's password, and returns true or false.
sub checkpassword ($$;$) {
    my $user=shift;
    my $password=shift;
    my $field=shift || "password";

    # It's very important that the user not be allowed to log in with
    # an empty password!
    if (! length $password) {
            return 0;
    }

    my $ret=0;
    if (! exists $config{unixauth_type}) {
            # admin needs to carefully think over his configuration
            return 0;
    }
    elsif ($config{unixauth_type} eq "checkpassword") {
            open UNIXAUTH, "|$config{unixauth_command} true 3<&0" or die("Could not run $config{unixauth_type}");
            print UNIXAUTH "$user\0$password\0Y123456\0";
            close UNIXAUTH;
            $ret=!($?>>8);
    }
    elsif ($config{unixauth_type} eq "pwauth") {
            open UNIXAUTH, "|$config{unixauth_command}" or die("Could not run $config{unixauth_type}");
            print UNIXAUTH "$user\n$password\n";
            close UNIXAUTH;
            $ret=!($?>>8);
    }
    else {
            # no such authentication type
            return 0;
    }

    if ($ret) {
        my $userinfo=IkiWiki::userinfo_retrieve();
        if (! length $user || ! defined $userinfo ||
            ! exists $userinfo->{$user} || ! ref $userinfo->{$user}) {
                IkiWiki::userinfo_setall($user, {
                    'email' => '',
                    'regdate' => time,
                });
        }
    }

    return $ret;
}

sub formbuilder_setup (@) {
    my %params=@_;

    my $form=$params{form};
    my $session=$params{session};
    my $cgi=$params{cgi};

    # if not under SSL, die before even showing a login form,
    # unless the admin explicitly says it's fine
    if (! exists $config{unixauth_requiressl}) {
            $config{unixauth_requiressl} = 1;
    }
    if ($config{unixauth_requiressl}) {
        if ((! $config{sslcookie}) || (! exists $ENV{'HTTPS'})) {
            die("SSL required to login. Contact your administrator.<br>");
        }
    }

    if ($form->title eq "signin") {
            $form->field(name => "name", required => 0);
            $form->field(name => "password", type => "password", required => 0);
            
            if ($form->submitted) {
                    my $submittype=$form->submitted;
                    # Set required fields based on how form was submitted.
                    my %required=(
                            "Login" => [qw(name password)],
                    );
                    foreach my $opt (@{$required{$submittype}}) {
                            $form->field(name => $opt, required => 1);
                    }
    
                    # Validate password against name for Login.
                    if ($submittype eq "Login") {
                            $form->field(
                                    name => "password",
                                    validate => sub {
                                            checkpassword($form->field("name"), shift);
                                    },
                            );
                    }
                    
                    # XXX is this reachable? looks like no
                    elsif ($submittype eq "Login") {
                            $form->field( 
                                    name => "name",
                                    validate => sub {
                                            my $name=shift;
                                            length $name &&
                                            IkiWiki::userinfo_get($name, "regdate");
                                    },
                            );
                    }
            }
            else {
                    # First time settings.
                    $form->field(name => "name");
                    if ($session->param("name")) {
                            $form->field(name => "name", value => $session->param("name"));
                    }
            }
    }
    elsif ($form->title eq "preferences") {
            $form->field(name => "name", disabled => 1, 
                    value => $session->param("name"), force => 1,
                    fieldset => "login");
            $form->field(name => "password", disabled => 1, type => "password",
                    fieldset => "login"),
    }
}

sub formbuilder (@) {
    my %params=@_;

    my $form=$params{form};
    my $session=$params{session};
    my $cgi=$params{cgi};
    my $buttons=$params{buttons};

    if ($form->title eq "signin") {
            if ($form->submitted && $form->validate) {
                    if ($form->submitted eq 'Login') {
                            $session->param("name", $form->field("name"));
                            IkiWiki::cgi_postsignin($cgi, $session);
                    }
            }
    }
    elsif ($form->title eq "preferences") {
            if ($form->submitted eq "Save Preferences" && $form->validate) {
                    my $user_name=$form->field('name');
            }
    }
}

sub sessioncgi ($$) {
    my $q=shift;
    my $session=shift;
}

1

"""]]