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 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 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 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*=((?: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 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"; 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 497The 498.Fn $$func{name} 499function 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 508The 509.Fn $$func{name} 510function returns 0 on success and -1 on failure. 511"; 512 } elsif ($$func{type} =~ m/\*$/) { 513 $mdoc .= ".Sh RETURN VALUES 514The 515.Fn $$func{name} 516function returns 517.Dv NULL 518on 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} 528function 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 538The 539.Fn $$func{name} 540function 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 551sub 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 576sub 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 624The 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 649The OpenPAM library and this manual page were $AUTHORS{THINKSEC} 650"; 651 close(FILE); 652} 653 654sub usage() { 655 656 print(STDERR "usage: gendoc [-op] source [...]\n"); 657 exit(1); 658} 659 660MAIN:{ 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} 684