gendoc.pl revision 97241
1#!/usr/bin/perl -w
2#-
3# Copyright (c) 2002 Networks Associates Technology, Inc.
4# All rights reserved.
5#
6# This software was developed for the FreeBSD Project by ThinkSec AS and
7# NAI Labs, the Security Research Division of Network Associates, Inc.
8# under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the
9# DARPA CHATS research program.
10#
11# Redistribution and use in source and binary forms, with or without
12# modification, are permitted provided that the following conditions
13# are met:
14# 1. Redistributions of source code must retain the above copyright
15#    notice, this list of conditions and the following disclaimer.
16# 2. Redistributions in binary form must reproduce the above copyright
17#    notice, this list of conditions and the following disclaimer in the
18#    documentation and/or other materials provided with the distribution.
19# 3. The name of the author may not be used to endorse or promote
20#    products derived from this software without specific prior written
21#    permission.
22#
23# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
24# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
27# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33# SUCH DAMAGE.
34#
35# $P4: //depot/projects/openpam/misc/gendoc.pl#14 $
36#
37
38use strict;
39use Fcntl;
40use POSIX qw(strftime);
41use vars qw($COPYRIGHT $TODAY %FUNCTIONS %PAMERR);
42
43%PAMERR = (
44    PAM_SUCCESS			=> "Success",
45    PAM_OPEN_ERR		=> "Failed to load module",
46    PAM_SYMBOL_ERR		=> "Invalid symbol",
47    PAM_SERVICE_ERR		=> "Error in service module",
48    PAM_SYSTEM_ERR		=> "System error",
49    PAM_BUF_ERR			=> "Memory buffer error",
50    PAM_CONV_ERR		=> "Conversation failure",
51    PAM_PERM_DENIED		=> "Permission denied",
52    PAM_MAXTRIES		=> "Maximum number of tries exceeded",
53    PAM_AUTH_ERR		=> "Authentication error",
54    PAM_NEW_AUTHTOK_REQD	=> "New authentication token required",
55    PAM_CRED_INSUFFICIENT	=> "Insufficient credentials",
56    PAM_AUTHINFO_UNAVAIL	=> "Authentication information is unavailable",
57    PAM_USER_UNKNOWN		=> "Unknown user",
58    PAM_CRED_UNAVAIL		=> "Failed to retrieve user credentials",
59    PAM_CRED_EXPIRED		=> "User credentials have expired",
60    PAM_CRED_ERR		=> "Failed to set user credentials",
61    PAM_ACCT_EXPIRED		=> "User accound has expired",
62    PAM_AUTHTOK_EXPIRED		=> "Password has expired",
63    PAM_SESSION_ERR		=> "Session failure",
64    PAM_AUTHTOK_ERR		=> "Authentication token failure",
65    PAM_AUTHTOK_RECOVERY_ERR	=> "Failed to recover old authentication token",
66    PAM_AUTHTOK_LOCK_BUSY	=> "Authentication token lock busy",
67    PAM_AUTHTOK_DISABLE_AGING	=> "Authentication token aging disabled",
68    PAM_NO_MODULE_DATA		=> "Module data not found",
69    PAM_IGNORE			=> "Ignore this module",
70    PAM_ABORT			=> "General failure",
71    PAM_TRY_AGAIN		=> "Try again",
72    PAM_MODULE_UNKNOWN		=> "Unknown module type",
73    PAM_DOMAIN_UNKNOWN		=> "Unknown authentication domain",
74);
75
76sub parse_source($) {
77    my $fn = shift;
78
79    local *FILE;
80    my $source;
81    my $func;
82    my $descr;
83    my $type;
84    my $args;
85    my $argnames;
86    my $man;
87    my $inlist;
88    my $inliteral;
89    my %xref;
90    my @errors;
91
92    if ($fn !~ m,\.c$,) {
93	warn("$fn: not C source, ignoring\n");
94	return;
95    }
96
97    sysopen(FILE, $fn, O_RDONLY)
98	or die("$fn: open(): $!\n");
99    $source = join('', <FILE>);
100    close(FILE);
101
102    return if ($source =~ m/^ \* NOPARSE\s*$/m);
103
104    if (!defined($COPYRIGHT) && $source =~ m,^(/\*-\n.*?)\s*\*/,s) {
105	$COPYRIGHT = $1;
106	$COPYRIGHT =~ s,^.\*,.\\\",gm;
107	$COPYRIGHT =~ s,(\$(?:)P4).*?\$,$1\$,;
108	$COPYRIGHT .= "\n.\\\"";
109    }
110    $func = $fn;
111    $func =~ s,^(?:.*/)?([^/]+)\.c$,$1,;
112    if ($source !~ m,\n \* ([\S ]+)\n \*/\n\n([\S ]+)\n$func\((.*?)\)\n\{,s) {
113	warn("$fn: can't find $func\n");
114	return;
115    }
116    ($descr, $type, $args) = ($1, $2, $3);
117    $descr =~ s,^([A-Z][a-z]),lc($1),e;
118    $descr =~ s,[\.\s]*$,,;
119    while ($args =~ s/^((?:[^\(]|\([^\)]*\))*),\s*/$1\" \"/g) {
120	# nothing
121    }
122    $args =~ s/,\s+/, /gs;
123    $args = "\"$args\"";
124
125    %xref = (
126	"pam 3" => 1
127    );
128
129    if ($type eq "int") {
130	foreach (split("\n", $source)) {
131	    next unless (m/^ \*\s+(!?PAM_[A-Z_]+|=[a-z_]+)\s*$/);
132	    push(@errors, $1);
133	}
134	$xref{"pam_strerror 3"} = 1;
135    }
136
137    $argnames = $args;
138    $argnames =~ s/\"[^\"]+\*?\b(\w+)\"/\"$1\"/g;
139    $argnames =~ s/([\|\[\]\(\)\.\*\+\?])/\\$1/g;
140    $argnames =~ s/\" \"/|/g;
141    $argnames =~ s/^\"(.*)\"$/($1)/;
142    $inliteral = $inlist = 0;
143    foreach (split("\n", $source)) {
144	s/\s*$//;
145	if (!defined($man)) {
146	    if (m/^\/\*\*$/) {
147		$man = "";
148	    }
149	    next;
150	}
151	last if (m/^ \*\/$/);
152	s/^ \* ?//;
153	s/\\(.)/$1/gs;
154	if (m/^$/) {
155	    if ($man ne "" && $man !~ m/\.Pp\n$/s) {
156		if ($inliteral) {
157		    $man .= "\0\n";
158		} elsif ($inlist) {
159		    $man .= ".El\n";
160		    $inlist = 0;
161		} else {
162		    $man .= ".Pp\n";
163		}
164	    }
165	    next;
166	}
167	if (m/^>(\w+)(?:\s+(\d))?$/) {
168	    ++$xref{$2 ? "$1 $2" : "$1 3"};
169	    next;
170	}
171	if (s/^\s+(=?\w+):\s*/.It $1/) {
172	    if ($inliteral) {
173		$man .= ".Ed\n";
174		$inliteral = 0;
175	    }
176	    if (!$inlist) {
177		$man =~ s/\.Pp\n$//s;
178		$man .= ".Bl -tag -width 18n\n";
179		$inlist = 1;
180	    }
181	    s/^\.It =([A-Z][A-Z_]+)$/.It Dv $1/gs;
182	    $man .= "$_\n";
183	    next;
184	} elsif ($inlist && m/^\S/) {
185	    $man .= ".El\n";
186	    $inlist = 0;
187	} elsif ($inliteral && m/^\S/) {
188	    $man .= ".Ed\n";
189	    $inliteral = 0;
190	} elsif ($inliteral) {
191	    $man .= "$_\n";
192	    next;
193	} elsif ($inlist) {
194	    s/^\s+//;
195	} elsif (m/^\s+/) {
196	    $man .= ".Bd -literal\n";
197	    $inliteral = 1;
198	    $man .= "$_\n";
199	    next;
200	}
201	s/\s*=$func\b\s*/\n.Nm\n/gs;
202	s/\s*=$argnames\b\s*/\n.Va $1\n/gs;
203	s/\s*=(struct \w+(?: \*)?)\b\s*/\n.Vt $1\n/gs;
204	s/\s*:([a-z_]+)\b\s*/\n.Va $1\n/gs;
205	s/\s*;([a-z_]+)\b\s*/\n.Dv $1\n/gs;
206	if (s/\s*=([a-z_]+)\b\s*/\n.Xr $1 3\n/gs) {
207	    ++$xref{"$1 3"};
208	}
209	s/\s*\"(?=\w)/\n.Do\n/gs;
210	s/\"(?!\w)\s*/\n.Dc\n/gs;
211	s/\s*=([A-Z][A-Z_]+)\b\s*(?![\.,:;])/\n.Dv $1\n/gs;
212	s/\s*=([A-Z][A-Z_]+)\b([\.,:;]+)\s*/\n.Dv $1 $2\n/gs;
213	s/\s*{([A-Z][a-z] .*?)}\s*/\n.$1\n/gs;
214	$man .= "$_\n";
215    }
216    if (defined($man)) {
217	if ($inlist) {
218	    $man .= ".El\n";
219	}
220	if ($inliteral) {
221	    $man .= ".Ed\n";
222	}
223	$man =~ s/(\n\.[A-Z][a-z] [\w ]+)\n([\.,:;-]\S*)\s*/$1 $2\n/gs;
224	$man =~ s/\s*$/\n/gm;
225	$man =~ s/\n+/\n/gs;
226	$man =~ s/\0//gs;
227	$man =~ s/\n\n\./\n\./gs;
228	chomp($man);
229    } else {
230	$man = "No description available.";
231    }
232
233    $FUNCTIONS{$func} = {
234	'name'		=> $func,
235	'descr'		=> $descr,
236	'type'		=> $type,
237	'args'		=> $args,
238	'man'		=> $man,
239	'xref'		=> \%xref,
240	'errors'	=> \@errors,
241    };
242    if ($source =~ m/^ \* NODOC\s*$/m) {
243	$FUNCTIONS{$func}->{'nodoc'} = 1;
244	$FUNCTIONS{$func}->{'nolist'} = 1;
245    }
246    if ($source =~ m/^ \* NOLIST\s*$/m) {
247	$FUNCTIONS{$func}->{'nolist'} = 1;
248    }
249    if ($source !~ m/^ \* XSSO \d/m) {
250	$FUNCTIONS{$func}->{'openpam'} = 1;
251    }
252}
253
254sub expand_errors($);
255sub expand_errors($) {
256    my $func = shift;		# Ref to function hash
257
258    my %errors;
259
260    if (defined($func->{'recursed'})) {
261	warn("$func->{'name'}(): loop in error spec\n");
262	return qw();
263    }
264    $func->{'recursed'} = 1;
265
266    foreach (@{$func->{'errors'}}) {
267	if (m/^(PAM_[A-Z_]+)$/) {
268	    if (!defined($PAMERR{$1})) {
269		warn("$func->{'name'}(): unrecognized error: $1\n");
270		next;
271	    }
272	    $errors{$1} = 1;
273	} elsif (m/^!(PAM_[A-Z_]+)$/) {
274	    # treat negations separately
275	} elsif (m/^=([a-z_]+)$/) {
276	    if (!defined($FUNCTIONS{$1})) {
277		warn("$func->{'name'}(): reference to unknown $1()\n");
278		next;
279	    }
280	    foreach (expand_errors($FUNCTIONS{$1})) {
281		$errors{$_} = 1;
282	    }
283	} else {
284	    warn("$func->{'name'}(): invalid error specification: $_\n");
285	}
286    }
287    foreach (@{$func->{'errors'}}) {
288	if (m/^!(PAM_[A-Z_]+)$/) {
289	    delete($errors{$1});
290	}
291    }
292    delete($func->{'recursed'});
293    return (sort(keys(%errors)));
294}
295
296sub gendoc($) {
297    my $func = shift;		# Ref to function hash
298
299    local *FILE;
300    my $mdoc;
301    my $fn;
302
303    return if defined($func->{'nodoc'});
304
305    $mdoc = "$COPYRIGHT
306.Dd $TODAY
307.Dt " . uc($func->{'name'}) . " 3
308.Os
309.Sh NAME
310.Nm $func->{'name'}
311.Nd $func->{'descr'}
312.Sh LIBRARY
313.Lb libpam
314.Sh SYNOPSIS
315.In security/pam_appl.h
316";
317    if ($func->{'name'} =~ m/_sm_/) {
318	$mdoc .= ".In security/pam_modules.h\n"
319    }
320    if ($func->{'name'} =~ m/openpam/) {
321	$mdoc .= ".In security/openpam.h\n"
322    }
323    $mdoc .= ".Ft $func->{'type'}
324.Fn $func->{'name'} $func->{'args'}
325.Sh DESCRIPTION
326$func->{'man'}
327";
328    if ($func->{'type'} eq "int") {
329	$mdoc .= ".Sh RETURN VALUES
330The
331.Nm
332function returns one of the following values:
333.Bl -tag -width 18n
334";
335	my @errors = expand_errors($func);
336	warn("$func->{'name'}(): no error specification\n")
337	    unless(@errors);
338	foreach (@errors) {
339	    $mdoc .= ".It Bq Er $_\n$PAMERR{$_}.\n";
340	}
341	$mdoc .= ".El\n";
342    } else {
343	if ($func->{'type'} =~ m/\*$/) {
344	    $mdoc .= ".Sh RETURN VALUES
345The
346.Nm
347function returns
348.Dv NULL
349on failure.
350";
351	}
352    }
353    $mdoc .= ".Sh SEE ALSO\n";
354    my @xref = sort(keys(%{$func->{'xref'}}));
355    while (@xref) {
356	$mdoc .= ".Xr " . shift(@xref) . (@xref ? " ,\n" : "\n");
357    }
358    $mdoc .= ".Sh STANDARDS\n";
359    if ($func->{'openpam'}) {
360	$mdoc .= "The
361.Nm
362function is an OpenPAM extension.
363";
364    } else {
365	$mdoc .= ".Rs
366.%T \"X/Open Single Sign-On Service (XSSO) - Pluggable Authentication Modules\"
367.%D \"June 1997\"
368.Re
369";
370    }
371    $mdoc .= ".Sh AUTHORS
372The
373.Nm
374function and this manual page were developed for the FreeBSD Project
375by ThinkSec AS and NAI Labs, the Security Research Division of Network
376Associates, Inc.  under DARPA/SPAWAR contract N66001-01-C-8035
377.Pq Dq CBOSS ,
378as part of the DARPA CHATS research program.
379";
380
381    $fn = "$func->{'name'}.3";
382    if (sysopen(FILE, $fn, O_RDWR|O_CREAT|O_TRUNC)) {
383	print(FILE $mdoc);
384	close(FILE);
385    } else {
386	warn("$fn: open(): $!\n");
387    }
388}
389
390sub gensummary() {
391
392    my $func;
393
394    print "$COPYRIGHT
395.Dd $TODAY
396.Dt PAM 3
397.Os
398.Sh NAME
399";
400    my @funcs = sort(keys(%FUNCTIONS));
401    while ($func = shift(@funcs)) {
402	next if (defined($FUNCTIONS{$func}->{'nolist'}));
403	print ".Nm $func". (@funcs ? " ,\n" : "\n");
404    }
405    print ".Nd Pluggable Authentication Modules Library
406.Sh LIBRARY
407.Lb libpam
408.Sh SYNOPSIS
409.In security/pam_appl.h
410";
411    foreach $func (sort(keys(%FUNCTIONS))) {
412	next if (defined($FUNCTIONS{$func}->{'nolist'}));
413	print ".Ft $FUNCTIONS{$func}->{'type'}\n";
414	print ".Fn $func $FUNCTIONS{$func}->{'args'}\n";
415    }
416    print ".Sh DESCRIPTION
417.Sh RETURN VALUES
418The following return codes are defined in the
419.In security/pam_constants.h
420header:
421.Bl -tag -width 18n
422";
423    foreach (sort(keys(%PAMERR))) {
424	print ".It Bq Er $_\n$PAMERR{$_}.\n";
425    }
426    print ".El
427.Sh SEE ALSO
428";
429    foreach $func (sort(keys(%FUNCTIONS))) {
430	next if (defined($FUNCTIONS{$func}->{'nolist'}));
431	print ".Xr $func 3 ,\n";
432    }
433    print ".Xr pam.conf 5
434.Sh STANDARDS
435.Rs
436.%T \"X/Open Single Sign-On Service (XSSO) - Pluggable Authentication Modules\"
437.%D \"June 1997\"
438.Re
439.Sh AUTHORS
440The OpenPAM library and this manual page were developed for the
441FreeBSD Project by ThinkSec AS and NAI Labs, the Security Research
442Division of Network Associates, Inc.  under DARPA/SPAWAR contract
443N66001-01-C-8035
444.Pq Dq CBOSS ,
445as part of the DARPA CHATS research program.
446"
447}
448
449MAIN:{
450    $TODAY = strftime("%B %e, %Y", localtime(time()));
451    $TODAY =~ s,\s+, ,g;
452    foreach my $fn (@ARGV) {
453	parse_source($fn);
454    }
455    foreach my $func (values(%FUNCTIONS)) {
456	gendoc($func);
457    }
458    gensummary();
459}
460