[PATCH] login-utils: Removed upper limit on passwd line size in is_local, added tests.

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Removed fixed-length buffer in is_local() by rewriting it to use a
state machine.  Characters from the passwd wfile are just compared
against characters in the ueername directly.  Added a set of test
cases, made the makefile run these for "make check".  Since is_local
does not modify the characters pointed to by its argument, we also
declare them to be const.
---
 login-utils/Makefile.am |    7 ++-
 login-utils/islocal.c   |  164 +++++++++++++++++++++++++++++++++++++++++-----
 login-utils/islocal.h   |    2 +-
 3 files changed, 153 insertions(+), 20 deletions(-)

diff --git a/login-utils/Makefile.am b/login-utils/Makefile.am
index 8f5dc89..7c9a7a3 100644
--- a/login-utils/Makefile.am
+++ b/login-utils/Makefile.am
@@ -130,7 +130,12 @@ install-exec-hook::
 
 endif
 
-noinst_PROGRAMS = checktty_test
+noinst_PROGRAMS = checktty_test islocal_test
 checktty_test_SOURCES = checktty.c login.h
 checktty_test_CPPFLAGS = -DMAIN_TEST_CHECKTTY $(AM_CPPFLAGS)
+islocal_test_SOURCES = islocal.c
+islocal_test_CPPFLAGS = -DMAIN_TEST_ISLOCAL $(AM_CPPFLAGS)
 
+check-local: islocal_test$(EXEEXT) checktty_test$(EXEEXT)
+	./islocal_test$(EXEEXT) 
+	./checktty_test$(EXEEXT)
diff --git a/login-utils/islocal.c b/login-utils/islocal.c
index 2976980..98c2089 100644
--- a/login-utils/islocal.c
+++ b/login-utils/islocal.c
@@ -12,7 +12,10 @@
 
    1999-02-22 Arkadiusz Mi¶kiewicz <misiek@xxxxxxxxxx>
    - added Native Language Support
-     
+
+   2008-04-06 James Youngman, jay@xxxxxxx
+   - Completely rewritten to remove assumption that /etc/passwd 
+     lines are < 1024 characters long.  Also added unit tests.
 
 */
 
@@ -24,29 +27,154 @@
 #include "pathnames.h"
 #include "islocal.h"
 
-#define MAX_LENGTH	1024
+#if MAIN_TEST_ISLOCAL 
+static const char *testdata;
+static FILE *
+fopen_passwd_file(void) {
+	FILE *f = tmpfile();
+        if (NULL == f) {
+		perror("Failed to create temporary file for test, exiting.");
+		exit(1);
+        }
+	if (fprintf(f, "%s", testdata) < 0) {
+		perror("Failed to write to temporary file for test, exiting.");
+		fclose(f);
+		exit(1);
+	}
+	/* don't use rewind(), it has no error reporting. */
+	if (fseek(f, 0L, SEEK_SET) < 0) {
+		perror("temporary file");
+		fclose(f);
+		exit(1);
+	}
+	return f;
+}
+#else
+static FILE *
+fopen_passwd_file(void) {
+	FILE *f = fopen(_PATH_PASSWD, "r");
+        if (NULL == f) {
+		perror(_PATH_PASSWD);
+                fprintf(stderr, _("Failed to read %s, exiting."), _PATH_PASSWD);
+                exit(1);
+        }
+	return f;
+}
+#endif 
+
 
 int
-is_local(char *user)
+is_local(const char *user)
 {
-	FILE *fd;
-	char line[MAX_LENGTH];
 	int local = 0;
-	size_t len;
-
-        if(!(fd = fopen(_PATH_PASSWD, "r"))) {
-                fprintf(stderr,_("Can't read %s, exiting."),_PATH_PASSWD);
-                exit(1);
-        }
+	size_t match;
+	int chin, skip;
+	FILE *f = fopen_passwd_file();
 
-	len = strlen(user);
-        while(fgets(line, MAX_LENGTH, fd)) {
-                if(!strncmp(line, user, len) && line[len] == ':') {
-			local = 1;
-			break;
-                }
+	match = 0u;
+	skip = 0;
+	while ((chin=getc(f)) != EOF) {
+		if (skip) {
+			/* Looking for the start of the next line. */
+			if ('\n' == chin) {
+				/* Start matching username at the next char. */
+				skip = 0;
+				match = 0u;
+			}
+		} else {
+			if (':' == chin) {
+				if (0 == user[match]) {
+					local = 1; /* Success. */
+					/* next line has no test coverage, but it is 
+					 * just an optimisation anyway. */
+					break;
+				} else {
+					/* we read a whole username, but it is 
+					 * the wrong user.  Skip to the next 
+					 * line. */
+					skip = 1;
+				}
+			} else if ('\n' == chin) {
+				/* This line contains no colon; it's malformed.
+				 * No skip since we are already at the start of
+				 * the next line. */
+				match = 0u;
+			} else if (chin != user[match]) {
+				/* username does not match. */
+				skip = 1;
+			} else {
+				++match;
+			}
+		}
 	}
-	fclose(fd);
+	fclose(f);
 	return local;
 }
 
+#if MAIN_TEST_ISLOCAL 
+static unsigned failures = 0u;
+static const char *testdata = 
+"root:x:0:0:root:/root:/bin/bash\n"
+"sys:x:3:3:sys:/dev:/bin/sh\n"
+"orac:x:33:33:sys:/dev:/bin/youngman\n"
+/* Next line should be >1024 but <2048 */
+"long:x:4:4:foo:/home/loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
 oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooonnnggg:/bin/sh\n"
+"al:x:5:5:fnord:/dev:/bin/sh\n"
+"abcdefgh:x:6:3:fnord:/dev:/bin/sh\n"
+"malformed\n"
+"\n"				/* see test case znobody */
+"nobody:x:65534:65534:nobody:/nonexistent:/bin/sh\n"
+"nonl:x:65532:65532:no newline:/:/bin/false";
+
+struct testcase {
+	int expected;
+	const char *name;
+};
+#define NUM_TESTS (sizeof(tests)/sizeof(tests[0]))
+
+const struct testcase tests[] = {
+	{ 1, "root" },		/* first */
+	{ 1, "nobody" },	/* last */
+	{ 0, "" },		/* empty */
+	{ 0, "youngman" },	/* 8 chars, is suffix of another entry (the shell of user orac) */
+	{ 0, "youngman2" },	/* 9 chars */
+	{ 0, "abcdefghx" },	/* verifies first skip */
+	{ 0, "nobo" },		/* prefix of existing entry */
+	{ 1, "long" },		/* very long entry */
+	{ 0, "rot", },		/* verifies that we skip the rest of the line when we see a mismatch */
+	{ 1, "al" },		/* immediately after long entry */
+	{ 0, "malformed" },	/* exists in field 0 but the line is malformed */
+	{ 1, "nonl" },          /* I guess you could argue this either way */
+	{ 0, "znobody" },	/* verify that after malformed line, we restart match at 0 (needs the empty line) */
+};
+
+static int 
+fail (int i) {
+	printf("FAIL: [%s], expected %s\n",
+		tests[i].name, 
+		(tests[i].expected ? "local" : "not local"));
+	++failures;
+}
+
+static int 
+pass (int i) {
+	printf("PASS: [%s]\n", tests[i].name);
+}
+
+int 
+main (int argc, char *argv[]) {
+	size_t i;
+	const char *me = "islocal_test";
+	for (i=0u; i<NUM_TESTS; i++) {
+		const int got = is_local(tests[i].name) ? 1 : 0;
+		((tests[i].expected == got) ? pass : fail)(i);
+	}
+	if (!failures) {
+		printf ("%s: All tests passed.\n", me);
+		return 0;
+	} else {
+		printf ("%s: %u tests FAILED.\n", me, failures);
+		return 1;
+	}
+}
+#endif
diff --git a/login-utils/islocal.h b/login-utils/islocal.h
index 305bc57..2c58799 100644
--- a/login-utils/islocal.h
+++ b/login-utils/islocal.h
@@ -1 +1 @@
-extern int is_local(char *user);
+extern int is_local(const char *user);
-- 
1.5.3.8

--
To unsubscribe from this list: send the line "unsubscribe util-linux-ng" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Netdev]     [Ethernet Bridging]     [Linux Wireless]     [Kernel Newbies]     [Security]     [Linux for Hams]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux RAID]     [Linux Admin]     [Samba]

  Powered by Linux