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