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.

spawn-fcgi.c 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  1. #include <glib.h>
  2. #include <fcntl.h>
  3. #include <stdlib.h>
  4. #include <string.h>
  5. #include <sys/stat.h>
  6. #include <sys/wait.h>
  7. #include <unistd.h>
  8. #include <grp.h>
  9. #include <pwd.h>
  10. #include <errno.h>
  11. #include <arpa/inet.h>
  12. #include <sys/un.h>
  13. #ifdef HAVE_CONFIG_H
  14. #include "config.h"
  15. #endif
  16. #define UNUSED(x) ((void)(x))
  17. #define FCGI_LISTENSOCK_FILENO 0
  18. #ifdef USE_LIMITS
  19. int pam_set_limits(const char *conf_file, const char *username);
  20. #endif
  21. /*
  22. spawn-fcgi - spawns fastcgi processes
  23. The basic code was extracted from lighttpd (http://www.lighttpd.net/) and modified
  24. by Stefan Buehler in 2008 (use glib2, keep fds open and change socket ownership).
  25. COPYING from lighttpd:
  26. Copyright (c) 2004, Jan Kneschke, incremental
  27. All rights reserved.
  28. Redistribution and use in source and binary forms, with or without
  29. modification, are permitted provided that the following conditions are met:
  30. - Redistributions of source code must retain the above copyright notice, this
  31. list of conditions and the following disclaimer.
  32. - Redistributions in binary form must reproduce the above copyright notice,
  33. this list of conditions and the following disclaimer in the documentation
  34. and/or other materials provided with the distribution.
  35. - Neither the name of the 'incremental' nor the names of its contributors may
  36. be used to endorse or promote products derived from this software without
  37. specific prior written permission.
  38. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  39. AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  40. IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  41. ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  42. LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  43. CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  44. SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  45. INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  46. CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  47. ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
  48. THE POSSIBILITY OF SUCH DAMAGE.
  49. */
  50. typedef struct {
  51. gchar *fcgiapp, **args;
  52. in_addr_t addr;
  53. gint port;
  54. gchar *unixsocket;
  55. gint php_childs; /* set env var */
  56. gint fork_childs;
  57. gchar *pid_file;
  58. gboolean no_fork;
  59. gboolean show_version;
  60. gboolean keep_fds, close_fds; /* keep/close STDOUT/STDERR */
  61. #ifdef USE_LIMITS
  62. gboolean use_limits;
  63. #endif
  64. gchar *chroot;
  65. gchar *uid, *gid;
  66. gchar *socketuid, *socketgid;
  67. gint socketmode;
  68. } options;
  69. struct data {
  70. socklen_t socklen;
  71. struct sockaddr *sockaddr;
  72. int socket;
  73. gboolean i_am_root;
  74. uid_t uid, socketuid;
  75. gid_t gid, socketgid;
  76. gchar *username;
  77. int pid_fd;
  78. };
  79. static options opts = {
  80. NULL, NULL,
  81. 0,
  82. 0,
  83. NULL,
  84. 5,
  85. 1,
  86. NULL,
  87. FALSE,
  88. FALSE,
  89. FALSE, FALSE,
  90. #ifdef USE_LIMITS
  91. FALSE,
  92. #endif
  93. NULL,
  94. NULL, NULL,
  95. NULL, NULL,
  96. 0600
  97. };
  98. static struct data data;
  99. /* only require an entry for name != NULL, otherwise a id as key is ok */
  100. static int readpwdent(gchar *key, uid_t *uid, gid_t *gid, gchar** name) {
  101. struct passwd *pwd;
  102. errno = 0;
  103. *gid = (gid_t) -1;
  104. if (NULL == (pwd = getpwnam(key)) && NULL == (pwd = getpwuid(atoi(key)))) {
  105. if (name == NULL && 0 < (*uid = (uid_t) atoi(key))) return 0;
  106. g_printerr("Couldn't find passwd entry for '%s': %s\n", key, g_strerror(errno));
  107. return -1;
  108. }
  109. *uid = pwd->pw_uid;
  110. *gid = pwd->pw_gid;
  111. if (name) *name = g_strdup(pwd->pw_name);
  112. return 0;
  113. }
  114. static int readgrpent(gchar *key, gid_t *gid) {
  115. struct group *grp;
  116. errno = 0;
  117. if (NULL == (grp = getgrnam(key)) && NULL == (grp = getgrgid(atoi(key)))) {
  118. if (0 < (*gid = (gid_t) atoi(key))) return 0;
  119. g_printerr("Couldn't find group entry for '%s': %s\n", key, g_strerror(errno));
  120. return -1;
  121. }
  122. *gid = grp->gr_gid;
  123. return 0;
  124. }
  125. static int create_sockaddr() {
  126. if (opts.addr != 0 && opts.port == 0) {
  127. g_printerr("Specified address without port\n");
  128. return -1;
  129. }
  130. if (opts.port != 0 && opts.unixsocket != NULL) {
  131. g_printerr("Either tcp:port or unix domain socket, not both\n");
  132. return -1;
  133. }
  134. if (opts.port == 0 && opts.unixsocket == NULL) {
  135. g_printerr("Need either tcp:port or unix domain socket\n");
  136. return -1;
  137. }
  138. if (opts.port != 0) {
  139. struct sockaddr_in *s_in;
  140. s_in = g_malloc0(sizeof(struct sockaddr_in));
  141. s_in->sin_family = AF_INET;
  142. if (opts.addr)
  143. s_in->sin_addr.s_addr = opts.addr;
  144. else
  145. s_in->sin_addr.s_addr = htonl(INADDR_ANY);
  146. s_in->sin_port = htons(opts.port);
  147. data.sockaddr = (struct sockaddr*) s_in;
  148. data.socklen = sizeof(struct sockaddr_in);
  149. } else {
  150. struct sockaddr_un *sun;
  151. gsize slen = strlen(opts.unixsocket), len = 1 + slen + (gsize) (((struct sockaddr_un *) 0)->sun_path);
  152. sun = (struct sockaddr_un*) g_malloc0(len);
  153. sun->sun_family = AF_UNIX;
  154. strcpy(sun->sun_path, opts.unixsocket);
  155. data.sockaddr = (struct sockaddr*) sun;
  156. data.socklen = len - 1;
  157. }
  158. return 0;
  159. }
  160. static int bind_socket() {
  161. int s, val;
  162. /* Check if socket is already open */
  163. /* TODO: should this be skippable? */
  164. if (-1 == (s = socket(data.sockaddr->sa_family, SOCK_STREAM, 0))) {
  165. g_printerr("Couldn't open socket: %s\n", g_strerror(errno));
  166. return -1;
  167. }
  168. if (0 == connect(s, data.sockaddr, data.socklen)) {
  169. close(s);
  170. g_printerr("Socket already in use, can't spawn\n");
  171. return -1;
  172. }
  173. close(s);
  174. if (opts.unixsocket) unlink(opts.unixsocket);
  175. if (-1 == (data.socket = socket(data.sockaddr->sa_family, SOCK_STREAM, 0))) {
  176. g_printerr("Couldn't open socket: %s\n", g_strerror(errno));
  177. return -1;
  178. }
  179. val = 1;
  180. if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0) {
  181. close(s);
  182. g_printerr("Couldn't set SO_REUSEADDR: %s\n", g_strerror(errno));
  183. return -1;
  184. }
  185. if (-1 == bind(s, data.sockaddr, data.socklen)) {
  186. close(s);
  187. g_printerr("Couldn't bind socket: %s\n", g_strerror(errno));
  188. return -1;
  189. }
  190. if (-1 == listen(s, 1024)) {
  191. close(s);
  192. g_printerr("Couldn't listen on socket: %s\n", g_strerror(errno));
  193. return -1;
  194. }
  195. if (opts.unixsocket) {
  196. data.socketuid = (uid_t) -1;
  197. data.socketgid = (gid_t) -1;
  198. if (opts.socketuid
  199. && 0 != (readpwdent(opts.socketuid, &data.socketuid, &data.socketgid, NULL))) {
  200. return -1;
  201. }
  202. if (opts.socketgid
  203. && 0 != (readgrpent(opts.socketgid, &data.socketgid))) {
  204. return -1;
  205. }
  206. if (-1 == chown(opts.unixsocket, data.socketuid, data.socketgid)) {
  207. close(s);
  208. g_printerr("Couldn't chown socket: %s\n", g_strerror(errno));
  209. return -1;
  210. }
  211. if (-1 == chmod(opts.unixsocket, opts.socketmode)) {
  212. close(s);
  213. g_printerr("Couldn't chmod socket: %s\n", g_strerror(errno));
  214. return -1;
  215. }
  216. }
  217. data.socket = s;
  218. return 0;
  219. }
  220. static int open_pidfile() {
  221. if (opts.pid_file) {
  222. struct stat st;
  223. if (0 == (data.pid_fd = open(opts.pid_file, O_WRONLY | O_CREAT | O_EXCL | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH))) {
  224. return 0;
  225. }
  226. if (errno != EEXIST) {
  227. g_printerr("Opening pid-file '%s' failed: %s\n", opts.pid_file, g_strerror(errno));
  228. return -1;
  229. }
  230. if (0 != stat(opts.pid_file, &st)) {
  231. g_printerr("Stating pid-file '%s' failed: %s\n", opts.pid_file, g_strerror(errno));
  232. return -1;
  233. }
  234. if (!S_ISREG(st.st_mode)) {
  235. g_printerr("pid-file exists and isn't regular file: '%s'\n", opts.pid_file);
  236. return -1;
  237. }
  238. if (-1 == (data.pid_fd = open(opts.pid_file, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH))) {
  239. g_printerr("Opening pid-file '%s' failed: %s\n", opts.pid_file, g_strerror(errno));
  240. return -1;
  241. }
  242. }
  243. return 0;
  244. }
  245. static void prepare_env() {
  246. GString *tmp;
  247. if (-1 != opts.php_childs) {
  248. tmp = g_string_sized_new(0);
  249. g_string_printf(tmp, "PHP_FCGI_CHILDREN=%d", opts.php_childs);
  250. putenv(tmp->str);
  251. g_string_free(tmp, FALSE);
  252. }
  253. }
  254. static int drop_priv() {
  255. if (data.i_am_root) {
  256. /* set user and group */
  257. data.uid = (uid_t) -1;
  258. data.gid = (gid_t) -1;
  259. data.username = NULL;
  260. if (opts.uid
  261. && 0 != (readpwdent(opts.uid, &data.uid, &data.gid, &data.username))) {
  262. return -1;
  263. }
  264. if (opts.gid
  265. && 0 != (readgrpent(opts.gid, &data.gid))) {
  266. return -1;
  267. }
  268. #ifdef USE_LIMITS
  269. if (opts.use_limits && data.username) {
  270. if (0 != pam_set_limits(NULL, data.username)) {
  271. }
  272. }
  273. #endif
  274. /* do the change before we do the chroot() */
  275. if (data.gid != (gid_t) -1) {
  276. if (0 != setgid(data.gid)) {
  277. g_printerr("setgid failed: %s\n", strerror(errno));
  278. return -1;
  279. }
  280. if (0 != setgroups(0, NULL)) {
  281. g_printerr("setgroups failed: %s\n", strerror(errno));
  282. return -1;
  283. }
  284. if (data.username
  285. && 0 != initgroups(data.username, data.gid)) {
  286. g_printerr("initgroups failed: %s\n", strerror(errno));
  287. return -1;
  288. }
  289. }
  290. if (opts.chroot) {
  291. if (-1 == chroot(opts.chroot)) {
  292. g_printerr("chroot failed: %s\n", strerror(errno));
  293. return -1;
  294. }
  295. if (-1 == chdir("/")) {
  296. g_printerr("chdir failed: %s\n", strerror(errno));
  297. return -1;
  298. }
  299. }
  300. /* drop root privs */
  301. if (data.uid != (uid_t) -1
  302. && 0 != setuid(data.uid)) {
  303. g_printerr("setuid failed: %s\n", strerror(errno));
  304. return -1;
  305. }
  306. }
  307. return 0;
  308. }
  309. /* move a fd to another and close the old one */
  310. static void move2fd(int srcfd, int dstfd) {
  311. if (srcfd != dstfd) {
  312. close(dstfd);
  313. dup2(srcfd, dstfd);
  314. close(srcfd);
  315. }
  316. }
  317. /* replace an fd with /dev/null */
  318. static void move2devnull(int fd) {
  319. move2fd(open("/dev/null", O_RDWR), fd);
  320. }
  321. static pid_t daemonize() {
  322. pid_t child;
  323. int status;
  324. struct timeval tv = { 0, 100 * 1000 };
  325. switch (child = fork()) {
  326. case 0:
  327. /* loose control terminal */
  328. setsid();
  329. return 0;
  330. case -1:
  331. g_printerr("Fork failed: %s\n", g_strerror(errno));
  332. return (pid_t) -1;
  333. default:
  334. /* wait */
  335. select(0, NULL, NULL, NULL, &tv);
  336. waitforchild:
  337. switch (waitpid(child, &status, WNOHANG)) {
  338. case 0:
  339. g_printerr("Child spawned successfully: PID: %d\n", child);
  340. return child;
  341. case -1:
  342. if (EINTR == errno) goto waitforchild;
  343. g_printerr("Unknown error: %s\n", g_strerror(errno));
  344. return (pid_t) -1;
  345. default:
  346. if (WIFEXITED(status)) {
  347. g_printerr("Child exited with: %d, %s\n", WEXITSTATUS(status), strerror(WEXITSTATUS(status)));
  348. } else if (WIFSIGNALED(status)) {
  349. g_printerr("Child signaled: %d\n", WTERMSIG(status));
  350. } else {
  351. g_printerr("Child died somehow: %d\n", status);
  352. }
  353. return (pid_t) -1;
  354. }
  355. }
  356. }
  357. void start() G_GNUC_NORETURN;
  358. void start() {
  359. int save_err_fileno = -1;
  360. move2fd(data.socket, FCGI_LISTENSOCK_FILENO);
  361. if (opts.close_fds) {
  362. move2devnull(STDOUT_FILENO);
  363. save_err_fileno = dup(STDERR_FILENO);
  364. fcntl(save_err_fileno, F_SETFD, FD_CLOEXEC);
  365. close(STDERR_FILENO);
  366. dup2(STDOUT_FILENO, STDERR_FILENO);
  367. }
  368. if (opts.args) {
  369. execv(opts.args[0], opts.args);
  370. } else {
  371. GString *tmp = g_string_sized_new(0);
  372. g_string_printf(tmp, "exec %s", opts.fcgiapp);
  373. execl("/bin/sh", "sh", "-c", tmp->str, (char *)NULL);
  374. }
  375. if (opts.close_fds) {
  376. dup2(save_err_fileno, STDERR_FILENO);
  377. }
  378. g_printerr("Exec failed: %s\n", g_strerror(errno));
  379. exit(errno);
  380. }
  381. static gboolean option_parse_address(const gchar *option_name, const gchar *value, gpointer d, GError **error) {
  382. UNUSED(option_name);
  383. UNUSED(d);
  384. UNUSED(error);
  385. opts.addr = inet_addr(value);
  386. return TRUE;
  387. }
  388. static const GOptionEntry entries[] = {
  389. { "application", 'f', 0, G_OPTION_ARG_FILENAME, &opts.fcgiapp, "Filename of the fcgi-application", "fcgiapp" },
  390. { "address", 'a', 0, G_OPTION_ARG_CALLBACK, (gpointer) (intptr_t) &option_parse_address, "Bind to ip address", "addr" },
  391. { "port", 'p', 0, G_OPTION_ARG_INT, &opts.port, "Bind to tcp-port", "port" },
  392. { "socket", 's', 0, G_OPTION_ARG_FILENAME, &opts.unixsocket, "Bind to unix-domain socket", "path" },
  393. { "socket-uid", 'U', 0, G_OPTION_ARG_STRING, &opts.socketuid, "change unix-domain socket owner to user-id", "user" },
  394. { "socket-gid", 'G', 0, G_OPTION_ARG_STRING, &opts.socketgid, "change unix-domain socket group to group-id", "group" },
  395. { "socket-mode", 'M', 0, G_OPTION_ARG_INT, &opts.socketmode, "change unix-domain socket mode", "mode" },
  396. { "phpchilds", 'C', 0, G_OPTION_ARG_INT, &opts.php_childs, "(PHP only) Number of childs to spawn (default 5)", "childs" },
  397. { "childs", 'F', 0, G_OPTION_ARG_INT, &opts.fork_childs, "Number of childs to fork (default 1)", "childs" },
  398. { "pid", 'P', 0, G_OPTION_ARG_FILENAME, &opts.pid_file, "Name of PID-file for spawned process", "path" },
  399. { "no-daemon", 'n', 0, G_OPTION_ARG_NONE, &opts.no_fork, "Don't fork (for daemontools)", NULL },
  400. { "keep-fds", 0, 0, G_OPTION_ARG_NONE, &opts.keep_fds, "Keep stdout/stderr open (default for --no-daemon)", NULL },
  401. { "close-fds", 0, 0, G_OPTION_ARG_NONE, &opts.close_fds, "Close stdout/stderr (default if not --no-daemon)", NULL },
  402. { "version", 'v', 0, G_OPTION_ARG_NONE, &opts.show_version, "Show version", NULL },
  403. { "chroot", 'c', 0, G_OPTION_ARG_FILENAME, &opts.chroot, "(root only) chroot to directory", "dir" },
  404. { "uid", 'u', 0, G_OPTION_ARG_STRING, &opts.uid, "(root only) change to user-id", "user" },
  405. { "gid", 'g', 0, G_OPTION_ARG_STRING, &opts.gid, "(root only) change to group-id", "group" },
  406. #ifdef USE_LIMITS
  407. { "limits", 'l', 0, G_OPTION_ARG_NONE, &opts.use_limits, "Set limits using /etc/security/limits.conf", NULL },
  408. #endif
  409. { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, &opts.args, "<fcgiapp> [fcgi app arguments]", NULL },
  410. { NULL, 0, 0, 0, NULL, NULL, NULL }
  411. };
  412. int main(int argc, char **argv) {
  413. GOptionContext *context;
  414. GError *error = NULL;
  415. int res;
  416. GString *tmp = g_string_sized_new(0);
  417. /* init */
  418. data.socket = -1;
  419. data.pid_fd = -1;
  420. data.i_am_root = (getuid() == 0);
  421. /* UID handling */
  422. if (!data.i_am_root && (geteuid() == 0 || getegid() == 0)) {
  423. /* we are setuid-root */
  424. g_printerr("Are you nuts ? Don't apply a SUID bit to this binary\n");
  425. return -1;
  426. }
  427. context = g_option_context_new("-- <fcgiapp> [fcgi app arguments]");
  428. g_option_context_add_main_entries(context, entries, NULL);
  429. g_option_context_set_summary(context, PACKAGE_NAME "-" PACKAGE_VERSION " - spawns fastcgi processes");
  430. if (!g_option_context_parse (context, &argc, &argv, &error)) {
  431. g_printerr("Option parsing failed: %s\n", error->message);
  432. return -1;
  433. }
  434. if (opts.show_version) {
  435. g_printerr(PACKAGE_NAME "-" PACKAGE_VERSION " - spawns fastcgi processes\n");
  436. g_printerr("Build-Date: " __DATE__ " " __TIME__ "\n");
  437. return 0;
  438. }
  439. /* Check options */
  440. opts.close_fds = opts.no_fork ? opts.close_fds : !opts.keep_fds;
  441. if ((opts.fcgiapp && opts.args) || (!opts.fcgiapp && !opts.args)) {
  442. g_printerr("Specify either a fcgi application with -f or the application with arguments after '--'\n");
  443. return -1;
  444. }
  445. if (0 != (res = create_sockaddr())) return res;
  446. if (0 != (res = bind_socket())) return res;
  447. if (0 != (res = open_pidfile())) return res;
  448. if (0 != (res = drop_priv())) return res;
  449. prepare_env();
  450. if (opts.no_fork) {
  451. start();
  452. } else {
  453. gint i;
  454. for (i = 0; i < opts.fork_childs; i++) {
  455. pid_t child = daemonize();
  456. if (child == (pid_t) -1) return -1;
  457. if (child == 0) start();
  458. /* write pid file */
  459. if (data.pid_fd != -1) {
  460. g_string_printf(tmp, "%d\n", child);
  461. if (i == opts.fork_childs-1) {
  462. /* avoid eol for the last one */
  463. write(data.pid_fd, tmp->str, tmp->len-1);
  464. } else {
  465. write(data.pid_fd, tmp->str, tmp->len);
  466. }
  467. }
  468. }
  469. }
  470. g_string_free(tmp, TRUE);
  471. return 0;
  472. }