diff options
author | Tobias Stoeckmann <tobias@stoeckmann.org> | 2023-12-12 15:01:45 +0100 |
---|---|---|
committer | Dmitry V. Levin <ldv@strace.io> | 2023-12-31 08:00:00 +0000 |
commit | e5e53ebe4d77cbb1c0762d4b3a0c8978aabcc598 (patch) | |
tree | fac0f3c9d61e9c7682ec7fdbb34df7d9ebd8125d /libpam | |
parent | 3c61d2f6f9843dc54c13a3c6b023563c041feaa4 (diff) | |
download | pam-e5e53ebe4d77cbb1c0762d4b3a0c8978aabcc598.tar.gz pam-e5e53ebe4d77cbb1c0762d4b3a0c8978aabcc598.tar.bz2 pam-e5e53ebe4d77cbb1c0762d4b3a0c8978aabcc598.zip |
libpam: support arbitrarily long config lines
Use getline in _pam_assemble_line to allow very long lines.
Also handle escaped newlines and NUL bytes better, even though
the latter are not valid for text files.
Signed-off-by: Tobias Stoeckmann <tobias@stoeckmann.org>
Diffstat (limited to 'libpam')
-rw-r--r-- | libpam/pam_handlers.c | 297 |
1 files changed, 229 insertions, 68 deletions
diff --git a/libpam/pam_handlers.c b/libpam/pam_handlers.c index aaf194fc..6c3a8ba7 100644 --- a/libpam/pam_handlers.c +++ b/libpam/pam_handlers.c @@ -18,14 +18,23 @@ #include <fcntl.h> #include <unistd.h> -#define BUF_SIZE 1024 #define MODULE_CHUNK 4 #define UNKNOWN_MODULE "<*unknown module*>" #ifndef _PAM_ISA #define _PAM_ISA "." #endif -static int _pam_assemble_line(FILE *f, char *buf, int buf_len); +struct line_buffer { + char *assembled; + char *chunk; + size_t chunk_size; + size_t len; + size_t size; +}; + +static void _pam_buffer_init(struct line_buffer *buffer); + +static int _pam_assemble_line(FILE *f, struct line_buffer *buf); static void _pam_free_handlers_aux(struct handler **hp); @@ -62,12 +71,15 @@ static int _pam_parse_conf_file(pam_handle_t *pamh, FILE *f #endif /* PAM_READ_BOTH_CONFS */ ) { - char buf[BUF_SIZE]; + struct line_buffer buffer; int x; /* read a line from the FILE *f ? */ + + _pam_buffer_init(&buffer); /* * read a line from the configuration (FILE *) f */ - while ((x = _pam_assemble_line(f, buf, BUF_SIZE)) > 0) { + while ((x = _pam_assemble_line(f, &buffer)) > 0) { + char *buf = buffer.assembled; char *tok, *nexttok=NULL; const char *this_service; const char *mod_path; @@ -572,94 +584,243 @@ int _pam_init_handlers(pam_handle_t *pamh) return PAM_SUCCESS; } +static int _pam_buffer_add(struct line_buffer *buffer, char *start, char *end) +{ + size_t len = end - start; + + D(("assembled: [%zu/%zu] '%s', adding [%zu] '%s'", + buffer->len, buffer->size, + buffer->assembled == NULL ? "" : buffer->assembled, len, start)); + + if (start == end) + return 0; + + if (buffer->assembled == NULL && buffer->chunk == start) { + /* no extra allocation needed, just move chunk to assembled */ + buffer->assembled = buffer->chunk; + buffer->len = len; + buffer->size = buffer->chunk_size; + + buffer->chunk = NULL; + buffer->chunk_size = 0; + + D(("exiting with quick exchange")); + return 0; + } + + if (buffer->len + len + 1 > buffer->size) { + size_t size; + char *p; + + size = buffer->len + len + 1; + if ((p = realloc(buffer->assembled, size)) == NULL) + return -1; + + buffer->assembled = p; + buffer->size = size; + } + + memcpy(buffer->assembled + buffer->len, start, len); + buffer->len += len; + buffer->assembled[buffer->len] = '\0'; + + D(("exiting")); + return 0; +} + +static inline int _pam_buffer_add_eol(struct line_buffer *buffer, + char *start, char *end) +{ + if (buffer->assembled != NULL || (*start != '\0' && *start != '\n')) + return _pam_buffer_add(buffer, start, end); + return 0; +} + +static void _pam_buffer_clear(struct line_buffer *buffer) +{ + _pam_drop(buffer->assembled); + _pam_drop(buffer->chunk); + buffer->chunk_size = 0; + buffer->len = 0; + buffer->size = 0; +} + +static void _pam_buffer_init(struct line_buffer *buffer) +{ + buffer->assembled = NULL; + buffer->chunk = NULL; + _pam_buffer_clear(buffer); +} + +static void _pam_buffer_purge(struct line_buffer *buffer) +{ + _pam_drop(buffer->chunk); + buffer->chunk_size = 0; +} + +static void _pam_buffer_shift(struct line_buffer *buffer) +{ + if (buffer->assembled == NULL) + return; + + _pam_buffer_purge(buffer); + buffer->chunk = buffer->assembled; + buffer->chunk_size = buffer->size; + + buffer->assembled = NULL; + buffer->size = 0; + buffer->len = 0; +} + +static inline int _pam_buffer_valid(struct line_buffer *buffer) +{ + return buffer->assembled != NULL && *buffer->assembled != '\0'; +} + +/* + * Trim string to relevant parts of a configuration line. + * + * Preceding whitespaces are skipped and comment (#) marks the end of + * configuration line. + * + * Returns start of configuration line. + */ +static inline char *_pam_str_trim(char *str) +{ + /* skip leading spaces */ + str += strspn(str, " \t"); + /* + * we are only interested in characters before the first '#' + * character + */ + str[strcspn(str, "#")] = '\0'; + + return str; +} + +/* + * Remove escaped newline from end of string. + * + * Configuration lines may span across multiple lines in a file + * by ending a line with a backslash (\). + * + * If an escaped newline is encountered, the backslash will be + * replaced with a blank ' ' and the newline itself removed. + * Then the variable "end" will point to the new end of line. + * + * Returns 0 if escaped newline was found and replaced, 1 otherwise. + */ +static inline int _pam_str_unescnl(char *start, char **end) +{ + int ret = 1; + char *p = *end; + + /* + * Check for backslash by scanning back from the end of + * the entered line, the '\n' should be included since + * normally a line is terminated with this character. + */ + while (p > start && ((*--p == ' ') || (*p == '\t') || (*p == '\n'))) + ; + if (*p == '\\') { + *p++ = ' '; /* replace backslash with ' ' */ + *p = '\0'; /* truncate the line here */ + *end = p; + ret = 0; + } + + return ret; +} + +/* + * Prepare line from file for configuration line parsing. + * + * A configuration line may span across multiple lines in a file. + * Remove comments and skip preceding whitespaces. + * + * Returns 0 if line spans across multiple lines, 1 if + * end of line is encountered. + */ +static inline int _pam_str_prepare(char *line, ssize_t len, + char **start, char **end) +{ + int ret; + + *start = line; + *end = line + len; + + ret = _pam_str_unescnl(*start, end) || strchr(*start, '#') != NULL; + + *start = _pam_str_trim(*start); + + return ret; +} + /* * This is where we read a line of the PAM config file. The line may be * preceded by lines of comments and also extended with "\\\n" + * + * Returns 0 on EOF, 1 on successful line parsing, or -1 on error. */ -static int _pam_assemble_line(FILE *f, char *buffer, int buf_len) +static int _pam_assemble_line(FILE *f, struct line_buffer *buffer) { - char *p = buffer; - char *endp = buffer + buf_len; - char *s, *os; - int used = 0; + int ret = 0; /* loop broken with a 'break' when a non-'\\n' ended line is read */ D(("called.")); + + _pam_buffer_shift(buffer); + for (;;) { - if (p >= endp - 1) { - /* Overflow */ - D(("overflow")); - return -1; - } - if (fgets(p, endp - p, f) == NULL) { - if (used) { + char *start, *end; + ssize_t n; + int eol; + + if ((n = getline(&buffer->chunk, &buffer->chunk_size, f)) == -1) { + if (ret) { /* Incomplete read */ - return -1; + ret = -1; } else { /* EOF */ - return 0; + ret = 0; } + break; } - if (strchr(p, '\n') == NULL && !feof(f)) { - /* Incomplete */ - D(("_pam_assemble_line: incomplete")); - return -1; - } - - /* skip leading spaces --- line may be blank */ - - s = p + strspn(p, " \n\t"); - if (*s && (*s != '#')) { - os = s; - - /* - * we are only interested in characters before the first '#' - * character - */ + eol = _pam_str_prepare(buffer->chunk, n, &start, &end); - while (*s && *s != '#') - ++s; - if (*s == '#') { - *s = '\0'; - used += strlen(os); - break; /* the line has been read */ + if (eol) { + if (_pam_buffer_add_eol(buffer, start, end)) { + ret = -1; + break; } - - s = os; - - /* - * Check for backslash by scanning back from the end of - * the entered line, the '\n' has been included since - * normally a line is terminated with this - * character. fgets() should only return one though! - */ - - s += strlen(s); - while (s > os && ((*--s == ' ') || (*s == '\t') - || (*s == '\n'))); - - /* check if it ends with a backslash */ - if (*s == '\\') { - *s++ = ' '; /* replace backslash with ' ' */ - *s = '\0'; /* truncate the line here */ - used += strlen(os); - p = s; /* there is more ... */ - } else { - /* End of the line! */ - used += strlen(os); - break; /* this is the complete line */ + if (_pam_buffer_valid(buffer)) { + /* Successfully parsed a line */ + ret = 1; + break; } - + /* Start parsing next line */ + _pam_buffer_shift(buffer); + ret = 0; } else { - /* Nothing in this line */ - /* Don't move p */ + /* Configuration line spans across multiple lines in file */ + if (_pam_buffer_add(buffer, start, end)) { + ret = -1; + break; + } + /* Keep parsing line */ + ret = 1; } } - return used; + if (ret == 1) + _pam_buffer_purge(buffer); + else + _pam_buffer_clear(buffer); + + return ret; } static char * |