hostname-util: rework read_hostname_config() a bit

First of all, let's rename it to read_etc_hostname(), to make clearer
what kind of configuration it actually reads: the file format defined in
/etc/hostname and nothing else.

Secondly: let's port this to use read_line(), i.e. the new way to read
lines from a file in a safe, bounded way.

Thirdly: let's strip leading/trailing whitespace from what we are
reading. Given that we are already pretty lenient what we read (comments
and empty lines), let's be permissive regarding whitespace too.

Fourthly: let's actually validate the hostname when reading it. So far
we tried to make it valid, but that's not always possible (for example,
we can't make an empty hostname valid, ever).
This commit is contained in:
Lennart Poettering 2017-11-14 19:51:06 +01:00
parent 9990ea0e59
commit f35cb39ed6
5 changed files with 58 additions and 35 deletions

View File

@ -25,6 +25,8 @@
#include <sys/utsname.h>
#include <unistd.h>
#include "alloc-util.h"
#include "def.h"
#include "fd-util.h"
#include "fileio.h"
#include "hostname-util.h"
@ -219,35 +221,55 @@ int sethostname_idempotent(const char *s) {
return 1;
}
int read_hostname_config(const char *path, char **hostname) {
_cleanup_fclose_ FILE *f = NULL;
char l[LINE_MAX];
char *name = NULL;
int read_etc_hostname_stream(FILE *f, char **ret) {
int r;
assert(path);
assert(hostname);
assert(f);
assert(ret);
for (;;) {
_cleanup_free_ char *line = NULL;
char *p;
r = read_line(f, LONG_LINE_MAX, &line);
if (r < 0)
return r;
if (r == 0) /* EOF without any hostname? the file is empty, let's treat that exactly like no file at all: ENOENT */
return -ENOENT;
p = strstrip(line);
/* File may have empty lines or comments, ignore them */
if (!IN_SET(*p, '\0', '#')) {
char *copy;
hostname_cleanup(p); /* normalize the hostname */
if (!hostname_is_valid(p, true)) /* check that the hostname we return is valid */
return -EBADMSG;
copy = strdup(p);
if (!copy)
return -ENOMEM;
*ret = copy;
return 0;
}
}
}
int read_etc_hostname(const char *path, char **ret) {
_cleanup_fclose_ FILE *f = NULL;
assert(ret);
if (!path)
path = "/etc/hostname";
f = fopen(path, "re");
if (!f)
return -errno;
/* may have comments, ignore them */
FOREACH_LINE(l, f, return -errno) {
truncate_nl(l);
if (!IN_SET(l[0], '\0', '#')) {
/* found line with value */
name = hostname_cleanup(l);
name = strdup(name);
if (!name)
return -ENOMEM;
break;
}
}
return read_etc_hostname_stream(f, ret);
if (!name)
/* no non-empty line found */
return -ENOENT;
*hostname = name;
return 0;
}

View File

@ -39,4 +39,5 @@ bool is_gateway_hostname(const char *hostname);
int sethostname_idempotent(const char *s);
int read_hostname_config(const char *path, char **hostname);
int read_etc_hostname_stream(FILE *f, char **ret);
int read_etc_hostname(const char *path, char **ret);

View File

@ -37,7 +37,7 @@ int hostname_setup(void) {
const char *hn;
int r;
r = read_hostname_config("/etc/hostname", &b);
r = read_etc_hostname(NULL, &b);
if (r < 0) {
if (r == -ENOENT)
enoent = true;

View File

@ -96,7 +96,7 @@ static int context_read_data(Context *c) {
if (!c->data[PROP_HOSTNAME])
return -ENOMEM;
r = read_hostname_config("/etc/hostname", &c->data[PROP_STATIC_HOSTNAME]);
r = read_etc_hostname(NULL, &c->data[PROP_STATIC_HOSTNAME]);
if (r < 0 && r != -ENOENT)
return r;

View File

@ -100,7 +100,7 @@ static void test_hostname_cleanup(void) {
assert_se(streq(hostname_cleanup(s), "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"));
}
static void test_read_hostname_config(void) {
static void test_read_etc_hostname(void) {
char path[] = "/tmp/hostname.XXXXXX";
char *hostname;
int fd;
@ -111,27 +111,27 @@ static void test_read_hostname_config(void) {
/* simple hostname */
write_string_file(path, "foo", WRITE_STRING_FILE_CREATE);
assert_se(read_hostname_config(path, &hostname) == 0);
assert_se(read_etc_hostname(path, &hostname) == 0);
assert_se(streq(hostname, "foo"));
hostname = mfree(hostname);
/* with comment */
write_string_file(path, "# comment\nfoo", WRITE_STRING_FILE_CREATE);
assert_se(read_hostname_config(path, &hostname) == 0);
assert_se(read_etc_hostname(path, &hostname) == 0);
assert_se(hostname);
assert_se(streq(hostname, "foo"));
hostname = mfree(hostname);
/* with comment and extra whitespace */
write_string_file(path, "# comment\n\n foo ", WRITE_STRING_FILE_CREATE);
assert_se(read_hostname_config(path, &hostname) == 0);
assert_se(read_etc_hostname(path, &hostname) == 0);
assert_se(hostname);
assert_se(streq(hostname, "foo"));
hostname = mfree(hostname);
/* cleans up name */
write_string_file(path, "!foo/bar.com", WRITE_STRING_FILE_CREATE);
assert_se(read_hostname_config(path, &hostname) == 0);
assert_se(read_etc_hostname(path, &hostname) == 0);
assert_se(hostname);
assert_se(streq(hostname, "foobar.com"));
hostname = mfree(hostname);
@ -139,11 +139,11 @@ static void test_read_hostname_config(void) {
/* no value set */
hostname = (char*) 0x1234;
write_string_file(path, "# nothing here\n", WRITE_STRING_FILE_CREATE);
assert_se(read_hostname_config(path, &hostname) == -ENOENT);
assert_se(read_etc_hostname(path, &hostname) == -ENOENT);
assert_se(hostname == (char*) 0x1234); /* does not touch argument on error */
/* nonexisting file */
assert_se(read_hostname_config("/non/existing", &hostname) == -ENOENT);
assert_se(read_etc_hostname("/non/existing", &hostname) == -ENOENT);
assert_se(hostname == (char*) 0x1234); /* does not touch argument on error */
unlink(path);
@ -155,7 +155,7 @@ int main(int argc, char *argv[]) {
test_hostname_is_valid();
test_hostname_cleanup();
test_read_hostname_config();
test_read_etc_hostname();
return 0;
}