linux-pam/libpam_internal/pam_line.c
Tobias Stoeckmann 644dc6f0c1 libpam_internal: introduce pam_line
The pam_assemble_line function is renamed to pam_line_assemble and
moved into libpam_internal so it can be shared across libpam and the
pam_env module.

Applied renaming to all other relevant functions and data structures
so it is easier to locate them in files.

Signed-off-by: Tobias Stoeckmann <tobias@stoeckmann.org>
2024-02-06 08:00:00 +00:00

254 lines
5.9 KiB
C

/* pam_line.c -- routine to parse configuration lines */
#include "config.h"
#include "security/_pam_macros.h"
#include "pam_line.h"
static int _pam_line_buffer_add(struct pam_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_line_buffer_add_eol(struct pam_line_buffer *buffer,
char *start, char *end)
{
if (buffer->assembled != NULL || (*start != '\0' && *start != '\n'))
return _pam_line_buffer_add(buffer, start, end);
return 0;
}
void _pam_line_buffer_clear(struct pam_line_buffer *buffer)
{
pam_overwrite_n(buffer->assembled, buffer->size);
_pam_drop(buffer->assembled);
pam_overwrite_n(buffer->chunk, buffer->chunk_size);
_pam_drop(buffer->chunk);
buffer->chunk_size = 0;
buffer->len = 0;
buffer->size = 0;
}
void _pam_line_buffer_init(struct pam_line_buffer *buffer)
{
buffer->assembled = NULL;
buffer->chunk = NULL;
_pam_line_buffer_clear(buffer);
}
static void _pam_line_buffer_purge(struct pam_line_buffer *buffer)
{
pam_overwrite_n(buffer->chunk, buffer->chunk_size);
_pam_drop(buffer->chunk);
buffer->chunk_size = 0;
}
static void _pam_line_buffer_shift(struct pam_line_buffer *buffer)
{
if (buffer->assembled == NULL)
return;
_pam_line_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_line_buffer_valid(struct pam_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 "repl" 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, char repl)
{
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 = repl; /* replace backslash with replacement char */
if (repl != '\0') {
*++p = '\0'; /* truncate the line here if repl is not NUL */
}
*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, char repl)
{
int ret;
*start = line;
*end = line + len;
ret = _pam_str_unescnl(*start, end, repl) || 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"
*
* The "repl" argument is used as replacement char for the backslash used
* in newline escaping, i.e. in "\\\n".
*
* Returns 0 on EOF, 1 on successful line parsing, or -1 on error.
*/
int _pam_line_assemble(FILE *f, struct pam_line_buffer *buffer, char repl)
{
int ret = 0;
/* loop broken with a 'break' when a non-'\\n' ended line is read */
D(("called."));
_pam_line_buffer_shift(buffer);
for (;;) {
char *start, *end;
ssize_t n;
int eol;
if ((n = getline(&buffer->chunk, &buffer->chunk_size, f)) == -1) {
if (ret) {
/* Incomplete read */
ret = -1;
} else {
/* EOF */
ret = 0;
}
break;
}
eol = _pam_str_prepare(buffer->chunk, n, &start, &end, repl);
if (eol) {
if (_pam_line_buffer_add_eol(buffer, start, end)) {
ret = -1;
break;
}
if (_pam_line_buffer_valid(buffer)) {
/* Successfully parsed a line */
ret = 1;
break;
}
/* Start parsing next line */
_pam_line_buffer_shift(buffer);
ret = 0;
} else {
/* Configuration line spans across multiple lines in file */
if (_pam_line_buffer_add(buffer, start, end)) {
ret = -1;
break;
}
/* Keep parsing line */
ret = 1;
}
}
if (ret == 1)
_pam_line_buffer_purge(buffer);
else
_pam_line_buffer_clear(buffer);
return ret;
}