Two of the attributes we need to parse are the uid and the gid. Note that we could be strict here and just accept numeric identifiers, but we want to allow names as well. And if we get one, we might have to look up the other. A resulting data structure is:
typedef struct {
int i;
char *sz;
} dval_t;
Note that we could try and use uid_t and gid_t here, but it will make it harder to factor the code later. A first pass at the uid case handling is:
case 'u' :
/*
* Note, we do not allow negative UIDs.
* And we do not allow usernames to start with a '-'.
*/
if (optarg[0] == '-') {
fprintf(stderr, "The uid must not start"
" with a '-' : %s\n", optarg);
goto usage;
}
if (isdigit(optarg[0])) {
uid.i = (int)strtol(optarg, &szJunk, 0);
/*
* Assume that it is a name.
* Could warn first char should be
* alphabetic.
*/
if (szJunk[0] != '\0') {
uid.sz = strdup(optarg);
uid.i = INVALID_ID;
}
} else {
uid.sz = strdup(optarg);
uid.i = INVALID_ID;
}
if (uid.sz) {
unsigned char bLower = 0;
if (strlen(uid.sz) > SOLARIS_NAME_LEN) {
fprintf(stderr, "The uid name %s must"
" be less than %d"
" characters.\n", uid.sz,
SOLARIS_NAME_LEN);
goto usage;
}
for (i = 0; i < SOLARIS_NAME_LEN; i++) {
if (uid.sz[i] == '\0') {
break;
} else if (!isalpha(uid.sz[i]) &&
!isdigit(uid.sz[i]) &&
(uid.sz[i] != '.') &&
(uid.sz[i] != '_') &&
(uid.sz[i] != '-')) {
fprintf(stderr, "%c is not"
" a valid name"
" character for %s\n",
uid.sz[i], uid.sz);
goto usage;
}
if (islower(uid.sz[i])) {
bLower = 1;
}
}
if (bLower == 0) {
fprintf(stderr, "%s must contain at"
" least 1 lower case"
" alphabetic character\n",
uid.sz);
goto usage;
}
}
So a username in OpenSolaris can be only 8 characters long. This should catch that and some other checks. How does it do?
stealth:spe tdh$ ./a.out -u tdh It was tdh, stealth:spe tdh$ ./a.out -u tdh10 It was tdh10, stealth:spe tdh$ ./a.out -u 10tdh It was 10tdh, stealth:spe tdh$ ./a.out -u TDH TDH must contain at least 1 lower case alphabetic character speadm explain -r rules-file [-v] [-p proposed-filename] [-u uid] [-g gid] [-i ip] [-h hour] [-d day] stealth:spe tdh$ ./a.out -u tdh@me @ is not a valid name character for tdh@me speadm explain -r rules-file [-v] [-p proposed-filename] [-u uid] [-g gid] [-i ip] [-h hour] [-d day] stealth:spe tdh$ ./a.out -u tdh:me : is not a valid name character for tdh:me speadm explain -r rules-file [-v] [-p proposed-filename] [-u uid] [-g gid] [-i ip] [-h hour] [-d day] stealth:spe tdh$ ./a.out -u t12345678 The uid name t12345678 must be less than 8 characters. speadm explain -r rules-file [-v] [-p proposed-filename] [-u uid] [-g gid] [-i ip] [-h hour] [-d day] stealth:spe tdh$ ./a.out -u 502 It was 502,
Looks good. Note that this may end up being wrong. It is not uncommon to see names like 'tdh@domain.null' in raw packet traces. Meh, we'll take care of it when we need to...
Now, we need to do the same thing for the gid. We could just cut and paste the code, pretty safe. But what if we need to do that change I just mentioned? And a lot of code in the switch makes it hard to read. Well, we just need to factor out the common code. But in looking at what a valid group name is, I've come to the conclusion that I am being too strict with my checks.
Both Linux and OS X clearly allow more than 8 characters. Linux does not want an uppercase letter present and OS X seems to hide accounts with a '_' prefix. While I am focused on OpenSolaris, the reality is that the server has to be able to handle names presented to it from any client. So, time to rip the code apart to just detect a numeric versus a name.
int
parse_dval_identifier(dval_t *dval, char *szIn, char *szID)
{
char *szJunk;
/*
* Note, we do not allow negative IDs..
* And we do not allow usernames to start with a '-'.
*/
if (szIn[0] == '-') {
fprintf(stderr, "The %s must not start"
" with a '-' : %s\n", szID, szIn);
goto error;
}
if (isdigit(szIn[0])) {
dval->i = (int)strtol(szIn, &szJunk, 0);
/*
* Assume that it is a name.
*/
if (szJunk[0] != '\0') {
dval->sz = strdup(szIn);
dval->i = INVALID_ID;
}
} else {
dval->sz = strdup(szIn);
dval->i = INVALID_ID;
}
return (0);
error:
return (EINVAL);
}
Which is called in either:
case 'g' :
rc = parse_dval_identifier(&gid, optarg, "gid");
if (rc) {
goto usage;
}
break;
case 'u' :
rc = parse_dval_identifier(&uid, optarg, "uid");
if (rc) {
goto usage;
}
break;
So I went down a path of compatibility that I should have avoided. Well, this is prototype code, so that is to be expected. I'd rather find it out from the man pages and system documentation than from when an user played with the tool. Note, while I've yet to run, much less compile, the code on another system, by keeping my eyes open, I believe this should just work. Big ;)
I've spent a lot of time coping with an environment that uses 9-digit usernames as the corporate standard. UNIX was out of scope when that project started....
As interoperability with other operating systems becomes increasingly important, allowing for compatibility with other operating systems that have more liberal username standards should be a priority. Consider the wording of POSIX chown(1) as a more reasonable approach:
http://www.opengroup.org/onlinepubs/007908799/xcu/chown.html
"The owner portion of this operand must be a user name from the user database or a numeric user ID. Either specifies a user ID to be given to each file named by one of the file operands. If a numeric owner operand exists in the user database as a user name, the user ID number associated with that user name will be used as the user ID."
Note that it considers the entire string, not just the first character. Your code breaks if the username is 2abc or similar. Such a username is completely valid in other operating systems and with very few exceptions works just fine in Solaris. There is folklore that says using the approach you suggest is OK (the right way?) but adding new code that makes this assumption seems like a bad idea to me. The traditional argument that I have heard is that this is the fast way to do it. The performance of a single test compared to up to 32 tests (long usernames mostly work today too...) on modern hardware is marginal.
Also, because you don't check the return value from strdup(3C), the code will behave improperly and confusingly if strdup(3C) returns NULL.
Posted by Mike Gerdts on December 23, 2007 at 03:15 PM CST #
Mike,
I was trying to get away with overloading the UID and username. Looks like -U and -G need to come into play.
As for the strdup(), think prototype and not production. Not that I won't fix it now.... :->
Thanks,
Tom
Posted by Tom Haynes on January 03, 2008 at 11:52 AM CST #