1# This file is part of Config::Parser::ldap                   -*- perl -*-
2# Copyright (C) 2019-2021 Sergey Poznyakoff <gray@gnu.org>
3#
4# Config::Parser::ldap is free software; you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation; either version 3, or (at your option)
7# any later version.
8#
9# Config::Parser::ldap is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with Config::Parser::ldap.  If not, see <http://www.gnu.org/licenses/>.
16
17package Config::Parser::ldap;
18use strict;
19use warnings;
20use parent 'Config::Parser';
21use Carp;
22
23our $VERSION = '1.02';
24
25=head1 NAME
26
27Config::Parser::ldap - configuration file parser for ldap.conf
28
29=head1 SYNOPSIS
30
31    $cfg = new Config::Parser::ldap($filename);
32
33    $base = $cfg->get('base');
34
35
36=head1 DESCRIPTION
37
38A parser for F<ldap.conf> and similar files.
39
40The syntax of F<ldap.conf> configuration file is very simple.  Each statement
41occupies one physical line and consists of a keyword and its value separated
42by one or more space characters.  Keywords are case-insensitive.  A value
43starts with the first non-blank character after the keyword, and terminates
44at the end of the line, or at the last sequence of blanks before the end of
45the line.
46
47Blank lines and lines beginning with a hash mark are ignored.
48
49=head1 CONSTRUCTOR
50
51=head2 $cfg = new Config::Parser::ldap(%opts);
52
53Parses the supplied configuration file and creates a new object for
54manipulating its settings.  Keyword arguments I<%opts> are:
55
56=over 4
57
58=item filename
59
60Name of the file to parse.  The file must exist.
61
62=item line
63
64Optional line where the configuration starts in I<$filename>. It is used
65to keep track of statement location in the file for correct diagnostics.
66If not supplied, 1 is assumed.
67
68=item fh
69
70File handle to read from. If it is not supplied, new handle will be
71created by using open on the supplied I<$filename>.
72
73=item lexicon
74
75Dictionary of configuration statements that are allowed in the file. You
76will most probably not need this parameter. It is listed here for completeness
77sake. Refer to the L<Config::AST> constructor for details.
78
79=back
80
81=cut
82
83sub new {
84    my ($class, %args) = @_;
85    $class->SUPER::new(%args, ci => 1);
86}
87
88=head1 METHODS
89
90All methods for accessing the configuration settings are inherited from
91L<Config::AST>.
92
93If you wish to use this class as a base class, please refer to
94L<Config::Parser> for implementation details.
95
96=head1 EXAMPLE
97
98The following simplified example shows how to use this module to connect
99and bind to a LDAP server.
100
101    use Config::Parser::ldap;
102    use Net::LDAP;
103
104    # Parse configuration file
105    $cf = new Config::Parser::ldap(filename => '/etc/ldap.conf');
106
107    # Connect to server.
108    $ldap = Net::LDAP->new($cf->uri->value);
109
110    # Start TLS if required
111    $args{capath} = $cf->get('tls_cacertdir');
112    $args{cafile} = $cf->get('tls_cacert');
113    $args{clientcert} = $cf->get('tls_cert');
114    $args{clientkey} = $cf->get('tls_key');
115    $args{ciphers} = $cf->get('tls_cipher_suite');
116    if ($reqcert = $cf->get('tls_reqcert')) {
117	my %tab = (
118	    none => 'never',
119	    allow => 'optional',
120	    demand => 'require',
121	    hard => 'require',
122	    try => 'optional'
123	);
124	$args{verify} = $tab{$reqcert}
125	    or die "unrecognized tls_reqcert: $reqcert";
126    }
127    $mesg = $ldap->start_tls(%args);
128    $mesg->code && die $mesg->error;
129
130    # Bind
131    @bindargs = ();
132    if (my $v = $cf->get('binddn')) {
133	push @bindargs, $v
134    }
135    if (my $v = $cf->get('bindpw')) {
136	push @bindargs, password => $v;
137    }
138    $mesg = $ldap->bind(@bindargs);
139    $mesg->code && die $mesg->error;
140
141=cut
142
143sub parse {
144    my $self = shift;
145    my $filename = shift // confess "No filename given";
146    local %_ = @_;
147    my $fh = delete $_{fh};
148    unless ($fh) {
149	open($fh, "<", $filename)
150	    or croak "can't open $filename: $!";
151    }
152    my $line = delete $_{line} // 0;
153
154    while (<$fh>) {
155	++$line;
156	chomp;
157	s/^\s+//;
158	s/\s+$//;
159	s/#.*//;
160	next if $_ eq "";
161	my ($kw, $val) = split /\s+/, $_, 2;
162	my $locus = new Text::Locus($filename, $line);
163	if (defined($kw) && defined($val)) {
164	    $self->add_value([$kw], $val, $locus);
165	} else {
166	    $self->error("malformed line", locus => $locus);
167	    $self->{_error_count}++;
168	}
169    }
170    return $self;
171}
172
173=head1 SEE ALSO
174
175L<Config::AST>.
176
177L<Config::Parser>.
178
179=cut
180
1811;
182