field description, home directory, and login shell:</para>
<programlisting>#Type Name ID GECOS Home directory Shell
-u httpd 404 "HTTP User"
-u _authd /usr/bin/authd "Authorization user"
-u postgres - "Postgresql Database" /var/lib/pgsql /usr/libexec/postgresdb
+u! httpd 404 "HTTP User"
+u! _authd /usr/bin/authd "Authorization user"
+u! postgres - "Postgresql Database" /var/lib/pgsql /usr/libexec/postgresdb
g input - -
m _authd input
u root 0 "Superuser" /root /bin/zsh
bearing the same name unless the ID field specifies it. The account will be
created disabled, so that logins are not allowed.</para>
+ <para>Type <varname>u</varname> may be suffixed with an exclamation mark (<literal>u!</literal>) to
+ create a fully locked account. This is recommended, since logins should typically not be allowed
+ for system users. With or without the exclamation mark an invalid password is set. For
+ <literal>u!</literal>, the account is also locked, which makes a difference for non-password forms
+ of authentication, such as SSH or similar.</para>
+
<xi:include href="version-info.xml" xpointer="v215"/></listitem>
</varlistentry>
bool uid_set;
+ bool locked;
+
bool todo_user;
bool todo_group;
} Item;
.sp_max = -1,
.sp_warn = -1,
.sp_inact = -1,
- .sp_expire = -1,
+ .sp_expire = i->locked ? 1 : -1, /* Negative expiration means "unset". Expiration 0 or 1 means "locked" */
.sp_flag = ULONG_MAX, /* this appears to be what everybody does ... */
};
return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
"Trailing garbage.");
- /* Verify action */
- if (strlen(action) != 1)
- return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
- "Unknown modifier '%s'.", action);
+ if (isempty(action))
+ return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG),
+ "Empty command specification.");
+
+ bool locked = false;
+ for (int pos = 1; action[pos]; pos++)
+ if (action[pos] == '!' && !locked)
+ locked = true;
+ else
+ return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG),
+ "Unknown modifiers in command '%s'.", action);
if (!IN_SET(action[0], ADD_USER, ADD_GROUP, ADD_MEMBER, ADD_RANGE))
return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG),
switch (action[0]) {
case ADD_RANGE:
+ if (locked)
+ return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
+ "Flag '!' not permitted on lines of type 'r'.");
+
if (resolved_name)
return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
"Lines of type 'r' don't take a name field.");
return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
"Lines of type 'm' require a user name in the second field.");
+ if (locked)
+ return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
+ "Flag '!' not permitted on lines of type 'm'.");
+
if (!resolved_id)
return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
"Lines of type 'm' require a group name in the third field.");
i->description = TAKE_PTR(resolved_description);
i->home = TAKE_PTR(resolved_home);
i->shell = TAKE_PTR(resolved_shell);
+ i->locked = locked;
h = c->users;
break;
return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
"Lines of type 'g' require a user name in the second field.");
+ if (locked)
+ return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
+ "Flag '!' not permitted on lines of type 'g'.");
+
if (description || home || shell)
return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
"Lines of type '%c' don't take a %s field.",