1\input texinfo @c -*-texinfo-*- 2@c %**start of header 3@setfilename pinentry.info 4 5@include version.texi 6 7@macro copyrightnotice 8Copyright @copyright{} 2002, 2005, 2015 g10 Code GmbH 9@end macro 10@macro permissionnotice 11Permission is granted to copy, distribute and/or modify this document 12under the terms of the GNU General Public License as published by the 13Free Software Foundation; either version 2 of the License, or (at your 14option) any later version. The text of the license can be found in the 15section entitled ``Copying''. 16@end macro 17 18@macro pinentry 19@sc{pinentry} 20@end macro 21 22@settitle Using the PIN-Entry 23 24@c Create a separate index for command line options. 25@defcodeindex op 26@c Merge the standard indexes into a single one. 27@syncodeindex fn cp 28@syncodeindex vr cp 29@syncodeindex ky cp 30@syncodeindex pg cp 31@syncodeindex tp cp 32 33@c printing stuff taken from gcc. 34@macro gnupgtabopt{body} 35@code{\body\} 36@end macro 37@macro gnupgoptlist{body} 38@smallexample 39\body\ 40@end smallexample 41@end macro 42@c Makeinfo handles the above macro OK, TeX needs manual line breaks; 43@c they get lost at some point in handling the macro. But if @macro is 44@c used here rather than @alias, it produces double line breaks. 45@iftex 46@alias gol = * 47@end iftex 48@ifnottex 49@macro gol 50@end macro 51@end ifnottex 52 53 54@c %**end of header 55 56@ifnottex 57@dircategory GNU Utilities 58@direntry 59* pinentry: (pinentry). Securely ask for a passphrase or PIN. 60@end direntry 61This file documents the use and the internals of the @pinentry{}. 62 63This is edition @value{EDITION}, last updated @value{UPDATED}, of 64@cite{The `PINEntry' Manual}, for version @value{VERSION}. 65@sp 1 66Published by g10 Code GmbH@* 67Hüttenstr. 61@* 6840699 Erkrath, Germany 69@sp 1 70@copyrightnotice{} 71@sp 1 72@permissionnotice{} 73@end ifnottex 74 75@setchapternewpage odd 76 77@titlepage 78@title Using the PIN-Entry 79@subtitle Version @value{VERSION} 80@subtitle @value{UPDATED} 81@author Werner Koch @code{(wk@@gnupg.org)} 82 83@page 84@vskip 0pt plus 1filll 85@copyrightnotice{} 86@sp 2 87@permissionnotice{} 88@end titlepage 89@summarycontents 90@contents 91@page 92 93 94@node Top 95@top Introduction 96@cindex introduction 97 98This manual documents how to use the @pinentry{} and its protocol. 99 100The @pinentry{} is a small GUI application used to enter PINs or 101passphrases. It is usually invoked by @sc{gpg-agent} 102(@pxref{Invoking GPG-AGENT, ,Invoking the gpg-agent, gnupg, 103 The `GNU Privacy Guard' Manual}, for details). 104 105@pinentry{} comes in several flavors to fit the look and feel of the 106used GUI toolkit: A @sc{GTK+} based one named @code{pinentry-gtk}; a 107@sc{Qt} based one named @code{pinentry-qt}; and, two non-graphical 108ones @code{pinentry-curses}, which uses curses, and 109@code{pinentry-tty}, which doesn't require anything more than a simple 110terminal. Not all of them are necessarily available on your 111installation. If curses is supported on your system, the GUI-based 112flavors fall back to curses when the @code{DISPLAY} variable is not 113set. 114 115 116@menu 117* Using pinentry:: How to use the beast. 118* Front ends:: Description and comparison of the front ends 119 120Developer information 121 122* Protocol:: The Assuan protocol description. 123* Implementation Details:: For those extending or writing a new pinentry. 124 125Miscellaneous 126 127* Copying:: GNU General Public License says 128 how you can copy and share PIN-Entry 129 as well as this manual. 130 131Indices 132 133* Option Index:: Index to command line options. 134* Index:: Index of concepts and symbol names. 135@end menu 136 137@node Using pinentry 138@chapter How to use the @pinentry{} 139 140@c man begin DESCRIPTION 141 142You may run @pinentry{} directly from the command line and pass the 143commands according to the Assuan protocol via stdin/stdout. 144 145 146@c man end 147 148@c man begin OPTIONS 149 150Here is a list of options supported by all flavors of pinentry: 151 152@table @gnupgtabopt 153@item --version 154@opindex version 155Print the program version and licensing information. 156 157@item --help 158@opindex help 159Print a usage message summarizing the most useful command line options. 160 161@item --debug 162@itemx -d 163@opindex debug 164@opindex d 165Turn on some debugging. Mostly useful for the maintainers. Note that 166this may reveal sensitive information like the entered passphrase. 167 168@c @item --enhanced 169@c @itemx -e 170@c @opindex enhanced 171@c @opindex e 172@c Ask for timeouts and insurance, too. Note that this is currently not 173@c fully supported. 174 175@item --no-global-grab 176@itemx -g 177@opindex no-global-grab 178@opindex g 179Grab the keyboard only when the window is focused. Use this option if 180you are debugging software using the @pinentry{}; otherwise you may 181not be able to to access your X session anymore (unless you have other 182means to connect to the machine to kill the @pinentry{}). 183 184@item --parent-wid @var{n} 185@opindex parent-wid 186Use window ID @var{n} as the parent window for positioning the window. 187Note, that this is not fully supported by all flavors of @pinentry{}. 188 189@item --timeout @var{seconds} 190@opindex timeout 191Give up waiting for input from the user after the specified number of 192seconds and return an error. The error returned is the same as if the 193Cancel button was selected. To disable the timeout and wait 194indefinitely, set this to 0, which is the default. 195 196@item --display @var{string} 197@itemx --ttyname @var{string} 198@itemx --ttytype @var{string} 199@itemx --lc-ctype @var{string} 200@itemx --lc-messages @var{string} 201@opindex display 202@opindex ttyname 203@opindex ttytype 204@opindex lc-ctype 205@opindex lc-messa 206These options are used to pass localization information to 207@pinentry{}. They are required because @pinentry{} is usually called 208by some background process which does not have any information about 209the locale and terminal to use. It is also possible to pass these 210options using Assuan protocol options. 211@end table 212 213@node Front ends 214@chapter Front Ends 215 216There are several different flavors of @pinentry{}. Concretely, there 217are Gtk+2, Qt@tie{}4, TQt, EFL, FLTK, Gnome@tie{}3, Emacs, curses and 218tty variants. These different implementations provide higher levels 219of integration with a specific environment. For instance, the 220Gnome@tie{}3 @pinentry{} uses Gnome@tie{}3 widgets to display the 221prompts. For Gnome@tie{}3 users, this higher level of integration 222provides a more consistent aesthetic. However, this comes at a cost. 223Because this @pinentry{} uses so many components, there is a larger 224chance of a failure. In particular, there is a larger chance that the 225passphrase is saved in memory and that memory is exposed to an 226attacker (consider the OpenSSL Heartbeat vulnerability). 227 228To understand how many components touch the passphrase, consider again 229the Gnome@tie{}3 implementation. When a user presses a button on the 230keyboard, the key is passed from the kernel to the X@tie{}server to 231the toolkit (Gtk+) and to the actual text entry widget. Along the 232way, the key is saved in memory and processed. In fact, the key 233presses are probably read using standard C library functions, which 234buffer the input. None of this code is careful to make sure the 235contents of the memory are not leaked by keeping the data in unpagable 236memory and wiping it when the buffer is freed. However, even if they 237did, there is still the problem that when a computer hibernates, the 238system writes unpagable memory to disk anyway. Further, many 239installations are virtualized (e.g., running on Xen) and have little 240control over their actual environment. 241 242The curses variant uses a significant smaller software stack and the 243tty variant uses an even smaller one. However, if they are run in an 244X@tie{}terminal, then a similar number of components are handling the 245passphrase as in the Gnome@tie{}3 case! Thus, to be most secure, you 246need to direct GPG@tie{}Agent to use a fixed virtual console. Since 247you need to remain logged in for GPG@tie{}Agent to use that console, 248you should run there and have @code{screen} or @code{tmux} lock the 249tty. 250 251The Emacs pinentry implementation interacts with a running Emacs 252session and directs the Emacs instance to display the passphrase 253prompt. Since this doesn't work very well if there is no Emacs 254running, the generic @pinentry{} backend checks if a 255@pinentry{}-enabled Emacs should be used. Specifically, it looks to 256see if the @code{INSIDE_EMACS} variable is set and then attempts to 257establish a connection to the specified address. If this is the case, 258then instead of, e.g., @code{pinentry-gtk2} displaying a Gtk+2 259pinentry, it interacts with the Emacs session. This functionality can 260be explicitly disabled by passing @code{--disable-inside-emacs} to 261@code{configure} when building @pinentry{}. 262 263Having Emacs get the passphrase is convenient, however, it is a 264significant security risk. Emacs is a huge program, which doesn't 265provide any process isolation to speak of. As such, having it handle 266the passphrase adds a huge chunk of code to the user's trusted computing 267base. Because of this concern, Emacs doesn't enable this by default, 268unless the @code{allow-emacs-pinentry} option is explicitly set in his 269or her @code{.gnupg/gpg-agent.conf} file. 270 271Similar to the inside-emacs check, the @pinentry{} frontends check 272whether the @code{DISPLAY} variable is set and a working X server is 273available. If this is not the case, then they fallback to the curses 274front end. This can also be disabled by passing 275@code{--disable-fallback-curses} to @code{configure} at build time. 276 277@c 278@c Assuan Protocol 279@c 280@node Protocol 281@chapter @pinentry{}'s Assuan Protocol 282 283The @pinentry{} should never service more than one connection at once. 284It is reasonable to exec the @pinentry{} prior to a request. 285 286The @pinentry{} does not need to stay in memory because the 287@sc{gpg-agent} has the ability to cache passphrases. The usual way to 288run the @pinentry{} is by setting up a pipe (not a socket) and then 289fork/exec the @pinentry{}. The communication is then done by means of 290the protocol described here until the client is satisfied with the 291result. 292 293Although it is called a @pinentry{}, it allows entering reasonably 294long strings (strings that are up to 2048 characters long are 295supported by every pinentry). The client using the @pinentry{} has to 296check for correctness. 297 298Note that all strings are expected to be encoded as UTF-8; @pinentry{} 299takes care of converting it to the locally used codeset. To include 300linefeeds or other special characters, you may percent-escape them 301(e.g., a line feed is encoded as @code{%0A}, the percent sign itself 302is encoded as @code{%25}, etc.). 303 304The following is a list of supported commands: 305 306@table @gnupgtabopt 307 308@item Set the timeout before returning an error 309@example 310 C: SETTIMEOUT 30 311 S: OK 312@end example 313 314@item Set the descriptive text to display 315@example 316 C: SETDESC Enter PIN for Richard Nixon <nobody@@trickydicky.gov> 317 S: OK 318@end example 319 320@item Set the prompt to show 321When asking for a PIN, set the text just before the widget for 322passphrase entry. 323@example 324 C: SETPROMPT PIN: 325 S: OK 326@end example 327 328You should use an underscore in the text only if you know that a 329modern version of pinentry is used. Modern versions underline the 330next character after the underscore and use the first such underlined 331character as a keyboard accelerator. Use a double underscore to 332escape an underscore. 333 334@item Set the window title 335This command may be used to change the default window title. When 336using this feature you should take care that the window is still 337identifiable as the pinentry. 338@example 339 C: SETTITLE Tape Recorder Room 340 S: OK 341@end example 342 343@item Set the button texts 344There are three texts which should be used to override the English 345defaults: 346 347To set the text for the button signaling confirmation (in UTF-8). 348See SETPROMPT on how to use an keyboard accelerator. 349@example 350 C: SETOK Yes 351 S: OK 352@end example 353 354 355To set the text for the button signaling cancellation or disagreement 356(in UTF-8). See SETPROMPT on how to use an keyboard accelerator. 357@example 358 C: SETCANCEL No 359 S: OK 360@end example 361 362 363In case three buttons are required, use the following command to set 364the text (UTF-8) for the non-affirmative response button. The 365affirmative button text is still set using SETOK and the CANCEL button 366text with SETCANCEL. See SETPROMPT on how to use an keyboard 367accelerator. 368@example 369 C: SETNOTOK Do not do this 370 S: OK 371@end example 372 373 374 375@item Set the Error text 376This is used by the client to display an error message. In contrast 377to the other commands, the error message is automatically reset with 378a GETPIN or CONFIRM, and is only displayed when asking for a PIN. 379@example 380 C: SETERROR Invalid PIN entered - please try again 381 S: OK 382@end example 383 384@item Enable a passphrase quality indicator 385Adds a quality indicator to the GETPIN window. This indicator is 386updated as the passphrase is typed. The clients needs to implement an 387inquiry named "QUALITY" which gets passed the current passphrase 388(percent-plus escaped) and should send back a string with a single 389numerical value between -100 and 100. Negative values will be 390displayed in red. 391@example 392 C: SETQUALITYBAR 393 S: OK 394@end example 395 396If a custom label for the quality bar is required, just add that label 397as an argument as a percent-escaped string. You will need this 398feature to translate the label because @pinentry{} has no internal 399gettext except for stock strings from the toolkit library. 400 401If you want to show a tooltip for the quality bar, you may use 402@example 403 C: SETQUALITYBAR_TT string 404 S: OK 405@end example 406 407@noindent 408With STRING being a percent escaped string shown as the tooltip. 409 410 411@item Ask for a PIN 412The meat of this tool is to ask for a passphrase of PIN, it is done with 413this command: 414@example 415 C: GETPIN 416 S: D no more tapes 417 S: OK 418@end example 419Note that the passphrase is transmitted in clear using standard data 420responses. Expect it to be in UTF-8. 421 422@item Ask for confirmation 423To ask for a confirmation (yes or no), you can use this command: 424@example 425 C: CONFIRM 426 S: OK 427@end example 428The client should use SETDESC to set an appropriate text before 429issuing this command, and may use SETPROMPT to set the button texts. 430The value returned is either OK for YES or the error code 431@code{ASSUAN_Not_Confirmed}. 432 433@item Show a message 434To show a message, you can use this command: 435@example 436 C: MESSAGE 437 S: OK 438@end example 439alternatively you may add an option to confirm: 440@example 441 C: CONFIRM --one-button 442 S: OK 443@end example 444The client should use SETDESC to set an appropriate text before issuing 445this command, and may use SETOK to set the text for the dismiss button. 446The value returned is OK or an error message. 447 448@item Set the output device 449When using X, the @pinentry{} program must be invoked with an 450appropriate @code{DISPLAY} environment variable or the 451@code{--display} option. 452 453When using a text terminal: 454@example 455 C: OPTION ttyname=/dev/tty3 456 S: OK 457 C: OPTION ttytype=vt100 458 S: OK 459 C: OPTION lc-ctype=de_DE.UTF-8 460 S: OK 461@end example 462The client should use the @code{ttyname} option to set the output TTY 463file name, the @code{ttytype} option to the @code{TERM} variable 464appropriate for this tty and @code{lc-ctype} to the locale which 465defines the character set to use for this terminal. 466 467@item Set the default strings 468To avoid having translations in Pinentry proper, the caller may set 469certain translated strings which are used by @pinentry{} as default 470strings. 471 472@example 473 C: OPTION default-ok=_Korrekt 474 S: OK 475 C: OPTION default-cancel=Abbruch 476 S: OK 477 C: OPTION default-prompt=PIN eingeben: 478 S: OK 479@end example 480The strings are subject to accelerator marking, see SETPROMPT for 481details. 482 483@item Passphrase caching 484 485Some environments, such as GNOME, cache passwords and passphrases. 486The @pinentry{} should only use an external cache if the 487@code{allow-external-password-cache} option was set and a stable key 488identifier (using SETKEYINFO) was provided. In this case, if the 489passphrase was read from the cache, the @pinentry{} should send the 490@code{PASSWORD_FROM_CACHE} status message before returning the 491passphrase. This indicates to GPG Agent that it should not increment 492the passphrase retry counter. 493 494@example 495 C: OPTION allow-external-password-cache 496 S: OK 497 C: SETKEYINFO key-grip 498 S: OK 499 C: getpin 500 S: S PASSWORD_FROM_CACHE 501 S: D 1234 502 S: OK 503@end example 504 505Note: if @code{allow-external-password-cache} is not specified, an 506external password cache must not be used: this can lead to subtle 507bugs. In particular, if this option is not specified, then GPG Agent 508does not recognize the @code{PASSWORD_FROM_CACHE} status message and 509will count trying a cached password against the password retry count. 510If the password retry count is 1, then the user will never have the 511opportunity to correct the cached password. 512 513Note: it is strongly recommended that a pinentry supporting this 514feature provide the user an option to enable it manually. That is, 515saving a passphrase in an external password manager should be opt-in. 516 517The key identifier provided SETKEYINFO must be considered opaque and 518may change in the future. It currently has the form 519@code{X/HEXSTRING} where @code{X} is either @code{n}, @code{s}, or 520@code{u}. In the former two cases, the HEXSTRING corresponds to the 521key grip. The key grip is not the OpenPGP Key ID, but it can be 522mapped to the key using the following: 523 524@example 525 # gpg2 --with-keygrip --list-secret-keys 526@end example 527 528@noindent 529and searching the output for the key grip. The same command-line 530options can also be used with gpgsm. 531 532@end table 533 534@node Implementation Details 535@chapter Implementation Details 536 537The pinentry source code can be divided into three categories. There 538is a backend module, which lives in @code{pinentry/}, there are 539utility functions, e.g., in @code{secmem/}, and there are various 540frontends. 541 542All of the low-level logic lives in the backend. This frees the 543frontends from having to implement, e.g., the Assuan protocol. When 544the backend receives an option, it updates the state in a 545@code{pinentry_t} struct. The frontend is called when the client 546either calls @code{GETPIN}, @code{CONFIRM} or @code{MESSAGE}. In 547these cases, the backend invokes the @code{pinentry_cmd_handler}, 548which is passed the @code{pinentry_t} struct. 549 550When the callback is invoked, the frontend should create a window 551based on the state in the @code{pinentry_t} struct. For instance, the 552title to use for the dialog's window (if any) is stored in the 553@code{title} field. If the is @code{NULL}, the frontend should choose 554a reasonable default value. (Default is not always provided, because 555different tool kits and environments have different reasonable 556defaults.) 557 558The widget needs to support a number of different interactions with 559the user. Each of them is described below. 560 561@table @gnupgtabopt 562@item Passphrase Confirmation 563 564When creating a new key, the passphrase should be entered twice. The 565client (typically GPG Agent) indicates this to the @pinentry{} by 566invoking @code{SETREPEAT}. In this case, the backend sets the 567@code{repeat_passphrase} field to a copy of the passed string. The 568value of this field should be used to label a second text input. 569 570It is the frontend's responsibility to check that the passwords match. 571If they don't match, the frontend should display an error message and 572continue to prompt the user. 573 574If the passwords do match, then, when the user presses the okay 575button, the @code{repeat_okay} field should be set to @code{1} (this 576causes the backend to emit the @code{S PIN_REPEATED} status message). 577 578@item Message Box 579 580Sometimes GPG Agent needs to display a message. In this case, the 581@code{pin} variable is @code{NULL}. 582 583At the Assuan level, this mode is selected by using either the 584@code{MESSAGE} or the @code{CONFIRM} command instead of the 585@code{GETPIN} command. The @code{MESSAGE} command never shows the 586cancel or an other button. The same holds for @code{CONFIRM} if it 587was passed the ``--one-button'' argument. If @code{CONFIRM} was not 588passed this argument, the dialog for @code{CONFIRM} should show both 589the @code{ok} and the @code{cancel} buttons and optionally the 590@code{notok} button. The frontend can determine whether the dialog is 591a one-button dialog by inspecting the @code{one_button} variable. 592 593@item Passphrase Entry 594 595If neither of the above cases holds, then GPG Agent is simply 596requesting the passphrase. In this case, the @code{ok} and 597@code{cancel} buttons should be displayed. 598 599@end table 600 601The layout of the three variants is quite similar. Here are the 602relevant elements that describe the layout: 603 604@table @gnupgtabopt 605@item @code{title} 606The window's title. 607 608@item @code{description} 609The reason for the dialog. When requesting a passphrase, this 610describes the key. When showing a message box, this is the message to 611show. 612 613@item @code{error} 614If GPG Agent determines that the passphrase was incorrect, it will 615call @code{GETPIN} again (up to a configurable number of times) to 616again prompt the user. In this case, this variable contains a 617description of the error message. This text should typically be 618highlighted in someway. 619 620@item @code{prompt}, @code{default-prompt} 621The string to associate with the passphrase entry box. 622 623There is a subtle difference between @code{prompt} and 624@code{default-prompt}. @code{default-prompt} means that a stylized 625prompt (e.g., an icon suggesting a prompt) may be used. @code{prompt} 626means that the entry's meaning is not consistent with such a style 627and, as such, no icon should be used. 628 629If both variables are set, the @code{prompt} variant takes precedence. 630 631@item @code{repeat_passphrase} 632The string to associate with the second passphrase entry box. The 633second passphrase entry box should only be shown if this is not 634@code{NULL}. 635 636@item @code{ok}, @code{default-ok} 637The string to show in the @code{ok} button. 638 639If there are any @code{_} characters, the following character should 640be used as an accelerator. (A double underscore means a plain 641underscore should be shown.) If the frontend does not support 642accelerators, then the underscores should be removed manually. 643 644There is a subtle difference between @code{ok} and @code{default-ok}. 645@code{default-ok} means that a stylized OK button should be used. For 646instance, it could include a check mark. @code{ok} means that the 647button's meaning is not consistent with such an icon and, as such, no 648icon should be used. Thus, if the @code{ok} button should have the 649text ``No password required'' then @code{ok} should be used because a 650check mark icon doesn't make sense. 651 652If this variable is @code{NULL}, the frontend should choose a 653reasonable default. 654 655If both variables are set, the @code{ok} variant takes precedence. 656 657@item @code{cancel}, @code{default-cancel} 658Like the @code{ok} and @code{default-ok} buttons except these strings 659are used for the cancel button. 660 661This button should not be shown if @code{one_button} is set. 662 663@code{default-notok} 664Like the @code{default-ok} button except this string is used for the 665other button. 666 667This button should only be displayed when showing a message box. If 668these variables are @code{NULL} or @code{one_button} is set, this 669button should not be displayed. 670 671@item @code{quality_bar} 672If this is set, a widget should be used to show the password's 673quality. The value of this field is a label for the widget. 674 675Note: to update the password quality, whenever the password changes, 676call the @code{pinentry_inq_quality} function and then update the 677password quality widget correspondingly. 678 679@item @code{quality_bar_tt} 680A tooltip for the quality bar. 681 682@item @code{default_pwmngr} 683If @code{may_cache_password} and @code{keyinfo} are set and the user 684consents, then the @pinentry{} may cache the password with an external 685manager. Note: getting the user's consent is essential, because 686password managers often provide a different level of security. If the 687above condition is true and @code{tried_password_cache} is false, then 688a check box with the specified string should be displayed. The check 689box must default to off. 690 691@item @code{default-cf-visi} 692The string to show with a question if you want to confirm that 693the user wants to change the visibility of the password. 694 695@item @code{default-tt-visi} 696Tooltip for an action that would reveal the entered password. 697 698@item @code{default-tt-hide} 699Tooltip for an action that would hide the password revealed 700by the action labeld with @code{default-tt-visi} 701 702@end table 703 704When the handler is done, it should store the passphrase in 705@code{pin}, if appropriate. This variable is allocated in secure 706memory. Use @code{pinentry_setbufferlen} to size the buffer. 707 708The actual return code is dependent on whether the dialog is in 709message mode or in passphrase mode. 710 711If the dialog is in message mode and the user pressed ok, return 1. 712Otherwise, return 0. If an error occurred, indicate this by setting it 713in @code{specific_err} or setting @code{locale_err} to @code{1} (for 714locale specific errors). If the dialog was canceled, then the handler 715should set the @code{canceled} variable to @code{1}. If the not ok 716button was pressed, don't do anything else. 717 718If the dialog is in passphrase mode return @code{1} if the user 719entered a password and pressed ok. If an error occurred, return 720@code{-1} and set @code{specific_err} or @code{locale_err}, as above. 721If the user canceled the dialog box, return @code{-1}. 722 723If the window was closed, then the handler should set the 724@code{close_button} variable and otherwise act as if the cancel button 725was pressed. 726 727 728 729@c --------------------------------------------------------------------- 730@c Legal Blurbs 731@c --------------------------------------------------------------------- 732 733@include gpl.texi 734 735@c --------------------------------------------------------------------- 736@c Indexes 737@c --------------------------------------------------------------------- 738 739@node Option Index 740@unnumbered Option Index 741 742@printindex op 743 744@node Index 745@unnumbered Index 746 747@printindex cp 748 749@c --------------------------------------------------------------------- 750@c Epilogue 751@c --------------------------------------------------------------------- 752 753@bye 754