/* ** 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); }