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.

execwrap.c 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. /*
  2. Superuser-exec wrapper for HTTP serves and other needs (made especially for lighttpd).
  3. Allows programs to be run with configurable uid/gid.
  4. Version 0.5 (2008-07-07)
  5. For documentation on how to configure the wrapper, see the README.
  6. Command line option -v displays version, while -V displays compile-time configuration.
  7. These options are only available for the super user and the server admin.
  8. Brief version history:
  9. Vers Date Changes
  10. -----------------------------------------------------------------------------------------
  11. 0.5 2008-07-07 Added proper handling of the supplementary group access list. Fixed a
  12. bug with passing on return values from the child process. Added
  13. compile-time configuration options to disable the CHECK_GID feature and
  14. to require the target user to have a pwent (in /etc/passwd).
  15. Thanks to stbuehler, hoffie and _lotek from #lighttpd for help.
  16. 0.4 2006-06-09 Added a BSD license.
  17. 0.3 2005-09-27 Changed the wrapper to stay resident and propagate SIGTERM to the
  18. target. Not doing so will prevent the server from managing the target.
  19. The old behaviour can be restored with a run-time option. Fixed a bug
  20. in the call to execl, which could cause it to fail or crash. No
  21. security risk. Added custom return codes on errors. Added some command
  22. line options.
  23. 0.2 2005-09-14 Cleaned up source a bit, and added compile-time security checks. Fixed
  24. a few minor issues. Compiles under C89 now (e.g. gcc -std=c89).
  25. 0.1 2005-09-08 Initial version.
  26. License:
  27. Copyright (c) 2008, Sune Foldager
  28. All rights reserved.
  29. Redistribution and use in source and binary forms, with or without modification, are
  30. permitted provided that the following conditions are met:
  31. - Redistributions of source code must retain the above copyright notice, this list of
  32. conditions and the following disclaimer.
  33. - Redistributions in binary form must reproduce the above copyright notice, this list of
  34. conditions and the following disclaimer in the documentation and/or other materials
  35. provided with the distribution.
  36. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
  37. EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  38. MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
  39. THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  40. SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
  41. OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  42. HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
  43. TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  44. SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  45. */
  46. /* Configuration. Shouldn't be changed. */
  47. #define ENV_UID "UID="
  48. #define ENV_GID "GID="
  49. #define ENV_TARGET "TARGET="
  50. #define ENV_CHECK_GID "CHECK_GID="
  51. #define ENV_NON_RESIDENT "NON_RESIDENT="
  52. #define ENV_DEBUG "DEBUG"
  53. /* Return values for various errors. */
  54. #define RC_CALLER_UID 10
  55. #define RC_TARGET_UID 11
  56. #define RC_TARGET_GID 12
  57. #define RC_TARGET 13
  58. #define RC_MISSING_CONFIG 14
  59. #define RC_SIGNAL_HANDLER 15
  60. #define RC_SETGID 16
  61. #define RC_SETUID 17
  62. #define RC_STAT 18
  63. #define RC_WORLD_WRITE 19
  64. #define RC_WRONG_USER 20
  65. #define RC_GROUP_WRITE 21
  66. #define RC_WRONG_GROUP 22
  67. #define RC_EXEC 23
  68. #define RC_BAD_OPTION 24
  69. #define RC_CHILD_ABNORMAL_EXIT 25
  70. #define RC_MISSING_PWENT 26
  71. /* User configuration. */
  72. #include "execwrap_config.h"
  73. /* Compile-time security checks. */
  74. #if !TARGET_MIN_UID
  75. #error SECURITY: TARGET_MIN_UID set to 0. See README for details.
  76. #endif
  77. #if !TARGET_MIN_GID
  78. #error SECURITY: TARGET_MIN_GID set to 0. See README for details.
  79. #endif
  80. #if DEFAULT_UID < TARGET_MIN_UID
  81. #error SECURITY: DEFAULT_UID set to a lower value than TARGET_MIN_UID. See README for details.
  82. #endif
  83. #if DEFAULT_GID < TARGET_MIN_GID
  84. #error SECURITY: DEFAULT_GID set to a lower value than TARGET_MIN_GID. See README for details.
  85. #endif
  86. /* Useful macro and other stuff. */
  87. #define VERSION_STRING "ExecWrap v0.5 Copyright (c) 2008, Sune Foldager."
  88. #define STRLEN(a) (sizeof(a)-1)
  89. /* Shortcuts. */
  90. #define TARGET_PATH_PREFIX_LEN STRLEN(TARGET_PATH_PREFIX)
  91. #define ENV_UID_LEN STRLEN(ENV_UID)
  92. #define ENV_GID_LEN STRLEN(ENV_GID)
  93. #define ENV_TARGET_LEN STRLEN(ENV_TARGET)
  94. #define ENV_CHECK_GID_LEN STRLEN(ENV_CHECK_GID)
  95. #define ENV_NON_RESIDENT_LEN STRLEN(ENV_NON_RESIDENT)
  96. /* Stuff we need. */
  97. #include <stdio.h>
  98. #include <stdlib.h>
  99. #include <string.h>
  100. #include <sys/stat.h>
  101. #include <sys/wait.h>
  102. #include <unistd.h>
  103. #include <signal.h>
  104. #include <pwd.h>
  105. #include <sys/types.h>
  106. #include <grp.h>
  107. #if USE_SYSLOG
  108. # include <syslog.h>
  109. # include <errno.h>
  110. #else /* USE_SYSLOG */
  111. /* this should be after all includes */
  112. # undef SKIP
  113. # undef openlog
  114. # undef setlogmask
  115. # undef syslog
  116. # define SKIP do { } while (0)
  117. # define openlog(...) SKIP
  118. # define setlogmask(...) SKIP
  119. # define syslog(...) SKIP
  120. #endif /* USE_SYSLOG */
  121. /* The global child PID and previous SIGTERM handler. */
  122. int pid;
  123. void (*oldHandler)(int);
  124. /* SIGTERM handler. */
  125. void sigTermHandler(int signal)
  126. {
  127. /* If we're in the parent, kill the child as well. */
  128. if(pid) kill(pid, SIGTERM);
  129. /* Call the default handler. */
  130. oldHandler(signal);
  131. }
  132. /* Down to business. */
  133. int main(int argc, char* argv[], char* envp[])
  134. {
  135. openlog("execwrap", LOG_PID, LOG_AUTHPRIV);
  136. setlogmask(LOG_UPTO(LOG_NOTICE));
  137. /* Verify parent UID. Only the super user and PARENT_UID are allowed. */
  138. int myuid = getuid();
  139. if(myuid != 0 && myuid != PARENT_UID) {
  140. syslog(LOG_ERR, "exiting with RC_CALLER_UID, UID=%d", myuid);
  141. return RC_CALLER_UID;
  142. }
  143. /* Command line options. */
  144. if(argc > 1)
  145. {
  146. if(argv[1][0] == '-') switch(argv[1][1])
  147. {
  148. case 'v': puts(VERSION_STRING);
  149. return 0;
  150. case 'V': puts(VERSION_STRING);
  151. puts("Compile-time configuration:");
  152. printf("PARENT_UID : %d\n", PARENT_UID);
  153. printf("TARGET_MIN_UID : %d\n", TARGET_MIN_UID);
  154. printf("TARGET_MIN_GID : %d\n", TARGET_MIN_GID);
  155. printf("TARGET_PATH_PREFIX : %s\n", TARGET_PATH_PREFIX);
  156. printf("DEFAULT_UID : %d\n", DEFAULT_UID);
  157. printf("DEFAULT_GID : %d\n", DEFAULT_GID);
  158. puts("");
  159. printf("REQUIRE_PWENT : %d\n", REQUIRE_PWENT);
  160. printf("ALLOW_CHECKGID : %d\n", ALLOW_CHECKGID);
  161. return 0;
  162. }
  163. /* Fail on unknown option. Known options return quietly above. */
  164. return RC_BAD_OPTION;
  165. }
  166. /* we need this check before the loop over the environment,
  167. as we cannot rely on it to be the first env var we find */
  168. if (NULL != getenv(ENV_DEBUG)) {
  169. setlogmask(LOG_UPTO(LOG_DEBUG));
  170. syslog(LOG_DEBUG, "activated debug output");
  171. }
  172. /* Grab stuff from environment, and set defaults. */
  173. uid_t uid = DEFAULT_UID;
  174. gid_t gid = DEFAULT_GID;
  175. char* target = 0;
  176. char check_gid = 0;
  177. char non_resident = 0;
  178. char** p = envp;
  179. char* s;
  180. while(NULL != (s = *p++))
  181. {
  182. /* Target UID. */
  183. if(!strncmp(ENV_UID, s, ENV_UID_LEN))
  184. {
  185. uid = atoi(s + ENV_UID_LEN);
  186. if(uid < TARGET_MIN_UID) {
  187. syslog(LOG_ERR, "exiting with RC_TARGET_UID, UID=%d", uid);
  188. return RC_TARGET_UID;
  189. }
  190. syslog(LOG_DEBUG, "target UID is %d", uid);
  191. }
  192. /* Target GID. */
  193. if(!strncmp(ENV_GID, s, ENV_GID_LEN))
  194. {
  195. gid = atoi(s + ENV_GID_LEN);
  196. if(gid < TARGET_MIN_GID) {
  197. syslog(LOG_ERR, "exiting with RC_TARGET_GID, GID=%d", gid);
  198. return RC_TARGET_GID;
  199. }
  200. syslog(LOG_DEBUG, "target GID is %d", gid);
  201. }
  202. /* Target script. */
  203. if(!strncmp(ENV_TARGET, s, ENV_TARGET_LEN))
  204. {
  205. target = s + ENV_TARGET_LEN;
  206. if((target[0] != '/') || strchr(target, '~') || strstr(target, "..") ||
  207. strncmp(TARGET_PATH_PREFIX, target, TARGET_PATH_PREFIX_LEN)) {
  208. syslog(LOG_ERR, "exiting with RC_TARGET, target=%s", target);
  209. return RC_TARGET;
  210. }
  211. syslog(LOG_DEBUG, "TARGET is %s", target);
  212. }
  213. /* Check GID instead of UID. */
  214. #if ALLOW_CHECKGID
  215. if(!strncmp(ENV_CHECK_GID, s, ENV_CHECK_GID_LEN))
  216. {
  217. check_gid = 1;
  218. }
  219. #endif
  220. /* Use non-resident wrapping style. */
  221. if(!strncmp(ENV_NON_RESIDENT, s, ENV_NON_RESIDENT_LEN))
  222. {
  223. non_resident = 1;
  224. }
  225. }
  226. /* See if we got all we need. */
  227. if(!target) {
  228. syslog(LOG_ERR, "exiting with RC_MISSING_CONFIG, no TARGET");
  229. return RC_MISSING_CONFIG;
  230. }
  231. /* Fetch user information from passwd. */
  232. struct passwd *pwent = getpwuid(uid);
  233. #if REQUIRE_PWENT
  234. if(!pwent) {
  235. syslog(LOG_ERR, "exiting with RC_MISSING_PWENT, pwent required by config, but not found");
  236. return RC_MISSING_PWENT;
  237. }
  238. #endif
  239. if (pwent) {
  240. syslog(LOG_DEBUG, "pwent found, pw_name=%s", pwent->pw_name);
  241. }
  242. /* Install the SIGTERM handler. */
  243. if(!non_resident)
  244. {
  245. oldHandler = signal(SIGTERM, sigTermHandler);
  246. if(oldHandler == SIG_ERR) return RC_SIGNAL_HANDLER;
  247. }
  248. /* Fork off (or, if we are a non-resident wrapper, just carry on). */
  249. if(non_resident || !(pid = fork()))
  250. {
  251. /* We're in the child. Drop privileges and set the group list. */
  252. if(pwent && initgroups(pwent->pw_name, gid)) {
  253. syslog(LOG_ERR, "exiting with RC_SETGID, initgroup failed, errno=%d", errno);
  254. return RC_SETGID;
  255. }
  256. if(setgid(gid)) {
  257. syslog(LOG_ERR, "exiting with RC_SETGID, setgid failed, errno=%d", errno);
  258. return RC_SETGID;
  259. }
  260. if(setuid(uid)) {
  261. syslog(LOG_ERR, "exiting with RC_SETUID, setuid failed, errno=%d", errno);
  262. return RC_SETUID;
  263. }
  264. /* Stat the target script. */
  265. char uid_ok = 1;
  266. struct stat stat_buf;
  267. if(stat(target, &stat_buf)) {
  268. syslog(LOG_ERR, "exiting with RC_STAT, stat failed, errno=%d", errno);
  269. return RC_STAT;
  270. }
  271. int modes = stat_buf.st_mode;
  272. /* Never allow world-write. */
  273. if(modes & S_IWOTH) {
  274. syslog(LOG_ERR, "exiting with RC_WORLD_WRITE, modes=%d", modes);
  275. return RC_WORLD_WRITE;
  276. }
  277. /* Only allow user miss-match if check_gid is set. */
  278. if(uid != stat_buf.st_uid)
  279. {
  280. if(!check_gid) {
  281. syslog(LOG_ERR, "exiting with RC_WRONG_USER");
  282. return RC_WRONG_USER;
  283. }
  284. uid_ok = 0;
  285. }
  286. /* If group doesn't match, don't allow group-write.
  287. Also, don't allow if neither user or group match. */
  288. if(gid != stat_buf.st_gid)
  289. {
  290. if(modes & S_IWGRP) {
  291. syslog(LOG_ERR, "exiting with RC_GROUP_WRITE, modes=%d", modes);
  292. return RC_GROUP_WRITE;
  293. }
  294. if(!uid_ok) {
  295. syslog(LOG_ERR, "exiting with RC_WRONG_GROUP");
  296. return RC_WRONG_GROUP;
  297. }
  298. }
  299. /* All checks passed, let's become the target! */
  300. syslog(LOG_NOTICE, "executing target=%s, UID=%d, GID=%d", target, uid, gid);
  301. execl(target, target, NULL);
  302. return RC_EXEC;
  303. }
  304. /* Here we're in the parent. Wait for the child to be done, and return. */
  305. int status;
  306. wait(&status);
  307. if(WIFEXITED(status)) return WEXITSTATUS(status);
  308. return RC_CHILD_ABNORMAL_EXIT;
  309. }