READERS.CONF(5)

๐ŸŒธ READERS.CONF(5) ๐ŸŒธ

InterNetNews Documentation โœจ okay bestie, buckle up, this is the readers.conf page ๐Ÿ’…


๐Ÿ’– NAME ๐Ÿ’–

readers.conf โ€” access control and configuration for nnrpd


๐Ÿฆ‹ IN A NUTSHELL ๐Ÿฆ‹

okay so, the readers.conf file is literally the bouncer at the club ๐ŸŽ€. Its parameters decide who's allowed to connect as a news reader and what they're allowed to do once they're in. Very gatekeeping, very main character energy ๐Ÿ‘‘

Bear in mind that in readers.conf, authentication and authorization are configured in different blocks. They're not the same thing, bestie, don't conflate them ๐Ÿ˜ญ

  1. First, a user gets authenticated, and is assigned an identity โ€” that happens in an "auth" block โœจ
  2. Then that identity gets authorized to access certain newsgroups with certain rights โ€” that happens in an "access" block ๐Ÿ’…

๐ŸŽ€ auth blocks: the vibe ๐ŸŽ€

As for authentication, your "auth" block for password users could look like this:

auth "foreignokay" {
    auth: "ckpasswd -f <pathdb in inn.conf>/newsusers"
    default: "<unauthenticated>"
}

Peep the documentation of the -f flag in the ckpasswd(8) man page for how to generate passwords and make use of this newsusers file ๐Ÿ‘€ iykyk.

So here's the tea โ˜•: with the "foreignokay" authentication block, a user who successfully authenticates as user "myusername" gets assigned the identity "myusername". slay. But if authentication fails, they get assigned the default identity "<unauthenticated>" โ€” which will later get checked in "access" blocks ๐Ÿซถ.

โš ๏ธ Authentication blocks are checked from the last one in the readers.conf file to the first one (bottom up). As soon as one matches, the corresponding identity is assigned to the user. The way that it's bottom-up?? lowkey iconic but also don't forget it ๐Ÿ˜ญ

๐ŸŒˆ access blocks: the actual permissions ๐ŸŒˆ

As for authorization, let's do something in an "access" block for people successfully authenticated with passwords:

access "authenticatedpeople" {
    users: "*"
    newsgroups: "*,!junk,!control,!control.*"
}

And then something like one of the following two, depending on whether unauthenticated users get any access at all:

access "restrictive" {
    users: "<unauthenticated>"
    newsgroups: "!*"
}

access "readonly" {
    users: "<unauthenticated>"
    read: "local.*"
    post: "!*"
}
๐Ÿ’€ Please note that the "authenticatedpeople" block must appear in readers.conf before "restrictive" or "readonly" blocks โ€” because access blocks are checked from the last one in the readers.conf file to the first one (bottom up), same deal as before. As soon as one matches the identity previously assigned by an authentication block, it is chosen. Periodt.

The "authenticatedpeople" access block matches every user that has not been assigned "<unauthenticated>" as identity ๐Ÿ’…


โœจ more coming, but real quick โœจ

More examples and features are detailed below in this man page โ€” notably without any password file, with PAM, with Perl or Python hooks ๐Ÿ‘‘๐Ÿฆ‹

And here's the best part, bestie: you don't need to reload anything after modifying readers.conf ๐ŸŽ‰ Every time a news client connects to the server, a new nnrpd process is spawned and reads its configuration from disk. No restart era needed ๐Ÿ™ˆ

Nonetheless, after any changes, you can run inncheck to perform basic syntax checks against the modified readers.conf file ๐Ÿซถ always run inncheck, it's giving responsible queen ๐Ÿ’œ

๐Ÿ’… DESCRIPTION ๐Ÿ’…

Okay bestie, so readers.conf lives in pathetc and it's literally the bouncer at the club for nnrpd(8). ๐Ÿซถ It controls who's allowed to connect as a news reader and what they're allowed to do once they're in. And here's the iconic part: nnrpd reads this file every single time a news client connects to the server โ€” even when it's running in daemon mode with -D โ€” which means any changes you make take effect immediately on all subsequent connections. โœจ No restart era, we love to see it. (And ngl, the location of readers.conf in pathetc is only the default โ€” the same format applies to any file specified with "nnrpd -c".)

There are two types of entries in readers.conf: parameter/value pairs and configuration groups. Blank lines and anything after a number sign ("#") are ignored, unless the character "#" is escaped with "\". And the max number of characters on each line is 8,191 โ€” she has limits, okay? ๐Ÿ’…

Parameter/value pairs consist of a keyword immediately followed by a colon, at least one whitespace character, and then a value. The case of the parameter is significant (parameter should generally be in all lowercase, bestie), and a parameter may contain any characters except colon, "#", and whitespace. Like, for example:

hosts: *.example.com

Values that contain whitespace should be quoted with double quotes, as in:

hosts: "*.example.com, *.example.net"

If the parameter doesn't contain whitespace, such as:

hosts: *.example.com,*.example.net

then it's not necessary to quote it โ€” although you may wish to anyway for clarity (aesthetic, obviously โœจ).

There is no way to continue a line on the next line, and therefore no way to have a single parameter with a value longer than about 8,180 characters. Sad but true, we don't make the rules. ๐Ÿ˜ญ

Many parameters take a boolean value. For all such parameters, the value may be specified as "true", "yes", or "on" to turn it on, and may be any of "false", "no", or "off" to turn it off. The case of these values is not significant. ๐ŸŽ€


๐Ÿฆ‹ the two group types ๐Ÿฆ‹

There are two basic types of configuration groups: auth and access. The auth group provides mechanisms to establish the identity of the user โ€” literally who they are. The access group determines, given the user's identity, what that user is permitted to do. ๐Ÿ’…

So writing a readers.conf file for your setup is a two-step process: first assigning an identity to each incoming connection using auth groups, and then giving each identity appropriate privileges with access group. We recommend not intermingling auth groups and access groups in the config file โ€” it's often more sensible (in the absence of the key parameter) to put all of the auth groups first, and all of the access groups below. Keep it organized, queen. ๐Ÿ‘‘

A user identity, as established by an auth group, looks like an e-mail address if a domain is specified (like "<username>@<domain>") or otherwise just "<username>".

If nnrpdauthsender is set in inn.conf, the user identity is also put into the Sender header field of posts made by that user. See the documentation of that option in inn.conf(5) for more details. ๐Ÿ‘€

An auth group definition looks like:

auth <name> {
    hosts: <host-wildmat>
    auth: <auth-program>
    res: <res-program>
    default: <defuser>
    default-domain: <defdomain>
    # ...possibly other settings
}

The <name> is used as a label for the group and is only for documentation purposes. (If your syslog configuration records the "news.debug" facility, the <name> will appear in the debugging output of nnrpd. Examining that output can be very helpful in understanding why your configuration doesn't do what you expect it to. ๐Ÿ” We've all been there.)

A given auth group applies only to hosts whose name or IP address matches the wildmat expression given with the hosts parameter (comma-separated wildmat expressions are allowed, but "@" is not supported). Rather than wildmat expressions, you may also use CIDR notation to match any IP address in a netblock โ€” for example, "10.10.10.0/24" will match any IP address between 10.10.10.0 and 10.10.10.255 inclusive. ๐ŸŽฏ

If compiled against the TLS/SSL or SASL libraries, an auth group with the require_encryption parameter set to true only applies if the incoming connection is using an encryption layer โ€” either from the beginning if the -S flag was passed to nnrpd, or after a successful use of STARTTLS, or after a successful authentication using an SASL mechanism which negotiates an encryption layer. ๐Ÿ”

For any connection from a host that matches that wildmat expression or netblock, each <res-program> (multiple res lines may be present in a block; they are run in sequence until one succeeds), if any, is run to determine the identity of the user just from the connection information. If all the resolvers fail, or if the res parameter isn't present, the user is assigned an identity of "<defuser>@<defdomain>" โ€” in other words, the values of the default and default-domain parameters are used. If <res-program> only returns a username, <defdomain> is used as the domain. ๐Ÿงš

If the user later authenticates via the AUTHINFO USER/PASS commands, the provided username and password are passed to each <auth-program> (multiple auth, perl_auth, or python_auth lines may be present in a block; they are run in sequence until one succeeds), if any. If one succeeds and returns a different identity than the one assigned at the time of the connection, it is matched against the available access groups again and the actions the user is authorized to do may change. ๐Ÿ˜ณ The most common <auth-program> to use is ckpasswd, which supports several ways of checking passwords including using PAM. See the ckpasswd(8) man page for more details.

When matching auth groups, the last auth group in the file that matches a given connection and username/password combination is used. Last one wins, periodt. ๐Ÿ’…


An access group definition usually looks like:

access <name> {
    users: <identity-wildmat>
    newsgroups: <group-wildmat>
    # ...possibly other settings
}

Again, <name> is just for documentation purposes. This says that all users whose identity matches <identity-wildmat> can read and post to all newsgroups matching <group-wildmat> (as before, comma-separated wildmat expressions are allowed, but "@" is not supported). Alternately, you can use the form:

access <name> {
    users: <identity-wildmat>
    read: <read-wildmat>
    post: <post-wildmat>
}

and matching users will be able to read any group that matches <read-wildmat> and post to any group that matches <post-wildmat>. You can also set several other things in the access group as well as override various inn.conf(5) parameters for just a particular group of users. ๐ŸŽ€

Just like with auth groups, when matching access groups, the last matching one in the file is used to determine the user's permissions. But there's an exception to this rule, bestie, listen up: if the auth group which matched the client contains a perl_access or python_access parameter, then the script given as argument is used to dynamically generate an access group. This new access group is then used to determine the access rights of the client; the access groups in the file are ignored. ๐Ÿคฏ


๐Ÿ”‘ the key parameter special case ๐Ÿ”‘

There is one additional special case to be aware of. When forming particularly complex authentication and authorization rules, it is sometimes useful for the identities provided by a given auth group to only apply to particular access groups โ€” in other words, rather than checking the identity against the users parameter of every access group, it's checked against the users parameter of only some specific access groups. This is done with the key parameter. For example:

auth example {
    key: special
    hosts: *.example.com
    default: <SPECIAL>
}

access example {
    key: special
    users: <SPECIAL>
    newsgroups: *
    addcanlockuser: none
}

In this case, the two key parameters bind this auth group with this access group. For any incoming connection matching "*.example.com" (assuming there isn't any later auth group that also matches such hosts), no access group that doesn't have "key: special" will even be considered. Similarly, the above access group will only be checked if the user was authenticated with an auth group containing "key: special". This mechanism normally isn't useful โ€” there is almost always a better way to achieve the same result. ๐Ÿ˜… Lowkey a last resort, fr fr.

Also note in the example that there's no default-domain parameter, which means that no domain is appended to the default username and the identity for such connections is just "<SPECIAL>". Note that some additional add-ons to INN may prefer that authenticated identities always return a full e-mail address (including a domain), so you may want to set up your system that way if you are using add-ons. And heads up: as several different persons can be assigned the same "<SPECIAL>" identity, no user-specific Cancel-Lock hashes should be generated. ๐Ÿšจ Important!!


Configuration files can be included in other configuration files via the syntax:

include <filename>

The file named <filename> is then included. This syntax is allowed only at top-level. ๐ŸŽ€

Below is the full list of allowable parameters for auth groups and access groups, and after that are some examples that may make this somewhat clearer. Buckle up, bestie, we're going in. ๐Ÿ’–

๐Ÿ” AUTH GROUP PARAMETERS ๐Ÿ”

Okay so real talk: an auth group with not one single res, auth, perl_auth, python_auth, or default parameter is literally pointless โ€” it makes no sense, and in practice it's just gonna get ignored. So likeโ€ฆ don't do that, bestie. ๐Ÿ’…

  • auth โ€” a simple command line for a user authenticator (and no, shell metacharacters are not supported, so don't try it ๐Ÿ™ˆ). If you don't give a full path, the program it runs has to live in the pathbin/auth/passwd directory. An authenticator is a program that handles a user-supplied username and password via a mechanism like AUTHINFO USER/PASS. Just like with res, one auth group can have multiple auth parameters โ€” they get tried in order, and the results of the first successful one are what you get. See also perl_auth. The most common authenticator to use is ckpasswd(8) โ€” go read its man page for the deets. โœจ
  • default โ€” the default username for connections matching this auth group. It's the username assigned to the user at connection time if all resolvers fail or if there are no res parameters. Heads up: it can be either a bare username (in which case default-domain, if present, gets appended after an "@") or a full identity string containing an "@" โ€” and then it's used verbatim, no edits. ๐Ÿซถ
  • default-domain โ€” the default domain string for this auth group. If a user resolver or authenticator doesn't provide a domain, or if the default username is used and it doesn't contain an "@", this domain is what forms the user identity. (And ngl, for a lot of setups it's not really necessary for user identities to be qualified with a domain name โ€” in that case there's zero need to use this parameter. Save yourself the typing.) ๐Ÿ’œ
  • hosts โ€” a comma-separated list of remote hosts: wildmat patterns matching either hostnames or IP addresses, or IP netblocks in CIDR notation. If a user connects from a host that doesn't match this parameter, this auth group will not match the connection and gets ignored. ๐Ÿšซ
Real-talk warning: if you have a large number of patterns that can't be merged into broader patterns (like a big pile of individual systems scattered around the net that should have access), the hosts parameter may exceed the maximum line length of 8,192 characters. In that case you'll need to break that auth group into multiple auth groups, each with a portion of the hosts listed in its hosts parameter, and each assigning the same user identity. Annoying? Yes. Do it anyway. ๐Ÿ˜ญ

All hosts match if this parameter is not given. โœจ

  • key โ€” if this parameter is present, any connection matching this auth group will have its privileges determined only by the subset of access groups containing a matching key parameter. That's the whole vibe. ๐Ÿ”‘
  • localaddress โ€” a comma-separated list of local host or address patterns, same syntax as the hosts parameter. If you specify it, its auth group will only match connections made to a matching local interface. (Obviously this is only useful for servers with multiple interfaces, so if that's not you, don't stress.) All local addresses match if this parameter is not given. ๐Ÿฆ‹
  • perl_access โ€” a path to a Perl script for dynamically generating an access group. If an auth group matches successfully and contains a perl_access parameter, then the argument Perl script gets used to create an access group. This group then determines the access rights of the client, overriding any access groups in readers.conf. If and only if a successful auth group contains the perl_access parameter, readers.conf access groups are ignored and the client's rights are instead determined dynamically. This parameter is only valid if INN is compiled with Perl support (--with-perl passed to "configure"). More info can be found in the file doc/hook-perl. ๐Ÿงš
  • perl_auth โ€” a path to a Perl script for authentication. This works exactly like auth, except that it calls the named script using the Perl hook rather than an external program. Multiple/mixed use of the auth, perl_auth, and python_auth parameters is permitted within any auth group; each line is tried in the order it appears. perl_auth has more power than auth in that it provides the authentication program with additional information about the client and the ability to return an error string and a username. This parameter is only valid if INN is compiled with Perl support (--with-perl passed to "configure"). More info can be found in doc/hook-perl. ๐Ÿ’–
  • python_access โ€” a Python script for dynamically generating an access group. If an auth group matches successfully and contains a python_access parameter, then the argument script (without its ".py" extension) located in the pathfilter directory will be used to create an access group. This group then determines the access rights of the client, overriding any access groups in readers.conf. If and only if a successful auth group contains the python_access parameter, readers.conf access groups are ignored and the client's rights are instead determined dynamically. This parameter is only valid if INN is compiled with Python support (--with-python passed to "configure"). More information may be found in the file doc/hook-python. ๐Ÿโœจ
  • python_auth โ€” a Python script for authentication. The python_auth parameter works exactly like auth, except that it calls the named script (without its ".py" extension) located in the pathfilter directory using the Python hook rather than an external program. Multiple/mixed use of the auth, perl_auth, and python_auth parameters is permitted within any auth group; each line is tried in the order it appears. python_auth has more power than auth in that it provides the authentication program with additional information about the client and the ability to return an error string and a username. This parameter is only valid if INN is compiled with Python support (--with-python passed to "configure"). More information may be found in doc/hook-python. ๐Ÿ๐Ÿ’…
  • python_dynamic โ€” a Python script for applying access control dynamically on a per-newsgroup basis. If an auth group matches successfully and contains a python_dynamic parameter, then the argument script (without its ".py" extension) located in the pathfilter directory will be used to determine the client's rights each time the user attempts to view a newsgroup, or read or post an article. Access rights as determined by python_dynamic override the values of access group parameters such as newsgroups, read and post. This parameter is only valid if INN is compiled with Python support (--with-python passed to "configure"). More information may be found in the file doc/hook-python. ๐Ÿ๐Ÿ”ฅ
  • require_encryption โ€” if set to true, an incoming connection only matches this auth group if it's encrypted: either from the beginning if the -S flag was passed to nnrpd, or after a successful use of STARTTLS, or after a successful authentication using a SASL mechanism which negotiates an encrypted layer. This parameter is only valid if INN is compiled with TLS/SSL or SASL support (by default if the OpenSSL SSL and crypto libraries or the Cyrus SASL library are found at configure time โ€” otherwise check out the --with-openssl and --with-sasl flags passed to "configure"). ๐Ÿ”’๐Ÿ‘‘
  • res โ€” a simple command line for a user resolver (shell metacharacters are not supported, so keep it clean ๐Ÿงผ). If a full path is not given, the program executed must be in the pathbin/auth/resolv directory. A resolver is an authentication program that attempts to figure out the identity of the connecting user using nothing but the connection information โ€” in other words, the user has not provided a username and password. An example of a resolver would be a program that assigns an identity from an ident callback or from the user's hostname. One auth group can have multiple res parameters, and they'll be tried in the order they're listed. The results of the first successful one will be used. ๐Ÿ’ซ

Alternatively, a res block can be used instead of a res parameter. The recognized parameters in such res blocks are:

  • log โ€” a string to log in pathlog/news.notice (with "res also-log:" prepended) before the resolver is tried. One res group can have multiple log parameters, and they will be logged in the order they're listed. ๐Ÿ“
  • program โ€” this parameter is mandatory in a res block. Its meaning is the same as the res parameter used directly in an auth block. ๐Ÿซก

So this:

auth <auth-name> {
    res: <res-program>
}

is therefore equivalent to:

auth <auth-name> {
    res <res-name> {
        program: <res-program>
    }
}

๐ŸŽŸ๏ธ ACCESS GROUP PARAMETERS ๐ŸŽŸ๏ธ

  • access โ€” a set of letters specifying the permissions granted to the client. The letters are chosen from the following set:
    • A โ€” the client may post articles with Approved header fields (in other words, may approve articles for moderated newsgroups). By default, this is not allowed. ๐Ÿ™…โ€โ™€๏ธ
    • I โ€” the client may inject articles with IHAVE. Note that in order to inject articles with the IHAVE command, the user must also have POST permission (the "P" option). Articles injected with IHAVE are treated as though they were injected with POST โ€” that is to say, such articles must not have been previously injected (they must not contain header fields like Injection-Info). ๐Ÿง
    • L โ€” the client may post to newsgroups that are set to disallow local posting (status fields "j", "n" and "x" in the active(5) file). ๐Ÿ“ฎ
    • N โ€” the client may use the NEWNEWS command, overriding the global setting. ๐Ÿ“ฐ
    • P โ€” the client may post articles. โœ๏ธ
    • R โ€” the client may read articles. ๐Ÿ‘€
Big note, don't skip it: if this parameter is given, allownewnews in inn.conf is ignored for connections matching this access group, and the client's ability to use NEWNEWS is entirely determined by the presence of "N" in the access string. So if you want to support NEWNEWS, make sure to include "N" in the access string when you use this parameter. Fr fr, don't forget. ๐Ÿ˜ญ๐Ÿ’€
  • access โ€” okay so real talk, bestie, this one is messy. If you give this parameter and "R" isn't in the access string, the client literally cannot read, no matter what newsgroups or read parameters say. Same energy with "P": give this parameter without "P" in the string and the client cannot post. This use of access is deprecated and confusing โ€” the manpage is strongly recommending that if you use the access parameter, you always include "R" and "P" in the access string and use newsgroups, read, and post to control access instead. (Wanna grant read access but no posting access? Just have a read parameter and no post parameter. Slay.)
  • addcanlockuser โ€” set this to "none" and posts made by these users will not have a user-specific hash in the Cancel-Lock header field (only the admin hash gets generated). And no Cancel-Key header field will be present in cancel or supersede requests. It's a string value, and the default is "username" โ€” meaning a user-specific hash based on the identity assigned to the connection is used. โœจ

Another possible value is "ip", in which case a user-specific hash based on the IP of the connection is used (and that should be a static IP so it actually identifies users, iykyk).

See inn-secrets.conf(5) for more on Cancel-Lock. The whole reason this parameter exists is to deactivate this authentication mechanism when the same identity or IP can be assigned by an access group to different people โ€” otherwise any of those people could send an authenticated withdrawal of an article originally sent by another person with the same identity or IP. Not the vibe.

  • domain โ€” when set, this value is used for the domain part (the right-hand side after "@") of autogenerated Message-IDs, and also for the server name indicated in Injection-Info header fields.

By default, this parameter inherits its value from the eponym parameter in inn.conf. But โ€” if domain is not explicitly set in readers.conf, that specific behaviour described above won't be triggered unless virtualhost is set to true.

You may also want to set the fromhost parameter (inherited from inn.conf) for the complaints address in the Injection-Info header field, if its default value should be changed, and the complaints parameter is not already set or newsmaster does not already contain an "@". ๐Ÿซถ

  • groupexactcount โ€” this parameter controls the threshold below which nnrpd should give the exact number of still existing articles in newsgroups instead of an estimate. The exact number is usually updated once a day during the expiry process by expirover (run by news.daily), and it's updated when new articles arrive but not necessarily when articles are cancelled or overwritten in self-expiring storage methods like CNFS.

When the estimated count of articles is strictly smaller than groupexactcount, nnrpd will recount the number of still existing articles and report the exact value. If groupexactcount is set to "0", nnrpd will always recount. If set to "1", it will never recount.

Exact article counts aren't required, but they can be useful to distinguish empty newsgroups by reporting an exact count of "0" instead of an estimate of "1" โ€” because when that happens, the news client may show that 1 article is present in the newsgroup, then it tries to retrieve the article, and finally does not show anything to the user. ๐Ÿ˜ญ Icky.

It is not recommended to set a high value to this parameter because it will slow the responses to news clients (recounting existing articles takes time, especially on newsgroups with a lot of articles).

This is an integer value, and the default is "5".

  • key โ€” if this parameter is present, this access group is only considered when finding privileges for users matching auth groups with this same key parameter. ๐Ÿ”‘
  • localtime โ€” if a Date or an Injection-Date header field is not included in a posted article, nnrpd normally adds these header fields in UTC. Set this to true and the Date header field will be formatted in local time instead. (The Injection-Date header field is added according to the behaviour of the addinjectiondate parameter in inn.conf, and will remain in UTC, though.) This is a boolean value and the default is false.

This parameter permits handling a relatively unusual corner case. It's mostly a tool for people who want to disclose their local time zone (it can be useful information in certain types of discussions), but whose clients don't for some reason, and who can arrange for the server to be in the same time zone as the client. ๐Ÿ•ฐ๏ธ

  • max_rate โ€” if this parameter is present (and non-zero), it's used for nnrpd's rate-limiting code. The client will only be able to download at this speed (in bytes/second). Note that if an encryption layer is being used, limiting is applied to the pre-encryption datastream. ๐ŸŽ๏ธ
  • newsgroups โ€” users that match this access group are allowed to read and post to all newsgroups matching this comma-separated list of wildmat patterns. The empty string is equivalent to newsgroups: *; if this parameter is missing, the connection will be rejected (unless read and/or post are used instead).
  • newsmaster โ€” used as the contact address in the help message returned by nnrpd(8), if the virtualhost: parameter is set to true. ๐Ÿ“‡
  • perlfilter โ€” if set to false, posts made by these users do not pass through the Perl filter even if it's otherwise enabled. This is a boolean value and the default is true.
  • post โ€” like the newsgroups parameter, but the client is only given permission to post to the matching newsgroups. This parameter is often used with read to define the patterns for reading and posting separately (usually to give the user permission to read more newsgroups than they're permitted to post to). It cannot be used in the same access group with a newsgroups parameter.
  • pythonfilter โ€” if set to false, posts made by these users do not pass through the Python filter even if it's otherwise enabled. This is a boolean value and the default is true.
This parameter currently has no effect as Python filtering is not implemented yet in nnrpd. If you want to do Python filtering, you have to use the one implemented in innd. ๐Ÿ’€
  • read โ€” like the newsgroups parameter, but the client is only given permission to read the matching newsgroups. This parameter is often used with post to specify some read-only groups; it cannot be used in the same access group with a newsgroups parameter. (If read is used and post is missing, the client will have only read-only access.)
  • reject_with โ€” if this parameter is present, a client matching this block will be disconnected with a "Permission denied" message containing the contents (a "reason" string) of this parameter. Some newsreaders will then display the reason to the user. ๐Ÿšซ
  • strippath โ€” if set to true, any Path header field provided by a user in a post is stripped rather than used as the beginning of the Path header field body of the article. This is a boolean value and the default is false.
  • users โ€” the privileges given by this access group apply to any user identity which matches this comma-separated list of wildmat patterns. If this parameter isn't given, the access group applies to all users (and is essentially equivalent to users: *). ๐Ÿ‘‘
  • virtualhost โ€” set this parameter to true to make nnrpd behave as if it's running on a server with a different name than it actually has. The domain parameter must then be set, either in inn.conf or in the same access group. All articles and overview data displayed to clients will have their Xref header field bodies altered to appear to be from the server named in domain, and posted articles will use that server name in the Message-ID and Injection-Info header field bodies. (Moreover, if the -I flag was given to nnrpd, posting agents will be proposed to use Message-IDs containing the string specified with -I.) ๐ŸŒˆ

Similarly, the Path header field bodies displayed to clients or generated in posted articles will use the value of domain (if pathhost is not set in the access group, or has the same value as in inn.conf) or pathhost (if pathhost is set in the access group to something different than is set in inn.conf).

Also, the domain part (the right-hand side after "@") of the newsmaster e-mail mentioned in the help message returned by nnrpd will use the value of domain if the newsmaster parameter does not already have a domain part.

At least one of the domain or pathhost parameters must be set in the access group to something different than in inn.conf, otherwise nnrpd will fail to start. ๐Ÿ˜ฑ

Note that setting this parameter requires the server modify all posts before presenting them to the client and therefore may decrease performance slightly.


In addition, all of the following parameters are valid in access groups and override the global setting in inn.conf. See inn.conf(5) for the descriptions of these parameters:

addinjectiondate, addinjectionpostingaccount,
addinjectionpostinghost, backoff_auth, backoff_db, backoff_k,
backoff_postfast, backoff_postslow, backoff_trigger,
checkincludedtext, clienttimeout, complaints, fromhost,
localmaxartsize, moderatormailer, nnrpdauthsender, nnrpdcheckart,
nnrpdoverstats, nnrpdposthost, nnrpdpostport, organization,
pathhost, readertrack, spoolfirst, strippostcc.

โœจ SUMMARY โœจ

okay bestie, here's the lowkey rundown of what actually happens when a client connects ๐Ÿซถ:

  • First, every auth group gets scanned, and any group that doesn't match the client (because of hosts, localaddress, require_encryption, etc.) is straight-up eliminated ๐Ÿ’….
  • The auth groups that survive get scanned from the last to the first, and each one gets a shot at being applied to the current connection. That means running res programs, if there are any, and otherwise applying default. The first auth group (starting from the bottom) to return a valid user is kept as the active auth group โœจ.
  • If no auth groups yield a valid user โ€” like, none of them have default parameters or successful res programs โ€” but some of the auth groups have auth lines (which hints that the user could authenticate and then get permissions), then the connection is considered to have no valid auth group. That means the access groups are ignored completely, but the connection isn't closed. Instead, a 480 response code (which means authentication is required) is returned for everything until the user authenticates ๐Ÿ˜ญ.
  • When the user does authenticate, the auth groups get rescanned, and only the matching ones that contain at least one auth, perl_auth, or python_auth line are considered. Those auth groups are scanned from the last to the first, running auth programs and perl_auth or python_auth scripts. The first auth group (starting from the bottom) to return a valid user is kept as the active auth group ๐Ÿ‘‘.
  • No matter how an auth group gets established, as soon as one is established, that auth group is used to assign a user identity. It does this by taking the result of the successful res, auth, perl_auth, or python_auth line (or the default if necessary), and appending default-domain if necessary. (This is if the perl_access or python_access parameter is present.) ๐Ÿฆ‹
  • Finally, an access group is selected by scanning the access groups from bottom up and finding the first match. (If the established auth group contained a perl_access or python_access line, the dynamically generated access group returned by the script is used instead.) User permissions are granted based on the established access group ๐ŸŒˆ.

โœจ EXAMPLES โœจ

Okay bestie, this is the fun part โ€” actual real-life readers.conf files you can steal from. ๐ŸŽ€ Probably the simplest useful complete example ever: it hands read and post permission on every group to literally anyone connecting from the example.com domain, except Bob's machine (sorry Bob ๐Ÿ’€), and gives zero privileges to anybody connecting from anywhere else:

auth example.com {
    hosts: "*.example.com, example.com, !bob.example.com"
    default: "<EXAMPLE>"
}

access full {
    users: "<EXAMPLE>"
    newsgroups: *
    addcanlockuser: none
}

Quick note, because this matters: that access realm up there could also be written without the users key, and then it just applies to whatever user identity shows up (though in this particular example, literally every matching connection gets assigned the identity "<EXAMPLE>"). That said, it's recommended to keep an explicit users key so you can actually see who the access block applies to โ€” future you will thank you. ๐Ÿซถ

And because the only auth realm available matches hosts in the example.com domain, any connection from any other host gets rejected immediately. No vibes, no entry. ๐Ÿšซ


Now, say you've got some systems that should only get read-only access to the server. Easy โ€” modify the example above slightly by adding an extra auth and access group:

auth lab {
    hosts: "*.lab.example.com"
    default: <LAB>
}

access lab {
    users: <LAB>
    read: *
}

Put those after the first example in the file and they'll take precedence (because they're later in the file, iykyk) for any user coming from a machine in the lab.example.com domain. Result: everyone from the lab only gets read access, no posting. Slay. โœจ


Here's a similar example for a news server that accepts connections from literally anywhere but makes the user hand over a username and password. Those credentials get checked first against an external database of usernames and passwords, and then it hits up PAM:

auth all {
    auth: "ckpasswd -f <pathdb in inn.conf>/newsusers"
    auth: ckpasswd
}

access full {
    users: *
    newsgroups: *
}

So when the user first connects, there are no res keys and no default, meaning they don't get any valid identity and the connection won't match any access groups โ€” even the ones with users: *. ๐Ÿ˜ญ Those users get nothing but authentication-required responses from nnrpd until they authenticate. That's just how it is.

Then if they authenticate later, the username and password get checked first by running ckpasswd with the -f option against an external file of encrypted passwords, and then it uses PAM (if INN was built with PAM support) to check the password โ€” and if that fails, it tries checking the password against the password field returned by getpwnam(3). If both of those flop, the user keeps having no identity. Otherwise an identity gets assigned (usually the supplied username, maybe with a domain tacked on, though an authenticator technically can provide a completely different username for the identity), and the access group matches, giving full access. ๐ŸŽ‰


It may be educational (lol, learning) to think about how to combine the examples above โ€” general groups always go first. The order of the auth groups actually doesn't matter here, because the hosts: example.com one only matches connections before username/password is sent, and the auth: ckpasswd one only matches after. Order would matter if either group applied to both cases. The order of the access groups, though, does matter in this case โ€” provided the newsgroups lines differ, the access group with no users line has to be first, with the users: <LOCAL> group after it.


Here's another common case: a server that only allows connections from a local domain and has an extra hierarchy that's password-restricted. ๐Ÿ‘‘

auth "example.com" {
    hosts: "*.example.com"
    auth: "ckpasswd -f <pathdb in inn.conf>/newsusers"
    default: "anonymous"
}

access regular {
    newsgroups: "*,!example.restricted.*"
    addcanlockuser: none
}

access full {
    users: "*,!anonymous"
    newsgroups: *
}

In this example, unauthenticated users get the identity "anonymous", which matches only the first access group โ€” so no access to the "example.restricted.*" hierarchy for them. And no user-specific Cancel-Lock elements should be generated for anonymous users, periodt. Anyone who authenticates with a password in the newsusers file gets full access to all groups. ๐Ÿ”ฅ

But heads up: the only authentication block is limited to hostnames in the example.com domain, so connections outside that domain will never be allowed access and won't even get an opportunity to authenticate. Not a drill. ๐Ÿ’€

Okay this next one is a very complicated example, buckle up. ๐ŸŒˆ

It's for an organization with an internal hierarchy "example.*" that's only available to local shell users, who are on machines where identd(8) can be trusted. Dialup users must provide a username and password, which then gets checked against RADIUS. Remote users have to use a username and password checked against a database on the news server. And finally, the admin staff (users "joe" and "jane") can post anywhere โ€” including the "example.admin.*" groups that are read-only for everyone else โ€” and they're exempted from the Perl filter. For an extra twist, posts from dialup users have their Sender header field replaced by their authenticated identity. Iconic.

Since this organization has some internal moderated newsgroups, the admin staff can also post messages with Approved header fields, but other users cannot.

auth default {
    auth: "ckpasswd -f <pathdb in inn.conf>/newsusers"
    default: <FAIL>
    default-domain: example.com
}

auth shell {
    hosts: *.shell.example.com
    res: ident
    auth: ckpasswd
    default: <FAIL>
    default-domain: shell.example.com
}

auth dialup {
    hosts: *.dialup.example.com
    auth: radius
    default: <FAIL>
    default-domain: dialup.example.com
}

access shell {
    users: *@shell.example.com
    read: *
    post: "*, !example.admin.*"
}

access dialup {
    users: *@dialup.example.com
    newsgroups: *,!example.*
    nnrpdauthsender: true
}

access other {
    users: "*@example.com, !<FAIL>@example.com"
    newsgroups: *,!example.*
}

access fail {
    users: "<FAIL>@*"
    newsgroups: !*
}

access admin {
    users: "joe@*,jane@*"
    newsgroups: *
    access: "RPA"
    perlfilter: false
}

Note the use of different domains to easily separate dialup from shell users. Another way to do that would be with key parameters, but this way gives slightly more intuitive identity strings. ๐Ÿฆ‹

Also note that the fail access group catches not only failing connections from external users but also failed authentication of shell and dialup users before they've authenticated. The identity string given for, say, dialup users before RADIUS authentication has been attempted matches both the dialup access group and the fail access group, since it's "<FAIL>@dialup.example.com" โ€” but the fail group is last, so it takes precedence. Sneaky but correct. ๐Ÿ˜Œ

The shell auth group has an auth parameter so that users "joe" and "jane" can, if they choose, use username and password authentication to get their special privileges even if they're logged on as a different user on the shell machines (or if ident isn't working). When they first connect they'd have the default access for that user, but they could then authenticate to level up to their extended access. ๐Ÿ‘‘

Also note that if "joe" and "jane" are using their own accounts, they get their special privileges regardless of how they connect โ€” whether it's the dialups, the shell machines, or even externally with a username and password. Main character energy fr fr.


Finally, a very simple example of a configuration for a public server for a particular hierarchy:

auth default {
    hosts: *
    default: <PUBLIC>
}

access default {
    users: <PUBLIC>
    newsgroups: example.*
    addcanlockuser: none
}

Notice that clients aren't allowed to read any other groups โ€” this keeps them from getting access to administrative groups or from reading control messages, just as a precaution. ๐ŸŽ€

When running a public server like this, be aware that many public hierarchies will later be pulled down and reinjected into the main Usenet, so it's highly recommended that you also run a Perl or Python filter to reject any messages crossposted out of your local hierarchy, and any messages containing a Supersedes header field. This keeps messages posted to your public hierarchy from hurting any of the rest of Usenet if they leak out. Please do this, bestie, I'm begging. ๐Ÿ˜ญ

For more complex cases โ€” or if you just prefer using a database to store authentication and/or authorization info โ€” you can use Perl or Python scripts to dynamically authenticate the users and/or generate their access blocks. Then you can put all the related info (user, password, newsgroups for reading, newsgroups for posting, etc.) for instance in an SQLite database, and use an nnrpd_auth.pl or nnrpd_auth.py script to authenticate the users, and/or an nnrpd_access.pl or nnrpd_access.py script to grant the access. ๐ŸŒธ

Then readers.conf only needs to define:

auth checkcredentials {
    perl_auth: "/path/to/script/nnrpd_auth.pl"
    perl_access: "/path/to/script/nnrpd_access.pl"
}

or:

auth checkcredentials {
    python_auth: "nnrpd_auth"
    python_access: "nnrpd_access"
}

The advantage of these external scripts is that you can code whatever rules you want, possibly based on a database shared between your news service and other services you may provide to your users. And you can more easily handle hundreds or thousands of users with external scripts than with the mere use of readers.conf stock blocks and a flat file for the credentials of each listed user. Scale, bestie. ๐Ÿ“ˆ

๐Ÿ” SECURITY CONSIDERATIONS ๐Ÿ”

Okay bestie, sit down for this one because it's giving important. ๐Ÿซถ

Lowkey the golden rule: use separate passwords for NNTP wherever possible. Like, fr fr. The NNTP protocol itself does not protect passwords from casual interception, and many implementations (including this one, yes, this one ๐Ÿ’€) do not "lock out" accounts or otherwise discourage password-guessing attacks. So the move is to make sure that a compromised password has minimal effects โ€” damage control energy, y'know?

Authentication using the AUTHINFO USER/PASS commands passes unencrypted over the network. So use extreme caution, especially with system passwords (e.g. auth: ckpasswd -s). ๐Ÿ˜ญ That one's serious, no cap.

The good news? Passwords can be protected by using NNTP over TLS/SSL or through ssh tunnels, and this usage can be enforced by a well-considered server configuration that only permits certain auth groups to be applied in certain cases. You can also authenticate using a strong SASL mechanism. Here are some ideas, queen:

  • To restrict connections on the standard NNTP port (119) to use an encryption layer for some (or all) of the auth groups to match, use the require_encryption parameter. Note that a client can use STARTTLS to negotiate an encrypted TLS connection. A secure layer can also be negotiated during authentication via AUTHINFO SASL. โœจ
  • If you consider your local network (but not the internet) secure, have some auth groups with a restrictive hosts parameter; they would go above, with ones having global applicability below. Order matters, iykyk. ๐ŸŽ€
  • Consider running nnrpd with the -S flag (either also with -D, or out of "super-server" like inetd) on the NNTPS port (563) for clients that support TLS/SSL. See nnrpd(8) for more details about how to configure that. You can use the require_encryption parameter or the -c flag to specify an alternate readers.conf file if you want a substantially different configuration for this case. ๐Ÿ’…
  • If you want to restrict an auth group to only match loopback connections (for users running newsreaders on localhost or connecting via an ssh tunnel), use the localaddress parameter. ๐ŸŒ€

๐Ÿ“œ HISTORY ๐Ÿ“œ

Written by Aidan Cully <aidan@panix.com> for InterNetNews. Substantially expanded by Russ Allbery <eagle@eyrie.org>.

Like, shoutout to the icons who built this. ๐Ÿ‘‘ Respect where respect is due.


๐Ÿ‘€ SEE ALSO ๐Ÿ‘€

auth_krb5(8), ckpasswd(8), inn.conf(5), innd(8), inn-secrets.conf(5), libinn_uwildmat(3), newsfeeds(5), nnrpd(8).

Go read those if you're nosy (affectionate). ๐Ÿ“š๐Ÿ’–


and that's the whole page, bestie!! ๐ŸŒˆ This iconic little document is INN 2.7.4, dated 2025-03-29, and it answers to the name READERS.CONF(5). Slay, go forth, and configure responsibly ๐Ÿ’…โœจ๐ŸŽ€

๐Ÿ“ฆ where this page comes from
  • arch rolling: inn 2.7.4-4 ยท ISC
  • epel 10: inn 2.7.4-5.el10_3 ยท ISC AND BSD-3-Clause AND BSD-4-Clause AND FSFAP AND (GPL-1.0-or-later OR Artistic-1.0-Perl) AND GPL-3.0-or-later WITH Bison-exception-2.2 AND LicenseRef-Fedora-Public-Domain AND MIT AND NTP
  • ๐Ÿ’– girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms