Published on (
 See this if you're having trouble printing code examples

Big Scary Daemons

Controlling User Logins


Some servers have hundreds of users, each with different purposes. If you want to assign different privileges to each, how do you do it? FreeBSD includes a few different ways to control user access. The /sbin/nologin program is useful for limited accounts. More general control is handled by /etc/login.access.

One of the most common cases you'll find is when an administrator wishes to allow a single user FTP access, but not shell access. If this is an isolated case, it's most simply accomplished with /sbin/nologin. All you have to do is add /sbin/nologin to /etc/shells, so ftpd will accept the connections on that user ID.

When a user has a shell of /sbin/nologin, their telnet and SSH requests are met with:

login: loserusername
Last login: Tue Mar 27 20:33:59 from localhost
  The Regents of the University of California. All rights reserved.

FreeBSD 5.0-CURRENT (TURTLEDAWN) #3: Wed Mar 28 14:24:46 EST 2001

This account is currently not available.
Connection closed by foreign host.

The problem with this approach is that the system processes the connection and builds an environment for the user. Setting the shell is done after expending a fair amount of processor time. When the system finally realizes that the user doesn't get a real shell, it's already done a lot of work.

If you have copious CPU time, /sbin/nologin is perfectly fine. It's an accepted standard across much of the Unix world. There's even an advanced version in /usr/ports/sysutils/no-login that logs rejected connections.

/sbin/nologin can be cumbersome in large installations, however. Mis-setting a user's shell takes either one typo, one inexperienced administrator, or one flat-out mistake. FreeBSD provides an easier way in its /etc/login.access file. Every time you try to open a connection to a FreeBSD system, login.access is checked. Properly configured, login.access provides all the functionality you need in a more simple manner.

Also in Big Scary Daemons:

Running Commercial Linux Software on FreeBSD

Building Detailed Network Reports with Netflow

Visualizing Network Traffic with Netflow and FlowScan

Monitoring Network Traffic with Netflow

Information Security with Colin Percival

Take a look at /etc/login.access. There's three colon-delimited fields. The first either grants (+) or denies (-) the right to log on. The second is a list of users or groups. The third is a list of connection sources. The file permits an "all" and "all except" syntax, allowing the administrator to make basic but expressive rules.

When the system finds the first rule where both the group and the connection source match, it immediately accepts or rejects the connection. This makes rule order very important.

For example, to only allow members of the "wheel" group and root to log on to the physical console, you could use:

+:wheel root:console

The interesting thing with this rule is that other rules will continue to be processed. It doesn't say reject others, it only says to accept these. After all, Joe Average user trying to log in on the console doesn't match this rule. You'll reject connections more quickly, and run less risk of administrator error, if you use the inverse.

-:ALL EXCEPT wheel root:console

Joe Average matches this rule. He's immediately rejected. There's no chance that a thoughtless later rule will match, permitting unintended access. It's best to build your lists based on rejected accounts, rather than permitted ones.

The greatest variety is in the last field, the connection source. You can use several different types of information here: host names, host addresses, network numbers, domain names, LOCAL, and ALL.

First, ALL always matches. This is particularly useful in combination with EXCEPT, as we'll see below.

Host names rely upon DNS and/or the hosts file. If you suspect your nameserver might suffer a hack at some time, you probably don't want to use this. Still, you could do:


Your wheel group could log in from the file server, but nobody else could.

Host addresses are similar, except they're immune to spoofed DNS.

-:ALL EXCEPT wheel:

A network number is anything that ends in a period, such as:

-:ALL EXCEPT wheel:169.254.8.

If you didn't want anyone able to access your firewall unless they were logging in from a management workstation, you could do something like this.


The most complicated location is LOCAL. This matches any host name without a dot in it. This generally means only hosts in the local domain. For example, thinks that any host in "" matches LOCAL. This works via reverse DNS. Although my laptop might claim a host name of "", its IP address reverses to something in the network. I can't use the LOCAL verification method.

So, how can we tie all this together? A one-line login.access will allow administrators to log into the server while rejecting all other remote connections.


This might be too restrictive for your environment; many companies have staff groups. I've previously set up environments with the groups "dns" (people who can edit domain zone files) and "www" (people who can edit web server configurations). Our login.access looked like this.

-:ALL EXCEPT wheel dns www:ALL

My systems all have login.access files that look like this:

-:ALL EXCEPT wheel:console
-:ALL EXCEPT wheel dns www:ALL

One line, changed one time, and your users can't log in unless you consciously add them to a permitted group. Setting the shell to /sbin/nologin doesn't hurt, but it's no longer necessary. FreeBSD makes it easy to touch one file instead of a thousand user accounts.

Michael W. Lucas

Read more Big Scary Daemons columns.

Return to the BSD DevCenter.

Copyright © 2009 O'Reilly Media, Inc.