39

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);
}
Danny Beckett
  • 20,529
  • 24
  • 107
  • 134
Alan Carwile
  • 735
  • 1
  • 7
  • 14
  • 2
    Keep in mind that sysadmins can also define their own rules using pam – Chris Eberle Aug 04 '11 at 23:13
  • I'm familiar with pam, but not with how to use it to define rules for usernames, as mentioned by Chris. I'd like more info on this. In particular, I'd like to be able to examine my system config files on CentOS 6 to find out what it allows and then to test around the edges of what it allows. – Alan Carwile Aug 05 '11 at 16:26
  • Well you'd need to write a PAM plugin that could determine the "requirements", and then put it into the config. I have no idea how common it is, I'm just saying there are other factors than just this one function. – Chris Eberle Aug 05 '11 at 16:30

1 Answers1

41

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.

Chankey Pathak
  • 21,187
  • 12
  • 85
  • 133
Pablo Castellazzi
  • 4,164
  • 23
  • 20
  • 1
    Thanks for the info so far. I have a safe subset currently but would like to expand what I allow to match more completely what my installation/config allows. I AM allowing mixed case, and have not had a problem with that; username "ACarwile" is distinct from "acarwile" for example. And fyi, my program is invoking the utilities useradd, usermod, and userdel to do the real work. – Alan Carwile Aug 05 '11 at 16:30
  • user{add,mod,del} are PAM aware tools, they will allow everything the underlaying pam modules allow. – Pablo Castellazzi Aug 05 '11 at 19:11
  • Appreciate the feedback from Chris and Pablo. In the end we were fine to use a subset of the potential user name space. What mattered was just that the user could create a desired name out of a fairly large space and that the name would be linux compatible and not too restrictive. The rules mentioned earlier are sufficient and we don't need to enumerate all possible usernames. Thanks! Alan – Alan Carwile Jan 13 '12 at 23:32
  • 12
    a gotcha: `([a-z_][a-z0-9_]{0,30})` reads as "a lowercase or underscore, followed by zero to thirty alphanumeric characters", meaning the total length of the username can be **31 characters**. – berkes Aug 29 '13 at 14:49
  • 5
    @berkes plus the terminating zero of a C-string makes 32, no? – Tobias Kienzler Jul 09 '14 at 11:19
  • What with a dot? – Daniel Hornik May 27 '20 at 15:47