In the last post, we’ve initially configured FreeBSD so that networking is up and running, additional packages can be installed, and the system was patched to the latest release. In this post we’ll look at how users and groups are managed in FreeBSD and what FreeBSD provides when it comes to additional security mechanisms.

On Linux systems users are usually created with useradd, and groups are created with groupadd. There is also usermod and groupmod, which are used to modify users and groups. With FreeBSD you can do all those tasks with pw, no matter if you want to manage users or groups:

root@freebsd14:~ $ pw
usage:
  pw [user|group|lock|unlock] [add|del|mod|show|next] [help|switches/values]

By giving the “help” switch to a sub command you may easily check the syntax (or read the man page, of course):

root@freebsd14:~ $ pw group help
usage:
  pw group [add|del|mod|show|next] [help|switches/values]
root@freebsd14:~ $ pw group add help
usage: pw groupadd [group|gid] [switches]
        -V etcdir      alternate /etc location
        -R rootdir     alternate root directory
        -C config      configuration file
        -q             quiet operation
        -n group       group name
        -g gid         group id
        -M usr1,usr2   add users as group members
        -o             duplicate gid ok
        -Y             update NIS maps
        -N             no update

A typical group would be created like this:

root@freebsd14:~ $ pw group add -n group1 -g 2001 

A new user with that group as the primary group would be:

root@freebsd14:~ $ pw user add -n user1 -u 2001 -c "a sample user" -d /home/user1 -g group1 -m -s /usr/local/bin/bash
root@freebsd14:~ $ id -a user1
uid=2001(user1) gid=2001(group1) groups=2001(group1)

Another option you have is adduser. This is basically a wrapper around “pw” and gives you an interactive way of creating users:

root@freebsd14:~ $ adduser 
Username: user2
Full name: second sample user
Uid (Leave empty for default): 2002
Login group [user2]: group2
Group group2 does not exist!
Login group [user2]: 
Login group is user2. Invite user2 into other groups? []: group1
Login class [default]: 
Shell (sh csh tcsh bash rbash nologin) [sh]: bash
Home directory [/home/user2]: 
Home directory permissions (Leave empty for default): 
Enable ZFS encryption? (yes/no) [no]: 
Use password-based authentication? [yes]: 
Use an empty password? (yes/no) [no]: 
Use a random password? (yes/no) [no]: 
Enter password: 
Enter password again: 
Lock out the account after creation? [no]: 
Username    : user2
Password    : *****
Full Name   : second sample user
Uid         : 2002
ZFS dataset : zroot/home/user2
Class       : 
Groups      : user2 group1
Home        : /home/user2
Home Mode   : 
Shell       : /usr/local/bin/bash
Locked      : no
OK? (yes/no) [yes]: yes
adduser: INFO: Successfully created ZFS dataset (zroot/home/user2).
adduser: INFO: Successfully added (user2) to the user database.
Add another user? (yes/no) [no]: no
Goodbye!
root@freebsd14:~ $ id -a user2
uid=2002(user2) gid=2002(user2) groups=2002(user2),2001(group1)

adduser also created a new ZFS file system for the new user (this did not happen with pw, and this is only done when the parent of the home directory is also ZFS):

root@freebsd14:~ $ df -h | grep user
zroot/home/user2       24G    128K     24G     0%    /home/user2

All those defaults can be controlled, as you can ask adduser to create a template:

root@freebsd14:~ $ adduser -C
Uid (Leave empty for default): 2003
Login group []: group1
Enter additional groups []: user2
Login class [default]: 
Shell (sh csh tcsh bash rbash nologin) [sh]: bash
Home directory [/home/]: 
Home directory permissions (Leave empty for default): 
Enable ZFS encryption? (yes/no) [no]: 
Use password-based authentication? [yes]: 
Use an empty password? (yes/no) [no]: 
Use a random password? (yes/no) [no]: 
Lock out the account after creation? [no]: 
Pass Type   : yes
Class       : 
Groups      : group1 user2
Home        : /home/
Home Mode   : 
Shell       : /usr/local/bin/bash
Locked      : no
OK? (yes/no) [yes]: yes
Re-edit the default configuration? (yes/no) [no]: 
Goodbye!

This created “/etc/adduser.conf”, which will be the template for new users created with adduser:

root@freebsd14:~ $ cat /etc/adduser.conf 
# Configuration file for adduser(8).
# NOTE: only *some* variables are saved.
# Last Modified on Tue Nov 26 16:04:34 CET 2024.

defaultHomePerm=
defaultLgroup=group1
defaultclass=
defaultgroups=user2
passwdtype=yes
homeprefix=/home
defaultshell=/usr/local/bin/bash
udotdir=/usr/share/skel
msgfile=/etc/adduser.msg
disableflag=
uidstart=2003

So far there is nothing special, the commands are not the same as on Linux, but the concepts are very similar. What you might have noticed is, that there is something which is called a “login class”. Login classes are used to setup users environments and optionally put restrictions on resource usage. Those classes are defined in “/etc/login.conf” and the default class looks like this:

default:\
        :passwd_format=sha512:\
        :copyright=/etc/COPYRIGHT:\
        :welcome=/var/run/motd:\
        :setenv=BLOCKSIZE=K:\
        :mail=/var/mail/$:\
        :path=/sbin /bin /usr/sbin /usr/bin /usr/local/sbin /usr/local/bin ~/bin:\
        :nologin=/var/run/nologin:\
        :cputime=unlimited:\
        :datasize=unlimited:\
        :stacksize=unlimited:\
        :memorylocked=64K:\
        :memoryuse=unlimited:\
        :filesize=unlimited:\
        :coredumpsize=unlimited:\
        :openfiles=unlimited:\
        :maxproc=unlimited:\
        :sbsize=unlimited:\
        :vmemoryuse=unlimited:\
        :swapuse=unlimited:\
        :pseudoterminals=unlimited:\
        :kqueues=unlimited:\
        :umtxp=unlimited:\
        :priority=0:\
        :ignoretime@:\
        :umask=022:\
        :charset=UTF-8:\
        :lang=C.UTF-8:

There are many more examples in that file, take your time and have a look at them to get an idea. If you change something in that file, you’ll need to re.generate the database with “cap_mkdb“. There is the same concept for the password files. If you check “/etc/master.passwd” you see the hashed passwords (in Linux it is /etc/shadow), but if you check “/etc/passwd” there are no hashes anymore:

root@freebsd14:~ $ egrep "^root|^user1|^user2" /etc/master.passwd
root:$6$rwisdlYvWBHh7XE/$UjQ7zuTqdDXKSjxCjNph6KBWAn.S5lfwal4FsZGWBJsKDsvbfWSJ3asp7BOa9o09iRVNWrpgXKqxh2J9RnUZs/:0:0::0:0:Charlie &:/root:/bin/sh
user1:*:2001:2001::0:0:a sample user:/home/user1:/usr/local/bin/bash
user2:$6$HdjGHoWTrlJChtkn$dLkcSNPn8.O98/rjm91GhGM7lxHb1rrumK0.SSXtO./5jr0LqddyG7Es8ijqVuge9cDdmwz0BF3q5uGq7ERDn/:2002:2002::0:0:second sample user:/home/user2:/usr/local/bin/bash

root@freebsd14:~ $ egrep "^root|^user1|^user2" /etc/passwd 
root:*:0:0:Charlie &:/root:/bin/sh
user1:*:2001:2001:a sample user:/home/user1:/usr/local/bin/bash
user2:*:2002:2002:second sample user:/home/user2:/usr/local/bin/bash

The reason is that normal users are not allowed to read “/etc/master.passwd” but still can get basic account information out of “/etc/passwd”. So there needs to be a way to generate the second one out of the first one, and this is done with “pwd_mkdb“. In the same way as “cap_mkdb” does it for the login classes, this one does it for the password databases:

root@freebsd14:~ $ ls -l /etc/*pwd*.db
-rw-r--r--  1 root wheel 40960 Nov 26 15:58 /etc/pwd.db
-rw-------  1 root wheel 40960 Nov 26 15:58 /etc/spwd.db

The result are binaries and faster to process than the text representation of the base files. In addition the “/etc/passwd” file is created out of the master file without the sensitive information.

Let’s do a simple test show how this works. The password database files currently have these timestamps:

root@freebsd14:~ $ ls -la /etc/*pwd*.db
-rw-r--r--  1 root wheel 40960 Nov 27 14:06 /etc/pwd.db
-rw-------  1 root wheel 40960 Nov 27 14:06 /etc/spwd.db
root@freebsd14:~ $ ls -la /etc/*passwd*
-rw-------  1 root wheel 2124 Nov 27 14:01 /etc/master.passwd
-rw-r--r--  1 root wheel 1781 Nov 26 15:58 /etc/passwd

Changing the shell of “user2” using “pw” will update all the files at once:

root@freebsd14:~ $ grep user2 /etc/passwd 
user2:*:2002:2002:second sample user:/home/user2:/usr/local/bin/bash
root@freebsd14:~ $ pw user mod user2 -s /bin/sh
root@freebsd14:~ $ grep user2 /etc/master.passwd 
user2:$6$HdjGHoWTrlJChtkn$dLkcSNPn8.O98/rjm91GhGM7lxHb1rrumK0.SSXtO./5jr0LqddyG7Es8ijqVuge9cDdmwz0BF3q5uGq7ERDn/:2002:2002::0:0:second sample user:/home/user2:/bin/sh
root@freebsd14:~ $ ls -la /etc/*passwd*
-rw-------  1 root wheel 2112 Nov 27 14:16 /etc/master.passwd
-rw-r--r--  1 root wheel 1757 Nov 27 14:16 /etc/passwd
root@freebsd14:~ $ ls -la /etc/*pwd*.db
-rw-r--r--  1 root wheel 40960 Nov 27 14:16 /etc/pwd.db
-rw-------  1 root wheel 40960 Nov 27 14:16 /etc/spwd.db

On the other hand, if we change the shell of “user2” back to bash manually in “/etc/master.passwd”:

# open, change manually back to /usr/local/bin/bash and save
root@freebsd14:~ $ grep user2 /etc/master.passwd 
user2:$6$HdjGHoWTrlJChtkn$dLkcSNPn8.O98/rjm91GhGM7lxHb1rrumK0.SSXtO./5jr0LqddyG7Es8ijqVuge9cDdmwz0BF3q5uGq7ERDn/:2002:2002::0:0:second sample user:/home/user2:/usr/local/bin/bash

… the other files have not been touched and still show the old timestamp:

root@freebsd14:~ $ ls -l /etc/*pwd*
-rw-r--r--  1 root wheel 40960 Nov 27 14:16 /etc/pwd.db
-rw-------  1 root wheel 40960 Nov 27 14:16 /etc/spwd.db
root@freebsd14:~ $ ls -l /etc/*passwd*
-rw-------  1 root wheel 2124 Nov 27 14:47 /etc/master.passwd
-rw-r--r--  1 root wheel 1757 Nov 27 14:16 /etc/passwd

In this case we need to run “pwd_mkdb” manually to get this done:

root@freebsd14:~ $ pwd_mkdb -p /etc/master.passwd
root@freebsd14:~ $ ls -l /etc/*pwd*
-rw-r--r--  1 root wheel 40960 Nov 27 14:52 /etc/pwd.db
-rw-------  1 root wheel 40960 Nov 27 14:52 /etc/spwd.db
root@freebsd14:~ $ ls -l /etc/*passwd*
-rw-------  1 root wheel 2124 Nov 27 14:47 /etc/master.passwd
-rw-r--r--  1 root wheel 1769 Nov 27 14:52 /etc/passwd
root@freebsd14:~ $ grep user2 /etc/passwd 
user2:*:2002:2002:second sample user:/home/user2:/usr/local/bin/bash

At least from my point of view it is not advisable to do it like this. If you want to edit users manually, instead of manually editing the master file you should use “vipw“. This at least does some sanity checks for you and is automatically rebuilding the password databases. Otherwise us “pw” or “chpass“.

The final topic for this post is something that very much reminds of PostgreSQL’s pg_hba.conf, which is called “login access control table” in FreeBSD. As in PostgreSQL, you can define who, from where, networked or not, is either accepted to login or not. Have a look at “/etc/login.access” to get an idea how that looks like. Here are some examples from that file:

# Disallow console logins to all but a few accounts.
#
#-:ALL EXCEPT wheel shutdown sync:console
#
# Disallow non-local logins to privileged accounts (group wheel).
#
#-:wheel:ALL EXCEPT LOCAL .win.tue.nl
#
# Some accounts are not allowed to login from anywhere:
#
#-:wsbscaro wsbsecr wsbspac wsbsym wscosor wstaiwde:ALL
#
# All other accounts are allowed to login from anywhere.

That’s it for today. In the next post we’ll look at services are managed in FreeBSD.