xref: /freebsd/contrib/openpam/misc/gendoc.pl (revision 7bd6fde3)
1#!/usr/bin/perl -w
2#-
3# Copyright (c) 2002-2003 Networks Associates Technology, Inc.
4# All rights reserved.
5#
6# This software was developed for the FreeBSD Project by ThinkSec AS and
7# Network Associates Laboratories, the Security Research Division of
8# Network Associates, Inc.  under DARPA/SPAWAR contract N66001-01-C-8035
9# ("CBOSS"), as part of the 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#30 $
36#
37
38use strict;
39use locale;
40use Fcntl;
41use Getopt::Std;
42use POSIX qw(locale_h strftime);
43use vars qw($COPYRIGHT $TODAY %FUNCTIONS %PAMERR);
44
45$COPYRIGHT = ".\\\"-
46.\\\" Copyright (c) 2001-2003 Networks Associates Technology, Inc.
47.\\\" All rights reserved.
48.\\\"
49.\\\" This software was developed for the FreeBSD Project by ThinkSec AS and
50.\\\" Network Associates Laboratories, the Security Research Division of
51.\\\" Network Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035
52.\\\" (\"CBOSS\"), as part of the DARPA CHATS research program.
53.\\\"
54.\\\" Redistribution and use in source and binary forms, with or without
55.\\\" modification, are permitted provided that the following conditions
56.\\\" are met:
57.\\\" 1. Redistributions of source code must retain the above copyright
58.\\\"    notice, this list of conditions and the following disclaimer.
59.\\\" 2. Redistributions in binary form must reproduce the above copyright
60.\\\"    notice, this list of conditions and the following disclaimer in the
61.\\\"    documentation and/or other materials provided with the distribution.
62.\\\" 3. The name of the author may not be used to endorse or promote
63.\\\"    products derived from this software without specific prior written
64.\\\"    permission.
65.\\\"
66.\\\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
67.\\\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
68.\\\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
69.\\\" ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
70.\\\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
71.\\\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
72.\\\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
73.\\\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
74.\\\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
75.\\\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
76.\\\" SUCH DAMAGE.
77.\\\"
78.\\\" \$" . "P4" . "\$
79.\\\"";
80
81%PAMERR = (
82    PAM_SUCCESS			=> "Success",
83    PAM_OPEN_ERR		=> "Failed to load module",
84    PAM_SYMBOL_ERR		=> "Invalid symbol",
85    PAM_SERVICE_ERR		=> "Error in service module",
86    PAM_SYSTEM_ERR		=> "System error",
87    PAM_BUF_ERR			=> "Memory buffer error",
88    PAM_CONV_ERR		=> "Conversation failure",
89    PAM_PERM_DENIED		=> "Permission denied",
90    PAM_MAXTRIES		=> "Maximum number of tries exceeded",
91    PAM_AUTH_ERR		=> "Authentication error",
92    PAM_NEW_AUTHTOK_REQD	=> "New authentication token required",
93    PAM_CRED_INSUFFICIENT	=> "Insufficient credentials",
94    PAM_AUTHINFO_UNAVAIL	=> "Authentication information is unavailable",
95    PAM_USER_UNKNOWN		=> "Unknown user",
96    PAM_CRED_UNAVAIL		=> "Failed to retrieve user credentials",
97    PAM_CRED_EXPIRED		=> "User credentials have expired",
98    PAM_CRED_ERR		=> "Failed to set user credentials",
99    PAM_ACCT_EXPIRED		=> "User account has expired",
100    PAM_AUTHTOK_EXPIRED		=> "Password has expired",
101    PAM_SESSION_ERR		=> "Session failure",
102    PAM_AUTHTOK_ERR		=> "Authentication token failure",
103    PAM_AUTHTOK_RECOVERY_ERR	=> "Failed to recover old authentication token",
104    PAM_AUTHTOK_LOCK_BUSY	=> "Authentication token lock busy",
105    PAM_AUTHTOK_DISABLE_AGING	=> "Authentication token aging disabled",
106    PAM_NO_MODULE_DATA		=> "Module data not found",
107    PAM_IGNORE			=> "Ignore this module",
108    PAM_ABORT			=> "General failure",
109    PAM_TRY_AGAIN		=> "Try again",
110    PAM_MODULE_UNKNOWN		=> "Unknown module type",
111    PAM_DOMAIN_UNKNOWN		=> "Unknown authentication domain",
112);
113
114sub parse_source($) {
115    my $fn = shift;
116
117    local *FILE;
118    my $source;
119    my $func;
120    my $descr;
121    my $type;
122    my $args;
123    my $argnames;
124    my $man;
125    my $inlist;
126    my $inliteral;
127    my %xref;
128    my @errors;
129
130    if ($fn !~ m,\.c$,) {
131	warn("$fn: not C source, ignoring\n");
132	return undef;
133    }
134
135    sysopen(FILE, $fn, O_RDONLY)
136	or die("$fn: open(): $!\n");
137    $source = join('', <FILE>);
138    close(FILE);
139
140    return undef
141	if ($source =~ m/^ \* NOPARSE\s*$/m);
142
143    $func = $fn;
144    $func =~ s,^(?:.*/)?([^/]+)\.c$,$1,;
145    if ($source !~ m,\n \* ([\S ]+)\n \*/\n\n([\S ]+)\n$func\((.*?)\)\n\{,s) {
146	warn("$fn: can't find $func\n");
147	return undef;
148    }
149    ($descr, $type, $args) = ($1, $2, $3);
150    $descr =~ s,^([A-Z][a-z]),lc($1),e;
151    $descr =~ s,[\.\s]*$,,;
152    while ($args =~ s/^((?:[^\(]|\([^\)]*\))*),\s*/$1\" \"/g) {
153	# nothing
154    }
155    $args =~ s/,\s+/, /gs;
156    $args = "\"$args\"";
157
158    %xref = (
159	3 => { 'pam' => 1 },
160    );
161
162    if ($type eq "int") {
163	foreach (split("\n", $source)) {
164	    next unless (m/^ \*\s+(!?PAM_[A-Z_]+|=[a-z_]+)\s*$/);
165	    push(@errors, $1);
166	}
167	++$xref{3}->{'pam_strerror'};
168    }
169
170    $argnames = $args;
171    # extract names of regular arguments
172    $argnames =~ s/\"[^\"]+\*?\b(\w+)\"/\"$1\"/g;
173    # extract names of function pointer arguments
174    $argnames =~ s/\"([\w\s\*]+)\(\*?(\w+)\)\([^\)]+\)\"/\"$2\"/g;
175    # escape metacharacters (there shouldn't be any, but...)
176    $argnames =~ s/([\|\[\]\(\)\.\*\+\?])/\\$1/g;
177    # separate argument names with |
178    $argnames =~ s/\" \"/|/g;
179    # and surround with ()
180    $argnames =~ s/^\"(.*)\"$/($1)/;
181    # $argnames is now a regexp that matches argument names
182    $inliteral = $inlist = 0;
183    foreach (split("\n", $source)) {
184	s/\s*$//;
185	if (!defined($man)) {
186	    if (m/^\/\*\*$/) {
187		$man = "";
188	    }
189	    next;
190	}
191	last if (m/^ \*\/$/);
192	s/^ \* ?//;
193	s/\\(.)/$1/gs;
194	if (m/^$/) {
195	    if ($man ne "" && $man !~ m/\.Pp\n$/s) {
196		if ($inliteral) {
197		    $man .= "\0\n";
198		} elsif ($inlist) {
199		    $man .= ".El\n.Pp\n";
200		    $inlist = 0;
201		} else {
202		    $man .= ".Pp\n";
203		}
204	    }
205	    next;
206	}
207	if (m/^>(\w+)(\s+\d)?$/) {
208	    my ($page, $sect) = ($1, $2 ? int($2) : 3);
209	    ++$xref{$sect}->{$page};
210	    next;
211	}
212	if (s/^\s+(=?\w+):\s*/.It $1/) {
213	    if ($inliteral) {
214		$man .= ".Ed\n";
215		$inliteral = 0;
216	    }
217	    if (!$inlist) {
218		$man =~ s/\.Pp\n$//s;
219		$man .= ".Bl -tag -width 18n\n";
220		$inlist = 1;
221	    }
222	    s/^\.It =([A-Z][A-Z_]+)$/.It Dv $1/gs;
223	    $man .= "$_\n";
224	    next;
225	} elsif ($inlist && m/^\S/) {
226	    $man .= ".El\n.Pp\n";
227	    $inlist = 0;
228	} elsif ($inliteral && m/^\S/) {
229	    $man .= ".Ed\n";
230	    $inliteral = 0;
231	} elsif ($inliteral) {
232	    $man .= "$_\n";
233	    next;
234	} elsif ($inlist) {
235	    s/^\s+//;
236	} elsif (m/^\s+/) {
237	    $man .= ".Bd -literal\n";
238	    $inliteral = 1;
239	    $man .= "$_\n";
240	    next;
241	}
242	s/\s*=$func\b\s*/\n.Nm\n/gs;
243	s/\s*=$argnames\b\s*/\n.Fa $1\n/gs;
244	s/\s*=(struct \w+(?: \*)?)\b\s*/\n.Vt $1\n/gs;
245	s/\s*:([a-z_]+)\b\s*/\n.Va $1\n/gs;
246	s/\s*;([a-z_]+)\b\s*/\n.Dv $1\n/gs;
247	while (s/\s*=([a-z_]+)\b\s*/\n.Xr $1 3\n/s) {
248	    ++$xref{3}->{$1};
249	}
250	s/\s*\"(?=\w)/\n.Do\n/gs;
251	s/\"(?!\w)\s*/\n.Dc\n/gs;
252	s/\s*=([A-Z][A-Z_]+)\b\s*(?![\.,:;])/\n.Dv $1\n/gs;
253	s/\s*=([A-Z][A-Z_]+)\b([\.,:;]+)\s*/\n.Dv $1 $2\n/gs;
254	s/\s*{([A-Z][a-z] .*?)}\s*/\n.$1\n/gs;
255	$man .= "$_\n";
256    }
257    if (defined($man)) {
258	if ($inlist) {
259	    $man .= ".El\n";
260	}
261	if ($inliteral) {
262	    $man .= ".Ed\n";
263	}
264	$man =~ s/(\n\.[A-Z][a-z] [\w ]+)\n([\.,:;-]\S*)\s*/$1 $2\n/gs;
265	$man =~ s/\s*$/\n/gm;
266	$man =~ s/\n+/\n/gs;
267	$man =~ s/\0//gs;
268	$man =~ s/\n\n\./\n\./gs;
269	chomp($man);
270    } else {
271	$man = "No description available.";
272    }
273
274    $FUNCTIONS{$func} = {
275	'source'	=> $fn,
276	'name'		=> $func,
277	'descr'		=> $descr,
278	'type'		=> $type,
279	'args'		=> $args,
280	'man'		=> $man,
281	'xref'		=> \%xref,
282	'errors'	=> \@errors,
283    };
284    if ($source =~ m/^ \* NODOC\s*$/m) {
285	$FUNCTIONS{$func}->{'nodoc'} = 1;
286    }
287    if ($source !~ m/^ \* XSSO \d/m) {
288	$FUNCTIONS{$func}->{'openpam'} = 1;
289    }
290    expand_errors($FUNCTIONS{$func});
291    return $FUNCTIONS{$func};
292}
293
294sub expand_errors($);
295sub expand_errors($) {
296    my $func = shift;		# Ref to function hash
297
298    my %errors;
299    my $ref;
300    my $fn;
301
302    if (defined($func->{'recursed'})) {
303	warn("$func->{'name'}(): loop in error spec\n");
304	return qw();
305    }
306    $func->{'recursed'} = 1;
307
308    foreach (@{$func->{'errors'}}) {
309	if (m/^(PAM_[A-Z_]+)$/) {
310	    if (!defined($PAMERR{$1})) {
311		warn("$func->{'name'}(): unrecognized error: $1\n");
312		next;
313	    }
314	    $errors{$1} = 1;
315	} elsif (m/^!(PAM_[A-Z_]+)$/) {
316	    # treat negations separately
317	} elsif (m/^=([a-z_]+)$/) {
318	    $ref = $1;
319	    if (!defined($FUNCTIONS{$ref})) {
320		$fn = $func->{'source'};
321		$fn =~ s/$func->{'name'}/$ref/;
322		parse_source($fn);
323	    }
324	    if (!defined($FUNCTIONS{$ref})) {
325		warn("$func->{'name'}(): reference to unknown $ref()\n");
326		next;
327	    }
328	    foreach (@{$FUNCTIONS{$ref}->{'errors'}}) {
329		$errors{$_} = 1;
330	    }
331	} else {
332	    warn("$func->{'name'}(): invalid error specification: $_\n");
333	}
334    }
335    foreach (@{$func->{'errors'}}) {
336	if (m/^!(PAM_[A-Z_]+)$/) {
337	    delete($errors{$1});
338	}
339    }
340    delete($func->{'recursed'});
341    $func->{'errors'} = [ sort(keys(%errors)) ];
342}
343
344sub dictionary_order($$) {
345    my ($a, $b) = @_;
346
347    $a =~ s/[^[:alpha:]]//g;
348    $b =~ s/[^[:alpha:]]//g;
349    $a cmp $b;
350}
351
352sub genxref($) {
353    my $xref = shift;		# References
354
355    my $mdoc = '';
356    my @refs = ();
357    foreach my $sect (sort(keys(%{$xref}))) {
358	foreach my $page (sort(dictionary_order keys(%{$xref->{$sect}}))) {
359	    push(@refs, "$page $sect");
360	}
361    }
362    while ($_ = shift(@refs)) {
363	$mdoc .= ".Xr $_" .
364	    (@refs ? " ,\n" : "\n");
365    }
366    return $mdoc;
367}
368
369sub gendoc($) {
370    my $func = shift;		# Ref to function hash
371
372    local *FILE;
373    my $mdoc;
374    my $fn;
375
376    return if defined($func->{'nodoc'});
377
378    $mdoc = "$COPYRIGHT
379.Dd $TODAY
380.Dt " . uc($func->{'name'}) . " 3
381.Os
382.Sh NAME
383.Nm $func->{'name'}
384.Nd $func->{'descr'}
385.Sh LIBRARY
386.Lb libpam
387.Sh SYNOPSIS
388.In sys/types.h
389.In security/pam_appl.h
390";
391    if ($func->{'name'} =~ m/_sm_/) {
392	$mdoc .= ".In security/pam_modules.h\n"
393    }
394    if ($func->{'name'} =~ m/openpam/) {
395	$mdoc .= ".In security/openpam.h\n"
396    }
397    $mdoc .= ".Ft \"$func->{'type'}\"
398.Fn $func->{'name'} $func->{'args'}
399.Sh DESCRIPTION
400$func->{'man'}
401";
402    if ($func->{'type'} eq "int") {
403	$mdoc .= ".Sh RETURN VALUES
404The
405.Nm
406function returns one of the following values:
407.Bl -tag -width 18n
408";
409	my @errors = @{$func->{'errors'}};
410	warn("$func->{'name'}(): no error specification\n")
411	    unless(@errors);
412	foreach (@errors) {
413	    $mdoc .= ".It Bq Er $_\n$PAMERR{$_}.\n";
414	}
415	$mdoc .= ".El\n";
416    } else {
417	if ($func->{'type'} =~ m/\*$/) {
418	    $mdoc .= ".Sh RETURN VALUES
419The
420.Nm
421function returns
422.Dv NULL
423on failure.
424";
425	}
426    }
427    $mdoc .= ".Sh SEE ALSO\n" . genxref($func->{'xref'});
428    $mdoc .= ".Sh STANDARDS\n";
429    if ($func->{'openpam'}) {
430	$mdoc .= "The
431.Nm
432function is an OpenPAM extension.
433";
434    } else {
435	$mdoc .= ".Rs
436.%T \"X/Open Single Sign-On Service (XSSO) - Pluggable Authentication Modules\"
437.%D \"June 1997\"
438.Re
439";
440    }
441    $mdoc .= ".Sh AUTHORS
442The
443.Nm
444function and this manual page were developed for the
445.Fx
446Project by ThinkSec AS and Network Associates Laboratories, the
447Security Research Division of Network Associates, Inc.\\& under
448DARPA/SPAWAR contract N66001-01-C-8035
449.Pq Dq CBOSS ,
450as part of the DARPA CHATS research program.
451";
452
453    $fn = "$func->{'name'}.3";
454    if (sysopen(FILE, $fn, O_RDWR|O_CREAT|O_TRUNC)) {
455	print(FILE $mdoc);
456	close(FILE);
457    } else {
458	warn("$fn: open(): $!\n");
459    }
460}
461
462sub readproto($) {
463    my $fn = shift;		# File name
464
465    local *FILE;
466    my %func;
467
468    sysopen(FILE, $fn, O_RDONLY)
469	or die("$fn: open(): $!\n");
470    while (<FILE>) {
471	if (m/^\.Nm ((?:open)?pam_.*?)\s*$/) {
472	    $func{'Nm'} = $func{'Nm'} || $1;
473	} elsif (m/^\.Ft (\S.*?)\s*$/) {
474	    $func{'Ft'} = $func{'Ft'} || $1;
475	} elsif (m/^\.Fn (\S.*?)\s*$/) {
476	    $func{'Fn'} = $func{'Fn'} || $1;
477	}
478    }
479    close(FILE);
480    if ($func{'Nm'}) {
481	$FUNCTIONS{$func{'Nm'}} = \%func;
482    } else {
483	warn("No function found\n");
484    }
485}
486
487sub gensummary($) {
488    my $page = shift;		# Which page to produce
489
490    local *FILE;
491    my $upage;
492    my $func;
493    my %xref;
494
495    sysopen(FILE, "$page.3", O_RDWR|O_CREAT|O_TRUNC)
496	or die("$page.3: $!\n");
497
498    $upage = uc($page);
499    print FILE "$COPYRIGHT
500.Dd $TODAY
501.Dt $upage 3
502.Os
503.Sh NAME
504";
505    my @funcs = sort(keys(%FUNCTIONS));
506    while ($func = shift(@funcs)) {
507	print FILE ".Nm $FUNCTIONS{$func}->{'Nm'}";
508	print FILE " ,"
509		if (@funcs);
510	print FILE "\n";
511    }
512    print FILE ".Nd Pluggable Authentication Modules Library
513.Sh LIBRARY
514.Lb libpam
515.Sh SYNOPSIS\n";
516    if ($page eq 'pam') {
517	print FILE ".In security/pam_appl.h\n";
518    } else {
519	print FILE ".In security/openpam.h\n";
520    }
521    foreach $func (sort(keys(%FUNCTIONS))) {
522	print FILE ".Ft $FUNCTIONS{$func}->{'Ft'}\n";
523	print FILE ".Fn $FUNCTIONS{$func}->{'Fn'}\n";
524    }
525    while (<STDIN>) {
526	if (m/^\.Xr (\S+)\s*(\d)\s*$/) {
527	    ++$xref{int($2)}->{$1};
528	}
529	print FILE $_;
530    }
531
532    if ($page eq 'pam') {
533	print FILE ".Sh RETURN VALUES
534The following return codes are defined by
535.In security/pam_constants.h :
536.Bl -tag -width 18n
537";
538	foreach (sort(keys(%PAMERR))) {
539	    print FILE ".It Bq Er $_\n$PAMERR{$_}.\n";
540	}
541	print FILE ".El\n";
542    }
543    print FILE ".Sh SEE ALSO
544";
545    if ($page eq 'pam') {
546	++$xref{3}->{'openpam'};
547    }
548    foreach $func (keys(%FUNCTIONS)) {
549	++$xref{3}->{$func};
550    }
551    print FILE genxref(\%xref);
552    print FILE ".Sh STANDARDS
553.Rs
554.%T \"X/Open Single Sign-On Service (XSSO) - Pluggable Authentication Modules\"
555.%D \"June 1997\"
556.Re
557.Sh AUTHORS
558The OpenPAM library and this manual page were developed for the
559.Fx
560Project by ThinkSec AS and Network Associates Laboratories, the
561Security Research Division of Network Associates, Inc.\\& under
562DARPA/SPAWAR contract N66001-01-C-8035
563.Pq Dq CBOSS ,
564as part of the DARPA CHATS research program.
565";
566    close(FILE);
567}
568
569sub usage() {
570
571    print(STDERR "usage: gendoc [-s] source [...]\n");
572    exit(1);
573}
574
575MAIN:{
576    my %opts;
577
578    usage()
579	unless (@ARGV && getopts("op", \%opts));
580    setlocale(LC_ALL, "en_US.ISO8859-1");
581    $TODAY = strftime("%B %e, %Y", localtime(time()));
582    $TODAY =~ s,\s+, ,g;
583    if ($opts{'o'} || $opts{'p'}) {
584	foreach my $fn (@ARGV) {
585	    readproto($fn);
586	}
587	gensummary('openpam')
588	    if ($opts{'o'});
589	gensummary('pam')
590	    if ($opts{'p'});
591    } else {
592	foreach my $fn (@ARGV) {
593	    my $func = parse_source($fn);
594	    gendoc($func)
595		if (defined($func));
596	}
597    }
598    exit(0);
599}
600