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