diff -r 71503088f51b -r f880f219c566 openpkg/openpkg.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/openpkg/openpkg.c Tue Jul 31 12:23:42 2012 +0200 @@ -0,0 +1,504 @@ +/* +** openpkg.c -- OpenPKG Frontend +** Copyright (c) 2000-2012 OpenPKG GmbH +** +** This software is property of the OpenPKG GmbH, DE MUC HRB 160208. +** All rights reserved. Licenses which grant limited permission to use, +** copy, modify and distribute this software are available from the +** OpenPKG GmbH. +** +** 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. +*/ + +/* program information */ +#define LICENSE \ + "Copyright (c) 2000-2012 OpenPKG GmbH \n" \ + "\n" \ + "This software is property of the OpenPKG GmbH, DE MUC HRB 160208.\n" \ + "All rights reserved. Licenses which grant limited permission to use,\n" \ + "copy, modify and distribute this software are available from the\n" \ + "OpenPKG GmbH.\n" \ + "\n" \ + "THIS SOFTWARE IS PROVIDED \"AS IS\" AND ANY EXPRESSED OR IMPLIED\n" \ + "WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\n" \ + "MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n" \ + "IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR\n" \ + "CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n" \ + "SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n" \ + "LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF\n" \ + "USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\n" \ + "ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n" \ + "OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT\n" \ + "OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n" \ + "SUCH DAMAGE.\n" + +/* system includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* sanity check compilation */ +#ifndef OPENPKG_PREFIX +#error OpenPKG instance prefix not defined +#endif +#ifndef OPENPKG_SUSR +#error OpenPKG super user not defined +#endif +#ifndef OPENPKG_MUSR +#error OpenPKG management user not defined +#endif + +/* platform specifics */ +#if defined(OPENPKG_PLATFORM_FREEBSD) || \ + defined(OPENPKG_PLATFORM_NETBSD) || \ + defined(OPENPKG_PLATFORM_OPENBSD) || \ + defined(OPENPKG_PLATFORM_SUNOS) || \ + defined(OPENPKG_PLATFORM_LINUX) || \ + defined(OPENPKG_PLATFORM_DARWIN) || \ + defined(OPENPKG_PLATFORM_AIX) || \ + defined(OPENPKG_PLATFORM_IRIX) || \ + defined(OPENPKG_PLATFORM_HPUX) +#define HAVE_INITGROUPS +#endif + +/* global debug enable flag */ +static int debug_enable = 0; + +/* helper function: emulate (still less portable) setenv(3) via (more portable) putenv(3) */ +static int my_setenv(const char *name, const char *value, int overwrite) +{ + char *pair; + + if (overwrite == 0 && getenv(name) != NULL) + return 0; + if ((pair = malloc(strlen(name) + 1 + strlen(value) + 1)) == NULL) + return -1; + strcpy(pair, name); + strcat(pair, "="); + strcat(pair, value); + putenv(pair); + return 0; +} + +/* helper function for printing a warning message */ +static void warn(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + fprintf(stderr, "openpkg:WARNING: "); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + return; +} + +/* helper function for printing a debug message */ +static void debug(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + if (debug_enable) { + fprintf(stderr, "openpkg:DEBUG: "); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + } + va_end(ap); + return; +} + +/* helper function for printing a fatal message and exit */ +static void fatal(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + fprintf(stderr, "openpkg:ERROR: "); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + exit(1); + return; +} + +/* adjust process privileges */ +static void adjust_privileges(uid_t uid, gid_t gid, int login) +{ + struct passwd *pw; + char cwd[MAXPATHLEN]; + + /* optionally emulate a more complete login */ + if (login) { + /* determine information about user id */ + if ((pw = getpwuid(uid)) == NULL) + fatal("unable to resolve user id \"%d\": %s", uid, strerror(errno)); + + /* reset some essential environment variables */ + my_setenv("LOGNAME", pw->pw_name, 1); + my_setenv("USER", pw->pw_name, 1); + my_setenv("SHELL", pw->pw_shell, 1); + my_setenv("HOME", pw->pw_dir, 1); + +#ifdef HAVE_INITGROUPS + /* initialize complete group access list */ + if (initgroups(pw->pw_name, pw->pw_gid) == -1) + fatal("failed to initialize access group list via initgroups(3): %s", strerror(errno)); +#endif + } + + /* switch to group id (first) */ + if (setgid(gid) == -1) + fatal("failed to set group id via setgid(2): %s", strerror(errno)); + + /* switch to user id (second) */ + if (setuid(uid) == -1) + fatal("failed to set user id via setuid(2): %s", strerror(errno)); + + /* in case the current working directory is NO LONGER accessible, + try to switch to the home directory of the target identity to + prevent failures in subsequently called programs (e.g. GNU bash) */ + if (login) { + if (getcwd(cwd, sizeof(cwd)) == NULL) { + warn("current working directory is no longer accessible -- switching to \"%s\"", pw->pw_dir); + if (chdir(pw->pw_dir) == -1) + fatal("unable to chdir(2) to \"%s\": %s", pw->pw_dir, strerror(errno)); + } + } + + return; +} + +/* check whether caller is an explictly configured management user */ +static int check_whether_is_manager(uid_t my_uid, gid_t my_gid, uid_t m_uid, gid_t m_gid) +{ + char buf[1024]; + char *username; + char *groupname; + char *filename; + struct stat sb; + char *cp; + FILE *fp; + struct passwd *pw; + struct group *gr; + int i; + int ok_uid; + int ok_gid; + int is_manager; + + is_manager = 0; + + /* path to the managers configuration file */ + filename = OPENPKG_PREFIX "/etc/openpkg/managers"; + + /* check permissions of file */ + if (stat(filename, &sb) == -1) { + warn("unable to determine information about configuration" + " file \"%s\": %s -- ignoring file", filename, strerror(errno)); + return 0; + } + if (sb.st_uid != m_uid) { + warn("invalid owner user id %d (expected %d) on configuration" + " file \"%s\" -- ignoring file", sb.st_uid, m_uid, filename); + return 0; + } + if (sb.st_gid != m_gid) { + warn("invalid owner group id %d (expected %d) on configuration" + " file \"%s\" -- ignoring file", sb.st_gid, m_gid, filename); + return 0; + } + if (sb.st_mode != (S_IFREG|S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH)) { + warn("invalid permissions on configuration" + " file \"%s\" -- ignoring file", filename); + return 0; + } + + /* parse configuration file */ + if ((fp = fopen(filename, "r")) == NULL) { + warn("unable to open configuration file \"%s\": %s -- ignoring file", filename, strerror(errno)); + return 0; + } + while ((cp = fgets(buf, sizeof(buf), fp)) != NULL) { + /* parse entry as "[:]" where both + and can be set and default to "*" + to indicate an arbitrary user or group */ + if ((i = strlen(buf)) == 0) { + warn("unexpected empty buffer during parsing of configuration file \"%\"", filename); + break; + } + if (i >= sizeof(buf)) { + warn("unexpected buffer overflow during parsing of configuration file \"%\"", filename); + break; + } + if (buf[i-1] != '\r' && buf[i-1] != '\n') { + warn("unexpected non-newline-terminated line found during parsing of configuration file \"%\"", filename); + break; + } + username = buf + strspn(buf, " \t"); + cp = username + strcspn(username, " \t#\r\n"); + *cp = '\0'; + if (username[0] == '#' || username[0] == '\r' || username[0] == '\n' || username[0] == '\0') + continue; + groupname = "*"; + if ((cp = strchr(username, ':')) != NULL) { + *cp++ = '\0'; + groupname = cp; + } + debug("parsing result: username=\"%s\" groupname=\"%s\"", username, groupname); + + /* check whether UID is ok */ + ok_uid = 0; + if (strcmp(username, "*") == 0) + ok_uid = 1; + else { + if ((pw = getpwnam(username)) == NULL) { + warn("invalid username \"%s\" in \"%s\"", username, filename); + continue; + } + if (pw->pw_uid == my_uid) + ok_uid = 1; + } + + /* check whether GID is ok */ + ok_gid = 0; + if (strcmp(groupname, "*") == 0) + ok_gid = 1; + else { + if ((gr = getgrnam(groupname)) == NULL) { + warn("invalid groupname \"%s\" in \"%s\"", groupname, filename); + continue; + } + if (gr->gr_gid == my_gid) + ok_gid = 1; + } + + /* if both UID and GID are ok, user is manager */ + debug("matching: username ok = %s, groupname ok = %s", ok_uid ? "yes" : "no", ok_gid ? "yes" : "no"); + if (ok_uid && ok_gid) { + is_manager = 1; + break; + } + } + fclose(fp); + return is_manager; +} + +/* check whether command requires super-user privileges */ +static int check_whether_require_superuser(int argc, char *argv[]) +{ + int require_superuser; + int i, j; + + require_superuser = 0; + if (argc > 1 && strcmp(argv[1], "rpm") == 0) { + for (i = 2; i < argc; i++) { + if (strcmp(argv[i], "--") == 0) + break; + else if ( strcmp(argv[i], "--erase") == 0 + || strcmp(argv[i], "--freshen") == 0 + || strcmp(argv[i], "--install") == 0 + || strcmp(argv[i], "--upgrade") == 0 + || strcmp(argv[i], "--import") == 0 + || strcmp(argv[i], "--initdb") == 0 + || strcmp(argv[i], "--rebuilddb") == 0 + || strcmp(argv[i], "--db-build") == 0 + || strcmp(argv[i], "--db-rebuild") == 0 + || strcmp(argv[i], "--db-cleanup") == 0 + || strcmp(argv[i], "--db-fixate") == 0 + || strcmp(argv[i], "--setperms") == 0 + || strcmp(argv[i], "--setugids") == 0) { + require_superuser = 1; + break; + } + else if ( argv[i][0] == '-' + && argv[i][1] != '-' + && argv[i][1] != 'b' + && argv[i][1] != 't' + ) { + for (j = 1; argv[i][j] != '\0'; j++) { + if ( ( argv[i][j] == 'q' + || argv[i][j] == 'K') + && argv[i][j+1] != '\0') { + j++; + continue; + } + else if ( argv[i][j] == 'i' + || argv[i][j] == 'U' + || argv[i][j] == 'F' + || argv[i][j] == 'V' + || argv[i][j] == 'e') { + require_superuser = 1; + break; + } + } + if (require_superuser) + break; + } + } + } + else if (argc > 1 && strcmp(argv[1], "rc") == 0) { + require_superuser = 1; + for (i = 2; i < argc; i++) { + if (strcmp(argv[i], "--") == 0) + break; + else if ( strcmp(argv[i], "-p") == 0 + || strcmp(argv[i], "--print") == 0 + || strcmp(argv[i], "-e") == 0 + || strcmp(argv[i], "--eval") == 0 + || strcmp(argv[i], "-q") == 0 + || strcmp(argv[i], "--query") == 0 + || strcmp(argv[i], "-c") == 0 + || strcmp(argv[i], "--config") == 0) { + require_superuser = 0; + break; + } + } + } + return require_superuser; +} + +/* main program */ +int main(int argc, char **argv, char **envp) +{ + int keep_original_privileges; + int is_manager; + int require_superuser; + uid_t my_uid, my_euid; + gid_t my_gid, my_egid; + uid_t m_uid; + gid_t m_gid; + uid_t s_uid; + gid_t s_gid; + struct passwd *pw; + struct group *gr; + int dry_run; + int license; + + /* parse command line options */ + license = 0; + dry_run = 0; + keep_original_privileges = 0; + if (argc <= 0) + abort(); + argv++; argc--; + while (argc > 0) { + if (argv[0][0] != '-') + break; + else if (strcmp(argv[0], "--") == 0) { + argv++; argc--; + break; + } + else if (strcmp(argv[0], "--license") == 0) { + license = 1; + argv++; argc--; + continue; + } + else if (strcmp(argv[0], "--debug") == 0) { + debug_enable = 1; + argv++; argc--; + continue; + } + else if (strcmp(argv[0], "--dry-run") == 0) { + dry_run = 1; + argv++; argc--; + continue; + } + else if (strcmp(argv[0], "--keep-privileges") == 0) { + keep_original_privileges = 1; + argv++; argc--; + continue; + } + break; + } + argv--; argc++; + + /* display stand-alone license information */ + if (license) { + fprintf(stdout, "%s", LICENSE); + exit(0); + } + + /* determine our current real and effective user/group ids */ + my_uid = getuid(); + my_gid = getgid(); + my_euid = geteuid(); + my_egid = getegid(); + if ((pw = getpwuid(my_uid)) == NULL) + fatal("unable to resolve current user id %d: %s", my_uid, strerror(errno)); + if ((gr = getgrgid(my_gid)) == NULL) + fatal("unable to resolve current group id %d: %s", my_gid, strerror(errno)); + debug("current-user: usr=%s uid=%d euid=%d grp=%s gid=%d egid=%d", + pw->pw_name, my_uid, my_euid, gr->gr_name, my_gid, my_egid); + + /* determine super user/group id */ + if ((pw = getpwnam(OPENPKG_SUSR)) == NULL) + fatal("unable to resolve OpenPKG superuser username \"%s\": %s", OPENPKG_SUSR, strerror(errno)); + s_uid = pw->pw_uid; + s_gid = pw->pw_gid; + debug("super-user: s_usr=%s s_uid=%d s_gid=%d", OPENPKG_SUSR, s_uid, s_gid); + + /* determine management user/group id */ + if ((pw = getpwnam(OPENPKG_MUSR)) == NULL) + fatal("unable to resolve OpenPKG management username \"%s\": %s", OPENPKG_MUSR, strerror(errno)); + m_uid = pw->pw_uid; + m_gid = pw->pw_gid; + debug("management-user: m_grp=%s m_uid=%d m_gid=%d", OPENPKG_MUSR, m_uid, m_gid); + + /* determine whether caller is explicitly configured as a management user */ + is_manager = 0; + if (!keep_original_privileges) + is_manager = check_whether_is_manager(my_uid, my_gid, m_uid, m_gid); + debug("current user is manager: %s", is_manager ? "yes" : "no"); + + /* determine whether command requires super-user privileges */ + require_superuser = check_whether_require_superuser(argc, argv); + debug("current command requires super user privileges: %s", require_superuser ? "yes" : "no"); + + /* adjust privileges according to determined information */ + if (!keep_original_privileges && require_superuser && is_manager && my_euid == 0) { + /* increase privileges to super user */ + debug("increase privileges to super user"); + adjust_privileges(s_uid, s_gid, 1); + } + else if (!keep_original_privileges && !require_superuser && is_manager && my_euid == 0) { + /* decrease privileges to management user */ + debug("decrease privileges to management user"); + adjust_privileges(m_uid, m_gid, 1); + } + else /* keep_original_privileges || !is_manager */ { + /* drop effective privileges for current user*/ + debug("drop effective privileges for current user"); + adjust_privileges(my_uid, my_gid, 0); + } + + /* pass-through control to real Execution Frontend (shell script) */ + argv[0] = OPENPKG_PREFIX "/lib/openpkg/openpkg"; + debug("execute \"%s\"", argv[0]); + if (!dry_run) { + if (execve(argv[0], argv, envp) == -1) + fatal("failed to execute \"%s\": %s", argv[0], strerror(errno)); + /* NOT REACHED */ + fatal("INTERNAL ERROR"); + } + return 0; +} +