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