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