<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">#!/usr/bin/perl
# Ikiwiki password authentication.
package IkiWiki::Plugin::passwordauth;

use warnings;
use strict;
use IkiWiki 3.00;

sub import {
	hook(type =&gt; "getsetup", id =&gt; "passwordauth", "call" =&gt; \&amp;getsetup);
	hook(type =&gt; "formbuilder_setup", id =&gt; "passwordauth", call =&gt; \&amp;formbuilder_setup);
	hook(type =&gt; "formbuilder", id =&gt; "passwordauth", call =&gt; \&amp;formbuilder);
	hook(type =&gt; "sessioncgi", id =&gt; "passwordauth", call =&gt; \&amp;sessioncgi);
	hook(type =&gt; "auth", id =&gt; "passwordauth", call =&gt; \&amp;auth);
}

sub getsetup () {
	return
		plugin =&gt; {
			safe =&gt; 1,
			rebuild =&gt; 0,
			section =&gt; "auth",
		},
		account_creation_password =&gt; {
			type =&gt; "string",
			example =&gt; "s3cr1t",
			description =&gt; "a password that must be entered when signing up for an account",
			safe =&gt; 1,
			rebuild =&gt; 0,
		},
		password_cost =&gt; {
			type =&gt; "integer",
			example =&gt; 8,
			description =&gt; "cost of generating a password using Authen::Passphrase::BlowfishCrypt",
			safe =&gt; 1,
			rebuild =&gt; 0,
		},
}

# 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 $userinfo=IkiWiki::userinfo_retrieve();
	if (! length $user || ! defined $userinfo ||
	    ! exists $userinfo-&gt;{$user} || ! ref $userinfo-&gt;{$user}) {
		return 0;
	}

	my $ret=0;
	if (exists $userinfo-&gt;{$user}-&gt;{"crypt".$field}) {
		eval q{use Authen::Passphrase};
		error $@ if $@;
		my $p = Authen::Passphrase-&gt;from_crypt($userinfo-&gt;{$user}-&gt;{"crypt".$field});
		$ret=$p-&gt;match($password);
	}
	elsif (exists $userinfo-&gt;{$user}-&gt;{$field}) {
		$ret=$password eq $userinfo-&gt;{$user}-&gt;{$field};
	}

	if ($ret &amp;&amp;
	    (exists $userinfo-&gt;{$user}-&gt;{resettoken} ||
	     exists $userinfo-&gt;{$user}-&gt;{cryptresettoken})) {
		# Clear reset token since the user has successfully logged in.
		delete $userinfo-&gt;{$user}-&gt;{resettoken};
		delete $userinfo-&gt;{$user}-&gt;{cryptresettoken};
		IkiWiki::userinfo_store($userinfo);
	}

	return $ret;
}

sub setpassword ($$;$) {
	my $user=shift;
	my $password=shift;
	my $field=shift || "password";

	eval q{use Authen::Passphrase::BlowfishCrypt};
	if (! $@) {
		my $p = Authen::Passphrase::BlowfishCrypt-&gt;new(
			cost =&gt; $config{password_cost} || 8,
			salt_random =&gt; 1,
			passphrase =&gt; $password,
		);
		IkiWiki::userinfo_set($user, "crypt$field", $p-&gt;as_crypt);
		IkiWiki::userinfo_set($user, $field, "");
	}
	else {
		IkiWiki::userinfo_set($user, $field, $password);
	}

	# Setting the password clears any passwordless login token.
	if ($field ne 'passwordless') {
		IkiWiki::userinfo_set($user, "passwordless", "");
	}
}

# Generates a token that can be used to log the user in.
# This needs to be hard to guess. Generating a cgi session id will
# make it as hard to guess as any cgi session.
sub gentoken ($$;$) {
	my $user=shift;
	my $tokenfield=shift;
	my $reversable=shift;

	eval q{use CGI::Session};
	error($@) if $@;
	my $token = CGI::Session-&gt;new-&gt;id;
	if (! $reversable) {
		setpassword($user, $token, $tokenfield);
	}
	else {
		IkiWiki::userinfo_set($user, $tokenfield, $token);
	}
	return $token;
}

# An anonymous user has no normal password, only a passwordless login
# token. Given an email address, this sets up such a user for that email,
# unless one already exists, and returns the username.
sub anonuser ($) {
	my $email=shift;

	# Want a username for this email that won't overlap with any other.
	my $user=$email;
	$user=~s/@/_/g;

	my $userinfo=IkiWiki::userinfo_retrieve();
	if (! exists $userinfo-&gt;{$user} || ! ref $userinfo-&gt;{$user}) {
		if (IkiWiki::userinfo_setall($user, {
		    	'email' =&gt; $email,
			'regdate' =&gt; time})) {
			gentoken($user, "passwordless", 1);
			return $user;
		}
		else {
			error(gettext("Error creating account."));
		}
	}
	elsif (defined anonusertoken($userinfo-&gt;{$user})) {
		return $user;
	}
	else {
		return undef;
	}
}

sub anonusertoken ($) {
	my $userhash=shift;
	if (exists $userhash-&gt;{passwordless} &amp;&amp;
	    length $userhash-&gt;{passwordless}) {
		return $userhash-&gt;{passwordless};
	}
	else {
		return undef;
	}
}

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

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

	my $do_register=defined $cgi-&gt;param("do") &amp;&amp; $cgi-&gt;param("do") eq "register";

	if ($form-&gt;title eq "signin" || $form-&gt;title eq "register" || $do_register) {
		$form-&gt;field(name =&gt; "name", required =&gt; 0);
		$form-&gt;field(name =&gt; "password", type =&gt; "password", required =&gt; 0);
		
		if ($form-&gt;submitted eq "Register" || $form-&gt;submitted eq "Create Account" || $do_register) {
			$form-&gt;field(name =&gt; "confirm_password", type =&gt; "password");
			$form-&gt;field(name =&gt; "account_creation_password", type =&gt; "password")
				 if (defined $config{account_creation_password} &amp;&amp;
				     length $config{account_creation_password});
			$form-&gt;field(name =&gt; "email", size =&gt; 50);
			$form-&gt;title("register");
			$form-&gt;text("");
		
			$form-&gt;field(name =&gt; "confirm_password",
				validate =&gt; sub {
					shift eq $form-&gt;field("password");
				},
			);
			$form-&gt;field(name =&gt; "password",
				validate =&gt; sub {
					shift eq $form-&gt;field("confirm_password");
				},
			);
		}

		if ($form-&gt;submitted) {
			my $submittype=$form-&gt;submitted;
			# Set required fields based on how form was submitted.
			my %required=(
				"Login" =&gt; [qw(name password)],
				"Register" =&gt; [],
				"Create Account" =&gt; [qw(name password confirm_password email)],
				"Reset Password" =&gt; [qw(name)],
			);
			foreach my $opt (@{$required{$submittype}}) {
				$form-&gt;field(name =&gt; $opt, required =&gt; 1);
			}
	
			if ($submittype eq "Create Account") {
				$form-&gt;field(
					name =&gt; "account_creation_password",
					validate =&gt; sub {
						shift eq $config{account_creation_password};
					},
					required =&gt; 1,
				) if (defined $config{account_creation_password} &amp;&amp;
				      length $config{account_creation_password});
				$form-&gt;field(
					name =&gt; "email",
					validate =&gt; "EMAIL",
				);
			}

			# Validate password against name for Login.
			if ($submittype eq "Login") {
				$form-&gt;field(
					name =&gt; "password",
					validate =&gt; sub {
						checkpassword(scalar $form-&gt;field("name"), shift);
					},
				);
			}
			elsif ($submittype eq "Register" ||
			       $submittype eq "Create Account" ||
			       $submittype eq "Reset Password") {
				$form-&gt;field(name =&gt; "password", validate =&gt; 'VALUE');
			}
			
			# And make sure the entered name exists when logging
			# in or sending email, and does not when registering.
			if ($submittype eq 'Create Account' ||
			    $submittype eq 'Register') {
				$form-&gt;field(
					name =&gt; "name",
					validate =&gt; sub {
						my $name=shift;
						length $name &amp;&amp;
						$name=~/$config{wiki_file_regexp}/ &amp;&amp;
						# don't allow registering
						# accounts that look like
						# openids, or email
						# addresses, even if the
						# file regexp allows it
						$name!~/[\/:\@]/ &amp;&amp;
						! IkiWiki::userinfo_get($name, "regdate");
					},
				);
			}
			elsif ($submittype eq "Login" ||
			       $submittype eq "Reset Password") {
				$form-&gt;field( 
					name =&gt; "name",
					validate =&gt; sub {
						my $name=shift;
						length $name &amp;&amp;
						IkiWiki::userinfo_get($name, "regdate");
					},
				);
			}
		}
		else {
			# First time settings.
			$form-&gt;field(name =&gt; "name");
			if ($session-&gt;param("name")) {
				$form-&gt;field(name =&gt; "name", value =&gt; $session-&gt;param("name"));
			}
		}
	}
	elsif ($form-&gt;title eq "preferences") {
		my $user=$session-&gt;param("name");
		if (! IkiWiki::openiduser($user) &amp;&amp; ! IkiWiki::emailuser($user)) {
			$form-&gt;field(name =&gt; "name", disabled =&gt; 1, 
				value =&gt; $user, force =&gt; 1,
				fieldset =&gt; "login");
			$form-&gt;field(name =&gt; "password", type =&gt; "password",
				fieldset =&gt; "login",
				validate =&gt; sub {
					shift eq $form-&gt;field("confirm_password");
				});
			$form-&gt;field(name =&gt; "confirm_password", type =&gt; "password",
				fieldset =&gt; "login",
				validate =&gt; sub {
					shift eq $form-&gt;field("password");
				});
			
			my $userpage=IkiWiki::userpage($user);
			if (exists $pagesources{$userpage}) {
				$form-&gt;text(gettext("Your user page: ").
					htmllink("", "", $userpage,
						noimageinline =&gt; 1));
			}
			else {
				$form-&gt;text("&lt;a href=\"".
					IkiWiki::cgiurl(do =&gt; "edit", page =&gt; $userpage).
					"\"&gt;".gettext("Create your user page")."&lt;/a&gt;");
			}
		}
	}
}

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

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

	my $do_register=defined $cgi-&gt;param("do") &amp;&amp; $cgi-&gt;param("do") eq "register";

	if ($form-&gt;title eq "signin" || $form-&gt;title eq "register") {
		if (($form-&gt;submitted &amp;&amp; $form-&gt;validate) || $do_register) {
			if ($form-&gt;submitted eq 'Login') {
				$session-&gt;param("name", $form-&gt;field("name"));
				IkiWiki::cgi_postsignin($cgi, $session);
			}
			elsif ($form-&gt;submitted eq 'Create Account') {
				my $user_name=$form-&gt;field('name');
				if (IkiWiki::userinfo_setall($user_name, {
				    	'email' =&gt; $form-&gt;field('email'),
					'regdate' =&gt; time})) {
					setpassword($user_name, $form-&gt;field('password'));
					$form-&gt;field(name =&gt; "confirm_password", type =&gt; "hidden");
					$form-&gt;field(name =&gt; "email", type =&gt; "hidden");
					$form-&gt;text(gettext("Account creation successful. Now you can Login."));
				}
				else {
					error(gettext("Error creating account."));
				}
			}
			elsif ($form-&gt;submitted eq 'Reset Password') {
				my $user_name=$form-&gt;field("name");
				my $email=IkiWiki::userinfo_get($user_name, "email");
				if (! length $email) {
					error(gettext("No email address, so cannot email password reset instructions."));
				}

				my $token=gentoken($user_name, "resettoken");
				
				my $template=template("passwordmail.tmpl");
				$template-&gt;param(
					user_name =&gt; $user_name,
					passwordurl =&gt; IkiWiki::cgiurl_abs(
						'do' =&gt; "reset",
						'name' =&gt; $user_name,
						'token' =&gt; $token,
					),
					wikiurl =&gt; $config{url},
					wikiname =&gt; $config{wikiname},
					remote_addr =&gt; $session-&gt;remote_addr(),
				);
				
				eval q{use Mail::Sendmail};
				error($@) if $@;
				sendmail(
					To =&gt; IkiWiki::userinfo_get($user_name, "email"),
					From =&gt; "$config{wikiname} admin &lt;".
						(defined $config{adminemail} ? $config{adminemail} : "")
						."&gt;",
					Subject =&gt; "$config{wikiname} information",
					Message =&gt; $template-&gt;output,
				) or error(gettext("Failed to send mail"));
				
				$form-&gt;text(gettext("You have been mailed password reset instructions."));
				$form-&gt;field(name =&gt; "name", required =&gt; 0);
				push @$buttons, "Reset Password";
			}
			elsif ($form-&gt;submitted eq "Register" || $do_register) {
				@$buttons="Create Account";
			}
		}
		elsif ($form-&gt;submitted eq "Create Account") {
			@$buttons="Create Account";
		}
		else {
			push @$buttons, "Register", "Reset Password";
		}
	}
	elsif ($form-&gt;title eq "preferences") {
		if ($form-&gt;submitted eq "Save Preferences" &amp;&amp; $form-&gt;validate) {
			my $user_name=$form-&gt;field('name');
			if (defined $form-&gt;field("password") &amp;&amp; length $form-&gt;field("password")) {
				setpassword($user_name, scalar $form-&gt;field('password'));
			}
		}
	}
}

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

	if ($q-&gt;param('do') eq 'reset') {
		my $name=$q-&gt;param("name");
		my $token=$q-&gt;param("token");

		if (! defined $name || ! defined $token ||
		    ! length $name  || ! length $token) {
			error(gettext("incorrect password reset url"));
	 	}
		if (! checkpassword($name, $token, "resettoken")) {
			error(gettext("password reset denied"));
		}

		$session-&gt;param("name", $name);
		IkiWiki::cgi_prefs($q, $session);
		exit;
	}
	elsif ($q-&gt;param('do') eq 'tokenauth') {
		my $name=$q-&gt;param("name");
		my $token=$q-&gt;param("token");

		if (! defined $name || ! defined $token ||
		    ! length $name  || ! length $token) {
			error(gettext("incorrect url"));
	 	}
		if (! checkpassword($name, $token, "passwordless")) {
			error(gettext("access denied"));
		}

		$session-&gt;param("name", $name);
		IkiWiki::cgi_prefs($q, $session);
		exit;
	}
	elsif ($q-&gt;param("do") eq "register") {
		# After registration, need to go somewhere, so show prefs page.
		$session-&gt;param(postsignin =&gt; "do=prefs");
		# Due to do=register, this will run in registration-only
		# mode.
		IkiWiki::cgi_signin($q, $session);
		exit;
	}
}

sub auth ($$) {
	# While this hook is not currently used, it needs to exist
	# so ikiwiki knows that the wiki supports logins, and will
	# enable the Preferences page.
}

1
</pre></body></html>