/*
** rpm.c -- OpenPKG RPM Auditing Wrapper
** Copyright (c) 2004 The OpenPKG Project
** Copyright (c) 2004 Ralf S. Engelschall
** Copyright (c) 2004 Cable & Wireless
**
** Permission to use, copy, modify, and distribute this software for
** any purpose with or without fee is hereby granted, provided that
** the above copyright notice and this permission notice appear in all
** copies.
**
** THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR
** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
** USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
** OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
** OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
** SUCH DAMAGE.
*/
/* This is a small OpenPKG RPM command wrapper which provides minimal
auditing/logging possibilities to an OpenPKG instance by writing
a /RPM/DB/Audit logfile containing the RPM commands which
actually led to a RPM database change. */
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* utility function for fatal program termination under error condition */
void die(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
fprintf(stderr, "openpkg-audit: rpm: ERROR: ");
vfprintf(stderr, fmt, ap);
fprintf(stderr, "\n");
va_end(ap);
exit(1);
}
/* the auditing logfile */
#ifndef LOGFILE
#define LOGFILE "/var/openpkg-audit/openpkg-audit.log"
#endif
/* the RPM database directory and owner/permission */
#define RPMDB_DIR "RPM/DB"
/* list of RPM database files to check */
static char *RPMDB_files[] = {
"Basenames",
"Conflictname",
"Depends",
"Dirnames",
"Filemd5s",
"Group",
"Installtid",
"Name",
"Packages",
"Providename",
"Provideversion",
"Pubkeys",
"Requirename",
"Requireversion",
"Sha1header",
"Sigmd5",
"Triggername"
};
/* utility function for making a concatenated string out of multiple arguments */
static char *mkstr(const char *pad, const char *s1, ...)
{
va_list ap;
va_list apbak;
int n;
const char *cp;
char *str;
va_start(ap, s1);
va_copy(apbak, ap);
n = 0;
for (cp = s1; cp != NULL; cp = (const char *)va_arg(ap, const char *))
n += strlen(cp);
n++;
va_copy(ap, apbak);
if ((str = (char *)malloc(n)) == NULL)
die("failed to allocate %d bytes of memory", n);
str[0] = '\0';
for (cp = s1; cp != NULL; cp = (const char *)va_arg(ap, const char *)) {
if (pad != NULL && str[0] != '\0')
strcat(str, pad);
strcat(str, cp);
}
va_end(ap);
return str;
}
/* utility function for making a concatenated string out of an array */
static char *mkstra(const char *pad, char **sa)
{
int n, i;
char *str;
n = 0;
for (i = 0; sa[i] != NULL; i++)
n += strlen(sa[i]);
n++;
if ((str = (char *)malloc(n)) == NULL)
die("failed to allocate %d bytes of memory", n);
str[0] = '\0';
for (i = 0; sa[i] != NULL; i++) {
if (pad != NULL && str[0] != '\0')
strcat(str, pad);
strcat(str, sa[i]);
}
return str;
}
/* utility function for determining the maximum mtime of the RPM database */
static time_t rpmdb_mtime(const char *cpPrefix)
{
time_t mtime = 0;
int i;
char *cp;
struct stat sb;
for (i = 0; i < sizeof(RPMDB_files)/sizeof(RPMDB_files[0]); i++) {
cp = mkstr(NULL, cpPrefix, "/", RPMDB_DIR, "/", RPMDB_files[i], NULL);
if (stat(cp, &sb) == 0) {
if (mtime < sb.st_mtime)
mtime = sb.st_mtime;
}
free(cp);
}
return mtime;
}
/* main procedure */
int main(int argc, char *argv[])
{
char *cpPrefix;
char *cpToolsCmdProg;
char *cpToolsCmdName;
int i, j;
char **argv2;
FILE *fp;
char *cpLogfile;
struct tm *tm;
time_t t;
struct passwd *pw;
char *cpCmd;
time_t mtime_before;
time_t mtime_after;
char *cp;
pid_t pid;
int status;
int rv;
int fd;
struct stat sb;
/* determine OpenPKG run-time information
(provided by /bin/openpkg execution wrapper) */
if ((cpPrefix = getenv("OPENPKG_PREFIX")) == NULL)
die("$OPENPKG_PREFIX not set");
if ((cpToolsCmdProg = getenv("OPENPKG_TOOLS_CMDPROG")) == NULL)
die("$OPENPKG_TOOLS_CMDPROG not set");
if ((cpToolsCmdName = getenv("OPENPKG_TOOLS_CMDNAME")) == NULL)
die("$OPENPKG_TOOLS_CMDNAME not set");
/* determine argument vector for real OpenPKG RPM command
argv: /lib/openpkg-audit/rpm ...
argv2: /bin/openpkg rpm ... */
if ((argv2 = (char **)malloc((2+argc+1) * sizeof(char *))) == NULL)
die("cannot malloc");
j = 0;
argv2[j++] = cpToolsCmdProg;
argv2[j++] = cpToolsCmdName;
i = 1;
while (i < argc)
argv2[j++] = argv[i++];
argv2[j++] = NULL;
/* determine whether access to the RPM database is possible
and it not, short-circuit processing */
cp = mkstr(NULL, cpPrefix, "/", RPMDB_DIR, "/", RPMDB_files[0], NULL);
if (access(cp, R_OK|W_OK) != 0) {
/* pass-through execution to real OpenPKG RPM command */
execvp(argv2[0], argv2);
/* NEVER REACHED */
abort();
}
free(cp);
/* determine maximum modification time (before operation) */
mtime_before = rpmdb_mtime(cpPrefix);
/* execute real OpenPKG RPM command */
if ((pid = fork()) == 0) {
/* pass-through execution to real OpenPKG RPM command */
execvp(argv2[0], argv2);
/* NEVER REACHED */
abort();
}
/* wait for child to terminate */
if (waitpid(pid, &status, 0) < 0)
die("failed to wait for child process with PID %d\n", pid);
/* determine return code */
rv = (WIFEXITED(status) ? WEXITSTATUS(status) : -1);
/* determine maximum modification time (after operation) */
mtime_after = rpmdb_mtime(cpPrefix);
/* if an operation was performed which has not changed the RPM
database we short-circuit processing and exit without logging */
if (mtime_after == mtime_before)
exit(rv);
/* determine time */
t = time(NULL);
if ((tm = localtime(&t)) == NULL)
die("cannot determine local time");
/* determine user */
if ((pw = getpwuid(getuid())) == NULL)
die("cannot determine user information");
/* determine command */
cpCmd = mkstra(" ", &argv2[1]);
/* optionally give up any root privileges to make sure
the file is written with the OpenPKG RPM database user/group */
cp = mkstr(NULL, cpPrefix, "/", RPMDB_DIR, "/", RPMDB_files[0], NULL);
if (stat(cp, &sb) < 0)
die("unable to stat the RPM database file %s", cp);
free(cp);
setgid(sb.st_gid); setegid(sb.st_gid);
setuid(sb.st_uid); seteuid(sb.st_uid);
/* write entry to logfile */
cpLogfile = mkstr(NULL, cpPrefix, "/", LOGFILE, NULL);
if ((fd = open(cpLogfile, O_CREAT|O_RDWR|O_APPEND, sb.st_mode)) == -1)
die("cannot open logfile \"%s\"", cpLogfile);
free(cpLogfile);
if ((fp = fdopen(fd, "a")) == NULL)
die("cannot open filedescriptor");
fprintf(fp, "%04d-%02d-%02d %02d:%02d:%02d user=%s, command=\"%s\", return=%d\n",
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
tm->tm_hour, tm->tm_min, tm->tm_sec,
pw->pw_name,
cpCmd, rv);
fclose(fp);
close(fd);
/* cleanup */
free(argv2);
free(cpCmd);
exit(0);
}