blob: 35a424378a85c07f5b41e8f332eefd169f513f23 [file] [log] [blame]
/* SPDX-License-Identifier: BSD-3-Clause */
/*
* swtpm_setup_utils.c: Utility functions for swtpm_setup et al.
*
* Author: Stefan Berger, stefanb@linux.ibm.com
*
* Copyright (c) IBM Corporation, 2021
*/
#include "config.h"
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <glib.h>
#include "swtpm_utils.h"
void append_to_file(const char *pathname, const char *str)
{
size_t n, len;
int fd = open(pathname, O_WRONLY|O_APPEND|O_CREAT|O_NOFOLLOW, S_IRUSR|S_IWUSR|S_IRGRP);
if (fd >= 0) {
len = strlen(str);
n = write(fd, str, len);
if (n != len) {
fprintf(stderr, "Error writing to %s: %s\n", pathname, strerror(errno));
}
close(fd);
} else {
fprintf(stderr, "Error opening file %s: %s\n", pathname, strerror(errno));
}
}
static void alog(FILE *stream, const char *logfile, const char *fmt, va_list ap)
{
char *str = NULL;
int n;
n = vasprintf(&str, fmt, ap);
if (n < 0)
return;
if (logfile == NULL)
fprintf(stream, "%s", str);
else
append_to_file(logfile, str);
free(str);
}
void logit(const char *logfile, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
alog(stdout, logfile, fmt, ap);
va_end(ap);
}
void logerr(const char *logfile, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
alog(stderr, logfile, fmt, ap);
va_end(ap);
}
/* Join paths of up to 3 parts into a pre-allocated buffer. The last part is optional */
char *pathjoin(char *buffer, size_t bufferlen, const char *p1, const char *p2, const char *p3)
{
char *res = NULL;
int n = snprintf(buffer, bufferlen, "%s%s%s%s%s",
p1,
G_DIR_SEPARATOR_S,
p2,
p3 ? G_DIR_SEPARATOR_S : "",
p3 ? p3 : "");
if (n < 0) {
logerr(gl_LOGFILE, "Error: Could not print into buffer.\n");
} else if ((size_t)n >= bufferlen) {
logerr(gl_LOGFILE, "Error: Buffer for path is too small.\n");
} else {
res = buffer;
}
return res;
}
/* Concatenate two NULL-terminated arrays creating one new one;
* This function does not duplicate the memory for the elements.
* Either one of the arrays may be NULL. The first array can be freed.
*/
gchar **concat_arrays(char **arr1, char **arr2, gboolean free_arr1)
{
size_t n = 0;
gchar **res;
size_t i;
for (i = 0; arr1 != NULL && arr1[i]; i++)
n++;
for (i = 0; arr2 != NULL && arr2[i]; i++)
n++;
res = g_malloc0(sizeof(char *) * (n + 1));
for (i = 0, n = 0; arr1 != NULL && arr1[i]; i++)
res[n++] = arr1[i];
for (i = 0; arr2 != NULL && arr2[i]; i++)
res[n++] = arr2[i];
if (free_arr1 && arr1)
g_free(arr1);
return res;
}
/* Concatenate buffers into a given buffer of a given length 'buflen' */
ssize_t concat(unsigned char *buffer, size_t buflen, ...)
{
va_list ap;
ssize_t offset = 0;
va_start(ap, buflen);
while (1) {
size_t len;
unsigned char *i = va_arg(ap, unsigned char *);
if (i == NULL)
break;
len = va_arg(ap, size_t);
if (offset + len > buflen) {
offset = -(offset + len);
break;
}
memcpy(&buffer[offset], i, len);
offset += len;
}
va_end(ap);
return offset;
}
/* Concatenate buffers and allocate a new buffer and return its size */
ssize_t memconcat(unsigned char **buffer, ...)
{
va_list ap;
ssize_t offset = 0;
size_t allocated = 128;
unsigned char *p;
*buffer = g_malloc(allocated);
p = *buffer;
va_start(ap, buffer);
while (1) {
size_t len;
unsigned char *i = va_arg(ap, unsigned char *);
if (i == NULL)
break;
len = va_arg(ap, size_t);
if (offset + len > allocated) {
allocated += offset + len;
*buffer = g_realloc(*buffer, allocated);
p = *buffer;
}
memcpy(&p[offset], i, len);
offset += len;
}
va_end(ap);
return offset;
}
/* Print an input buffer as hex number */
gchar *print_as_hex(const unsigned char *input, size_t input_len)
{
gchar *out = g_malloc(input_len * 2 + 1);
size_t i;
for (i = 0; i < input_len; i++)
g_snprintf(&out[i * 2], 3, "%02x", input[i]);
return out;
}
/* Split a command line and remove all trailing and leading spaces from all entries and remove
* 0-length strings entirely.
*/
gchar **split_cmdline(const gchar *cmdline) {
gchar **result = g_strsplit(cmdline, " ", -1);
size_t i, j;
for (i = 0, j = 0; result[i] != NULL; i++) {
gchar *chomped = g_strchomp(result[i]);
if (strlen(chomped) == 0) {
g_free(chomped);
result[i] = NULL;
} else {
result[i] = NULL;
result[j++] = chomped;
}
}
return result;
}
/* resolve environment variables in a string */
gchar *resolve_string(const gchar *inp) {
char *pe;
gchar *result = NULL;
gchar *new_res, *tmp;
size_t sidx = 0;
const gchar *envval;
while (1) {
gchar *ps = g_strstr_len(&inp[sidx], -1, "${");
if (ps == NULL) {
if (sidx == 0) {
g_free(result); /* coverity */
return g_strdup(inp);
}
new_res = g_strconcat(result ? result : "", &inp[sidx], NULL);
g_free(result);
return new_res;
}
tmp = g_strndup(&inp[sidx], ps - &inp[sidx]);
new_res = g_strconcat(result ? result : "", tmp, NULL);
g_free(tmp);
g_free(result);
result = new_res;
pe = g_strstr_len(&ps[2], -1, "}");
if (pe == NULL) {
new_res = g_strconcat(result ? result : "", ps, NULL);
g_free(result);
return new_res;
}
/* name of environment variable */
tmp = g_strndup(&ps[2], pe - &ps[2]);
envval = g_getenv(tmp);
new_res = g_strconcat(result ? result : "",
envval ? envval : "",
NULL);
g_free(tmp);
g_free(result);
result = new_res;
sidx = &pe[1] - inp;
}
}
/* Read an entire file */
int read_file(const gchar *filename, gchar **buffer, gsize *buffer_len)
{
GError *error = NULL;
if (!g_file_get_contents(filename, buffer, buffer_len, &error)) {
logerr(gl_LOGFILE, "%s\n", error->message);
g_error_free(error);
return 1;
}
return 0;
}
/* Read a file and convert its lines into a NULL-termianted array */
int read_file_lines(const char *filename, gchar ***config_file_lines)
{
g_autofree gchar *buffer = NULL;
gsize buffer_len;
size_t start = 0;
gchar **array;
gsize array_len = 1; /* null-terminated array */
if (read_file(filename, &buffer, &buffer_len) != 0)
return 1;
array = g_malloc0(sizeof(char *) * array_len);
while (start < buffer_len) {
size_t off = start;
/* search for end-of-string or next newline */
while (off < buffer_len && buffer[off] != '\n')
off++;
if (off > start) {
/* non-empty line */
array = g_realloc(array, sizeof(char *) * (array_len + 1));
array[array_len - 1] = g_strndup(&buffer[start], off - start);
array_len++;
}
start = off + 1;
}
array[array_len - 1] = NULL;
*config_file_lines = array;
return 0;
}
static ssize_t write_to_fd(int fd, const unsigned char *data, size_t data_len)
{
ssize_t n;
n = write(fd, data, data_len);
if (n < 0) {
logerr(gl_LOGFILE, "Could not write to file: %s\n", strerror(errno));
} else if ((size_t)n != data_len) {
logerr(gl_LOGFILE, "Could not write all bytes to the file.\n");
n = -1;
}
return n;
}
/* Write to a file with the given name */
int write_file(const gchar *filename, const unsigned char *data, size_t data_len)
{
ssize_t n;
int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC,
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH );
if (fd < 0) {
logerr(gl_LOGFILE, "Could not open file %s for writing: %s\n",
filename, strerror(errno));
return 1;
}
n = write_to_fd(fd, data, data_len);
close(fd);
if (n < 0)
return 1;
return 0;
}
int write_to_tempfile(gchar **filename, const unsigned char *data, size_t data_len)
{
GError *error = NULL;
int fd = g_file_open_tmp("XXXXXX", filename, &error);
ssize_t n;
if (error) {
logerr(gl_LOGFILE, "Could not create temporary file: %s\n", error->message);
g_error_free(error);
return 1;
}
n = write_to_fd(fd, data, data_len);
if (n < 0)
goto error;
n = lseek(fd, 0, SEEK_SET);
if (n < 0) {
logerr(gl_LOGFILE, "Could not seek(0) on file '%s': %s\n", filename, strerror(errno));
goto error;
}
return fd;
error:
close(fd);
return -1;
}
/* replace occurrences of 'torep' with 'rep' in a string 'in' */
gchar *str_replace(const char *in, const char *torep, const char *rep)
{
char *res;
const char *s, *b;
size_t torep_len;
size_t rep_len;
size_t ctr = 0;
size_t off = 0;
if (in == NULL || torep == NULL || rep == NULL)
return NULL;
torep_len = strlen(torep);
if (torep_len == 0)
return NULL;
rep_len = strlen(rep);
s = in;
while ((s = strstr(s, torep)) != NULL) {
s += torep_len;
ctr++;
}
res = g_malloc(strlen(in) - ctr * torep_len + ctr * rep_len + 1);
b = s = in;
while ((s = strstr(s, torep)) != NULL) {
strncpy(&res[off], b, s - b);
off += (s - b);
s += torep_len;
b = s;
strcpy(&res[off], rep);
off += rep_len;
}
strcpy(&res[off], b);
return res;
}
int check_directory_access(const gchar *directory, int mode, const struct passwd *curr_user)
{
struct stat statbuf;
if (stat(directory, &statbuf) != 0 || (statbuf.st_mode & S_IFMT) != S_IFDIR) {
logerr(gl_LOGFILE,
"User %s cannot access directory %s. Make sure it exists and is a directory.\n",
curr_user ? curr_user->pw_name : "<unknown>", directory);
return 1;
}
if ((mode & R_OK) && access(directory, R_OK) != 0) {
logerr(gl_LOGFILE, "Need read rights on directory %s for user %s.\n",
directory, curr_user ? curr_user->pw_name : "<unknown>");
return 1;
}
if ((mode & W_OK) && access(directory, W_OK) != 0) {
logerr(gl_LOGFILE, "Need write rights on directory %s for user %s.\n",
directory, curr_user ? curr_user->pw_name : "<unknown>");
return 1;
}
return 0;
}