]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - misc/gendoc.pl
Vendor import of OpenPAM Radula.
[FreeBSD/FreeBSD.git] / misc / gendoc.pl
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
39 use strict;
40 use warnings;
41 use open qw(:utf8);
42 use utf8;
43 use Fcntl;
44 use Getopt::Std;
45 use POSIX qw(strftime);
46 use vars qw(%AUTHORS $TODAY %FUNCTIONS %PAMERR);
47
48 %AUTHORS = (
49     THINKSEC => "developed for the
50 .Fx
51 Project by ThinkSec AS and Network Associates Laboratories, the
52 Security Research Division of Network Associates, Inc.\\& under
53 DARPA/SPAWAR contract N66001-01-C-8035
54 .Pq Dq CBOSS ,
55 as part of the DARPA CHATS research program.
56 .Pp
57 The 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
98 sub 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
354 sub expand_errors($);
355 sub 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
404 sub dictionary_order($$) {
405     my ($a, $b) = @_;
406
407     $a =~ s/[^[:alpha:]]//g;
408     $b =~ s/[^[:alpha:]]//g;
409     $a cmp $b;
410 }
411
412 sub 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
429 sub 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
497 The
498 .Fn $$func{name}
499 function 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
508 The
509 .Fn $$func{name}
510 function returns 0 on success and -1 on failure.
511 ";
512     } elsif ($$func{type} =~ m/\*$/) {
513         $mdoc .= ".Sh RETURN VALUES
514 The
515 .Fn $$func{name}
516 function returns
517 .Dv NULL
518 on 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}
528 function 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
538 The
539 .Fn $$func{name}
540 function 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
551 sub 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
576 sub 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
624 The 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
649 The OpenPAM library and this manual page were $AUTHORS{THINKSEC}
650 ";
651     close(FILE);
652 }
653
654 sub usage() {
655
656     print(STDERR "usage: gendoc [-op] source [...]\n");
657     exit(1);
658 }
659
660 MAIN:{
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 }