AUTHINFO GENERIC Protocol Draft, including INN Server Semantics and suggested UNIX newsreader API. Chris Lewis clewis@elegant.com Rev 2. 94/07/19 This paper describes a new authentication mechanism for NNTP. The most important feature of this mechanism is that the authentication algorithm itself it is server and reader independent. That is, the authentication itself can be encapsulated in an external mechanism, and different methods can be selected on the fly without having to alter the readers or servers. Indeed, a server can implement several different authenticators with relatively little restriction on their type. It is suggested, for example, that a system could support authentication through YP/NIS password checking, SecureID, Kerberos and GSSAPI simultaneously, and each newsreader can select whichever one is appropriate for them. There are several components in the AUTHINFO GENERIC implementation to be supported in INN1.5. While only the first (authentication protocol) necessitates a change to the NNTP standards, it is thought that a description of the semantics and interfaces used by the INN1.5 implementation will be of use in: a) Allowing a common set of server-end authenticators that can be used with several different server packages. b) Ensuring that the reader-end modifications are consistent and can be used with all AUTHINFO GENERIC-capable servers and all AUTHINFO-capable readers, thus allowing a common set of reader-end authenticators. The pieces are: 1) the AUTHINFO GENERIC protocol 2) server-to-"server-authenticator" interface 3) reader-to-"reader-authenticator" interface 4) authenticator protocols A fully worked example of a suggested authenticator protocol is given at the end of this paper. This may seem rather complicated, but the modifications to newsreaders and servers are actually quite trivial and well-localized, and it is hoped that the example will make this all clear. If not, the sample authenticator source plus reader and server patches will. AUTHINFO GENERIC protocol: Authentication can occur at two different times. The newsreader may either decide to authenticate on its own initiative sometime after connection, or the reader software will be told by the server after issuing a command that it must authenticate before the command can be satisfied. The reader indicates to the server that it wishes to use generic authentication by issuing the following NNTP command: AUTHINFO GENERIC Where "authenticator" is the name of an authenticator, and "arguments" are a list of arguments to be passed to the authenticator. The only prohibited character sequence is "../" in the authenticator name. The reader and server then invoke their "ends" of the authentication protocol (if any). The server will respond with: 500 If "authinfo" is an unknown command 501 If only "authinfo user/pass" is supported. 501 If a detectable error was found in the command (eg: ../ in authenticator, no parameters after "authinfo generic" etc.) 502 authentication failed 281 authentication succeeded 503 If authenticator could not be invoked (missing, fork failed etc. Try authentication again.) nnn authenticator-specific protocol. The two authenticators are connected to the NNTP channel (details later) and converse with their own protocol. Once the authenticators complete, control is returned to the reader and server software which are told by their respective authenticators whether authentication has succeeded (details later). If the authenticators implement their own authentication protocol, the authenticators converse with each other over the NNTP stream. Once authentication has succeeded (or failed), the server will issue the 281 or 502 to indicate whether the authentication succeeded. [Implementation note: INN1.5 will drop the connection if the server-end authenticator indicates that authentication has failed.] The above described the situation where the newsreader has decided on its own to authenticate in advance of the server insisting on authentication. The newsreader can also choose to wait until the server requests authentication (which is probably the preferred approach, because it avoids unnecessary user-interaction with the authenticator if authentication isn't actually needed). The return code "480 Authentication required" can be returned as a response to any NNTP command. In existing software, this is an invitation for the reader to use the old NNTP 1.5 "AUTHINFO USER" and "AUTHINFO PASS". In addition, the reader can now respond with "AUTHINFO GENERIC" as described above. After successful completion of the authenticator interaction, the client has to resubmit the NNTP request that prompted the "480" return. Server authenticator API: This is the description of how it works in INN1.5 on UNIX. It is provided to allow other UNIX server implementors to implement compatible authenticator interfaces. And to allow implementors of server-authenticators to understand the interface API. When the server receives the "AUTHINFO GENERIC " request, it forks and execs an invocation of: program: / argv[0]: argv[1]: ... argv[n]: is a INN configuration variable pointing at a directory that contains authenticators only. As "../" has been prohibited in the authenticator name, and the authenticator invocation is via exec (so the shell does not interpret the arguments), this is thought to be immune to command-line attacks, but allows essentially anything to be sent. The authenticator's stdin has been connected to the server's NNTP "read socket" (from reader), and stdout has been connected to the server's NNTP "write socket" (to reader). The authenticator's stderr has been connected via a pipe to the server, and the server may read one line from it. When the authenticator is invoked, it should immediately start the authenticator protocol with the authenticator on the client side, which will be connected to standard input and output. When authentication has completed, the authenticator exits. An exit code of 0 indicates to the server that authentication has succeeded, and the authenticator prints a nnrp.access entry to stderr (this will naturally have to be parameterized for different access control formats in non-INN servers). In INN, the nnrp.access entry indicates, amongst other things, which newsgroups the user is allowed to access. Which implies that the server authenticator may have to consult external databases to find that information, in addition to the authentication information itself. If the authentication fails, the authenticator should exit(1), and the server will not read stderr. The server will issue the 281 or 502 response determined by the return code of the server authenticator. The implementation in NNTP 1.5.12 is almost identical. Reader authenticator API: There is an environment variable called "NNTPAUTH" which is used to determine the contents of the "AUTHINFO GENERIC" command. This will usually be set up in system-wide shell environment defaults (eg: /etc/profile) or a user's own custom environment. [Along with, for example, NNTPSERVER] When the reader software decides to emit the "AUTHINFO GENERIC" command, it uses the contents of the NNTPAUTH variable as the parameters to the command. Example, if NNTPAUTH contains "any foo", it should issue the command "AUTHINFO GENERIC any foo" to the server. After sending the AUTHINFO GENERIC command to the server, it should putenv() into its own environment: NNTP_AUTH_FDS=.. readfd and writefd are the file numbers of the read and write NNTP sockets connected to the server. Cookiefd is the fileno for a file that's been opened for read and write and immediately unlinked after creation, (eg: via tmpfile(), or mktemp()/unlink()). [Note that if NNTP_AUTH_FDS is already in the reader's environment, it should be left alone. Thus, if the reader (or any child, such as inews) needs to authenticate again, NNTP_AUTH_FDS is remembered from the previous invocation. As the cookie file no longer has a name, the contents of the file are not visible to other processes, unless they're a child of the reader.] Then the reader should invoke the NNTPAUTH value as a command by the system() function (or equivalent). Note that the reader may choose to alter the command line passed to system() in order to encapsulate the authenticator within some other interface tool. For example, an X windows reader could invoke xterm with NNTPAUTH as a command for it to execute, thus causing the authentication to occur in a pop-up xterm. Authenticator specifics: The client authenticator should examine NNTP_AUTH_FDS, and can read and write commands to the server authenticator by using readfd and writefd. The authenticator should fstat() to determine whether anything has been written into it. If anything has, it is the data from a previous authentication run, and the authenticator is free to supply the contained information to the server authentication instead of prompting the user for it. If the cookiefd file is empty, there has been no previous authentication, and the authenticator should prompt the user via standard input and standard output in response to requests from the server-authenticator for information. The conversation with the server authenticator continues to completion, and the reader authenticator signals the reader software whether authentication succeeded or not by exit() of 0 or 1. If authentication succeeds, it should write information into cookiefd that allows subsequent invocations to reauthenticate without having to prompt the user. [The above description is somewhat confusing I think, the sample authenticator will make it clear.] Authenticator protocol: The authenticators are free to use whatever protocol they wish. This section contains a sample protocol for authentication via YP/NIS lookup on the server. The password is in plaintext. server authenticator: 100 enter userid client authenticator: user clewis server authenticator: 101 enter password client authenticator: pass foobarB If the transaction has succeeded, then the server authenticator exit(0), and the server issues: "281" If the transaction has failed, then the server authenticator exit(1), and the server issues: "502". The sample implementation will continue to prompt for three iterations before giving up on failed password matches. A complete example of the protocol includling a sample authenticator protocol, and additional UNIX API semantics: (r=reader, s=server, sa=server authenticator, "ra" reader authenticator) s: 200 Server ready r: LIST s: 480 Authentication required [NNTPAUTH contains "ypauth"] r: AUTHINFO GENERIC ypauth [server spawns off server authenticator connected to NNTP port] [NNTP_AUTH_FDS nonexistant. reader sets NNTP_AUTH_FDS as above, and spawns reader authenticator via system($NNTPAUTH), cookiefd is empty.] sa: 100 enter userid ra: user clewis sa: 101 enter password ra: pass foobarB [server checks authentication. Passes. Issues nnrp.access entry to stderr, and exit(0)] s: 281 Ok [reader authenticator writes "clewis foobarB" into cookiefd, and exits with 0] r: LIST [reissue of command that prompted the authentication request] s: xxx Ok . Subsequent invocations while NNTP_AUTH_FDS is still "alive" (eg: server requests authentication again, server times out and reader attempts to reconnect automatically, reader invokes subprocess that calls inews) can therefore authenticate without having to prompt the user. We pick this up at the AUTHINFO GENERIC command: r: AUTHINFO GENERIC ypauth [server spawns off server authenticator connected to NNTP port] [reader: NNTP_AUTH_FDS exists, simply spawn reader authenticator. Reader authenticator fstats cookiefd, and finds it has something in it. It reads it and gets "clewis foobarB" from previous invocation] sa: 100 enter userid ra: user clewis (parsed from data read from cookiefd) sa: 101 enter password ra: pass foobarB (parsed from data read from cookiefd) [server checks authentication. Passes. Issues nnrp.access entry to stderr, and exit(0)] s: 281 Ok [reader authenticator writes "clewis foobarB" into cookiefd, and exits with 0] r: LIST [reissue of command] s: xxx Ok . Failed authentication, pick up at "pass": ra: pass [server authenticator exits with 1] s: 502 Authentication failed [server disconnects] -- Every once in a while I write something that someone might use in a signature. So I go back and make it real spiffy. No luck. Then some guy goes and uses *that* dog. Argh!