Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 90 additions & 2 deletions lib/chkname.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@


/*
* is_valid_user_name(), is_valid_group_name() - check the new user/group
* name for validity;
* check user/group names for valid syntax
* return values:
* true - OK
* false - bad name
Expand All @@ -33,12 +32,19 @@

#include "defines.h"
#include "chkname.h"
#include "sizeof.h"
#include "string/ctype/isascii.h"
#include "string/strcmp/streq.h"
#include "string/strcmp/strcaseeq.h"
#include "string/strspn/strrcspn.h"
#include "string/strtok/stpsep.h"
#include "sysconf.h"


#define DOMAIN_MAXLEN 255
#define LABEL_MAXLEN 63
Comment thread
ikerexxe marked this conversation as resolved.


int allow_bad_names = false;


Expand Down Expand Up @@ -126,3 +132,85 @@ is_valid_group_name(const char *name)

return is_valid_name (name);
}


// Validate a single DNS domain label according to RFC 1035 2.3.1.
static bool
is_valid_domain_label(const char *label)
{
if (strlen(label) > LABEL_MAXLEN) {
errno = EOVERFLOW;
return false;
}
if (!strspn(label, CTYPE_ALPHA_C) || !strrcspn(label, "-")) {
errno = EINVAL;
return false;
}
if (!streq(stpspn(label, CTYPE_ALNUM_C "-"), "")) {
errno = EINVAL;
return false;
}

return true;
}
Comment thread
ikerexxe marked this conversation as resolved.


/*
* Validate a domain name according to RFC 1035 by splitting
* it into labels and validating each label individually.
*/
Comment thread
alejandro-colomar marked this conversation as resolved.
static bool
is_valid_domain_name(const char *domain)
{
Comment thread
ikerexxe marked this conversation as resolved.
char *d;
const char *l;

if (!strcspn(domain, ".")) {
errno = EINVAL;
return false;
}

if (strlen(domain) + !!strrcspn(domain, ".") > DOMAIN_MAXLEN) {
errno = EOVERFLOW;
return false;
}
d = strdupa(domain);

while (NULL != (l = strsep(&d, "."))) {
if (d == NULL && streq(l, "")) // trailing root label
break;
if (!is_valid_domain_label(l))
return false;
}

return true;
}
Comment thread
ikerexxe marked this conversation as resolved.


/*
* is_valid_upn - is valid User Principal Name
*
* Check UPN format (user@domain) for validity.
*
* This function only validates syntax, not whether the UPN exists
* in any authentication system.
*/
bool
is_valid_upn(const char *upn)
{
char *u, *d;

if (strlen(upn) >= LOGIN_NAME_MAX + STRLEN("@") + DOMAIN_MAXLEN) {
errno = EOVERFLOW;
return false;
}
u = strdupa(upn);

d = stpsep(u, "@");
if (d == NULL) {
errno = EINVAL;
return false;
}

return is_valid_user_name(u) && is_valid_domain_name(d);
}
Comment thread
ikerexxe marked this conversation as resolved.
1 change: 1 addition & 0 deletions lib/chkname.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@

extern bool is_valid_user_name (const char *name);
extern bool is_valid_group_name (const char *name);
extern bool is_valid_upn (const char *name);

#endif
2 changes: 1 addition & 1 deletion src/passwd.c
Original file line number Diff line number Diff line change
Expand Up @@ -993,7 +993,7 @@ main(int argc, char **argv)
}
myname = xstrdup (pw->pw_name);
if (optind < argc) {
if (!is_valid_user_name (argv[optind])) {
if (!is_valid_user_name (argv[optind]) && !is_valid_upn (argv[optind])) {
fprintf (stderr, _("%s: Provided user name is not a valid name\n"), Prog);
fail_exit (E_NOPERM, process_selinux);
}
Expand Down
131 changes: 131 additions & 0 deletions tests/unit/test_chkname.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ static void test_is_valid_user_name_nok_empty(MAYBE_UNUSED void ** _1);
static void test_is_valid_user_name_nok_numeric(MAYBE_UNUSED void ** _1);
static void test_is_valid_user_name_nok_otherchars(MAYBE_UNUSED void ** _1);
static void test_is_valid_user_name_long(MAYBE_UNUSED void ** _1);
static void test_is_valid_upn_ok(MAYBE_UNUSED void ** _1);
static void test_is_valid_upn_nok_not_upn(MAYBE_UNUSED void ** _1);
static void test_is_valid_upn_nok_structure(MAYBE_UNUSED void ** _1);
static void test_is_valid_upn_nok_domain(MAYBE_UNUSED void ** _1);
static void test_is_valid_upn_ok_limits(MAYBE_UNUSED void ** _1);
static void test_is_valid_upn_nok_limits(MAYBE_UNUSED void ** _1);


int
Expand All @@ -45,6 +51,12 @@ main(void)
cmocka_unit_test(test_is_valid_user_name_nok_numeric),
cmocka_unit_test(test_is_valid_user_name_nok_otherchars),
cmocka_unit_test(test_is_valid_user_name_long),
cmocka_unit_test(test_is_valid_upn_ok),
cmocka_unit_test(test_is_valid_upn_nok_not_upn),
cmocka_unit_test(test_is_valid_upn_nok_structure),
cmocka_unit_test(test_is_valid_upn_nok_domain),
cmocka_unit_test(test_is_valid_upn_ok_limits),
cmocka_unit_test(test_is_valid_upn_nok_limits),
};

return cmocka_run_group_tests(tests, NULL, NULL);
Expand Down Expand Up @@ -146,3 +158,122 @@ test_is_valid_user_name_long(MAYBE_UNUSED void ** _1)

free(name);
}


static void
test_is_valid_upn_ok(MAYBE_UNUSED void ** _1)
{
assert_true(is_valid_upn("user@example.com"));
assert_true(is_valid_upn("john.doe@corp.example.org"));
assert_true(is_valid_upn("test@sub.domain.net"));
assert_true(is_valid_upn("a@b.c"));
assert_true(is_valid_upn("user123@test123.example"));
assert_true(is_valid_upn("user_name@example-domain.com"));
assert_true(is_valid_upn("test.user@example.domain.org"));
assert_true(is_valid_upn("user@domain"));
assert_true(is_valid_upn("user@domain.com."));
assert_true(is_valid_upn("user@sub."));
}


static void
test_is_valid_upn_nok_not_upn(MAYBE_UNUSED void ** _1)
{
assert_true(false == is_valid_upn("regularuser"));
assert_true(false == is_valid_upn("user.name"));
assert_true(false == is_valid_upn("user_name"));
assert_true(false == is_valid_upn("123user"));
assert_true(false == is_valid_upn("USER"));
}


static void
test_is_valid_upn_nok_structure(MAYBE_UNUSED void ** _1)
{
// Empty parts
assert_true(false == is_valid_upn("@domain.com"));
assert_true(false == is_valid_upn("user@"));
assert_true(false == is_valid_upn("@"));

// Multiple @ symbols
assert_true(false == is_valid_upn("user@domain@com"));
assert_true(false == is_valid_upn("@@domain.com"));
assert_true(false == is_valid_upn("user@@domain.com"));

// Empty string
assert_true(false == is_valid_upn(""));

// Invalid prefix
assert_true(false == is_valid_upn("-user@domain.com"));
assert_true(false == is_valid_upn("123@domain.com"));
assert_true(false == is_valid_upn("user space@domain.com"));
}


static void
test_is_valid_upn_nok_domain(MAYBE_UNUSED void ** _1)
{
// Invalid domain formats
assert_true(false == is_valid_upn("user@.domain.com"));
assert_true(false == is_valid_upn("user@domain..com"));
Comment thread
ikerexxe marked this conversation as resolved.

// Invalid domain characters
assert_true(false == is_valid_upn("user@domain_name.com"));
assert_true(false == is_valid_upn("user@domain name.com"));
}


static void
test_is_valid_upn_ok_limits(MAYBE_UNUSED void ** _1)
{
char *upn;
char *domain;

// Test domain at maximum allowed length (254 chars + implicit root = 255)
domain = malloc_T(254 + 1, char);
assert_true(domain != NULL);
strcpy(domain,
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa." // 63 a's + dot
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb." // 63 b's + dot
"ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc." // 63 c's + dot
"ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd." // 59 d's + dot
"co"); // 2
// Total: 63 + 1 + 63 + 1 + 63 + 1 + 59 + 1 + 2 (+ 1 implicit root) = 255 chars

upn = malloc_T(5 + 254 + 1, char); // "user@" + domain + '\0'
assert_true(upn != NULL);
strcpy(upn, "user@");
strcat(upn, domain);
assert_true(is_valid_upn(upn));

free(upn);
free(domain);
}


static void
test_is_valid_upn_nok_limits(MAYBE_UNUSED void ** _1)
{
char *upn;
char *domain;

// Test domain exceeding maximum length (255 chars + implicit root = 256 > 255 limit)
domain = malloc_T(255 + 1, char);
assert_true(domain != NULL);
memset(domain, 'a', 252);
strcpy(&domain[252], ".co");

upn = malloc_T(5 + 255 + 1, char); // "user@" + domain + '\0'
assert_true(upn != NULL);
strcpy(upn, "user@");
strcat(upn, domain);
assert_true(false == is_valid_upn(upn));

free(upn);
free(domain);

// Domain label too long (>63 chars)
assert_true(false == is_valid_upn("user@"
"verylongdomainlabelthatexceedssixtythreecharacterslimitsetbyRFC1035"
".com"));
}
Loading