summaryrefslogtreecommitdiffstats
path: root/contrib
diff options
context:
space:
mode:
authorOlivier Croquette <ocroquette@free.fr>2012-12-18 10:05:06 -0500
committerChad Horohoe <chorohoe@wikimedia.org>2012-12-19 22:28:34 -0500
commit4b6de14811bd49f2df8e86b65a4e895fea0dc3ef (patch)
tree297eabb7d17bd5739ce1dfffe2c6769c4c5dcca5 /contrib
parent09d2e6007e79b0a18951c2f1144e36c26b1c6047 (diff)
Lightweight LDAP server for debugging
Diffstat (limited to 'contrib')
-rw-r--r--contrib/fake_ldap.pl333
1 files changed, 333 insertions, 0 deletions
diff --git a/contrib/fake_ldap.pl b/contrib/fake_ldap.pl
new file mode 100644
index 0000000000..5d423a785b
--- /dev/null
+++ b/contrib/fake_ldap.pl
@@ -0,0 +1,333 @@
+#!/usr/bin/env perl
+
+# Fake LDAP server for Gerrit
+# Author: Olivier Croquette <ocroquette@free.fr>
+# Last change: 2012-11-12
+#
+# Abstract:
+# ====================================================================
+#
+# Gerrit currently supports several authentication schemes, but
+# unfortunately not the most basic one, e.g. local accounts with
+# local passwords.
+#
+# As a workaround, this script implements a minimal LDAP server
+# that can be used to authenticate against Gerrit. The information
+# required by Gerrit relative to users (user ID, password, display
+# name, email) is stored in a text file similar to /etc/passwd
+#
+#
+# Usage (see below for the setup)
+# ====================================================================
+#
+# To create a new file to store the user information:
+# fake-ldap edituser --datafile /path/datafile --username maxpower \
+# --displayname "Max Power" --email max.power@provider.com
+#
+# To modify an existing user (for instance the email):
+# fake-ldap edituser --datafile /path/datafile --username ocroquette \
+# --email max.power@provider2.com
+#
+# To set a new password for an existing user:
+# fake-ldap edituser --datafile /path/datafile --username ocroquette \
+# --password ""
+#
+# To start the server:
+# fake-ldap start --datafile /path/datafile
+#
+# The server reads the user data file on each new connection. It's not
+# scalable but it should not be a problem for the intended usage
+# (small teams, testing,...)
+#
+#
+# Setup
+# ===================================================================
+#
+# Install the dependencies
+#
+# Install the Perl module dependencies. On Debian and MacPorts,
+# all modules are available as packages, except Net::LDAP::Server.
+#
+# Debian: apt-get install libterm-readkey-perl
+#
+# Since Net::LDAP::Server consists only of one file, you can put it
+# along the script in Net/LDAP/Server.pm
+#
+# Create the data file with the first user (see above)
+#
+# Start as the script a server ("start" command, see above)
+#
+# Configure Gerrit with the following options:
+#
+# gerrit.canonicalWebUrl = ... (workaround for a known Gerrit bug)
+# auth.type = LDAP_BIND
+# ldap.server = ldap://localhost:10389
+# ldap.accountBase = ou=People,dc=nodomain
+# ldap.groupBase = ou=Group,dc=nodomain
+#
+# Start Gerrit
+#
+# Log on in the Web interface
+#
+# If you want the fake LDAP server to start at boot time, add it to
+# /etc/inittab, with a line like:
+#
+# ld1:6:respawn:su someuser /path/fake-ldap start --datafile /path/datafile
+#
+# ===================================================================
+
+use strict;
+
+# Global var containing the options passed on the command line:
+my %cmdLineOptions;
+
+# Global var containing the user data read from the data file:
+my %userData;
+
+my $defaultport = 10389;
+
+package MyServer;
+
+use Data::Dumper;
+use Net::LDAP::Server;
+use Net::LDAP::Constant qw(LDAP_SUCCESS LDAP_INVALID_CREDENTIALS LDAP_OPERATIONS_ERROR);
+use IO::Socket;
+use IO::Select;
+use Term::ReadKey;
+
+use Getopt::Long;
+
+use base 'Net::LDAP::Server';
+
+sub bind {
+ my $self = shift;
+ my ($reqData, $fullRequest) = @_;
+
+ print "bind called\n" if $cmdLineOptions{verbose} >= 1;
+ print Dumper(\@_) if $cmdLineOptions{verbose} >= 2;
+ my $sha1 = undef;
+ my $uid = undef;
+ eval{
+ $uid = $reqData->{name};
+ $sha1 = main::encryptpwd($uid, $reqData->{authentication}->{simple})
+ };
+ if ($@) {
+ warn $@;
+ return({
+ 'matchedDN' => '',
+ 'errorMessage' => $@,
+ 'resultCode' => LDAP_OPERATIONS_ERROR
+ });
+ }
+
+ print $sha1 . "\n" if $cmdLineOptions{verbose} >= 2;
+ print Dumper($userData{$uid}) . "\n" if $cmdLineOptions{verbose} >= 2;
+
+ if ( defined($sha1) && $sha1 && $userData{$uid} && ( $sha1 eq $userData{$uid}->{password} ) ) {
+ print "authentication of $uid succeeded\n" if $cmdLineOptions{verbose} >= 1;
+ return({
+ 'matchedDN' => "dn=$uid,ou=People,dc=nodomain",
+ 'errorMessage' => '',
+ 'resultCode' => LDAP_SUCCESS
+ });
+ }
+ else {
+ print "authentication of $uid failed\n" if $cmdLineOptions{verbose} >= 1;
+ return({
+ 'matchedDN' => '',
+ 'errorMessage' => '',
+ 'resultCode' => LDAP_INVALID_CREDENTIALS
+ });
+ }
+}
+
+sub search {
+ my $self = shift;
+ my ($reqData, $fullRequest) = @_;
+ print "search called\n" if $cmdLineOptions{verbose} >= 1;
+ print Dumper($reqData) if $cmdLineOptions{verbose} >= 2;
+ my @entries;
+ if ( $reqData->{baseObject} eq 'ou=People,dc=nodomain' ) {
+ my $uid = $reqData->{filter}->{equalityMatch}->{assertionValue};
+ push @entries, Net::LDAP::Entry->new ( "dn=$uid,ou=People,dc=nodomain",
+ , 'objectName'=>"dn=uid,ou=People,dc=nodomain", 'uid'=>$uid, 'mail'=>$userData{$uid}->{email}, 'displayName'=>$userData{$uid}->{displayName});
+ }
+ elsif ( $reqData->{baseObject} eq 'ou=Group,dc=nodomain' ) {
+ push @entries, Net::LDAP::Entry->new ( 'dn=Users,ou=Group,dc=nodomain',
+ , 'objectName'=>'dn=Users,ou=Group,dc=nodomain');
+ }
+
+ return {
+ 'matchedDN' => '',
+ 'errorMessage' => '',
+ 'resultCode' => LDAP_SUCCESS
+ }, @entries;
+}
+
+
+package main;
+
+use Digest::SHA1 qw(sha1 sha1_hex sha1_base64);
+
+sub exitWithError {
+ my $msg = shift;
+ print STDERR $msg . "\n";
+ exit(1);
+}
+
+sub encryptpwd {
+ my ($uid, $passwd) = @_;
+ # Use the user id to compute the hash, to avoid rainbox table attacks
+ return sha1_hex($uid.$passwd);
+}
+
+my $result = Getopt::Long::GetOptions (
+ "port=i" => \$cmdLineOptions{port},
+ "datafile=s" => \$cmdLineOptions{datafile},
+ "email=s" => \$cmdLineOptions{email},
+ "displayname=s" => \$cmdLineOptions{displayName},
+ "username=s" => \$cmdLineOptions{userName},
+ "password=s" => \$cmdLineOptions{password},
+ "verbose=i" => \$cmdLineOptions{verbose},
+);
+exitWithError("Failed to parse command line arguments") if ! $result;
+exitWithError("Please provide a valid path for the datafile") if ! $cmdLineOptions{datafile};
+
+my @commands = qw(start edituser);
+if ( @ARGV != 1 || ! grep {$_ eq $ARGV[0]} @commands ) {
+ exitWithError("Please provide a valid command among: " . join(",", @commands));
+}
+
+my $command = $ARGV[0];
+if ( $command eq "start") {
+ startServer();
+}
+elsif ( $command eq "edituser") {
+ editUser();
+}
+
+
+sub startServer() {
+
+ my $port = $cmdLineOptions{port} || $defaultport;
+
+ print "starting on port $port\n" if $cmdLineOptions{verbose} >= 1;
+
+ my $sock = IO::Socket::INET->new(
+ Listen => 5,
+ Proto => 'tcp',
+ Reuse => 1,
+ LocalAddr => "localhost", # Comment this line if Gerrit doesn't run on this host
+ LocalPort => $port
+ );
+
+ my $sel = IO::Select->new($sock);
+ my %Handlers;
+ while (my @ready = $sel->can_read) {
+ foreach my $fh (@ready) {
+ if ($fh == $sock) {
+ # Make sure the data is up to date on new every connection
+ readUserData();
+
+ # let's create a new socket
+ my $psock = $sock->accept;
+ $sel->add($psock);
+ $Handlers{*$psock} = MyServer->new($psock);
+ } else {
+ my $result = $Handlers{*$fh}->handle;
+ if ($result) {
+ # we have finished with the socket
+ $sel->remove($fh);
+ $fh->close;
+ delete $Handlers{*$fh};
+ }
+ }
+ }
+ }
+}
+
+sub readUserData {
+ %userData = ();
+ open (MYFILE, "<$cmdLineOptions{datafile}") || exitWithError("Could not open \"$cmdLineOptions{datafile}\" for reading");
+ while (<MYFILE>) {
+ chomp;
+ my @fields = split(/:/, $_);
+ $userData{$fields[0]} = { password=>$fields[1], displayName=>$fields[2], email=>$fields[3] };
+ }
+ close (MYFILE);
+}
+
+sub writeUserData {
+ open (MYFILE, ">$cmdLineOptions{datafile}") || exitWithError("Could not open \"$cmdLineOptions{datafile}\" for writing");
+ foreach my $userid (sort(keys(%userData))) {
+ my $userInfo = $userData{$userid};
+ print MYFILE join(":",
+ $userid,
+ $userInfo->{password},
+ $userInfo->{displayName},
+ $userInfo->{email}
+ ). "\n";
+ }
+ close (MYFILE);
+}
+
+sub readPassword {
+ Term::ReadKey::ReadMode('noecho');
+ my $password = Term::ReadKey::ReadLine(0);
+ Term::ReadKey::ReadMode('normal');
+ print "\n";
+ return $password;
+}
+
+sub readAndConfirmPassword {
+ print "Please enter the password: ";
+ my $pwd = readPassword();
+ print "Please re-enter the password: ";
+ my $pwdCheck = readPassword();
+ exitWithError("The passwords are different") if $pwd ne $pwdCheck;
+ return $pwd;
+}
+
+sub editUser {
+ exitWithError("Please provide a valid user name") if ! $cmdLineOptions{userName};
+ my $userName = $cmdLineOptions{userName};
+
+ readUserData() if -r $cmdLineOptions{datafile};
+
+ my $encryptedPassword = undef;
+ if ( ! defined($userData{$userName}) ) {
+ # New user
+
+ exitWithError("Please provide a valid display name") if ! $cmdLineOptions{displayName};
+ exitWithError("Please provide a valid email") if ! $cmdLineOptions{email};
+
+ $userData{$userName} = { };
+
+ if ( ! defined($cmdLineOptions{password}) ) {
+ # No password provided on the command line. Force reading from terminal.
+ $cmdLineOptions{password} = "";
+ }
+ }
+
+ if ( defined($cmdLineOptions{password}) && ! $cmdLineOptions{password} ) {
+ $cmdLineOptions{password} = readAndConfirmPassword();
+ exitWithError("Please provide a non empty password") if ! $cmdLineOptions{password};
+ }
+
+
+ if ( $cmdLineOptions{password} ) {
+ $encryptedPassword = encryptpwd($userName, $cmdLineOptions{password});
+ }
+
+
+ $userData{$userName}->{password} = $encryptedPassword if $encryptedPassword;
+ $userData{$userName}->{displayName} = $cmdLineOptions{displayName} if $cmdLineOptions{displayName};
+ $userData{$userName}->{email} = $cmdLineOptions{email} if $cmdLineOptions{email};
+ # print Data::Dumper::Dumper(\%userData);
+
+ print "New user data for $cmdLineOptions{userName}:\n";
+ foreach ( sort(keys(%{$userData{$userName}}))) {
+ printf " %-15s : %s\n", $_, $userData{$userName}->{$_}
+ }
+ writeUserData();
+} \ No newline at end of file