diff --git a/_tests/pillar/core/test_users.py b/_tests/pillar/core/test_users.py
index ffe6c25..883e5f3 100644
--- a/_tests/pillar/core/test_users.py
+++ b/_tests/pillar/core/test_users.py
@@ -1,37 +1,38 @@
 import unittest
 import yaml
 
 
 PILLAR_FILE = '../pillar/core/users.sls'
 
 USER_PROPERTIES_MANDATORY = set(["fullname", "ssh_keys", "uid"])
 USER_PROPERTIES_OPTIONAL = set([
+    "class",
     "shell",
     "yubico_keys",
     "deploy_dotfiles_to_devserver"
 ])
 
 
 class Testinstance(unittest.TestCase):
 
     def setUp(self):
         with open(PILLAR_FILE, 'r') as fd:
             self.pillar = yaml.safe_load(fd)
 
     # users must have an username, an UID and SSH keys
     def test_users_properties(self):
         is_valid = True
         errors = []
 
         for user, properties in self.pillar["shellusers"].items():
             missing_properties = USER_PROPERTIES_MANDATORY - set(properties)
             if missing_properties:
                 errors.append(f"  Missing properties for {user}: {missing_properties}")
                 is_valid = False
 
             invalid_properties = set(properties) - USER_PROPERTIES_MANDATORY - USER_PROPERTIES_OPTIONAL
             if invalid_properties:
                 errors.append(f"  Invalid properties for {user}: {invalid_properties}")
                 is_valid = False
 
         self.assertTrue(is_valid, "\n" + "\n".join(errors))
diff --git a/roles/core/init.sls b/roles/core/init.sls
index ec5b0d6..94b8871 100644
--- a/roles/core/init.sls
+++ b/roles/core/init.sls
@@ -1,20 +1,21 @@
 #   -------------------------------------------------------------
 #   Salt — Core units
 #   - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 #   Project:        Nasqueron
 #   License:        Trivial work, not eligible to copyright
 #   -------------------------------------------------------------
 
 include:
   - .rc
   - .hostname
+  - .login
   - .network
   - .motd
   - .rsyslog
   - .salt
   - .sshd
   - .sudo
   - .sysctl
   - .timezone
   - .userland-software
   - .users
diff --git a/roles/core/login/files/login.conf b/roles/core/login/files/login.conf
new file mode 100644
index 0000000..8bf30c8
--- /dev/null
+++ b/roles/core/login/files/login.conf
@@ -0,0 +1,97 @@
+#   -------------------------------------------------------------
+#   Login class capabilities database
+#   - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+#   Project:        Nasqueron
+#   Created:        2020-01-20
+#   License:        Trivial work, not eligible to copyright
+#   Based on:       FreeBSD releng/12.1/usr.bin/login/login.conf
+#   VCS info:       338399 2018-08-30 15:52:03Z brd
+#   Source file:    roles/core/login/files/login.conf
+#   -------------------------------------------------------------
+#
+#   <auto-generated>
+#       This file is managed by our rOPS SaltStack repository.
+#
+#       Changes to this file may cause incorrect behavior
+#       and will be lost if the state is redeployed.
+#   </auto-generated>
+
+#   -------------------------------------------------------------
+#   Default settings
+#   - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+default:\
+	:passwd_format=sha512:\
+	:copyright=/etc/COPYRIGHT:\
+	:welcome=/etc/motd:\
+	:setenv=MAIL=/var/mail/$,BLOCKSIZE=K:\
+	: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:
+
+#   -------------------------------------------------------------
+#   Common class names to forward to 'default'
+#   - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+standard:\
+	:tc=default:
+xuser:\
+	:tc=default:
+staff:\
+	:tc=default:
+daemon:\
+	:memorylocked=128M:\
+	:tc=default:
+news:\
+	:tc=default:
+dialer:\
+	:tc=default:
+
+#   -------------------------------------------------------------
+#   Root class
+#
+#   Root can always login.
+#   - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+root:\
+	:ignorenologin:\
+	:memorylocked=unlimited:\
+	:tc=default:
+
+#   -------------------------------------------------------------
+#   Users classes
+#
+#   Provide proper UTF-8 environment
+#   - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+english|English Users Accounts:\
+	:charset=UTF-8:\
+	:lang=en_US.UTF-8:\
+	:tc=default:
+
+french|French Users Accounts:\
+	:charset=UTF-8:\
+	:lang=fr_FR.UTF-8:\
+	:tc=default:
+
+russian|Russian Users Accounts:\
+	:charset=UTF-8:\
+	:lang=ru_RU.UTF-8:\
+	:tc=default:
diff --git a/roles/core/login/init.sls b/roles/core/login/init.sls
new file mode 100644
index 0000000..af874fb
--- /dev/null
+++ b/roles/core/login/init.sls
@@ -0,0 +1,22 @@
+#   -------------------------------------------------------------
+#   Set login capabilities
+#   - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+#   Project:        Nasqueron
+#   Created:        2020-01-20
+#   License:        Trivial work, not eligible to copyright
+#   -------------------------------------------------------------
+
+{% if grains['os'] == 'FreeBSD' %}
+
+/etc/login.conf:
+  file.managed:
+    - source: salt://roles/core/login/files/login.conf
+    - mode: 644
+
+compile_login_db:
+  cmd.run:
+    - name: cap_mkdb /etc/login.conf
+    - onchanges:
+      - file: /etc/login.conf
+
+{% endif %}
diff --git a/roles/core/users/init.sls b/roles/core/users/init.sls
index 12a828d..28ab5e1 100644
--- a/roles/core/users/init.sls
+++ b/roles/core/users/init.sls
@@ -1,126 +1,127 @@
 #   -------------------------------------------------------------
 #   Salt — Provision users accounts
 #   - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 #   Project:        Nasqueron
 #   Created:        2017-11-09
 #   Description:    Adds and revokes user accounts, in the relevant
 #                   groups and with their stable SSH keys.
 #   License:        Trivial work, not eligible to copyright
 #   -------------------------------------------------------------
 
 #   -------------------------------------------------------------
 #   Table of contents
 #   -------------------------------------------------------------
 #
 #   :: Disabled accounts
 #   :: ZFS (before user account creation)
 #   :: Active accounts
 #   :: ZFS (after user account creation)
 #   :: Groups
 #   :: SSH keys
 #
 #   -------------------------------------------------------------
 
 {% from "map.jinja" import dirs, shells with context %}
 
 {% set users = salt['forest.get_users']() %}
 {% set zfs_tank = salt['node.get']("zfs:pool") %}
 
 #   -------------------------------------------------------------
 #   Disabled accounts
 #   -------------------------------------------------------------
 
 {% for username in pillar.get('revokedusers') %}
 {{ username }}:
   user.absent
 {% endfor %}
 
 #   -------------------------------------------------------------
 #   ZFS datasets
 #
 #   Where ZFS is available, home directories are created as separate
 #   datasets. That has several benefits, like allowing users to create
 #   snapshots or manage backups.
 #   -------------------------------------------------------------
 
 {% if zfs_tank %}
 zfs_home_permissions_sets:
   cmd.run:
     - name: |
         zfs allow -s @local allow,clone,create,diff,hold,mount,promote,receive,release,rollback,snapshot,send {{ zfs_tank }}{{ dirs.home }}
         zfs allow -s @descendent allow,clone,create,diff,destroy,hold,mount,promote,receive,release,rename,rollback,snapshot,send {{ zfs_tank }}{{ dirs.home }}
         touch {{ dirs.home }}/.zfs-permissions-set
     - creates: {{ dirs.home }}/.zfs-permissions-set
 
 {% for username in users %}
 {% set home_directory = zfs_tank + dirs['home'] + '/' + username %}
 
 {{ home_directory }}:
   zfs.filesystem_present
 
 zfs_permissions_home_local_{{ username }}:
   cmd.run:
     - name: zfs allow -lu {{ username }} @local {{ home_directory }}
     - require:
         - user: {{ username }}
     - onchanges:
         - zfs: {{ home_directory }}
 
 zfs_permissions_home_descendant_{{ username }}:
   cmd.run:
     - name: zfs allow -du {{ username }} @descendent {{ home_directory }}
     - require:
         - user: {{ username }}
     - onchanges:
         - zfs: {{ home_directory }}
 
 {% endfor %}
 {% endif %}
 
 #   -------------------------------------------------------------
 #   Active accounts
 #   -------------------------------------------------------------
 
 {% for username, user in users.items() %}
 {{ username }}:
   user.present:
     - fullname: {{ user['fullname'] }}
     - shell: {{ shells[user['shell']|default('bash')] }}
     - uid: {{ user['uid'] }}
+    - loginclass: {{ user['class']|default('english') }}
 {% endfor %}
 
 #   -------------------------------------------------------------
 #   Groups
 #   -------------------------------------------------------------
 
 {% for groupname, group in salt['forest.get_groups']().items() %}
 group_{{ groupname }}:
   group.present:
     - name: {{ groupname }}
     - gid: {{ group['gid'] }}
     - members: {{ group['members'] }}
 {% endfor %}
 
 #   -------------------------------------------------------------
 #   SSH keys
 #   -------------------------------------------------------------
 
 {% for username, user in users.items() %}
 
 /home/{{ username }}/.ssh:
   file.directory:
     - user: {{ username }}
     - group: {{ username }}
     - dir_mode: 700
 
 /home/{{ username}}/.ssh/authorized_keys:
   file.managed:
     - source: salt://roles/core/users/files/authorized_keys
     - user: {{ username }}
     - group: {{ username }}
     - mode: 600
     - template: jinja
     - context:
         keys: {{ user['ssh_keys']|default([]) }}
 
 {% endfor %}