You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
289 lines
9.2 KiB
289 lines
9.2 KiB
/* |
|
|
|
Superuser-exec wrapper for HTTP serves and other needs (made especially for lighttpd). |
|
Allows programs to be run with configurable uid/gid. |
|
Version 0.5 (2008-07-07) |
|
|
|
For documentation on how to configure the wrapper, see the README. |
|
Command line option -v displays version, while -V displays compile-time configuration. |
|
These options are only available for the super user and the server admin. |
|
|
|
Brief version history: |
|
|
|
Vers Date Changes |
|
----------------------------------------------------------------------------------------- |
|
0.5 2008-07-07 Added proper handling of the supplementary group access list. Fixed a |
|
bug with passing on return values from the child process. Added |
|
compile-time configuration options to disable the CHECK_GID feature and |
|
to require the target user to have a pwent (in /etc/passwd). |
|
Thanks to stbuehler, hoffie and _lotek from #lighttpd for help. |
|
0.4 2006-06-09 Added a BSD license. |
|
0.3 2005-09-27 Changed the wrapper to stay resident and propagate SIGTERM to the |
|
target. Not doing so will prevent the server from managing the target. |
|
The old behaviour can be restored with a run-time option. Fixed a bug |
|
in the call to execl, which could cause it to fail or crash. No |
|
security risk. Added custom return codes on errors. Added some command |
|
line options. |
|
0.2 2005-09-14 Cleaned up source a bit, and added compile-time security checks. Fixed |
|
a few minor issues. Compiles under C89 now (e.g. gcc -std=c89). |
|
0.1 2005-09-08 Initial version. |
|
|
|
|
|
License: |
|
|
|
Copyright (c) 2008, Sune Foldager |
|
All rights reserved. |
|
|
|
Redistribution and use in source and binary forms, with or without modification, are |
|
permitted provided that the following conditions are met: |
|
|
|
- Redistributions of source code must retain the above copyright notice, this list of |
|
conditions and the following disclaimer. |
|
- Redistributions in binary form must reproduce the above copyright notice, this list of |
|
conditions and the following disclaimer in the documentation and/or other materials |
|
provided with the distribution. |
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY |
|
EXPRESS 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 COPYRIGHT OWNER OR 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. |
|
|
|
*/ |
|
|
|
|
|
/* Configuration. Shouldn't be changed. */ |
|
|
|
#define ENV_UID "UID=" |
|
#define ENV_GID "GID=" |
|
#define ENV_TARGET "TARGET=" |
|
#define ENV_CHECK_GID "CHECK_GID=" |
|
#define ENV_NON_RESIDENT "NON_RESIDENT=" |
|
|
|
/* Return values for various errors. */ |
|
|
|
#define RC_CALLER_UID 10 |
|
#define RC_TARGET_UID 11 |
|
#define RC_TARGET_GID 12 |
|
#define RC_TARGET 13 |
|
#define RC_MISSING_CONFIG 14 |
|
#define RC_SIGNAL_HANDLER 15 |
|
#define RC_SETGID 16 |
|
#define RC_SETUID 17 |
|
#define RC_STAT 18 |
|
#define RC_WORLD_WRITE 19 |
|
#define RC_WRONG_USER 20 |
|
#define RC_GROUP_WRITE 21 |
|
#define RC_WRONG_GROUP 22 |
|
#define RC_EXEC 23 |
|
#define RC_BAD_OPTION 24 |
|
#define RC_CHILD_ABNORMAL_EXIT 25 |
|
#define RC_MISSING_PWENT 26 |
|
|
|
|
|
/* User configuration. */ |
|
#include "execwrap_config.h" |
|
|
|
/* Compile-time security checks. */ |
|
#if !TARGET_MIN_UID |
|
#error SECURITY: TARGET_MIN_UID set to 0. See README for details. |
|
#endif |
|
#if !TARGET_MIN_GID |
|
#error SECURITY: TARGET_MIN_GID set to 0. See README for details. |
|
#endif |
|
#if DEFAULT_UID < TARGET_MIN_UID |
|
#error SECURITY: DEFAULT_UID set to a lower value than TARGET_MIN_UID. See README for details. |
|
#endif |
|
#if DEFAULT_GID < TARGET_MIN_GID |
|
#error SECURITY: DEFAULT_GID set to a lower value than TARGET_MIN_GID. See README for details. |
|
#endif |
|
|
|
/* Useful macro and other stuff. */ |
|
#define VERSION_STRING "ExecWrap v0.5 Copyright (c) 2008, Sune Foldager." |
|
#define STRLEN(a) (sizeof(a)-1) |
|
|
|
/* Shortcuts. */ |
|
#define TARGET_PATH_PREFIX_LEN STRLEN(TARGET_PATH_PREFIX) |
|
#define ENV_UID_LEN STRLEN(ENV_UID) |
|
#define ENV_GID_LEN STRLEN(ENV_GID) |
|
#define ENV_TARGET_LEN STRLEN(ENV_TARGET) |
|
#define ENV_CHECK_GID_LEN STRLEN(ENV_CHECK_GID) |
|
#define ENV_NON_RESIDENT_LEN STRLEN(ENV_NON_RESIDENT) |
|
|
|
/* Stuff we need. */ |
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include <sys/stat.h> |
|
#include <sys/wait.h> |
|
#include <unistd.h> |
|
#include <signal.h> |
|
#include <pwd.h> |
|
|
|
|
|
/* The global child PID and previous SIGTERM handler. */ |
|
int pid; |
|
void (*oldHandler)(int); |
|
|
|
|
|
/* SIGTERM handler. */ |
|
void sigTermHandler(int signal) |
|
{ |
|
|
|
/* If we're in the parent, kill the child as well. */ |
|
if(pid) kill(pid, SIGTERM); |
|
|
|
/* Call the default handler. */ |
|
oldHandler(signal); |
|
|
|
} |
|
|
|
|
|
/* Down to business. */ |
|
int main(int argc, char* argv[], char* envp[]) |
|
{ |
|
|
|
/* Verify parent UID. Only the super user and PARENT_UID are allowed. */ |
|
if(getuid() != 0 && getuid() != PARENT_UID) return RC_CALLER_UID; |
|
|
|
/* Command line options. */ |
|
if(argc > 1) |
|
{ |
|
if(argv[1][0] == '-') switch(argv[1][1]) |
|
{ |
|
case 'v': puts(VERSION_STRING); |
|
return 0; |
|
case 'V': puts(VERSION_STRING); |
|
puts("Compile-time configuration:"); |
|
printf("PARENT_UID : %d\n", PARENT_UID); |
|
printf("TARGET_MIN_UID : %d\n", TARGET_MIN_UID); |
|
printf("TARGET_MIN_GID : %d\n", TARGET_MIN_GID); |
|
printf("TARGET_PATH_PREFIX : %s\n", TARGET_PATH_PREFIX); |
|
printf("DEFAULT_UID : %d\n", DEFAULT_UID); |
|
printf("DEFAULT_GID : %d\n", DEFAULT_GID); |
|
puts(""); |
|
printf("REQUIRE_PWENT : %d\n", REQUIRE_PWENT); |
|
printf("ALLOW_CHECKGID : %d\n", ALLOW_CHECKGID); |
|
return 0; |
|
} |
|
|
|
/* Fail on unknown option. Known options return quietly above. */ |
|
return RC_BAD_OPTION; |
|
} |
|
|
|
/* Grab stuff from environment, and set defaults. */ |
|
int uid = DEFAULT_UID; |
|
int gid = DEFAULT_GID; |
|
char* target = 0; |
|
char* args = 0; |
|
char check_gid = 0; |
|
char non_resident = 0; |
|
char** p = envp; |
|
char* s; |
|
char* t; |
|
while(s = *p++) |
|
{ |
|
|
|
/* Target UID. */ |
|
if(!strncmp(ENV_UID, s, ENV_UID_LEN)) |
|
{ |
|
uid = atoi(s + ENV_UID_LEN); |
|
if(uid < TARGET_MIN_UID) return RC_TARGET_UID; |
|
} |
|
|
|
/* Target GID. */ |
|
if(!strncmp(ENV_GID, s, ENV_GID_LEN)) |
|
{ |
|
gid = atoi(s + ENV_GID_LEN); |
|
if(gid < TARGET_MIN_GID) return RC_TARGET_GID; |
|
} |
|
|
|
/* Target script. */ |
|
if(!strncmp(ENV_TARGET, s, ENV_TARGET_LEN)) |
|
{ |
|
target = s + ENV_TARGET_LEN; |
|
if((target[0] != '/') || strchr(target, '~') || strstr(target, "..") || |
|
strncmp(TARGET_PATH_PREFIX, target, TARGET_PATH_PREFIX_LEN)) return RC_TARGET; |
|
} |
|
|
|
/* Check GID instead of UID. */ |
|
#if ALLOW_CHECKGID |
|
if(!strncmp(ENV_CHECK_GID, s, ENV_CHECK_GID_LEN)) |
|
{ |
|
check_gid = 1; |
|
} |
|
#endif |
|
|
|
/* Use non-resident wrapping style. */ |
|
if(!strncmp(ENV_NON_RESIDENT, s, ENV_NON_RESIDENT_LEN)) |
|
{ |
|
non_resident = 1; |
|
} |
|
|
|
} |
|
|
|
/* See if we got all we need. */ |
|
if(!target) return RC_MISSING_CONFIG; |
|
|
|
/* Fetch user information from passwd. */ |
|
struct passwd *pwent = getpwuid(uid); |
|
#if REQUIRE_PWENT |
|
if(!pwent) return RC_MISSING_PWENT; |
|
#endif |
|
|
|
/* Install the SIGTERM handler. */ |
|
if(!non_resident) |
|
{ |
|
oldHandler = signal(SIGTERM, sigTermHandler); |
|
if(oldHandler == SIG_ERR) return RC_SIGNAL_HANDLER; |
|
} |
|
|
|
/* Fork off (or, if we are a non-resident wrapper, just carry on). */ |
|
if(non_resident || !(pid = fork())) |
|
{ |
|
|
|
/* We're in the child. Drop privileges and set the group list. */ |
|
if(pwent && initgroups(pwent->pw_name, gid)) return RC_SETGID; |
|
if(setgid(gid)) return RC_SETGID; |
|
if(setuid(uid)) return RC_SETUID; |
|
|
|
/* Stat the target script. */ |
|
char uid_ok = 1; |
|
struct stat stat_buf; |
|
if(stat(target, &stat_buf)) return RC_STAT; |
|
int modes = stat_buf.st_mode; |
|
|
|
/* Never allow world-write. */ |
|
if(modes & S_IWOTH) return RC_WORLD_WRITE; |
|
|
|
/* Only allow user miss-match if check_gid is set. */ |
|
if(uid != stat_buf.st_uid) |
|
{ |
|
if(!check_gid) return RC_WRONG_USER; |
|
uid_ok = 0; |
|
} |
|
|
|
/* If group doesn't match, don't allow group-write. |
|
Also, don't allow if neither user or group match. */ |
|
if(gid != stat_buf.st_gid) |
|
{ |
|
if(modes & S_IWGRP) return RC_GROUP_WRITE; |
|
if(!uid_ok) return RC_WRONG_GROUP; |
|
} |
|
|
|
/* All checks passed, let's become the target! */ |
|
execl(target, target, NULL); |
|
return RC_EXEC; |
|
|
|
} |
|
|
|
/* Here we're in the parent. Wait for the child to be done, and return. */ |
|
int status; |
|
wait(&status); |
|
if(WIFEXITED(status)) return WEXITSTATUS(status); |
|
return RC_CHILD_ABNORMAL_EXIT; |
|
}
|
|
|