package Fripost::Schema; =head1 NAME Schema.pm - =cut =head1 DESCRIPTION Schema.pm abstracts the LDAP schema definition and provides methods to add, list or delete virtual domains, users, aliases or lists. =cut use 5.010_000; use strict; use warnings; use utf8; use Net::LDAP; use Authen::SASL; use Fripost::Schema::Util qw/canonical_dn ldap_explode_dn split_addr/; use Fripost::Schema::Domain; use Fripost::Schema::User; use Fripost::Schema::Alias; use Fripost::Schema::List; use Fripost::Schema::Local; use Net::IDN::Encode 'email_to_ascii'; =head1 METHODS =over 4 =item B (I, I) Start a LDAP connection, and SASL-authenticate (with the GSSAPI mechanism) using proxy authentication for the given (fully-qualified) user. I should contain definitions for the LDAP suffix and the authentication ID. =cut sub SASLauth { my $class = shift; my ($l,$d) = split_addr(shift); my %cfg = @_; return unless defined $cfg{ldap_SASL_mechanism}; my $self = bless {}, $class; $self->suffix( ldap_explode_dn(@{$cfg{ldap_suffix}}) ); $self->whoami( canonical_dn( {fvu => $l}, {fvd => $d}, @{$self->suffix} )); $self->ldap( Net::LDAP::->new( $cfg{ldap_uri}, async => 0 )); my $callback; if ($cfg{ldap_SASL_mechanism} eq 'DIGEST-MD5') { $callback = { user => $cfg{ldap_authcID} , pass => $cfg{ldap_authcPW} , authname => 'dn:'.$self->whoami }; } elsif ($cfg{ldap_SASL_mechanism} eq 'GSSAPI') { $callback = { user => 'dn:'.$self->whoami }; } else { die "Unknown SASL mechanism: ".$cfg{ldap_SASL_mechanism}; } my $sasl = Authen::SASL::->new( mechanism => $cfg{ldap_SASL_mechanism} , callback => $callback ); my $host = $cfg{krb5_service_instance} // 'localhost'; my $conn = $sasl->client_new( 'ldap', $host ); die $conn->error if $conn->code; my $mesg = $self->ldap->bind( undef, sasl => $conn ); # This is not supposed to happen. die $mesg->error if $mesg->code; return $self; } =item B (I, I, I) Start a LDAP connection, and (simple-) binds the given user. I should contain definitions for the LDAP suffix and URI. =cut sub auth { my $class = shift; my $id = shift; my $pw = shift; my %cfg = @_; my $self = bless {}, $class; $self->suffix( ldap_explode_dn(@{$cfg{ldap_suffix}}) ); if (not (defined $id) or defined $cfg{ldap_bind_dn}) { $self->whoami( join ',', @{$cfg{ldap_bind_dn}} ); } else { my ($l,$d) = split_addr($id); $self->whoami( canonical_dn( {fvu => $l}, {fvd => $d}, @{$self->suffix} )); } $self->ldap( Net::LDAP::->new( $cfg{ldap_uri}, async => 0 ) ); my $mesg = $self->ldap->bind( $self->whoami, password => $pw ); if ($mesg->code) { if (defined $cfg{'-die'}) { return unless $cfg{'-die'}; die $cfg{'-die'}."\n"; } die $mesg->error; } return $self; } # The DN of the authorization ID sub whoami { shift->_set_or_get('_whoami',@_); } # The LDAP object (of class Net::LDAP) sub ldap { shift->_set_or_get('_ldap',@_); } # The suffix under which virtual domains are. sub suffix { shift->_set_or_get('_suffix',@_); } # Set or get a key (the first argument), depending on whether a second # argument is given or not. sub _set_or_get { my $self = shift; my $what = shift; if (@_) { $self->{$what} = $_[0]; } else { return $self->{$what}; } } =item B Bless the object to C, to access domain-specific methods. =cut sub domain { bless shift, 'Fripost::Schema::Domain'; } =item B Bless the object to C, to access user-specific methods. =cut sub user { bless shift, 'Fripost::Schema::User'; } =item B Bless the object to C, to access alias-specific methods. =cut sub alias { bless shift, 'Fripost::Schema::Alias'; } =item B Bless the object to C, to access list-specific methods. =cut sub list { bless shift, 'Fripost::Schema::List'; } =item B Bless the object to C, to access local-specific (users, aliases and lists) methods. =cut sub local { bless shift, 'Fripost::Schema::Local'; } =item B Unbinds from the LDAP server. =cut sub done { my $self = shift; $self->ldap->unbind if defined $self and defined $self->ldap; } =back =head1 AUTHOR Guilhem Moulin C<< >> =head1 COPYRIGHT Copyright 2012,2013 Guilhem Moulin. =head1 LICENSE This program is free software; you can redistribute it and/or modify it under the same terms as perl itself. =cut sub _dn2fvu { my $self = shift; my $dn = ldap_explode_dn(shift); return '@'. $dn->[0]->{fvd} if exists $dn->[0]->{fvd}; return $dn->[0]->{fvu} .'@'. $dn->[1]->{fvd}; } sub _fvu2dn { my $self = shift; my $email = shift; my ($l,$d) = split_addr($email); my @dn = ({fvd => $d}, @{$self->suffix}); unshift @dn, {fvu => $l} if defined $l and $l ne ''; canonical_dn( @dn ); } 1; __END__