gendoc.pl revision 294192
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 736 2013-09-07 12:52:42Z 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 des\@des.no .",
59    UIO => "developed for the University of Oslo by
60.An Dag-Erling Sm\\(/orgrav Aq des\@des.no .",
61    DES => "developed by
62.An Dag-Erling Sm\\(/orgrav Aq 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*=(struct \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.Sh LIBRARY
450.Lb libpam
451.Sh SYNOPSIS
452.In sys/types.h
453";
454    if ($$func{args} =~ m/\bFILE \*\b/) {
455	$mdoc .= ".In stdio.h\n";
456    }
457    $mdoc .= ".In security/pam_appl.h
458";
459    if ($$func{name} =~ m/_sm_/) {
460	$mdoc .= ".In security/pam_modules.h\n";
461    }
462    if ($$func{name} =~ m/openpam/) {
463	$mdoc .= ".In security/openpam.h\n";
464    }
465    $mdoc .= ".Ft \"$$func{type}\"
466.Fn $$func{name} $$func{args}
467.Sh DESCRIPTION
468";
469    if (defined($$func{deprecated})) {
470	$mdoc .= ".Bf Sy\n" .
471	    "This function is deprecated and may be removed " .
472	    "in a future release without further warning.\n";
473	if ($$func{deprecated}) {
474	    $mdoc .= "The\n.Fn $$func{deprecated}\nfunction " .
475		"may be used to achieve similar results.\n";
476	}
477	$mdoc .= ".Ef\n.Pp\n";
478    }
479    if ($$func{experimental}) {
480	$mdoc .= ".Bf Sy\n" .
481	    "This function is experimental and may be modified or removed " .
482	    "in a future release without prior warning.\n";
483	$mdoc .= ".Ef\n.Pp\n";
484    }
485    $mdoc .= "$$func{man}\n";
486    my @errors = @{$$func{errors}};
487    if ($$func{customrv}) {
488	# leave it
489    } elsif ($$func{type} eq "int" && @errors) {
490	$mdoc .= ".Sh RETURN VALUES
491The
492.Fn $$func{name}
493function returns one of the following values:
494.Bl -tag -width 18n
495";
496	foreach (@errors) {
497	    $mdoc .= ".It Bq Er $_\n$PAMERR{$_}.\n";
498	}
499	$mdoc .= ".El\n";
500    } elsif ($$func{type} eq "int") {
501	$mdoc .= ".Sh RETURN VALUES
502The
503.Fn $$func{name}
504function returns 0 on success and -1 on failure.
505";
506    } elsif ($$func{type} =~ m/\*$/) {
507	$mdoc .= ".Sh RETURN VALUES
508The
509.Fn $$func{name}
510function returns
511.Dv NULL
512on failure.
513";
514    } elsif ($$func{type} ne "void") {
515	warn("$$func{name}(): no error specification\n");
516    }
517    $mdoc .= ".Sh SEE ALSO\n" . genxref($$func{xref});
518    $mdoc .= ".Sh STANDARDS\n";
519    if ($$func{openpam}) {
520	$mdoc .= "The
521.Fn $$func{name}
522function is an OpenPAM extension.
523";
524    } else {
525	$mdoc .= ".Rs
526.%T \"X/Open Single Sign-On Service (XSSO) - Pluggable Authentication Modules\"
527.%D \"June 1997\"
528.Re
529";
530    }
531    $mdoc .= ".Sh AUTHORS
532The
533.Fn $$func{name}
534function and this manual page were\n";
535    $mdoc .= $AUTHORS{$$func{author} // 'THINKSEC_DARPA'} . "\n";
536    $fn = "$$func{name}.3";
537    if (open(FILE, ">", $fn)) {
538	print(FILE $mdoc);
539	close(FILE);
540    } else {
541	warn("$fn: open(): $!\n");
542    }
543}
544
545sub readproto($) {
546    my $fn = shift;		# File name
547
548    local *FILE;
549    my %func;
550
551    open(FILE, "<", "$fn")
552	or die("$fn: open(): $!\n");
553    while (<FILE>) {
554	if (m/^\.Nm ((?:open)?pam_.*?)\s*$/) {
555	    $func{Nm} = $func{Nm} || $1;
556	} elsif (m/^\.Ft (\S.*?)\s*$/) {
557	    $func{Ft} = $func{Ft} || $1;
558	} elsif (m/^\.Fn (\S.*?)\s*$/) {
559	    $func{Fn} = $func{Fn} || $1;
560	}
561    }
562    close(FILE);
563    if ($func{Nm}) {
564	$FUNCTIONS{$func{Nm}} = \%func;
565    } else {
566	warn("No function found\n");
567    }
568}
569
570sub gensummary($) {
571    my $page = shift;		# Which page to produce
572
573    local *FILE;
574    my $upage;
575    my $func;
576    my %xref;
577
578    open(FILE, ">", "$page.3")
579	or die("$page.3: $!\n");
580
581    $page =~ m/(\w+)$/;
582    $upage = uc($1);
583    print FILE ".\\\" Generated by gendoc.pl
584.Dd $TODAY
585.Dt $upage 3
586.Os
587.Sh NAME
588";
589    my @funcs = sort(keys(%FUNCTIONS));
590    while ($func = shift(@funcs)) {
591	print FILE ".Nm $FUNCTIONS{$func}->{Nm}";
592	print FILE " ,"
593		if (@funcs);
594	print FILE "\n";
595    }
596    print FILE ".Nd Pluggable Authentication Modules Library
597.Sh LIBRARY
598.Lb libpam
599.Sh SYNOPSIS\n";
600    if ($page eq 'pam') {
601	print FILE ".In security/pam_appl.h\n";
602    } else {
603	print FILE ".In security/openpam.h\n";
604    }
605    foreach $func (sort(keys(%FUNCTIONS))) {
606	print FILE ".Ft $FUNCTIONS{$func}->{Ft}\n";
607	print FILE ".Fn $FUNCTIONS{$func}->{Fn}\n";
608    }
609    while (<STDIN>) {
610	if (m/^\.Xr (\S+)\s*(\d)\s*$/) {
611	    ++$xref{int($2)}->{$1};
612	}
613	print FILE $_;
614    }
615
616    if ($page eq 'pam') {
617	print FILE ".Sh RETURN VALUES
618The following return codes are defined by
619.In security/pam_constants.h :
620.Bl -tag -width 18n
621";
622	foreach (sort(keys(%PAMERR))) {
623	    print FILE ".It Bq Er $_\n$PAMERR{$_}.\n";
624	}
625	print FILE ".El\n";
626    }
627    print FILE ".Sh SEE ALSO
628";
629    if ($page eq 'pam') {
630	++$xref{3}->{openpam};
631    }
632    foreach $func (keys(%FUNCTIONS)) {
633	++$xref{3}->{$func};
634    }
635    print FILE genxref(\%xref);
636    print FILE ".Sh STANDARDS
637.Rs
638.%T \"X/Open Single Sign-On Service (XSSO) - Pluggable Authentication Modules\"
639.%D \"June 1997\"
640.Re
641.Sh AUTHORS
642The OpenPAM library and this manual page were developed for the
643.Fx
644Project by ThinkSec AS and Network Associates Laboratories, the
645Security Research Division of Network Associates, Inc.\\& under
646DARPA/SPAWAR contract N66001-01-C-8035
647.Pq Dq CBOSS ,
648as part of the DARPA CHATS research program.
649.Pp
650The OpenPAM library is maintained by
651.An Dag-Erling Sm\\(/orgrav Aq des\@des.no .
652";
653    close(FILE);
654}
655
656sub usage() {
657
658    print(STDERR "usage: gendoc [-op] source [...]\n");
659    exit(1);
660}
661
662MAIN:{
663    my %opts;
664
665    usage()
666	unless (@ARGV && getopts("op", \%opts));
667    $TODAY = strftime("%B %e, %Y", localtime(time()));
668    $TODAY =~ s,\s+, ,g;
669    if ($opts{o} || $opts{p}) {
670	foreach my $fn (@ARGV) {
671	    readproto($fn);
672	}
673	gensummary('openpam')
674	    if ($opts{o});
675	gensummary('pam')
676	    if ($opts{p});
677    } else {
678	foreach my $fn (@ARGV) {
679	    my $func = parse_source($fn);
680	    gendoc($func)
681		if (defined($func));
682	}
683    }
684    exit(0);
685}
686