I'm writing some web UI pages that can be used to create Linux user accounts. This web UI will be used on CentOS 6 (which is derived from RHEL 6). I'm finding inconsistent and incomplete information about what constitutes a valid Linux user name. I went to the source, examining a Linux shadow-utils source package but I did not ensure that the version I was looking at is in fact the same as that which is part of CentOS 6.
Below is the code fragment I currently use, which includes copy/paste of the comments from the shadow-utils package version 4.1.4.3, plus some of my own notes, and a Java regular expression search to follow my understanding from looking at shadow-utils source.
The referenced "is_valid_name()" check in chkname.c is apparently not what is used from the useradd command on Linux, since the comments (and the C-code source) do not allow names beginning with a number. However, useradd does allow one to create an account like "1234".
I'd appreciate assistance adjusting from what I have now to what would be more correct, as well as info about how useradd.c is implemented with some slightly different is_valid_name function.
Thanks! Alan
/**
* Define constants for use in isNameLinuxCompatible(...) method.
*
* The source for the Linux compatible user name rule is is_valid_name(...) a function in the "shadow" package
* for Linux. The source file for that function has a comment as follows:
* User/group names must match [a-z_][a-z0-9_-]*[$]
* That expression is a little loose/sloppy since
* (1) the trailing $ sign is optional, and
* (2) uppercase A-Z is also ok (and case is significant, 'A' != 'a').
*
* We deal with (1) by using the [$]? form where the ? means zero or more characters (aka "greedy").
* We deal with (2) by using the CASE_INSENSITIVE option.
*
* Another way to express this is:
* 1st character: a-z_ required at least one char
* chars other than first and last: a-z0-9_- optional
* last character: $ optional
* Max length is 31. Min length is 1.
*
* NOTE: The initial ^ and final $ below are important since we need the entire string to satisfy the rule,
* from beginning to end.
*
* See http://download.oracle.com/javase/6/docs/api/java/util/regex/Pattern.html for reference info on pattern matching.
*/
private static final String LINUX_USERNAME_REGEX = "^[a-z_][a-z0-9_-]*[$]?$";
private static final Pattern LINUX_USERNAME_PATTERN = Pattern.compile(LINUX_USERNAME_REGEX, Pattern.CASE_INSENSITIVE);
private static final int LINUX_USERNAME_MINLENGTH = 1;
private static final int LINUX_USERNAME_MAXLENGTH = 31;
/**
* See if username is compatible with standard Linux rules for usernames, in terms of length and
* in terms of content.
*
* @param username the name to be checked for validity
* @return true if Linux compatible, else false
*/
public boolean isNameLinuxCompatible (final String username) {
boolean nameOK = false;
if (username != null) {
int len = username.length();
if ((len >= LINUX_USERNAME_MINLENGTH) && (len <= LINUX_USERNAME_MAXLENGTH)) {
Matcher m = LINUX_USERNAME_PATTERN.matcher(username);
nameOK = m.find();
}
}
return (nameOK);
}
A basic gnu/linux username is a 32 character string (useradd(8)
). This is a legacy format from the BSD 4.3 standard. passwd(5)
adds some additional restrictions like, do not use capital letters, do not use dots, do not end it in dash, it must not include colons.
To be on the safe side of things, follow the same rules of a C identifier:
([a-z_][a-z0-9_]{0,30})
That's half the problem. Modern GNU/Linux distributions use PAM for user authentication. With it you can choose any rule you want and also any data source.
Since you are writing a program it's better to define your own format, and then use something like pam_ldap
, pam_mysql
, etc. to access it.