[[!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](http://cr.yp.to/checkpwd.html) or [pwauth](http://www.unixpapa.com/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](http://checkpasswd-pam.sourceforge.net/). 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](/security/#index14h2), 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 """]]