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