Index: trunk/tools/switchboard2/swexec.c |
— | — | @@ -0,0 +1,368 @@ |
| 2 | +/* Copyright 2008 The Apache Software Foundation. */ |
| 3 | +/* Copyright 2008 River Tarnell <river@wikimedia.org */ |
| 4 | +/* Licensed to the Apache Software Foundation (ASF) under one or more |
| 5 | + * contributor license agreements. See the NOTICE file distributed with |
| 6 | + * this work for additional information regarding copyright ownership. |
| 7 | + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| 8 | + * (the "License"); you may not use this file except in compliance with |
| 9 | + * the License. You may obtain a copy of the License at |
| 10 | + * |
| 11 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 12 | + * |
| 13 | + * Unless required by applicable law or agreed to in writing, software |
| 14 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 15 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 16 | + * See the License for the specific language governing permissions and |
| 17 | + * limitations under the License. |
| 18 | + */ |
| 19 | + |
| 20 | +/* |
| 21 | + * suexec.c -- "Wrapper" support program for suEXEC behaviour for Apache |
| 22 | + */ |
| 23 | + |
| 24 | +#if defined(__sun) && defined(__svr4__) |
| 25 | +/* Solaris */ |
| 26 | +# define USE_PROJECTS |
| 27 | +#endif |
| 28 | + |
| 29 | +#define SB_SUEXEC_UMASK 022 |
| 30 | + |
| 31 | +#include <sys/param.h> |
| 32 | +#include <sys/stat.h> |
| 33 | +#include <sys/types.h> |
| 34 | +#include <string.h> |
| 35 | +#include <time.h> |
| 36 | +#include <unistd.h> |
| 37 | +#include <stdio.h> |
| 38 | +#include <stdarg.h> |
| 39 | +#include <stdlib.h> |
| 40 | +#include <errno.h> |
| 41 | + |
| 42 | +#ifdef USE_PROJECTS |
| 43 | +# include <sys/task.h> |
| 44 | +# include <project.h> |
| 45 | +#endif |
| 46 | + |
| 47 | +#include <pwd.h> |
| 48 | +#include <grp.h> |
| 49 | + |
| 50 | +#if defined(PATH_MAX) |
| 51 | +#define SB_MAXPATH PATH_MAX |
| 52 | +#elif defined(MAXPATHLEN) |
| 53 | +#define SB_MAXPATH MAXPATHLEN |
| 54 | +#else |
| 55 | +#define SB_MAXPATH 8192 |
| 56 | +#endif |
| 57 | + |
| 58 | +#define SB_ENVBUF 256 |
| 59 | + |
| 60 | +extern char **environ; |
| 61 | +static FILE *log = NULL; |
| 62 | + |
| 63 | +static const char *const safe_env_lst[] = |
| 64 | +{ |
| 65 | + "TZ=", |
| 66 | + NULL |
| 67 | +}; |
| 68 | + |
| 69 | + |
| 70 | +static void |
| 71 | +err_output(int is_error, const char *fmt, va_list ap) |
| 72 | +{ |
| 73 | + time_t timevar; |
| 74 | + struct tm *lt; |
| 75 | + |
| 76 | + if (!log) { |
| 77 | + if ((log = fopen(SB_LOG_EXEC, "a")) == NULL) { |
| 78 | + fprintf(stderr, "suexec failure: could not open log file\n"); |
| 79 | + perror("fopen"); |
| 80 | + exit(1); |
| 81 | + } |
| 82 | + } |
| 83 | + |
| 84 | + if (is_error) { |
| 85 | + fprintf(stderr, "suexec policy violation: see suexec log for more " |
| 86 | + "details\n"); |
| 87 | + } |
| 88 | + |
| 89 | + time(&timevar); |
| 90 | + lt = localtime(&timevar); |
| 91 | + |
| 92 | + fprintf(log, "[%d-%.2d-%.2d %.2d:%.2d:%.2d]: ", |
| 93 | + lt->tm_year + 1900, lt->tm_mon + 1, lt->tm_mday, |
| 94 | + lt->tm_hour, lt->tm_min, lt->tm_sec); |
| 95 | + |
| 96 | + vfprintf(log, fmt, ap); |
| 97 | + |
| 98 | + fflush(log); |
| 99 | +} |
| 100 | + |
| 101 | +static void |
| 102 | +log_err(const char *fmt,...) |
| 103 | +{ |
| 104 | + va_list ap; |
| 105 | + |
| 106 | + va_start(ap, fmt); |
| 107 | + err_output(1, fmt, ap); /* 1 == is_error */ |
| 108 | + va_end(ap); |
| 109 | +} |
| 110 | + |
| 111 | +static void |
| 112 | +log_no_err(const char *fmt,...) |
| 113 | +{ |
| 114 | + va_list ap; |
| 115 | + |
| 116 | + va_start(ap, fmt); |
| 117 | + err_output(0, fmt, ap); /* 0 == !is_error */ |
| 118 | + va_end(ap); |
| 119 | +} |
| 120 | + |
| 121 | +static void |
| 122 | +clean_env(void) |
| 123 | +{ |
| 124 | + char envbuf[512]; |
| 125 | + char **cleanenv; |
| 126 | + char **ep; |
| 127 | + int cidx = 0; |
| 128 | + int idx; |
| 129 | + |
| 130 | + /* While cleaning the environment, the environment should be clean. |
| 131 | + * (e.g. malloc() may get the name of a file for writing debugging info. |
| 132 | + * Bad news if MALLOC_DEBUG_FILE is set to /etc/passwd. Sprintf() may be |
| 133 | + * susceptible to bad locale settings....) |
| 134 | + * (from PR 2790) |
| 135 | + */ |
| 136 | + char **envp = environ; |
| 137 | + char *empty_ptr = NULL; |
| 138 | + |
| 139 | + environ = &empty_ptr; /* VERY safe environment */ |
| 140 | + |
| 141 | + if ((cleanenv = (char **) calloc(SB_ENVBUF, sizeof(char *))) == NULL) { |
| 142 | + log_err("failed to malloc memory for environment\n"); |
| 143 | + exit(120); |
| 144 | + } |
| 145 | + |
| 146 | + sprintf(envbuf, "PATH=%s", SB_SAFE_PATH); |
| 147 | + cleanenv[cidx] = strdup(envbuf); |
| 148 | + cidx++; |
| 149 | + sprintf(envbuf, "PHP_FCGI_MAX_REQUESTS=5000"); |
| 150 | + cleanenv[cidx] = strdup(envbuf); |
| 151 | + cidx++; |
| 152 | + |
| 153 | + for (ep = envp; *ep && cidx < SB_ENVBUF-1; ep++) { |
| 154 | + for (idx = 0; safe_env_lst[idx]; idx++) { |
| 155 | + if (!strncmp(*ep, safe_env_lst[idx], |
| 156 | + strlen(safe_env_lst[idx]))) { |
| 157 | + cleanenv[cidx] = *ep; |
| 158 | + cidx++; |
| 159 | + break; |
| 160 | + } |
| 161 | + } |
| 162 | + } |
| 163 | + |
| 164 | + cleanenv[cidx] = NULL; |
| 165 | + |
| 166 | + environ = cleanenv; |
| 167 | +} |
| 168 | + |
| 169 | +int |
| 170 | +main(int argc, char *argv[]) |
| 171 | +{ |
| 172 | + int userdir = 0; /* ~userdir flag */ |
| 173 | + uid_t uid; /* user information */ |
| 174 | + gid_t gid; /* target group placeholder */ |
| 175 | + char *target_uname; /* target user name */ |
| 176 | + char *target_gname; /* target group name */ |
| 177 | + char *target_homedir; /* target home directory */ |
| 178 | + char *actual_uname; /* actual user name */ |
| 179 | + char *actual_gname; /* actual group name */ |
| 180 | + char *prog; /* name of this program */ |
| 181 | + struct passwd *pw; /* password entry holder */ |
| 182 | + struct group *gr; /* group entry holder */ |
| 183 | +#ifdef USE_PROJECTS |
| 184 | + struct project proj; |
| 185 | + char nssbuf[PROJECT_BUFSZ]; |
| 186 | +#endif |
| 187 | + |
| 188 | + /* |
| 189 | + * Start with a "clean" environment |
| 190 | + */ |
| 191 | + clean_env(); |
| 192 | + |
| 193 | + prog = argv[0]; |
| 194 | + /* |
| 195 | + * Check existence/validity of the UID of the user |
| 196 | + * running this program. Error out if invalid. |
| 197 | + */ |
| 198 | + uid = getuid(); |
| 199 | + if ((pw = getpwuid(uid)) == NULL) { |
| 200 | + log_err("crit: invalid uid: (%ld)\n", (long) uid); |
| 201 | + exit(102); |
| 202 | + } |
| 203 | + |
| 204 | + /* |
| 205 | + * If there are a proper number of arguments, set |
| 206 | + * all of them to variables. Otherwise, error out. |
| 207 | + */ |
| 208 | + if (argc < 3) { |
| 209 | + log_err("too few arguments\n"); |
| 210 | + exit(101); |
| 211 | + } |
| 212 | + target_uname = argv[1]; |
| 213 | + target_gname = argv[2]; |
| 214 | + |
| 215 | + /* |
| 216 | + * Check to see if the user running this program |
| 217 | + * is the user allowed to do so as defined in |
| 218 | + * suexec.h. If not the allowed user, error out. |
| 219 | + */ |
| 220 | + if (strcmp(SB_USER, pw->pw_name)) { |
| 221 | + log_err("user mismatch (%s instead of %s)\n", pw->pw_name, SB_USER); |
| 222 | + exit(103); |
| 223 | + } |
| 224 | + |
| 225 | + /* |
| 226 | + * Error out if the target username is invalid. |
| 227 | + */ |
| 228 | + if (strspn(target_uname, "1234567890") != strlen(target_uname)) { |
| 229 | + if ((pw = getpwnam(target_uname)) == NULL) { |
| 230 | + log_err("invalid target user name: (%s)\n", target_uname); |
| 231 | + exit(105); |
| 232 | + } |
| 233 | + } else { |
| 234 | + if ((pw = getpwuid(atoi(target_uname))) == NULL) { |
| 235 | + log_err("invalid target user id: (%s)\n", target_uname); |
| 236 | + exit(121); |
| 237 | + } |
| 238 | + } |
| 239 | + |
| 240 | + /* |
| 241 | + * Error out if the target group name is invalid. |
| 242 | + */ |
| 243 | + if (strspn(target_gname, "1234567890") != strlen(target_gname)) { |
| 244 | + if ((gr = getgrnam(target_gname)) == NULL) { |
| 245 | + log_err("invalid target group name: (%s)\n", target_gname); |
| 246 | + exit(106); |
| 247 | + } |
| 248 | + } else { |
| 249 | + if ((gr = getgrgid(atoi(target_gname))) == NULL) { |
| 250 | + log_err("invalid target group id: (%s)\n", target_gname); |
| 251 | + exit(106); |
| 252 | + } |
| 253 | + } |
| 254 | + gid = gr->gr_gid; |
| 255 | + actual_gname = strdup(gr->gr_name); |
| 256 | + |
| 257 | + /* |
| 258 | + * Save these for later since initgroups will hose the struct |
| 259 | + */ |
| 260 | + uid = pw->pw_uid; |
| 261 | + actual_uname = strdup(pw->pw_name); |
| 262 | + target_homedir = strdup(pw->pw_dir); |
| 263 | + |
| 264 | + /* |
| 265 | + * Log the transaction here to be sure we have an open log |
| 266 | + * before we setuid(). |
| 267 | + */ |
| 268 | + log_no_err("uid: (%s/%s) gid: (%s/%s) cmd: %s\n", |
| 269 | + target_uname, actual_uname, |
| 270 | + target_gname, actual_gname, |
| 271 | + PHP_BIN); |
| 272 | + |
| 273 | + /* |
| 274 | + * Error out if attempt is made to execute as root or as |
| 275 | + * a UID less than SB_UID_MIN. Tsk tsk. |
| 276 | + */ |
| 277 | + if ((uid == 0) || (uid < SB_UID_MIN)) { |
| 278 | + log_err("cannot run as forbidden uid (%d/%s)\n", uid, PHP_BIN); |
| 279 | + exit(107); |
| 280 | + } |
| 281 | + |
| 282 | + /* |
| 283 | + * Error out if attempt is made to execute as root group |
| 284 | + * or as a GID less than SB_GID_MIN. Tsk tsk. |
| 285 | + */ |
| 286 | + if ((gid == 0) || (gid < SB_GID_MIN)) { |
| 287 | + log_err("cannot run as forbidden gid (%d/%s)\n", gid, PHP_BIN); |
| 288 | + exit(108); |
| 289 | + } |
| 290 | + |
| 291 | +#ifdef USE_PROJECTS |
| 292 | + if (getdefaultproj(actual_uname, &proj, nssbuf, sizeof(nssbuf)) == NULL) { |
| 293 | + log_err("failed to get default project (%s: %s)", |
| 294 | + actual_uname, strerror(errno)); |
| 295 | + exit(113); |
| 296 | + } |
| 297 | + |
| 298 | + if (setproject(proj.pj_name, actual_uname, TASK_FINAL) != 0) { |
| 299 | + log_err("failed to set project (%s: %s)", |
| 300 | + actual_uname, strerror(errno)); |
| 301 | + exit(114); |
| 302 | + } |
| 303 | +#endif |
| 304 | + |
| 305 | + /* |
| 306 | + * Change UID/GID here so that the following tests work over NFS. |
| 307 | + * |
| 308 | + * Initialize the group access list for the target user, |
| 309 | + * and setgid() to the target group. If unsuccessful, error out. |
| 310 | + */ |
| 311 | + if (((setgid(gid)) != 0) || (initgroups(actual_uname, gid) != 0)) { |
| 312 | + log_err("failed to setgid (%ld: %s)\n", (long) gid, PHP_BIN); |
| 313 | + exit(109); |
| 314 | + } |
| 315 | + |
| 316 | + /* |
| 317 | + * setuid() to the target user. Error out on fail. |
| 318 | + */ |
| 319 | + if ((setuid(uid)) != 0) { |
| 320 | + log_err("failed to setuid (%ld: %s)\n", (long) uid, PHP_BIN); |
| 321 | + exit(110); |
| 322 | + } |
| 323 | + |
| 324 | + if (chdir(target_homedir) != 0) { |
| 325 | + log_err("cannot chdir to home directory (%s)\n", target_homedir); |
| 326 | + exit(112); |
| 327 | + } |
| 328 | + |
| 329 | +#ifdef SB_SUEXEC_UMASK |
| 330 | + /* |
| 331 | + * umask() uses inverse logic; bits are CLEAR for allowed access. |
| 332 | + */ |
| 333 | + if ((~SB_SUEXEC_UMASK) & 0022) { |
| 334 | + log_err("notice: SB_SUEXEC_UMASK of %03o allows " |
| 335 | + "write permission to group and/or other\n", SB_SUEXEC_UMASK); |
| 336 | + } |
| 337 | + umask(SB_SUEXEC_UMASK); |
| 338 | +#endif /* SB_SUEXEC_UMASK */ |
| 339 | + |
| 340 | + /* |
| 341 | + * Be sure to close the log file so the CGI can't |
| 342 | + * mess with it. If the exec fails, it will be reopened |
| 343 | + * automatically when log_err is called. Note that the log |
| 344 | + * might not actually be open if SB_LOG_EXEC isn't defined. |
| 345 | + * However, the "log" call isn't ifdef'd so let's be defensive |
| 346 | + * and assume someone might have done something with it |
| 347 | + * outside an ifdef'd SB_LOG_EXEC block. |
| 348 | + */ |
| 349 | + if (log != NULL) { |
| 350 | + fclose(log); |
| 351 | + log = NULL; |
| 352 | + } |
| 353 | + |
| 354 | + /* |
| 355 | + * Execute the command, replacing our image with its own. |
| 356 | + */ |
| 357 | + execl(PHP_BIN, PHP_BIN, NULL); |
| 358 | + |
| 359 | + /* |
| 360 | + * (I can't help myself...sorry.) |
| 361 | + * |
| 362 | + * Uh oh. Still here. Where's the kaboom? There was supposed to be an |
| 363 | + * EARTH-shattering kaboom! |
| 364 | + * |
| 365 | + * Oh well, log the failure and error out. |
| 366 | + */ |
| 367 | + log_err("(%d)%s: exec failed (%s)\n", errno, strerror(errno), PHP_BIN); |
| 368 | + exit(255); |
| 369 | +} |
Index: trunk/tools/switchboard2/swkill.c |
— | — | @@ -0,0 +1,247 @@ |
| 2 | +/* Licensed to the Apache Software Foundation (ASF) under one or more |
| 3 | + * contributor license agreements. See the NOTICE file distributed with |
| 4 | + * this work for additional information regarding copyright ownership. |
| 5 | + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| 6 | + * (the "License"); you may not use this file except in compliance with |
| 7 | + * the License. You may obtain a copy of the License at |
| 8 | + * |
| 9 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | + * |
| 11 | + * Unless required by applicable law or agreed to in writing, software |
| 12 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | + * See the License for the specific language governing permissions and |
| 15 | + * limitations under the License. |
| 16 | + */ |
| 17 | + |
| 18 | +/* |
| 19 | + * suexec.c -- "Wrapper" support program for suEXEC behaviour for Apache |
| 20 | + * |
| 21 | + *********************************************************************** |
| 22 | + * |
| 23 | + * NOTE! : DO NOT edit this code!!! Unless you know what you are doing, |
| 24 | + * editing this code might open up your system in unexpected |
| 25 | + * ways to would-be crackers. Every precaution has been taken |
| 26 | + * to make this code as safe as possible; alter it at your own |
| 27 | + * risk. |
| 28 | + * |
| 29 | + *********************************************************************** |
| 30 | + * |
| 31 | + * |
| 32 | + */ |
| 33 | + |
| 34 | +#define SB_SUEXEC_UMASK 022 |
| 35 | + |
| 36 | +#include <sys/param.h> |
| 37 | +#include <sys/stat.h> |
| 38 | +#include <sys/types.h> |
| 39 | +#include <string.h> |
| 40 | +#include <time.h> |
| 41 | +#include <unistd.h> |
| 42 | +#include <stdio.h> |
| 43 | +#include <stdarg.h> |
| 44 | +#include <stdlib.h> |
| 45 | +#include <errno.h> |
| 46 | +#include <signal.h> |
| 47 | + |
| 48 | +#include <pwd.h> |
| 49 | +#include <grp.h> |
| 50 | + |
| 51 | +#if defined(PATH_MAX) |
| 52 | +#define SB_MAXPATH PATH_MAX |
| 53 | +#elif defined(MAXPATHLEN) |
| 54 | +#define SB_MAXPATH MAXPATHLEN |
| 55 | +#else |
| 56 | +#define SB_MAXPATH 8192 |
| 57 | +#endif |
| 58 | + |
| 59 | +#define SB_ENVBUF 256 |
| 60 | + |
| 61 | +static FILE *log = NULL; |
| 62 | + |
| 63 | +static void err_output(int is_error, const char *fmt, va_list ap) |
| 64 | +{ |
| 65 | +#ifdef SB_LOG_EXEC |
| 66 | + time_t timevar; |
| 67 | + struct tm *lt; |
| 68 | + |
| 69 | + if (!log) { |
| 70 | + if ((log = fopen(SB_LOG_EXEC, "a")) == NULL) { |
| 71 | + fprintf(stderr, "suexec failure: could not open log file\n"); |
| 72 | + perror("fopen"); |
| 73 | + exit(1); |
| 74 | + } |
| 75 | + } |
| 76 | + |
| 77 | + if (is_error) { |
| 78 | + fprintf(stderr, "suexec policy violation: see suexec log for more " |
| 79 | + "details\n"); |
| 80 | + } |
| 81 | + |
| 82 | + time(&timevar); |
| 83 | + lt = localtime(&timevar); |
| 84 | + |
| 85 | + fprintf(log, "[%d-%.2d-%.2d %.2d:%.2d:%.2d]: swkill: ", |
| 86 | + lt->tm_year + 1900, lt->tm_mon + 1, lt->tm_mday, |
| 87 | + lt->tm_hour, lt->tm_min, lt->tm_sec); |
| 88 | + |
| 89 | + vfprintf(log, fmt, ap); |
| 90 | + |
| 91 | + fflush(log); |
| 92 | +#endif /* SB_LOG_EXEC */ |
| 93 | + return; |
| 94 | +} |
| 95 | + |
| 96 | +static void log_err(const char *fmt,...) |
| 97 | +{ |
| 98 | +#ifdef SB_LOG_EXEC |
| 99 | + va_list ap; |
| 100 | + |
| 101 | + va_start(ap, fmt); |
| 102 | + err_output(1, fmt, ap); /* 1 == is_error */ |
| 103 | + va_end(ap); |
| 104 | +#endif /* SB_LOG_EXEC */ |
| 105 | + return; |
| 106 | +} |
| 107 | + |
| 108 | +static void log_no_err(const char *fmt,...) |
| 109 | +{ |
| 110 | +#ifdef SB_LOG_EXEC |
| 111 | + va_list ap; |
| 112 | + |
| 113 | + va_start(ap, fmt); |
| 114 | + err_output(0, fmt, ap); /* 0 == !is_error */ |
| 115 | + va_end(ap); |
| 116 | +#endif /* SB_LOG_EXEC */ |
| 117 | + return; |
| 118 | +} |
| 119 | + |
| 120 | +int main(int argc, char *argv[]) |
| 121 | +{ |
| 122 | + char *prog; |
| 123 | + char *target_uname, *target_gname; |
| 124 | + char *actual_uname, *actual_gname; |
| 125 | + uid_t uid; /* user information */ |
| 126 | + gid_t gid; /* target group placeholder */ |
| 127 | + pid_t target_pid; |
| 128 | + struct passwd *pw; /* password entry holder */ |
| 129 | + struct group *gr; /* group entry holder */ |
| 130 | + |
| 131 | + prog = argv[0]; |
| 132 | + /* |
| 133 | + * Check existence/validity of the UID of the user |
| 134 | + * running this program. Error out if invalid. |
| 135 | + */ |
| 136 | + uid = getuid(); |
| 137 | + if ((pw = getpwuid(uid)) == NULL) { |
| 138 | + log_err("crit: invalid uid: (%ld)\n", uid); |
| 139 | + exit(102); |
| 140 | + } |
| 141 | + |
| 142 | + /* |
| 143 | + * If there are a proper number of arguments, set |
| 144 | + * all of them to variables. Otherwise, error out. |
| 145 | + */ |
| 146 | + if (argc < 4) { |
| 147 | + log_err("too few arguments\n"); |
| 148 | + exit(101); |
| 149 | + } |
| 150 | + target_uname = argv[1]; |
| 151 | + target_gname = argv[2]; |
| 152 | + target_pid = atoi(argv[3]); |
| 153 | + |
| 154 | + if (target_pid < 1) { |
| 155 | + log_err("invalid target pid (%s)\n", argv[3]); |
| 156 | + exit(102); |
| 157 | + } |
| 158 | + |
| 159 | + /* |
| 160 | + * Check to see if the user running this program |
| 161 | + * is the user allowed to do so as defined in |
| 162 | + * suexec.h. If not the allowed user, error out. |
| 163 | + */ |
| 164 | + if (strcmp(SB_USER, pw->pw_name)) { |
| 165 | + log_err("user mismatch (%s instead of %s)\n", pw->pw_name, SB_USER); |
| 166 | + exit(103); |
| 167 | + } |
| 168 | + |
| 169 | + /* |
| 170 | + * Error out if the target username is invalid. |
| 171 | + */ |
| 172 | + if (strspn(target_uname, "1234567890") != strlen(target_uname)) { |
| 173 | + if ((pw = getpwnam(target_uname)) == NULL) { |
| 174 | + log_err("invalid target user name: (%s)\n", target_uname); |
| 175 | + exit(105); |
| 176 | + } |
| 177 | + } |
| 178 | + else { |
| 179 | + if ((pw = getpwuid(atoi(target_uname))) == NULL) { |
| 180 | + log_err("invalid target user id: (%s)\n", target_uname); |
| 181 | + exit(121); |
| 182 | + } |
| 183 | + } |
| 184 | + |
| 185 | + /* |
| 186 | + * Error out if the target group name is invalid. |
| 187 | + */ |
| 188 | + if (strspn(target_gname, "1234567890") != strlen(target_gname)) { |
| 189 | + if ((gr = getgrnam(target_gname)) == NULL) { |
| 190 | + log_err("invalid target group name: (%s)\n", target_gname); |
| 191 | + exit(106); |
| 192 | + } |
| 193 | + } |
| 194 | + else { |
| 195 | + if ((gr = getgrgid(atoi(target_gname))) == NULL) { |
| 196 | + log_err("invalid target group id: (%s)\n", target_gname); |
| 197 | + exit(106); |
| 198 | + } |
| 199 | + } |
| 200 | + gid = gr->gr_gid; |
| 201 | + actual_gname = strdup(gr->gr_name); |
| 202 | + |
| 203 | + /* |
| 204 | + * Save these for later since initgroups will hose the struct |
| 205 | + */ |
| 206 | + uid = pw->pw_uid; |
| 207 | + actual_uname = strdup(pw->pw_name); |
| 208 | + |
| 209 | + /* |
| 210 | + * Error out if attempt is made to execute as root or as |
| 211 | + * a UID less than SB_UID_MIN. Tsk tsk. |
| 212 | + */ |
| 213 | + if ((uid == 0) || (uid < SB_UID_MIN)) { |
| 214 | + log_err("cannot run as forbidden uid (%d)\n", uid); |
| 215 | + exit(107); |
| 216 | + } |
| 217 | + |
| 218 | + /* |
| 219 | + * Error out if attempt is made to execute as root group |
| 220 | + * or as a GID less than SB_GID_MIN. Tsk tsk. |
| 221 | + */ |
| 222 | + if ((gid == 0) || (gid < SB_GID_MIN)) { |
| 223 | + log_err("cannot run as forbidden gid (%d)\n", gid); |
| 224 | + exit(108); |
| 225 | + } |
| 226 | + |
| 227 | + /* |
| 228 | + * Change UID/GID here so that the following tests work over NFS. |
| 229 | + * |
| 230 | + * Initialize the group access list for the target user, |
| 231 | + * and setgid() to the target group. If unsuccessful, error out. |
| 232 | + */ |
| 233 | + if (((setgid(gid)) != 0) || (initgroups(actual_uname, gid) != 0)) { |
| 234 | + log_err("failed to setgid (%ld)\n", gid); |
| 235 | + exit(109); |
| 236 | + } |
| 237 | + |
| 238 | + /* |
| 239 | + * setuid() to the target user. Error out on fail. |
| 240 | + */ |
| 241 | + if ((setuid(uid)) != 0) { |
| 242 | + log_err("failed to setuid (%ld)\n", uid); |
| 243 | + exit(110); |
| 244 | + } |
| 245 | + |
| 246 | + kill(target_pid, 9); |
| 247 | + exit(0); |
| 248 | +} |
Index: trunk/tools/switchboard2/config.cc |
— | — | @@ -0,0 +1,296 @@ |
| 2 | +/* Copyright (c) 2008 River Tarnell <river@wikimedia.org>. */ |
| 3 | +/* |
| 4 | + * Permission is granted to anyone to use this software for any purpose, |
| 5 | + * including commercial applications, and to alter it and redistribute it |
| 6 | + * freely. This software is provided 'as-is', without any express or implied |
| 7 | + * warranty. |
| 8 | + */ |
| 9 | +/* $Id$ */ |
| 10 | + |
| 11 | +#include <string> |
| 12 | +#include <fstream> |
| 13 | +#include <cerrno> |
| 14 | + |
| 15 | +#include <boost/format.hpp> |
| 16 | +#include <boost/tokenizer.hpp> |
| 17 | +#include <boost/assign/list_of.hpp> |
| 18 | +#include <boost/bind.hpp> |
| 19 | +#include <boost/lexical_cast.hpp> |
| 20 | +using boost::format; |
| 21 | + |
| 22 | +#include <log4cxx/logger.h> |
| 23 | + |
| 24 | +#include "config.h" |
| 25 | + |
| 26 | +config mainconf; |
| 27 | + |
| 28 | +config::config() |
| 29 | + : max_procs(0) |
| 30 | + , max_procs_per_user(0) |
| 31 | +{ |
| 32 | +} |
| 33 | + |
| 34 | +configuration_loader::configuration_loader() |
| 35 | + : logger(log4cxx::Logger::getLogger("config")) |
| 36 | +{ |
| 37 | +} |
| 38 | + |
| 39 | +bool |
| 40 | +configuration_loader::load(std::string const &filename, config &newconf) |
| 41 | +{ |
| 42 | + std::string line; |
| 43 | + std::ifstream file(filename.c_str()); |
| 44 | + |
| 45 | + lineno_ = 0; |
| 46 | + file_ = filename; |
| 47 | + |
| 48 | + if (!file) { |
| 49 | + LOG4CXX_ERROR(logger, |
| 50 | + format("cannot open configuration file \"%s\": %s") |
| 51 | + % filename % std::strerror(errno)); |
| 52 | + return false; |
| 53 | + } |
| 54 | + |
| 55 | + while (std::getline(file, line)) { |
| 56 | + lineno_++; |
| 57 | + |
| 58 | + if (line.empty() || line[0] == '#') |
| 59 | + continue; |
| 60 | + |
| 61 | + if (!parse_config_line(line, newconf)) |
| 62 | + return false; |
| 63 | + } |
| 64 | + |
| 65 | + LOG4CXX_INFO(logger, |
| 66 | + format("read configuration from \"%s\"") |
| 67 | + % filename); |
| 68 | + |
| 69 | + return true; |
| 70 | +} |
| 71 | + |
| 72 | +std::map<std::string, configuration_loader::confline_t> |
| 73 | + configuration_loader::conflines = |
| 74 | + boost::assign::map_list_of |
| 75 | + ("listen", boost::bind(&configuration_loader::f_listen, _1, _2, _3)) |
| 76 | + ("logconf", boost::bind(&configuration_loader::f_logconf, _1, _2, _3)) |
| 77 | + ("sockdir", boost::bind(&configuration_loader::f_sockdir, _1, _2, _3)) |
| 78 | + ("docroot", boost::bind(&configuration_loader::f_docroot, _1, _2, _3)) |
| 79 | + ("userdir", boost::bind(&configuration_loader::f_userdir, _1, _2, _3)) |
| 80 | + ("max-procs", boost::bind(&configuration_loader::f_max_procs, _1, _2, _3)) |
| 81 | + ("max-procs-per-user", boost::bind(&configuration_loader::f_max_procs_per_user, _1, _2, _3)) |
| 82 | + ("max-q-per-user", boost::bind(&configuration_loader::f_max_q_per_user, _1, _2, _3)) |
| 83 | + ("server-type", boost::bind(&configuration_loader::f_server_type, _1, _2, _3)) |
| 84 | + ; |
| 85 | + |
| 86 | +bool |
| 87 | +configuration_loader::parse_config_line( |
| 88 | + std::string const &line, |
| 89 | + config &newconf) |
| 90 | +{ |
| 91 | + boost::escaped_list_separator<char> sep('\\', ' ', '"'); |
| 92 | + boost::tokenizer<boost::escaped_list_separator<char> > tok(line, sep); |
| 93 | + std::vector<std::string> fields; |
| 94 | + |
| 95 | + std::copy(tok.begin(), tok.end(), std::back_inserter(fields)); |
| 96 | + |
| 97 | + if (fields.empty()) |
| 98 | + return false; |
| 99 | + |
| 100 | + std::map<std::string, confline_t>::iterator it = |
| 101 | + conflines.find(fields[0]); |
| 102 | + |
| 103 | + if ((it = conflines.find(fields[0])) == conflines.end()) { |
| 104 | + LOG4CXX_ERROR(logger, |
| 105 | + format("\"%s\", line %d: unknown directive: \"%s\"") |
| 106 | + % file_ % lineno_ % fields[0]); |
| 107 | + return false; |
| 108 | + } |
| 109 | + |
| 110 | + it->second(this, fields, newconf); |
| 111 | + |
| 112 | + return true; |
| 113 | +} |
| 114 | + |
| 115 | +bool |
| 116 | +configuration_loader::f_listen( |
| 117 | + std::vector<std::string> &fields, |
| 118 | + config &newconf) |
| 119 | +{ |
| 120 | + if (fields.size() < 2 || |
| 121 | + (fields[1][0] == '/' && fields.size() != 2) || |
| 122 | + (fields[1][0] != '/' && fields.size() != 3)) { |
| 123 | + LOG4CXX_ERROR(logger, |
| 124 | + format("\"%s\", line %d: usage: listen <address> <port>\n") |
| 125 | + % file_ % lineno_); |
| 126 | + LOG4CXX_ERROR(logger, |
| 127 | + format("\"%s\", line %d: listen <pathname>\n") |
| 128 | + % file_ % lineno_); |
| 129 | + return false; |
| 130 | + } |
| 131 | + |
| 132 | + conf_listener lsnr; |
| 133 | + lsnr.host = fields[1]; |
| 134 | + if (fields.size() == 3) |
| 135 | + lsnr.port = fields[2]; |
| 136 | + newconf.listeners.push_back(lsnr); |
| 137 | + return true; |
| 138 | +} |
| 139 | + |
| 140 | +bool |
| 141 | +configuration_loader::f_logconf( |
| 142 | + std::vector<std::string> &fields, |
| 143 | + config &newconf) |
| 144 | +{ |
| 145 | + if (fields.size() != 2) { |
| 146 | + LOG4CXX_ERROR(logger, |
| 147 | + format("\"%s\", line %d: usage: logconf <file>\n") |
| 148 | + % file_ % lineno_); |
| 149 | + return false; |
| 150 | + } |
| 151 | + |
| 152 | + newconf.logconf = fields[1]; |
| 153 | + return true; |
| 154 | +} |
| 155 | + |
| 156 | +bool |
| 157 | +configuration_loader::f_sockdir( |
| 158 | + std::vector<std::string> &fields, |
| 159 | + config &newconf) |
| 160 | +{ |
| 161 | + if (fields.size() != 2) { |
| 162 | + LOG4CXX_ERROR(logger, |
| 163 | + format("\"%s\", line %d: usage: sockdir <directory>\n") |
| 164 | + % file_ % lineno_); |
| 165 | + return false; |
| 166 | + } |
| 167 | + |
| 168 | + newconf.sockdir = fields[1]; |
| 169 | + return true; |
| 170 | +} |
| 171 | + |
| 172 | +bool |
| 173 | +configuration_loader::f_userdir( |
| 174 | + std::vector<std::string> &fields, |
| 175 | + config &newconf) |
| 176 | +{ |
| 177 | + if (fields.size() != 2) { |
| 178 | + LOG4CXX_ERROR(logger, |
| 179 | + format("\"%s\", line %d: usage: userdir <directory>\n") |
| 180 | + % file_ % lineno_); |
| 181 | + return false; |
| 182 | + } |
| 183 | + |
| 184 | + newconf.userdir = fields[1]; |
| 185 | + return true; |
| 186 | +} |
| 187 | + |
| 188 | +bool |
| 189 | +configuration_loader::f_docroot( |
| 190 | + std::vector<std::string> &fields, |
| 191 | + config &newconf) |
| 192 | +{ |
| 193 | + if (fields.size() != 2) { |
| 194 | + LOG4CXX_ERROR(logger, |
| 195 | + format("\"%s\", line %d: usage: docroot <directory>\n") |
| 196 | + % file_ % lineno_); |
| 197 | + return false; |
| 198 | + } |
| 199 | + |
| 200 | + newconf.docroot = fields[1]; |
| 201 | + return true; |
| 202 | +} |
| 203 | + |
| 204 | +bool |
| 205 | +configuration_loader::f_max_procs( |
| 206 | + std::vector<std::string> &fields, |
| 207 | + config &newconf) |
| 208 | +{ |
| 209 | + if (fields.size() != 2) { |
| 210 | + LOG4CXX_ERROR(logger, |
| 211 | + format("\"%s\", line %d: usage: max-procs <number>\n") |
| 212 | + % file_ % lineno_); |
| 213 | + return false; |
| 214 | + } |
| 215 | + |
| 216 | + try { |
| 217 | + newconf.max_procs = boost::lexical_cast<int>(fields[1]); |
| 218 | + return true; |
| 219 | + } catch (boost::bad_lexical_cast &e) { |
| 220 | + LOG4CXX_ERROR(logger, |
| 221 | + format("\"%s\", line %d: usage: max-procs <number>\n") |
| 222 | + % file_ % lineno_); |
| 223 | + return false; |
| 224 | + } |
| 225 | +} |
| 226 | + |
| 227 | +bool |
| 228 | +configuration_loader::f_max_procs_per_user( |
| 229 | + std::vector<std::string> &fields, |
| 230 | + config &newconf) |
| 231 | +{ |
| 232 | + if (fields.size() != 2) { |
| 233 | + LOG4CXX_ERROR(logger, |
| 234 | + format("\"%s\", line %d: usage: max-procs-per-user <number>\n") |
| 235 | + % file_ % lineno_); |
| 236 | + return false; |
| 237 | + } |
| 238 | + |
| 239 | + try { |
| 240 | + newconf.max_procs_per_user = boost::lexical_cast<int>(fields[1]); |
| 241 | + return true; |
| 242 | + } catch (boost::bad_lexical_cast &e) { |
| 243 | + LOG4CXX_ERROR(logger, |
| 244 | + format("\"%s\", line %d: usage: max-procs-per-user <number>\n") |
| 245 | + % file_ % lineno_); |
| 246 | + return false; |
| 247 | + } |
| 248 | +} |
| 249 | + |
| 250 | +bool |
| 251 | +configuration_loader::f_max_q_per_user( |
| 252 | + std::vector<std::string> &fields, |
| 253 | + config &newconf) |
| 254 | +{ |
| 255 | + if (fields.size() != 2) { |
| 256 | + LOG4CXX_ERROR(logger, |
| 257 | + format("\"%s\", line %d: usage: max-q-per-user <number>\n") |
| 258 | + % file_ % lineno_); |
| 259 | + return false; |
| 260 | + } |
| 261 | + |
| 262 | + try { |
| 263 | + newconf.max_q_per_user = boost::lexical_cast<int>(fields[1]); |
| 264 | + return true; |
| 265 | + } catch (boost::bad_lexical_cast &e) { |
| 266 | + LOG4CXX_ERROR(logger, |
| 267 | + format("\"%s\", line %d: usage: max-q-per-user <number>\n") |
| 268 | + % file_ % lineno_); |
| 269 | + return false; |
| 270 | + } |
| 271 | +} |
| 272 | + |
| 273 | +bool |
| 274 | +configuration_loader::f_server_type( |
| 275 | + std::vector<std::string> &fields, |
| 276 | + config &newconf) |
| 277 | +{ |
| 278 | + if (fields.size() != 2) { |
| 279 | + LOG4CXX_ERROR(logger, |
| 280 | + format("\"%s\", line %d: usage: server-type <sjs|apache>\n") |
| 281 | + % file_ % lineno_); |
| 282 | + return false; |
| 283 | + } |
| 284 | + |
| 285 | + if (fields[1] == "sjs") |
| 286 | + newconf.servtype = serv_sjs; |
| 287 | + else if (fields[1] == "apache") |
| 288 | + newconf.servtype = serv_apache; |
| 289 | + else { |
| 290 | + LOG4CXX_ERROR(logger, |
| 291 | + format("\"%s\", line %d: usage: server-type <sjs|apache>\n") |
| 292 | + % file_ % lineno_); |
| 293 | + return false; |
| 294 | + } |
| 295 | + |
| 296 | + return true; |
| 297 | +} |
Index: trunk/tools/switchboard2/process_factory.cc |
— | — | @@ -0,0 +1,184 @@ |
| 2 | +/* Copyright (c) 2008 River Tarnell <river@wikimedia.org>. */ |
| 3 | +/* |
| 4 | + * Permission is granted to anyone to use this software for any purpose, |
| 5 | + * including commercial applications, and to alter it and redistribute it |
| 6 | + * freely. This software is provided 'as-is', without any express or implied |
| 7 | + * warranty. |
| 8 | + */ |
| 9 | +/* $Id$ */ |
| 10 | + |
| 11 | +#include <sys/stat.h> |
| 12 | +#include <pwd.h> |
| 13 | + |
| 14 | +#include <cerrno> |
| 15 | + |
| 16 | +#include "process_factory.h" |
| 17 | +#include "config.h" |
| 18 | +#include "util.h" |
| 19 | + |
| 20 | +process_factory & |
| 21 | +process_factory::instance() |
| 22 | +{ |
| 23 | + static process_factory inst; |
| 24 | + return inst; |
| 25 | +} |
| 26 | + |
| 27 | +process_factory::process_factory() |
| 28 | + : id_(0) |
| 29 | +{ |
| 30 | +} |
| 31 | + |
| 32 | +process_factory::~process_factory() |
| 33 | +{ |
| 34 | +} |
| 35 | + |
| 36 | +processp |
| 37 | +process_factory::get_process( |
| 38 | + std::map<std::string, std::string> ¶ms) |
| 39 | +{ |
| 40 | + std::map<std::string, std::string>::const_iterator it; |
| 41 | + |
| 42 | + uid_t uid; |
| 43 | + gid_t gid; |
| 44 | + |
| 45 | + std::string script_path; |
| 46 | + |
| 47 | + /* |
| 48 | + * Trying to find the script from path the env is a mess. Under SJS |
| 49 | + * web server, we take the value from SCRIPT_NAME, and translate it |
| 50 | + * into a path on disk using the 'docroot' and 'userdir' configuration |
| 51 | + * options. Under Apache, this doesn't work, because SCRIPT_NAME |
| 52 | + * contains garbage. Instead we take PATH_TRANSLATED, which is the |
| 53 | + * on-disk path with the PATH_INFO appended, and remove path components |
| 54 | + * from it until we end up with a path which exists. |
| 55 | + * |
| 56 | + * Other web servers might require different handling; I haven't tested |
| 57 | + * any other than SJS and Apache. |
| 58 | + */ |
| 59 | + if (mainconf.servtype == serv_apache) { |
| 60 | + if ((it = params.find("PATH_TRANSLATED")) == params.end()) |
| 61 | + throw creation_failure("PATH_TRANSLATED not specified"); |
| 62 | + |
| 63 | + std::string s = it->second; |
| 64 | + struct stat sb; |
| 65 | + bool err = false; |
| 66 | + |
| 67 | + while (stat(s.c_str(), &sb) == -1 |
| 68 | + && errno == ENOTDIR) |
| 69 | + { |
| 70 | + std::string::size_type n; |
| 71 | + if ((n = s.rfind('/')) == std::string::npos) |
| 72 | + throw creation_failure("script not found"); |
| 73 | + |
| 74 | + s.erase(n); |
| 75 | + } |
| 76 | + |
| 77 | + script_path = s; |
| 78 | + } else if (mainconf.servtype == serv_sjs) { |
| 79 | + if ((it = params.find("SCRIPT_NAME")) == params.end()) |
| 80 | + throw creation_failure("neither SCRIPT_NAME nor PATH_TRANSLATED specified"); |
| 81 | + |
| 82 | + std::string script_name = it->second; |
| 83 | + if (script_name.empty()) |
| 84 | + throw creation_failure("SCRIPT_NAME is empty"); |
| 85 | + |
| 86 | + if (script_name.size() >= 2 && |
| 87 | + script_name[0] == '/' && script_name[1] == '~') { |
| 88 | + /* |
| 89 | + * The format is /~user/path/to/script.php |
| 90 | + * We need to change it to |
| 91 | + * /home/user/public_html/path/to/script.php. |
| 92 | + */ |
| 93 | + std::string username; |
| 94 | + std::string script; |
| 95 | + script_name.erase(script_name.begin(), script_name.begin() + 2); |
| 96 | + if (script_name.empty()) |
| 97 | + throw creation_failure("invalid SCRIPT_NAME"); |
| 98 | + std::string::size_type n = script_name.find('/'); |
| 99 | + username.assign(script_name.begin(), script_name.begin() + n); |
| 100 | + script.assign(script_name.begin() + n + 1, script_name.end()); |
| 101 | + |
| 102 | + struct passwd *pwd; |
| 103 | + if ((pwd = getpwnam(username.c_str())) == NULL) |
| 104 | + throw creation_failure("user does not exist"); |
| 105 | + script_path = std::string(pwd->pw_dir) + '/' + mainconf.userdir |
| 106 | + + '/' + script; |
| 107 | + } else { |
| 108 | + /* |
| 109 | + * Script is relative to docroot. |
| 110 | + */ |
| 111 | + script_path = mainconf.docroot + script_name; |
| 112 | + } |
| 113 | + |
| 114 | + params["SCRIPT_FILENAME"] = script_path; |
| 115 | + } |
| 116 | + |
| 117 | + struct stat sb; |
| 118 | + if (lstat(script_path.c_str(), &sb) == 0) { |
| 119 | + uid = sb.st_uid; |
| 120 | + gid = sb.st_gid; |
| 121 | + |
| 122 | + /* |
| 123 | + * Make sure the path is under the docroot or the |
| 124 | + * user's userdir. |
| 125 | + */ |
| 126 | + std::string x = mainconf.docroot + '/'; |
| 127 | + if (script_path.substr(0, x.size()) != x) { |
| 128 | + struct passwd *pwd = getpwuid(uid); |
| 129 | + if (pwd == NULL) |
| 130 | + throw creation_failure("script owner doesn't exist"); |
| 131 | + x = std::string(pwd->pw_dir) + '/' + mainconf.userdir + '/'; |
| 132 | + if (script_path.substr(0, x.size()) != x) |
| 133 | + throw creation_failure("script not under docroot or userdir"); |
| 134 | + } |
| 135 | + } else |
| 136 | + throw creation_failure("script doesn't exist"); |
| 137 | + |
| 138 | + std::ostringstream strm; |
| 139 | + strm << mainconf.sockdir << '/' << "php_" << uid << "_" << id_++; |
| 140 | + |
| 141 | + /* |
| 142 | + * Look for a process in the freelist. |
| 143 | + */ |
| 144 | + int mppu = mainconf.max_procs_per_user; |
| 145 | + int mp = mainconf.max_procs; |
| 146 | + |
| 147 | + pthread_mutex_lock(&lock_); |
| 148 | + int &thisuser = peruser_[uid]; |
| 149 | + |
| 150 | + while ( (mppu > 0 && thisuser >= mppu) || |
| 151 | + (mp > 0 && curprocs_ > mp)) |
| 152 | + { |
| 153 | + pthread_cond_wait(&curprocs_cond, &lock_); |
| 154 | + } |
| 155 | + |
| 156 | + peruser_[uid]++; |
| 157 | + curprocs_++; |
| 158 | + |
| 159 | + freelist_t::index<by_uid>::type &idx = freelist_.get<by_uid>(); |
| 160 | + freelist_t::index<by_uid>::type::iterator fit; |
| 161 | + if ((fit = idx.find(uid)) != idx.end()) { |
| 162 | + processp p = fit->proc; |
| 163 | + idx.erase(fit); |
| 164 | + pthread_mutex_unlock(&lock_); |
| 165 | + return p; |
| 166 | + } |
| 167 | + |
| 168 | + pthread_mutex_unlock(&lock_); |
| 169 | + |
| 170 | + return processp(new process(uid, gid, strm.str())); |
| 171 | +} |
| 172 | + |
| 173 | +void |
| 174 | +process_factory::release_process(processp proc) |
| 175 | +{ |
| 176 | + free_process p; |
| 177 | + p.uid = proc->uid(); |
| 178 | + p.proc = proc; |
| 179 | + |
| 180 | + lock lck(lock_); |
| 181 | + freelist_.push_back(p); |
| 182 | + curprocs_--; |
| 183 | + peruser_[p.uid]--; |
| 184 | + pthread_cond_signal(&curprocs_cond); |
| 185 | +} |
Index: trunk/tools/switchboard2/config.h |
— | — | @@ -0,0 +1,84 @@ |
| 2 | +/* Copyright (c) 2008 River Tarnell <river@wikimedia.org>. */ |
| 3 | +/* |
| 4 | + * Permission is granted to anyone to use this software for any purpose, |
| 5 | + * including commercial applications, and to alter it and redistribute it |
| 6 | + * freely. This software is provided 'as-is', without any express or implied |
| 7 | + * warranty. |
| 8 | + */ |
| 9 | +/* $Id$ */ |
| 10 | + |
| 11 | +#ifndef CONFIG_H |
| 12 | +#define CONFIG_H |
| 13 | + |
| 14 | +#include <string> |
| 15 | +#include <vector> |
| 16 | +#include <stdexcept> |
| 17 | +#include <map> |
| 18 | + |
| 19 | +#include <boost/function.hpp> |
| 20 | + |
| 21 | +#include <log4cxx/logger.h> |
| 22 | + |
| 23 | +struct config_error : std::runtime_error { |
| 24 | + config_error(char const *what) : std::runtime_error(what) {} |
| 25 | +}; |
| 26 | + |
| 27 | +struct conf_listener { |
| 28 | + std::string host; |
| 29 | + std::string port; |
| 30 | +}; |
| 31 | + |
| 32 | +enum server_type { |
| 33 | + serv_unknown = 0, |
| 34 | + serv_sjs, |
| 35 | + serv_apache |
| 36 | +}; |
| 37 | + |
| 38 | +struct config { |
| 39 | + config(); |
| 40 | + |
| 41 | + std::vector<conf_listener> |
| 42 | + listeners; |
| 43 | + std::string logconf; |
| 44 | + std::string sockdir; |
| 45 | + std::string docroot; |
| 46 | + std::string userdir; |
| 47 | + int max_procs; |
| 48 | + int max_procs_per_user; |
| 49 | + int max_q_per_user; |
| 50 | + server_type servtype; |
| 51 | +}; |
| 52 | + |
| 53 | +struct configuration_loader { |
| 54 | + configuration_loader(); |
| 55 | + |
| 56 | + bool load(std::string const &filename, config &newconf); |
| 57 | + |
| 58 | +private: |
| 59 | + bool parse_config_line(std::string const &line, config &newconf); |
| 60 | + |
| 61 | + int lineno_; |
| 62 | + std::string file_; |
| 63 | + |
| 64 | + typedef boost::function<bool (configuration_loader *, |
| 65 | + std::vector<std::string> &fields, |
| 66 | + config &newconf)> confline_t; |
| 67 | + |
| 68 | + static std::map<std::string, confline_t> conflines; |
| 69 | + |
| 70 | + bool f_listen(std::vector<std::string> &fields, config &newconf); |
| 71 | + bool f_logconf(std::vector<std::string> &fields, config &newconf); |
| 72 | + bool f_sockdir(std::vector<std::string> &fields, config &newconf); |
| 73 | + bool f_docroot(std::vector<std::string> &fields, config &newconf); |
| 74 | + bool f_userdir(std::vector<std::string> &fields, config &newconf); |
| 75 | + bool f_max_procs(std::vector<std::string> &fields, config &newconf); |
| 76 | + bool f_max_procs_per_user(std::vector<std::string> &fields, config &newconf); |
| 77 | + bool f_max_q_per_user(std::vector<std::string> &fields, config &newconf); |
| 78 | + bool f_server_type(std::vector<std::string> &fields, config &newconf); |
| 79 | + |
| 80 | + log4cxx::LoggerPtr logger; |
| 81 | +}; |
| 82 | + |
| 83 | +extern config mainconf; |
| 84 | + |
| 85 | +#endif |
Index: trunk/tools/switchboard2/process_factory.h |
— | — | @@ -0,0 +1,66 @@ |
| 2 | +/* Copyright (c) 2008 River Tarnell <river@wikimedia.org>. */ |
| 3 | +/* |
| 4 | + * Permission is granted to anyone to use this software for any purpose, |
| 5 | + * including commercial applications, and to alter it and redistribute it |
| 6 | + * freely. This software is provided 'as-is', without any express or implied |
| 7 | + * warranty. |
| 8 | + */ |
| 9 | +/* $Id$ */ |
| 10 | + |
| 11 | +#ifndef PROCESS_FACTORY_H |
| 12 | +#define PROCESS_FACTORY_H |
| 13 | + |
| 14 | +#include <map> |
| 15 | +#include <string> |
| 16 | + |
| 17 | +#include <pthread.h> |
| 18 | + |
| 19 | +#include <boost/noncopyable.hpp> |
| 20 | +#include <boost/multi_index_container.hpp> |
| 21 | +#include <boost/multi_index/indexed_by.hpp> |
| 22 | +#include <boost/multi_index/member.hpp> |
| 23 | +#include <boost/multi_index/ordered_index.hpp> |
| 24 | +#include <boost/multi_index/tag.hpp> |
| 25 | +#include <boost/multi_index/sequenced_index.hpp> |
| 26 | + |
| 27 | +#include "process.h" |
| 28 | + |
| 29 | +namespace mi = boost::multi_index; |
| 30 | + |
| 31 | +struct free_process { |
| 32 | + uid_t uid; |
| 33 | + processp proc; |
| 34 | +}; |
| 35 | + |
| 36 | +struct by_uid {}; |
| 37 | +typedef boost::multi_index_container< |
| 38 | + free_process, |
| 39 | + mi::indexed_by< |
| 40 | + mi::sequenced<>, |
| 41 | + mi::ordered_non_unique<mi::tag<by_uid>, |
| 42 | + mi::member<free_process, uid_t, &free_process::uid> > |
| 43 | + > |
| 44 | +> freelist_t; |
| 45 | + |
| 46 | +struct process_factory : boost::noncopyable { |
| 47 | + static process_factory &instance(); |
| 48 | + |
| 49 | + process_factory(); |
| 50 | + ~process_factory(); |
| 51 | + |
| 52 | + processp get_process(std::map<std::string, std::string> ¶ms); |
| 53 | + void release_process(processp proc); |
| 54 | + |
| 55 | +private: |
| 56 | + int id_; |
| 57 | + freelist_t freelist_; |
| 58 | + pthread_mutex_t lock_; |
| 59 | + |
| 60 | + std::map<uid_t, int> peruser_; |
| 61 | + int curprocs_; |
| 62 | + |
| 63 | + //pthread_mutex_t curprocs_mtx; |
| 64 | + pthread_cond_t curprocs_cond; |
| 65 | +}; |
| 66 | + |
| 67 | +#endif /* !PROCESS_FACTORY_H */ |
Index: trunk/tools/switchboard2/fcgi.cc |
— | — | @@ -0,0 +1,145 @@ |
| 2 | +/* Copyright (c) 2008 River Tarnell <river@wikimedia.org>. */ |
| 3 | +/* |
| 4 | + * Permission is granted to anyone to use this software for any purpose, |
| 5 | + * including commercial applications, and to alter it and redistribute it |
| 6 | + * freely. This software is provided 'as-is', without any express or implied |
| 7 | + * warranty. |
| 8 | + */ |
| 9 | +/* $Id$ */ |
| 10 | + |
| 11 | +#include <iostream> |
| 12 | +#include <cerrno> |
| 13 | +#include <cstring> |
| 14 | +#include <unistd.h> |
| 15 | + |
| 16 | +#include "fcgi.h" |
| 17 | + |
| 18 | +namespace fcgi { |
| 19 | + |
| 20 | +void |
| 21 | +pretty_print_string(std::ostream &strm, std::string const &str) |
| 22 | +{ |
| 23 | + strm << '"'; |
| 24 | + for (int i = 0; i < str.size(); ++i) { |
| 25 | + if (str[i] > 0x1F && str[i] < 0x7F) |
| 26 | + strm << str[i]; |
| 27 | + else |
| 28 | + strm << "\\0" << std::oct << (int)str[i]; |
| 29 | + } |
| 30 | + strm << '"'; |
| 31 | +} |
| 32 | + |
| 33 | +void |
| 34 | +pretty_print_record(std::ostream &strm, fcgi::record const &rec) |
| 35 | +{ |
| 36 | + switch (rec.type_) { |
| 37 | + case rectype::begin_request: |
| 38 | + strm << "{FCGI_BEGIN_REQUEST, "; |
| 39 | + strm << rec.request_id() << ", "; |
| 40 | + break; |
| 41 | + |
| 42 | + case rectype::end_request: |
| 43 | + strm << "{FCGI_END_REQUEST, "; |
| 44 | + strm << rec.request_id() << ", "; |
| 45 | + break; |
| 46 | + |
| 47 | + case rectype::params: |
| 48 | + strm << "{FCGI_PARAMS, "; |
| 49 | + strm << rec.request_id() << ", "; |
| 50 | + pretty_print_string(strm, std::string(rec.contentData.begin(), rec.contentData.end())); |
| 51 | + break; |
| 52 | + |
| 53 | + case rectype::stdin_: |
| 54 | + strm << "{FCGI_STDIN, "; |
| 55 | + strm << rec.request_id() << ", "; |
| 56 | + pretty_print_string(strm, std::string(rec.contentData.begin(), rec.contentData.end())); |
| 57 | + break; |
| 58 | + |
| 59 | + case rectype::stdout_: |
| 60 | + strm << "{FCGI_STDOUT, "; |
| 61 | + strm << rec.request_id() << ", "; |
| 62 | + pretty_print_string(strm, std::string(rec.contentData.begin(), rec.contentData.end())); |
| 63 | + break; |
| 64 | + |
| 65 | + default: |
| 66 | + strm << "{unknown type, "; |
| 67 | + strm << rec.request_id() << ", "; |
| 68 | + break; |
| 69 | + } |
| 70 | + |
| 71 | + strm << "}\n"; |
| 72 | +} |
| 73 | + |
| 74 | +bool |
| 75 | +read_fcgi_record(int fd, fcgi::record *rec) |
| 76 | +{ |
| 77 | + /* |
| 78 | + * Format: |
| 79 | + * version 1 byte |
| 80 | + * type 1 byte |
| 81 | + * request id 2 bytes |
| 82 | + * content length 2 bytes |
| 83 | + * padding length 1 byte |
| 84 | + * reserved 1 byte |
| 85 | + * content data variable |
| 86 | + * padding data variable |
| 87 | + */ |
| 88 | + |
| 89 | + ssize_t i; |
| 90 | + |
| 91 | + if ((i = read(fd, static_cast<void *>(rec), 8)) < 8) { |
| 92 | + std::fprintf(stderr, "couldn't read entire record header\n"); |
| 93 | + if (i == -1) |
| 94 | + std::fprintf(stderr, " error: %s\n", std::strerror(errno)); |
| 95 | + else |
| 96 | + std::fprintf(stderr, " bytes read = %d\n", i); |
| 97 | + return false; |
| 98 | + } |
| 99 | + |
| 100 | + rec->contentData.resize(rec->content_length()); |
| 101 | + rec->paddingData.resize(rec->padding_length()); |
| 102 | + |
| 103 | + if (rec->content_length() > 0) { |
| 104 | + if (read(fd, &rec->contentData[0], rec->content_length()) < |
| 105 | + rec->content_length()) { |
| 106 | + std::fprintf(stderr, "couldn't read entire content\n"); |
| 107 | + return false; |
| 108 | + } |
| 109 | + } |
| 110 | + |
| 111 | + if (rec->padding_length() > 0) { |
| 112 | + if (read(fd, &rec->paddingData[0], rec->padding_length()) < |
| 113 | + rec->padding_length()) { |
| 114 | + std::fprintf(stderr, "couldn't read entire padding\n"); |
| 115 | + return false; |
| 116 | + } |
| 117 | + } |
| 118 | + |
| 119 | +#if 0 |
| 120 | + std::cerr << "read: "; |
| 121 | + pretty_print_record(std::cerr, *rec); |
| 122 | +#endif |
| 123 | + |
| 124 | + return true; |
| 125 | +} |
| 126 | + |
| 127 | +bool |
| 128 | +write_fcgi_record(int fd, fcgi::record const &rec) |
| 129 | +{ |
| 130 | +#if 0 |
| 131 | + std::cerr << "write: "; |
| 132 | + pretty_print_record(std::cerr, rec); |
| 133 | +#endif |
| 134 | + |
| 135 | + write(fd, static_cast<void const *>(&rec), 8); |
| 136 | + if (rec.content_length() > 0) |
| 137 | + write(fd, static_cast<void const *>(&rec.contentData[0]), |
| 138 | + rec.content_length()); |
| 139 | + if (rec.padding_length() > 0) |
| 140 | + write(fd, static_cast<void const *>(&rec.paddingData[0]), |
| 141 | + rec.padding_length()); |
| 142 | + |
| 143 | + return true; |
| 144 | +} |
| 145 | + |
| 146 | +} // namespace fcgi |
Index: trunk/tools/switchboard2/fcgi.h |
— | — | @@ -0,0 +1,199 @@ |
| 2 | +/* Copyright (c) 2008 River Tarnell <river@wikimedia.org>. */ |
| 3 | +/* |
| 4 | + * Permission is granted to anyone to use this software for any purpose, |
| 5 | + * including commercial applications, and to alter it and redistribute it |
| 6 | + * freely. This software is provided 'as-is', without any express or implied |
| 7 | + * warranty. |
| 8 | + */ |
| 9 | +/* $Id: fcgi.h 38741 2008-08-06 21:00:45Z river $ */ |
| 10 | + |
| 11 | +#ifndef FCGI_H |
| 12 | +#define FCGI_H |
| 13 | + |
| 14 | +#include <vector> |
| 15 | +#include <stdexcept> |
| 16 | +#include <map> |
| 17 | + |
| 18 | +#include <boost/shared_ptr.hpp> |
| 19 | + |
| 20 | +namespace fcgi { |
| 21 | + |
| 22 | + namespace rectype { |
| 23 | + enum rectype_t { |
| 24 | + begin_request = 1, |
| 25 | + abort_request = 2, |
| 26 | + end_request = 3, |
| 27 | + params = 4, |
| 28 | + stdin_ = 5, |
| 29 | + stdout_ = 6, |
| 30 | + stderr_ = 7, |
| 31 | + data = 8, |
| 32 | + get_values = 9, |
| 33 | + get_values_result = 10, |
| 34 | + unknown = 11 |
| 35 | + }; |
| 36 | + } |
| 37 | + |
| 38 | + namespace role { |
| 39 | + enum role_t { |
| 40 | + responder = 1, |
| 41 | + authorizer = 2, |
| 42 | + filter = 3 |
| 43 | + }; |
| 44 | + } |
| 45 | + |
| 46 | + struct record { |
| 47 | + unsigned char version_; |
| 48 | + unsigned char type_; |
| 49 | + unsigned char requestId1_; |
| 50 | + unsigned char requestId0_; |
| 51 | + unsigned char contentLength1_; |
| 52 | + unsigned char contentLength0_; |
| 53 | + unsigned char paddingLength_; |
| 54 | + unsigned char reserved_; |
| 55 | + std::vector<unsigned char> contentData; |
| 56 | + std::vector<unsigned char> paddingData; |
| 57 | + |
| 58 | + int version() const { |
| 59 | + return version_; |
| 60 | + } |
| 61 | + |
| 62 | + rectype::rectype_t type() const { |
| 63 | + return rectype::rectype_t(type_); |
| 64 | + } |
| 65 | + |
| 66 | + std::size_t content_length() const { |
| 67 | + return (contentLength1_ << 8) | contentLength0_; |
| 68 | + } |
| 69 | + |
| 70 | + void content_length(std::size_t n) { |
| 71 | + contentLength1_ = n >> 8; |
| 72 | + contentLength0_ = n & 0xFF; |
| 73 | + } |
| 74 | + |
| 75 | + std::size_t padding_length() const { |
| 76 | + return paddingLength_; |
| 77 | + } |
| 78 | + |
| 79 | + int request_id() const { |
| 80 | + return (requestId1_ << 8) | requestId0_; |
| 81 | + } |
| 82 | + |
| 83 | + void request_id(int n) { |
| 84 | + requestId1_ = n >> 8; |
| 85 | + requestId0_ = n & 0xFF; |
| 86 | + } |
| 87 | + }; |
| 88 | + |
| 89 | + struct begin_request_payload { |
| 90 | + unsigned char role1_; |
| 91 | + unsigned char role0_; |
| 92 | + unsigned char flags_; |
| 93 | + unsigned char reserved[5]; |
| 94 | + |
| 95 | + role::role_t role() const { |
| 96 | + return role::role_t((role1_ << 8) | role0_); |
| 97 | + } |
| 98 | + |
| 99 | + int flags() const { |
| 100 | + return flags_; |
| 101 | + } |
| 102 | + }; |
| 103 | + |
| 104 | + typedef boost::shared_ptr<record> recordp; |
| 105 | + |
| 106 | + typedef std::map<std::string, std::string> params; |
| 107 | + |
| 108 | + struct short_data : std::runtime_error { |
| 109 | + short_data() : std::runtime_error("FastCGI parameter data is truncated") {} |
| 110 | + }; |
| 111 | + |
| 112 | + template<typename InputIterator> |
| 113 | + int decode_length(InputIterator &begin, InputIterator end) |
| 114 | + { |
| 115 | + if (std::distance(begin, end) < 1) |
| 116 | + throw short_data(); |
| 117 | + |
| 118 | + int len; |
| 119 | + if (*begin <= 0x7F) { |
| 120 | + len = *begin; |
| 121 | + begin++; |
| 122 | + return len; |
| 123 | + } |
| 124 | + |
| 125 | + if (std::distance(begin, end) < 4) |
| 126 | + throw short_data(); |
| 127 | + |
| 128 | + len = (int)((*begin & 0x7F) << 24) |
| 129 | + | (*(begin + 1) << 16) |
| 130 | + | (*(begin + 2) << 8) |
| 131 | + | *(begin + 3); |
| 132 | + begin += 4; |
| 133 | + return len; |
| 134 | + } |
| 135 | + |
| 136 | + template<typename InputIterator, typename OutputIterator> |
| 137 | + void decode_params( |
| 138 | + InputIterator begin, |
| 139 | + InputIterator end, |
| 140 | + OutputIterator output) |
| 141 | + { |
| 142 | + /* |
| 143 | + * Params are encoded as <name len><value len><name><value>. |
| 144 | + * Lengths are either 1 byte < 0x7f, or 4 bytes. |
| 145 | + */ |
| 146 | + while (std::distance(begin, end) > 0) { |
| 147 | + int namelen, valuelen; |
| 148 | + |
| 149 | + namelen = decode_length(begin, end); |
| 150 | + valuelen = decode_length(begin, end); |
| 151 | + |
| 152 | + if (std::distance(begin, end) < (namelen + valuelen)) |
| 153 | + throw short_data(); |
| 154 | + |
| 155 | + std::string name(begin, begin + namelen); |
| 156 | + std::string value(begin + namelen, begin + namelen + valuelen); |
| 157 | + *output++ = std::make_pair(name, value); |
| 158 | + begin += (namelen + valuelen); |
| 159 | + } |
| 160 | + } |
| 161 | + |
| 162 | + template<typename OutputIterator> |
| 163 | + void encode_length(std::size_t s, OutputIterator &output) { |
| 164 | + if (s <= 127) { |
| 165 | + *output++ = (unsigned char) s; |
| 166 | + } else { |
| 167 | + *output++ = (unsigned char) (((s & 0xFF000000) >> 24) | 0x80); |
| 168 | + *output++ = (unsigned char) ((s & 0x00FF0000) >> 16); |
| 169 | + *output++ = (unsigned char) ((s & 0x0000FF00) >> 8); |
| 170 | + *output++ = (unsigned char) (s & 0x000000FF); |
| 171 | + } |
| 172 | + } |
| 173 | + |
| 174 | + template<typename InputIterator, typename OutputIterator> |
| 175 | + void encode_params( |
| 176 | + InputIterator begin, |
| 177 | + InputIterator end, |
| 178 | + OutputIterator output) |
| 179 | + { |
| 180 | + while (begin != end) { |
| 181 | + std::pair<std::string, std::string> const &p(*begin); |
| 182 | + |
| 183 | + encode_length(p.first.size(), output); |
| 184 | + encode_length(p.second.size(), output); |
| 185 | + |
| 186 | + std::copy(p.first.begin(), p.first.end(), output); |
| 187 | + std::copy(p.second.begin(), p.second.end(), output); |
| 188 | + |
| 189 | + begin++; |
| 190 | + } |
| 191 | + } |
| 192 | + |
| 193 | + bool read_fcgi_record(int fd, record *rec); |
| 194 | + bool write_fcgi_record(int fd, record const &rec); |
| 195 | + |
| 196 | + void pretty_print_record(std::ostream &strm, record const &rec); |
| 197 | + |
| 198 | +} // namespace fcgi |
| 199 | + |
| 200 | +#endif |
Index: trunk/tools/switchboard2/process.cc |
— | — | @@ -0,0 +1,121 @@ |
| 2 | +/* Copyright (c) 2008 River Tarnell <river@wikimedia.org>. */ |
| 3 | +/* |
| 4 | + * Permission is granted to anyone to use this software for any purpose, |
| 5 | + * including commercial applications, and to alter it and redistribute it |
| 6 | + * freely. This software is provided 'as-is', without any express or implied |
| 7 | + * warranty. |
| 8 | + */ |
| 9 | +/* $Id$ */ |
| 10 | + |
| 11 | +#include <sys/types.h> |
| 12 | +#include <sys/socket.h> |
| 13 | +#include <sys/un.h> |
| 14 | +#include <sys/stat.h> |
| 15 | +#include <unistd.h> |
| 16 | + |
| 17 | +#include <cstring> |
| 18 | + |
| 19 | +#include <boost/format.hpp> |
| 20 | +#include <log4cxx/logger.h> |
| 21 | + |
| 22 | +#include "process.h" |
| 23 | + |
| 24 | +process::process( |
| 25 | + uid_t uid, gid_t gid, |
| 26 | + std::string const &bindpath) |
| 27 | + : bindpath_(bindpath) |
| 28 | + , uid_(uid) |
| 29 | + , gid_(gid) |
| 30 | + , logger(log4cxx::Logger::getLogger("process")) |
| 31 | +{ |
| 32 | + LOG4CXX_DEBUG(logger, boost::format("creating new process; uid=%d bindpath=%s") |
| 33 | + % uid % bindpath); |
| 34 | + int sock; |
| 35 | + char uids[64], gids[64]; |
| 36 | + |
| 37 | + /* |
| 38 | + * Create a listening socket that we'll use to connect to the child. |
| 39 | + */ |
| 40 | + if ((sock = ::socket(AF_UNIX, SOCK_STREAM, 0)) == -1) |
| 41 | + throw creation_failure("socket failed"); |
| 42 | + |
| 43 | + unlink(bindpath.c_str()); |
| 44 | + struct sockaddr_un addr; |
| 45 | + std::memset(&addr, 0, sizeof(addr)); |
| 46 | + addr.sun_family = AF_UNIX; |
| 47 | + std::strcpy(addr.sun_path, bindpath.c_str()); |
| 48 | + |
| 49 | + LOG4CXX_DEBUG(logger, "binding..."); |
| 50 | + if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) == -1) |
| 51 | + throw creation_failure("startup failed"); |
| 52 | + |
| 53 | + chmod(bindpath.c_str(), 0777); |
| 54 | + |
| 55 | + LOG4CXX_DEBUG(logger, "listening..."); |
| 56 | + if (listen(sock, 1) == -1) |
| 57 | + throw creation_failure("listen failed"); |
| 58 | + |
| 59 | + /* |
| 60 | + * Spawn a new php-cgi process. |
| 61 | + */ |
| 62 | + switch(pid_ = fork()) { |
| 63 | + case -1: |
| 64 | + throw creation_failure("can't fork"); |
| 65 | + |
| 66 | + case 0: |
| 67 | + snprintf(uids, sizeof(uids), "%lu", (unsigned long) uid); |
| 68 | + snprintf(gids, sizeof(gids), "%lu", (unsigned long) gid); |
| 69 | + |
| 70 | + close(0); |
| 71 | + close(1); |
| 72 | + close(2); |
| 73 | + dup2(sock, 0); |
| 74 | + close(sock); |
| 75 | + execl(PREFIX "/lib/switchboard/swexec", "swexec", uids, gids, NULL); |
| 76 | + _exit(1); |
| 77 | + |
| 78 | + default: |
| 79 | + close(sock); |
| 80 | + break; |
| 81 | + } |
| 82 | + LOG4CXX_DEBUG(logger, boost::format("process created; pid=%d") % pid_); |
| 83 | +} |
| 84 | + |
| 85 | +process::~process() |
| 86 | +{ |
| 87 | + /* |
| 88 | + * Because the process is setuid, trying to kill it normally won't work. |
| 89 | + * We use the swkill wrapper, which is similar to swexec, except it kills |
| 90 | + * a process. |
| 91 | + */ |
| 92 | + char uids[64]; |
| 93 | + char gids[64]; |
| 94 | + char pids[64]; |
| 95 | + |
| 96 | + LOG4CXX_DEBUG(logger, boost::format("process@%p destroyed, killing pid %d") % this % pid_); |
| 97 | + |
| 98 | + switch(fork()) { |
| 99 | + case -1: |
| 100 | + return; |
| 101 | + |
| 102 | + case 0: |
| 103 | + snprintf(uids, sizeof(uids), "%lu", (unsigned long) uid_); |
| 104 | + snprintf(gids, sizeof(gids), "%lu", (unsigned long) gid_); |
| 105 | + snprintf(pids, sizeof(pids), "%lu", (unsigned long) pid_); |
| 106 | + |
| 107 | + execl(PREFIX "/lib/switchboard/swkill", "swkill", uids, gids, pids, NULL); |
| 108 | + _exit(1); |
| 109 | + } |
| 110 | +} |
| 111 | + |
| 112 | +int |
| 113 | +process::connect(int socket) |
| 114 | +{ |
| 115 | + LOG4CXX_DEBUG(logger, boost::format("connecting to process socket... %s") % bindpath_); |
| 116 | + struct sockaddr_un addr; |
| 117 | + memset(&addr, 0, sizeof(addr)); |
| 118 | + addr.sun_family = AF_UNIX; |
| 119 | + strcpy(addr.sun_path, bindpath_.c_str()); |
| 120 | + |
| 121 | + return ::connect(socket, (sockaddr *) &addr, sizeof(addr)); |
| 122 | +} |
Index: trunk/tools/switchboard2/request_thread.cc |
— | — | @@ -0,0 +1,325 @@ |
| 2 | +/* Copyright (c) 2008 River Tarnell <river@wikimedia.org>. */ |
| 3 | +/* |
| 4 | + * Permission is granted to anyone to use this software for any purpose, |
| 5 | + * including commercial applications, and to alter it and redistribute it |
| 6 | + * freely. This software is provided 'as-is', without any express or implied |
| 7 | + * warranty. |
| 8 | + */ |
| 9 | +/* $Id$ */ |
| 10 | + |
| 11 | +#include <iostream> |
| 12 | +#include <cerrno> |
| 13 | + |
| 14 | +#include <sys/types.h> |
| 15 | +#include <sys/socket.h> |
| 16 | +#include <sys/un.h> |
| 17 | + |
| 18 | +#include <fcntl.h> |
| 19 | +#include <pthread.h> |
| 20 | + |
| 21 | +#include "request_thread.h" |
| 22 | +#include "fcgi.h" |
| 23 | +#include "process_factory.h" |
| 24 | +#include "util.h" |
| 25 | + |
| 26 | +namespace { |
| 27 | + |
| 28 | +/* |
| 29 | + * This is the number of idle threads, not total. |
| 30 | + */ |
| 31 | +#if 0 |
| 32 | +int nthreads; |
| 33 | +pthread_mutex_t nthreads_lock = PTHREAD_MUTEX_INITIALIZER; |
| 34 | + |
| 35 | +work_queue<request_thread *> work; |
| 36 | +#endif |
| 37 | + |
| 38 | +extern "C" void* |
| 39 | +do_start_thread(void *arg) { |
| 40 | +#if 0 |
| 41 | + for (;;) { |
| 42 | + request_thread *req = work.wait(); |
| 43 | + |
| 44 | + try { |
| 45 | + req->start_request(); |
| 46 | + } catch (std::exception &e) { |
| 47 | + std::fprintf(stderr, "exception handling request: %s\n", e.what()); |
| 48 | + } |
| 49 | + |
| 50 | + delete req; |
| 51 | + |
| 52 | +#if 0 |
| 53 | + pthread_mutex_lock(&nthreads_lock); |
| 54 | + nthreads++; |
| 55 | + pthread_mutex_unlock(&nthreads_lock); |
| 56 | +#endif |
| 57 | + __sync_fetch_and_add(&nthreads, 1); |
| 58 | + } |
| 59 | +#endif |
| 60 | + request_thread *req = static_cast<request_thread *>(arg); |
| 61 | + try { |
| 62 | + req->start_request(); |
| 63 | + } catch (std::exception &e) { |
| 64 | + std::fprintf(stderr, "exception handling request: %s\n", e.what()); |
| 65 | + } |
| 66 | + |
| 67 | + delete req; |
| 68 | + |
| 69 | + return NULL; |
| 70 | +} |
| 71 | + |
| 72 | +} // anonymous namespace |
| 73 | + |
| 74 | +request_thread::request_thread(int fd) |
| 75 | + : fd_(fd) |
| 76 | + , rid_(-1) |
| 77 | + , cfd_(-1) |
| 78 | +{ |
| 79 | +} |
| 80 | + |
| 81 | +request_thread::~request_thread() |
| 82 | +{ |
| 83 | + if (cfd_ != -1) |
| 84 | + close(cfd_); |
| 85 | + |
| 86 | + if (fd_ != -1) |
| 87 | + close(fd_); |
| 88 | +} |
| 89 | + |
| 90 | +void |
| 91 | +request_thread::start() |
| 92 | +{ |
| 93 | +#if 0 |
| 94 | + pthread_mutex_lock(&nthreads_lock); |
| 95 | + if (nthreads == 0) { |
| 96 | +#endif |
| 97 | + pthread_attr_t attr; |
| 98 | + pthread_attr_init(&attr); |
| 99 | + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); |
| 100 | + pthread_create(&tid_, &attr, &do_start_thread, static_cast<void *>(this)); |
| 101 | +#if 0 |
| 102 | + } else |
| 103 | + nthreads--; |
| 104 | + pthread_mutex_unlock(&nthreads_lock); |
| 105 | + |
| 106 | + work.add_work(this); |
| 107 | +#endif |
| 108 | +} |
| 109 | + |
| 110 | +void |
| 111 | +request_thread::start_request() |
| 112 | +{ |
| 113 | + fcgi::record rec; |
| 114 | + |
| 115 | + /* |
| 116 | + * Start by parsing the request header. |
| 117 | + */ |
| 118 | + fcgi::read_fcgi_record(fd_, &rec); |
| 119 | + |
| 120 | + if (rec.version() != 1) |
| 121 | + throw request_exception("unknown FCGI version"); |
| 122 | + |
| 123 | + rid_ = rec.request_id(); |
| 124 | + |
| 125 | + switch (rec.type()) { |
| 126 | + case fcgi::rectype::begin_request: |
| 127 | + handle_normal_request(rec); |
| 128 | + return; |
| 129 | + |
| 130 | + |
| 131 | + case fcgi::rectype::get_values: |
| 132 | + handle_get_values(rec); |
| 133 | + return; |
| 134 | + |
| 135 | + default: |
| 136 | + throw request_exception("unexpected request type at this point"); |
| 137 | + } |
| 138 | +} |
| 139 | + |
| 140 | +void |
| 141 | +request_thread::handle_normal_request(fcgi::record &initial) |
| 142 | +{ |
| 143 | + fcgi::record rec; |
| 144 | + fcgi::begin_request_payload *pl = reinterpret_cast<fcgi::begin_request_payload *>(&initial.contentData[0]); |
| 145 | + |
| 146 | + if (initial.content_length() != 8) |
| 147 | + throw request_exception("begin request had unexpected content length"); |
| 148 | + |
| 149 | + switch (pl->role()) { |
| 150 | + case fcgi::role::responder: |
| 151 | + break; |
| 152 | + |
| 153 | + default: |
| 154 | + throw request_exception("begin_request had unexpected role"); |
| 155 | + } |
| 156 | + |
| 157 | + /* |
| 158 | + * Now we should receive at least one params. |
| 159 | + */ |
| 160 | + std::vector<unsigned char> paramdata; |
| 161 | + |
| 162 | + for (;;) { |
| 163 | + fcgi::read_fcgi_record(fd_, &rec); |
| 164 | + |
| 165 | + if (rec.type() != fcgi::rectype::params) |
| 166 | + throw request_exception("unexpected request type at this point"); |
| 167 | + |
| 168 | + if (rec.content_length() == 0) |
| 169 | + break; |
| 170 | + |
| 171 | + paramdata.insert(paramdata.end(), rec.contentData.begin(), rec.contentData.end()); |
| 172 | + } |
| 173 | + |
| 174 | + /* |
| 175 | + * And at least one stdin. |
| 176 | + */ |
| 177 | + std::vector<unsigned char> stdin_; |
| 178 | + |
| 179 | + for (;;) { |
| 180 | + fcgi::read_fcgi_record(fd_, &rec); |
| 181 | + |
| 182 | + if (rec.type() != fcgi::rectype::stdin_) |
| 183 | + throw request_exception("unexpected request type at this point"); |
| 184 | + |
| 185 | + if (rec.content_length() == 0) |
| 186 | + break; |
| 187 | + |
| 188 | + stdin_.insert(stdin_.end(), rec.contentData.begin(), rec.contentData.end()); |
| 189 | + } |
| 190 | + |
| 191 | + /* |
| 192 | + * Handle the actual request. |
| 193 | + */ |
| 194 | + std::map<std::string, std::string> params; |
| 195 | + fcgi::decode_params(paramdata.begin(), paramdata.end(), std::inserter(params, params.begin())); |
| 196 | + |
| 197 | +#if 0 |
| 198 | + for (std::map<std::string, std::string>::iterator |
| 199 | + it = params.begin(), end = params.end(); |
| 200 | + it != end; ++it) |
| 201 | + { |
| 202 | + std::cout << "[" << it->first << "] = [" << it->second << "]\n"; |
| 203 | + } |
| 204 | +#endif |
| 205 | + |
| 206 | + if ((cfd_ = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) |
| 207 | + throw request_exception("unix socket creation failed"); |
| 208 | + |
| 209 | + fcntl(cfd_, F_SETFD, FD_CLOEXEC); |
| 210 | + int r = 0, tries = 0; |
| 211 | + static int const max_tries = 10; |
| 212 | + |
| 213 | + do { |
| 214 | + process_ = process_factory::instance().get_process(params); |
| 215 | + if ((r = process_->connect(cfd_)) == -1) { |
| 216 | + if (errno != ECONNREFUSED) |
| 217 | + throw request_exception("can't create PHP"); |
| 218 | + } else |
| 219 | + break; |
| 220 | + } while (++tries < max_tries); |
| 221 | + |
| 222 | + if (r == -1) |
| 223 | + throw request_exception("can't create PHP (too many tries)"); |
| 224 | + |
| 225 | + /* |
| 226 | + * Write the request to the child. |
| 227 | + */ |
| 228 | + fcgi::record r_begin; |
| 229 | + fcgi::record r_params; |
| 230 | + fcgi::record r_stdin; |
| 231 | + |
| 232 | + r_begin.version_ = r_params.version_ = r_stdin.version_ = 1; |
| 233 | + r_begin.paddingLength_ = r_params.paddingLength_ = r_stdin.paddingLength_ = 0; |
| 234 | + r_begin.reserved_ = r_params.reserved_ = r_stdin.reserved_ = 0; |
| 235 | + |
| 236 | + r_begin.request_id(rid_); |
| 237 | + r_begin.type_ = fcgi::rectype::begin_request; |
| 238 | + r_begin.content_length(8); |
| 239 | + r_begin.contentData.resize(8); |
| 240 | + r_begin.contentData[0] = 0; |
| 241 | + r_begin.contentData[1] = 1; |
| 242 | + r_begin.contentData[2] = 0; |
| 243 | + fcgi::write_fcgi_record(cfd_, r_begin); |
| 244 | + |
| 245 | + std::vector<unsigned char> newparams; |
| 246 | + fcgi::encode_params(params.begin(), params.end(), std::back_inserter(newparams)); |
| 247 | + r_params.request_id(rid_); |
| 248 | + r_params.type_ = fcgi::rectype::params; |
| 249 | + r_params.contentData.swap(newparams); |
| 250 | + //r_params.contentData.swap(paramdata); |
| 251 | + r_params.content_length(r_params.contentData.size()); |
| 252 | + fcgi::write_fcgi_record(cfd_, r_params); |
| 253 | + |
| 254 | + if (!r_params.contentData.empty()) { |
| 255 | + r_params.contentData.clear(); |
| 256 | + r_params.content_length(0); |
| 257 | + fcgi::write_fcgi_record(cfd_, r_params); |
| 258 | + } |
| 259 | + |
| 260 | + r_stdin.request_id(rid_); |
| 261 | + r_stdin.type_ = fcgi::rectype::stdin_; |
| 262 | + r_stdin.contentData.swap(stdin_); |
| 263 | + r_stdin.content_length(r_stdin.contentData.size()); |
| 264 | + fcgi::write_fcgi_record(cfd_, r_stdin); |
| 265 | + |
| 266 | + if (!r_stdin.contentData.empty()) { |
| 267 | + r_stdin.contentData.clear(); |
| 268 | + r_stdin.content_length(0); |
| 269 | + fcgi::write_fcgi_record(cfd_, r_stdin); |
| 270 | + } |
| 271 | + |
| 272 | + /* |
| 273 | + * Now read response requests and forward them to the server. |
| 274 | + */ |
| 275 | + fcgi::record resp; |
| 276 | + for (;;) { |
| 277 | + if (!fcgi::read_fcgi_record(cfd_, &resp)) |
| 278 | + throw request_exception("couldn't read record from child"); |
| 279 | + |
| 280 | + if (resp.request_id() != rid_) |
| 281 | + throw request_exception("child sent incorrect request id"); |
| 282 | + |
| 283 | + fcgi::write_fcgi_record(fd_, resp); |
| 284 | + if (resp.type() == fcgi::rectype::end_request) |
| 285 | + break; |
| 286 | + } |
| 287 | + |
| 288 | + close(cfd_); |
| 289 | + cfd_ = -1; |
| 290 | + process_factory::instance().release_process(process_); |
| 291 | + process_.reset(); |
| 292 | +} |
| 293 | + |
| 294 | +void |
| 295 | +request_thread::handle_get_values(fcgi::record &initial) |
| 296 | +{ |
| 297 | + std::map<std::string, std::string> request; |
| 298 | + std::map<std::string, std::string> response; |
| 299 | + |
| 300 | + fcgi::decode_params(initial.contentData.begin(), initial.contentData.end(), |
| 301 | + std::inserter(request, request.begin())); |
| 302 | + |
| 303 | + for (std::map<std::string, std::string>::iterator |
| 304 | + it = request.begin(), end = request.end(); |
| 305 | + it != end; ++it) |
| 306 | + { |
| 307 | + if (it->first == "FCGI_MAX_CONNS") |
| 308 | + response["FCGI_MAX_CONNS"] = "100"; |
| 309 | + else if (it->first == "FCGI_MAX_REQS") |
| 310 | + response["FCGI_MAX_REQS"] = "100"; |
| 311 | + else if (it->first == "FCGI_MPXS_CONNS") |
| 312 | + response["FCGI_MPXS_CONNS"] = "0"; |
| 313 | + } |
| 314 | + |
| 315 | + fcgi::record rec; |
| 316 | + std::vector<unsigned char> responsedata; |
| 317 | + fcgi::encode_params(response.begin(), response.end(), |
| 318 | + std::back_inserter(rec.contentData)); |
| 319 | + rec.version_ = 1; |
| 320 | + rec.type_ = fcgi::rectype::get_values_result; |
| 321 | + rec.requestId0_ = initial.requestId0_; |
| 322 | + rec.requestId1_ = initial.requestId1_; |
| 323 | + rec.reserved_ = 0; |
| 324 | + rec.content_length(rec.contentData.size()); |
| 325 | + fcgi::write_fcgi_record(fd_, rec); |
| 326 | +} |
Index: trunk/tools/switchboard2/util.h |
— | — | @@ -0,0 +1,65 @@ |
| 2 | +/* Copyright (c) 2008 River Tarnell <river@wikimedia.org>. */ |
| 3 | +/* |
| 4 | + * Permission is granted to anyone to use this software for any purpose, |
| 5 | + * including commercial applications, and to alter it and redistribute it |
| 6 | + * freely. This software is provided 'as-is', without any express or implied |
| 7 | + * warranty. |
| 8 | + */ |
| 9 | +/* $Id$ */ |
| 10 | + |
| 11 | +#ifndef UTIL_H |
| 12 | +#define UTIL_H |
| 13 | + |
| 14 | +#include <deque> |
| 15 | + |
| 16 | +#include <pthread.h> |
| 17 | + |
| 18 | +struct lock { |
| 19 | + lock(pthread_mutex_t &mtx) |
| 20 | + : mtx_(mtx) |
| 21 | + { |
| 22 | + pthread_mutex_lock(&mtx_); |
| 23 | + } |
| 24 | + |
| 25 | + ~lock() { |
| 26 | + pthread_mutex_unlock(&mtx_); |
| 27 | + } |
| 28 | + |
| 29 | +private: |
| 30 | + pthread_mutex_t &mtx_; |
| 31 | +}; |
| 32 | + |
| 33 | +template<typename T> |
| 34 | +struct work_queue { |
| 35 | + void add_work(T const &work) { |
| 36 | + pthread_mutex_lock(&wq_mutex); |
| 37 | + wq_work.push_back(work); |
| 38 | + pthread_cond_signal(&wq_cond); |
| 39 | + pthread_mutex_unlock(&wq_mutex); |
| 40 | + } |
| 41 | + |
| 42 | + T wait() { |
| 43 | + T work; |
| 44 | + |
| 45 | + pthread_mutex_lock(&wq_mutex); |
| 46 | + while (wq_work.empty()) |
| 47 | + pthread_cond_wait(&wq_cond, &wq_mutex); |
| 48 | + |
| 49 | + work = wq_work.front(); |
| 50 | + wq_work.pop_front(); |
| 51 | + pthread_mutex_unlock(&wq_mutex); |
| 52 | + return work; |
| 53 | + } |
| 54 | + |
| 55 | + work_queue() { |
| 56 | + pthread_mutex_init(&wq_mutex, NULL); |
| 57 | + pthread_cond_init(&wq_cond, NULL); |
| 58 | + } |
| 59 | + |
| 60 | +private: |
| 61 | + std::deque<T> wq_work; |
| 62 | + pthread_cond_t wq_cond; |
| 63 | + pthread_mutex_t wq_mutex; |
| 64 | +}; |
| 65 | + |
| 66 | +#endif /* !UTIL_H */ |
Index: trunk/tools/switchboard2/process.h |
— | — | @@ -0,0 +1,54 @@ |
| 2 | +/* Copyright (c) 2008 River Tarnell <river@wikimedia.org>. */ |
| 3 | +/* |
| 4 | + * Permission is granted to anyone to use this software for any purpose, |
| 5 | + * including commercial applications, and to alter it and redistribute it |
| 6 | + * freely. This software is provided 'as-is', without any express or implied |
| 7 | + * warranty. |
| 8 | + */ |
| 9 | +/* $Id$ */ |
| 10 | + |
| 11 | +#ifndef PROCESS_H |
| 12 | +#define PROCESS_H |
| 13 | + |
| 14 | +#include <sys/types.h> |
| 15 | + |
| 16 | +#include <stdexcept> |
| 17 | + |
| 18 | +#include <boost/shared_ptr.hpp> |
| 19 | +#include <boost/noncopyable.hpp> |
| 20 | +#include <log4cxx/logger.h> |
| 21 | + |
| 22 | +struct connect_failure : std::runtime_error { |
| 23 | + connect_failure(char const *error) : std::runtime_error(error) {} |
| 24 | +}; |
| 25 | + |
| 26 | +struct creation_failure : std::runtime_error { |
| 27 | + creation_failure(char const *error) : std::runtime_error(error) {} |
| 28 | +}; |
| 29 | + |
| 30 | +struct security_violation : creation_failure { |
| 31 | + security_violation(char const *error) : creation_failure(error) {} |
| 32 | +}; |
| 33 | + |
| 34 | +struct process : boost::noncopyable { |
| 35 | + process(uid_t, gid_t, std::string const &bindpath); |
| 36 | + ~process(); |
| 37 | + |
| 38 | + int connect(int); |
| 39 | + |
| 40 | + uid_t uid() const { return uid_; } |
| 41 | + gid_t gid() const { return gid_; } |
| 42 | + pid_t pid() const { return pid_; } |
| 43 | + |
| 44 | +private: |
| 45 | + std::string bindpath_; |
| 46 | + pid_t pid_; |
| 47 | + uid_t uid_; |
| 48 | + gid_t gid_; |
| 49 | + |
| 50 | + log4cxx::LoggerPtr logger; |
| 51 | +}; |
| 52 | + |
| 53 | +typedef boost::shared_ptr<process> processp; |
| 54 | + |
| 55 | +#endif |
Index: trunk/tools/switchboard2/request_thread.h |
— | — | @@ -0,0 +1,44 @@ |
| 2 | +/* Copyright (c) 2008 River Tarnell <river@wikimedia.org>. */ |
| 3 | +/* |
| 4 | + * Permission is granted to anyone to use this software for any purpose, |
| 5 | + * including commercial applications, and to alter it and redistribute it |
| 6 | + * freely. This software is provided 'as-is', without any express or implied |
| 7 | + * warranty. |
| 8 | + */ |
| 9 | +/* $Id$ */ |
| 10 | + |
| 11 | +#ifndef REQUEST_THREAD_H |
| 12 | +#define REQUEST_THREAD_H |
| 13 | + |
| 14 | +#include <stdexcept> |
| 15 | + |
| 16 | +#include <pthread.h> |
| 17 | + |
| 18 | +#include "fcgi.h" |
| 19 | +#include "process.h" |
| 20 | + |
| 21 | +struct request_exception : std::runtime_error { |
| 22 | + request_exception(char const *what) |
| 23 | + : std::runtime_error(what) {} |
| 24 | +}; |
| 25 | + |
| 26 | +struct request_thread { |
| 27 | + request_thread(int fd); |
| 28 | + ~request_thread(); |
| 29 | + |
| 30 | + void start(); |
| 31 | + void start_request(); |
| 32 | + |
| 33 | +private: |
| 34 | + void handle_normal_request(fcgi::record &); |
| 35 | + void handle_get_values(fcgi::record &); |
| 36 | + |
| 37 | + int fd_; |
| 38 | + int cfd_; |
| 39 | + int rid_; |
| 40 | + pthread_t tid_; |
| 41 | + |
| 42 | + processp process_; |
| 43 | +}; |
| 44 | + |
| 45 | +#endif /* !REQUEST_THREAD_H */ |
Index: trunk/tools/switchboard2/main.cc |
— | — | @@ -0,0 +1,365 @@ |
| 2 | +/* Copyright (c) 2008 River Tarnell <river@wikimedia.org>. */ |
| 3 | +/* |
| 4 | + * Permission is granted to anyone to use this software for any purpose, |
| 5 | + * including commercial applications, and to alter it and redistribute it |
| 6 | + * freely. This software is provided 'as-is', without any express or implied |
| 7 | + * warranty. |
| 8 | + */ |
| 9 | +/* $Id$ */ |
| 10 | + |
| 11 | +#include <iostream> |
| 12 | +#include <cstdio> |
| 13 | +#include <cerrno> |
| 14 | +#include <cstring> |
| 15 | +#include <map> |
| 16 | +#include <string> |
| 17 | +#include <functional> |
| 18 | +#include <iterator> |
| 19 | +#include <csignal> |
| 20 | + |
| 21 | +#include <sys/types.h> |
| 22 | +#include <sys/socket.h> |
| 23 | + |
| 24 | +#include <netinet/in.h> |
| 25 | +#include <arpa/inet.h> |
| 26 | + |
| 27 | +#include <pthread.h> |
| 28 | +#include <pwd.h> |
| 29 | +#include <grp.h> |
| 30 | +#include <fcntl.h> |
| 31 | +#include <netdb.h> |
| 32 | +#include <cstdio> |
| 33 | + |
| 34 | +#include <boost/format.hpp> |
| 35 | +#include <log4cxx/logger.h> |
| 36 | +#include <log4cxx/propertyconfigurator.h> |
| 37 | + |
| 38 | +#include "fcgi.h" |
| 39 | +#include "request_thread.h" |
| 40 | +#include "config.h" |
| 41 | +#include "version.h" |
| 42 | + |
| 43 | + void * acceptor_thread(void *); |
| 44 | + |
| 45 | +extern "C" void |
| 46 | +sigexit(int) |
| 47 | +{ |
| 48 | + std::exit(0); |
| 49 | +} |
| 50 | + |
| 51 | +int |
| 52 | +main(int argc, char **argv) |
| 53 | +{ |
| 54 | + std::string initlog(CONFDIR "/initlog.conf"); |
| 55 | + std::string conf(CONFDIR "/main.conf"); |
| 56 | + int c, dflag = 0; |
| 57 | + |
| 58 | + while ((c = getopt(argc, argv, "df:i:")) != -1) { |
| 59 | + switch(c) { |
| 60 | + case 'd': |
| 61 | + dflag = 1; |
| 62 | + break; |
| 63 | + case 'f': |
| 64 | + conf = optarg; |
| 65 | + break; |
| 66 | + case 'i': |
| 67 | + initlog = optarg; |
| 68 | + break; |
| 69 | + default: |
| 70 | + return 1; |
| 71 | + } |
| 72 | + } |
| 73 | + |
| 74 | + if (access(initlog.c_str(), R_OK) == -1) { |
| 75 | + std::cerr << boost::format("switchboard: initial log configuration file \"%s\" " |
| 76 | + "cannot be read: %s\n") |
| 77 | + % initlog % std::strerror(errno); |
| 78 | + return 1; |
| 79 | + } |
| 80 | + |
| 81 | + log4cxx::LoggerPtr mainlogger(log4cxx::Logger::getLogger("main")); |
| 82 | + log4cxx::PropertyConfigurator::configure(initlog); |
| 83 | + |
| 84 | + LOG4CXX_INFO(mainlogger, "switchboard " SB_VERSION " starting up..."); |
| 85 | + |
| 86 | + configuration_loader loader; |
| 87 | + if (!loader.load(conf, mainconf)) { |
| 88 | + LOG4CXX_ERROR(mainlogger, "cannot load configuration"); |
| 89 | + return 1; |
| 90 | + } |
| 91 | + |
| 92 | + if (mainconf.sockdir.empty()) { |
| 93 | + LOG4CXX_ERROR(mainlogger, "sockdir must be specified"); |
| 94 | + return 1; |
| 95 | + } |
| 96 | + |
| 97 | + if (mainconf.userdir.empty()) { |
| 98 | + LOG4CXX_ERROR(mainlogger, "userdir must be specified"); |
| 99 | + return 1; |
| 100 | + } |
| 101 | + |
| 102 | + if (mainconf.docroot.empty()) { |
| 103 | + LOG4CXX_ERROR(mainlogger, "docroot must be specified"); |
| 104 | + return 1; |
| 105 | + } |
| 106 | + |
| 107 | + if (mainconf.servtype == serv_unknown) { |
| 108 | + LOG4CXX_ERROR(mainlogger, "server type not set"); |
| 109 | + return 1; |
| 110 | + } |
| 111 | + |
| 112 | + LOG4CXX_INFO(mainlogger, boost::format("loaded configuration from \"%s\"") % conf); |
| 113 | + |
| 114 | + for (std::size_t i = 0; i < mainconf.listeners.size(); ++i) { |
| 115 | + if (mainconf.listeners[i].host[0] == '/') { |
| 116 | + struct sockaddr_un addr; |
| 117 | + int lsnsock; |
| 118 | + std::memset(&addr, 0, sizeof(addr)); |
| 119 | + strncpy(addr.sun_path, mainconf.listeners[i].host.c_str(), sizeof(addr.sun_path) - 1); |
| 120 | + addr.sun_family = AF_UNIX; |
| 121 | + |
| 122 | + unlink(addr.sun_path); |
| 123 | + |
| 124 | + if ((lsnsock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { |
| 125 | + LOG4CXX_ERROR(mainlogger, |
| 126 | + boost::format("%s: cannot create socket: %s\n") |
| 127 | + % mainconf.listeners[i].host % std::strerror(errno)); |
| 128 | + return 1; |
| 129 | + } |
| 130 | + |
| 131 | + int one = 1; |
| 132 | + setsockopt(lsnsock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); |
| 133 | + fcntl(lsnsock, F_SETFD, FD_CLOEXEC); |
| 134 | + |
| 135 | + if (bind(lsnsock, (sockaddr *) &addr, sizeof(addr)) == -1) { |
| 136 | + LOG4CXX_ERROR(mainlogger, |
| 137 | + boost::format("%s: cannot bind: %s\n") |
| 138 | + % mainconf.listeners[i].host % std::strerror(errno)); |
| 139 | + return 1; |
| 140 | + } |
| 141 | + |
| 142 | + if (listen(lsnsock, 20) == -1) { |
| 143 | + LOG4CXX_ERROR(mainlogger, |
| 144 | + boost::format("%s cannot listen: %s\n") |
| 145 | + % mainconf.listeners[i].host % std::strerror(errno)); |
| 146 | + return 1; |
| 147 | + } |
| 148 | + } else { |
| 149 | + int r; |
| 150 | + struct addrinfo hints, *res, *iter; |
| 151 | + std::memset(&hints, 0, sizeof(hints)); |
| 152 | + hints.ai_flags = AI_PASSIVE; |
| 153 | + hints.ai_socktype = SOCK_STREAM; |
| 154 | + |
| 155 | + if ((r = getaddrinfo(mainconf.listeners[i].host.c_str(), |
| 156 | + mainconf.listeners[i].port.c_str(), |
| 157 | + &hints, &res)) != 0) { |
| 158 | + LOG4CXX_ERROR(mainlogger, |
| 159 | + boost::format("cannot resolve [%s]:%s: %s\n") |
| 160 | + % mainconf.listeners[i].host |
| 161 | + % mainconf.listeners[i].port |
| 162 | + % gai_strerror(r)); |
| 163 | + return 1; |
| 164 | + } |
| 165 | + |
| 166 | + for (iter = res; iter; iter = iter->ai_next) { |
| 167 | + char ahost[NI_MAXHOST], aserv[NI_MAXSERV]; |
| 168 | + int lsnsock; |
| 169 | + |
| 170 | + getnameinfo(iter->ai_addr, iter->ai_addrlen, |
| 171 | + ahost, sizeof(ahost), aserv, sizeof(aserv), |
| 172 | + AI_NUMERICHOST | AI_ADDRCONFIG); |
| 173 | + |
| 174 | + if ((lsnsock = socket(iter->ai_family, iter->ai_socktype, iter->ai_protocol)) == -1) { |
| 175 | + LOG4CXX_ERROR(mainlogger, |
| 176 | + boost::format("[%s]:%s: cannot create socket: %s\n") |
| 177 | + % ahost % aserv % std::strerror(errno)); |
| 178 | + return 1; |
| 179 | + } |
| 180 | + |
| 181 | + int one = 1; |
| 182 | + setsockopt(lsnsock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); |
| 183 | + fcntl(lsnsock, F_SETFD, FD_CLOEXEC); |
| 184 | + |
| 185 | + if (bind(lsnsock, iter->ai_addr, iter->ai_addrlen) == -1) { |
| 186 | + LOG4CXX_ERROR(mainlogger, |
| 187 | + boost::format("[%s]:%s: cannot bind: %s\n") |
| 188 | + % ahost % aserv % std::strerror(errno)); |
| 189 | + return 1; |
| 190 | + } |
| 191 | + |
| 192 | + if (listen(lsnsock, 20) == -1) { |
| 193 | + LOG4CXX_ERROR(mainlogger, |
| 194 | + boost::format("[%s]:%s: cannot listen: %s\n") |
| 195 | + % ahost % aserv % std::strerror(errno)); |
| 196 | + return 1; |
| 197 | + } |
| 198 | + |
| 199 | + pthread_t tid; |
| 200 | + pthread_create(&tid, NULL, acceptor_thread, reinterpret_cast<void *>(lsnsock)); |
| 201 | + } |
| 202 | + |
| 203 | + freeaddrinfo(res); |
| 204 | + } |
| 205 | + } |
| 206 | + |
| 207 | + std::string logconf = CONFDIR "/log.conf"; |
| 208 | + if (!mainconf.logconf.empty()) |
| 209 | + logconf = mainconf.logconf; |
| 210 | + |
| 211 | + if (access(logconf.c_str(), R_OK) == -1) { |
| 212 | + LOG4CXX_WARN(mainlogger, |
| 213 | + boost::format("main log configuration \"%s\" cannot be read: %s") |
| 214 | + % logconf % std::strerror(errno)); |
| 215 | + return 1; |
| 216 | + } else { |
| 217 | + LOG4CXX_INFO(mainlogger, boost::format("re-initialising logging from \"%s\"") |
| 218 | + % logconf); |
| 219 | + log4cxx::PropertyConfigurator::configure(logconf); |
| 220 | + } |
| 221 | + |
| 222 | + if (!getuid()) { |
| 223 | + struct passwd *pw; |
| 224 | + struct group *gr; |
| 225 | + |
| 226 | + if ((pw = getpwnam(SB_USER)) == NULL) { |
| 227 | + LOG4CXX_ERROR(mainlogger, |
| 228 | + boost::format("cannot setuid to \"%s\": user lookup failed: %s") |
| 229 | + % SB_USER % std::strerror(errno)); |
| 230 | + return 1; |
| 231 | + } |
| 232 | + |
| 233 | + if ((gr = getgrnam(SB_GROUP)) == NULL) { |
| 234 | + LOG4CXX_ERROR(mainlogger, |
| 235 | + boost::format("cannot setgid to \"%s\": group lookup failed: %s") |
| 236 | + % SB_USER % std::strerror(errno)); |
| 237 | + return 1; |
| 238 | + } |
| 239 | + |
| 240 | + if (!getgid()) { |
| 241 | + if (setgid(gr->gr_gid) == -1) { |
| 242 | + LOG4CXX_ERROR(mainlogger, |
| 243 | + boost::format("cannot setgid to \"%s\": %s") |
| 244 | + % SB_GROUP % std::strerror(errno)); |
| 245 | + return 1; |
| 246 | + } |
| 247 | + } |
| 248 | + |
| 249 | + if (initgroups(SB_USER, gr->gr_gid) == -1) { |
| 250 | + LOG4CXX_ERROR(mainlogger, |
| 251 | + boost::format("cannot initialise supplemental group list: %s") |
| 252 | + % std::strerror(errno)); |
| 253 | + return 1; |
| 254 | + } |
| 255 | + |
| 256 | + if (!getuid()) { |
| 257 | + if (setuid(pw->pw_uid) == -1) { |
| 258 | + LOG4CXX_ERROR(mainlogger, |
| 259 | + boost::format("cannot setuid to \"%s\": %s") |
| 260 | + % SB_USER % std::strerror(errno)); |
| 261 | + return 1; |
| 262 | + } |
| 263 | + } |
| 264 | + |
| 265 | + LOG4CXX_INFO(mainlogger, |
| 266 | + boost::format("now running as %s:%s") |
| 267 | + % SB_USER % SB_GROUP); |
| 268 | + } |
| 269 | + |
| 270 | + /* |
| 271 | + * Make sure our sockdir is valid, and has the right permissions. |
| 272 | + */ |
| 273 | +dostat: |
| 274 | + struct stat sb; |
| 275 | + if (stat(mainconf.sockdir.c_str(), &sb) == -1) { |
| 276 | + if (errno == ENOENT) { |
| 277 | + if (mkdir(mainconf.sockdir.c_str(), 0750) == -1) { |
| 278 | + LOG4CXX_ERROR(mainlogger, |
| 279 | + boost::format("cannot create sockdir \"%s\": %s") |
| 280 | + % mainconf.sockdir % std::strerror(errno)); |
| 281 | + return 1; |
| 282 | + } |
| 283 | + |
| 284 | + LOG4CXX_INFO(mainlogger, |
| 285 | + boost::format("created sockdir \"%s\"") |
| 286 | + % mainconf.sockdir); |
| 287 | + goto dostat; |
| 288 | + } else { |
| 289 | + LOG4CXX_ERROR(mainlogger, |
| 290 | + boost::format("cannot stat sockdir \"%s"": %s") |
| 291 | + % mainconf.sockdir % std::strerror(errno)); |
| 292 | + return 1; |
| 293 | + } |
| 294 | + } |
| 295 | + |
| 296 | + if (sb.st_uid != getuid()) { |
| 297 | + LOG4CXX_ERROR(mainlogger, |
| 298 | + boost::format("sockdir \"%s\" is not owned by my uid %d") |
| 299 | + % mainconf.sockdir % getuid()); |
| 300 | + return 1; |
| 301 | + } |
| 302 | + |
| 303 | + if (sb.st_gid != getgid()) { |
| 304 | + LOG4CXX_ERROR(mainlogger, |
| 305 | + boost::format("sockdir \"%s\" is not owned by my gid %d") |
| 306 | + % mainconf.sockdir % getgid()); |
| 307 | + return 1; |
| 308 | + } |
| 309 | + |
| 310 | + if ((sb.st_mode & S_IRWXO) != 0) { |
| 311 | + LOG4CXX_ERROR(mainlogger, |
| 312 | + boost::format("sockdir \"%s\" has wrong mode (access for others)") |
| 313 | + % mainconf.sockdir); |
| 314 | + return 1; |
| 315 | + } |
| 316 | + |
| 317 | + |
| 318 | + if (!dflag) { |
| 319 | + LOG4CXX_INFO(mainlogger, "detaching to background..."); |
| 320 | + switch (fork()) { |
| 321 | + case -1: |
| 322 | + LOG4CXX_INFO(mainlogger, boost::format("cannot fork: %s") |
| 323 | + % std::strerror(errno)); |
| 324 | + return 1; |
| 325 | + case 0: |
| 326 | + break; |
| 327 | + default: |
| 328 | + return 0; |
| 329 | + } |
| 330 | + |
| 331 | + setsid(); |
| 332 | + chdir("/"); |
| 333 | + int fd; |
| 334 | + if ((fd = open("/dev/null", O_RDWR, 0)) != -1) { |
| 335 | + dup2(fd, STDIN_FILENO); |
| 336 | + dup2(fd, STDOUT_FILENO); |
| 337 | + dup2(fd, STDERR_FILENO); |
| 338 | + if (fd > STDERR_FILENO) |
| 339 | + close(fd); |
| 340 | + } |
| 341 | + } |
| 342 | + |
| 343 | + std::signal(SIGTERM, sigexit); |
| 344 | + std::signal(SIGINT, sigexit); |
| 345 | + |
| 346 | + int stat; |
| 347 | + for (;;) |
| 348 | + wait(&stat); |
| 349 | +} |
| 350 | + |
| 351 | +void * |
| 352 | +acceptor_thread(void *arg) |
| 353 | +{ |
| 354 | + int fd = reinterpret_cast<int>(arg); |
| 355 | + int newfd; |
| 356 | + struct sockaddr addr; |
| 357 | + socklen_t addrlen = sizeof(addr); |
| 358 | + |
| 359 | + while ((newfd = accept(fd, &addr, &addrlen)) != -1) { |
| 360 | + request_thread *req = new request_thread(newfd); |
| 361 | + req->start(); |
| 362 | + } |
| 363 | + |
| 364 | + std::printf("accept failed: %s\n", std::strerror(errno)); |
| 365 | + return NULL; |
| 366 | +} |
Index: trunk/tools/switchboard2/version.h |
— | — | @@ -0,0 +1,15 @@ |
| 2 | +/* Copyright (c) 2008 River Tarnell <river@wikimedia.org>. */ |
| 3 | +/* |
| 4 | + * Permission is granted to anyone to use this software for any purpose, |
| 5 | + * including commercial applications, and to alter it and redistribute it |
| 6 | + * freely. This software is provided 'as-is', without any express or implied |
| 7 | + * warranty. |
| 8 | + */ |
| 9 | +/* $Id$ */ |
| 10 | + |
| 11 | +#ifndef VERSION_H |
| 12 | +#define VERSION_H |
| 13 | + |
| 14 | +#define SB_VERSION "V-2.0.0" |
| 15 | + |
| 16 | +#endif /* !VERSION_H */ |
Index: trunk/tools/switchboard2/Makefile.config.example |
— | — | @@ -0,0 +1,71 @@ |
| 2 | +### Compiler |
| 3 | +# switchboard can be compiled with gcc (tested on 3.4.3) or |
| 4 | +# Sun Studio. When using Studio, -library=stlport4 is required. |
| 5 | +# Make sure log4cxx is compiled with the same flags. |
| 6 | +CC = cc |
| 7 | +CXX = CC |
| 8 | + |
| 9 | +### Suffix for Boost library names |
| 10 | + |
| 11 | +# Sun: |
| 12 | +BOOSTTAG = -sw-mt |
| 13 | + |
| 14 | +# gcc: |
| 15 | +#BOOSTTAG = -gcc43-mt |
| 16 | + |
| 17 | +### Compiler flags |
| 18 | +# See the note about -library=stlport8 above. |
| 19 | +EXTRA_CFLAGS = -xO3 -g |
| 20 | +EXTRA_CXXFLAGS = $(EXTRA_CFLAGS) -library=stlport4 |
| 21 | + |
| 22 | +# Extra preprocessor flags, e.g. -I to find include files |
| 23 | +EXTRA_CPPFLAGS = -I/opt/boost/include/boost-1_35 |
| 24 | + |
| 25 | +# Solaris needs this. |
| 26 | +EXTRA_CPPFLAGS += -D_XOPEN_SOURCE=500 -D__EXTENSIONS__ |
| 27 | + |
| 28 | +# Extra linker flags, e.g. -R or -L to find libraries |
| 29 | +EXTRA_LDFLAGS = -L/opt/boost/lib -R /opt/boost/lib |
| 30 | + |
| 31 | +# Extra libs. |
| 32 | +EXTRA_LIBS= |
| 33 | + |
| 34 | +# For Solaris: |
| 35 | +EXTRA_LIBS+=-lsocket -lnsl -lproject |
| 36 | + |
| 37 | +# swexec needs this for project support on solaris |
| 38 | +SWEXEC_EXTRA_LIBS = -lproject |
| 39 | + |
| 40 | +# install program, /usr/ucb/install for Solaris, otherwise /usr/bin/install |
| 41 | +INSTALL=/usr/ucb/install |
| 42 | + |
| 43 | +# The prefix we install into |
| 44 | +PREFIX=/opt/switchboard |
| 45 | + |
| 46 | +# Directory where configuration files are stored |
| 47 | +CONFDIR=/etc/switchboard |
| 48 | + |
| 49 | +# The name of the PHP executable on this system. |
| 50 | +# For security reasons, this is compiled into swexec and therefore must |
| 51 | +# be set at compile time. It cannot be changed in the configuration |
| 52 | +# file. |
| 53 | +PHP_BIN=/opt/php/bin/php-cgi |
| 54 | + |
| 55 | +# The lowest UID and GID that PHP scripts will be executed for. root |
| 56 | +# is always excluded, no matter what this is set to. |
| 57 | +SB_UID_MIN=100 |
| 58 | +SB_GID_MIN=100 |
| 59 | + |
| 60 | +# The user and group that switchboard (NOT httpd) is running as. |
| 61 | +SB_USER=swtchbd |
| 62 | + |
| 63 | +# The group that switchboard runs as. For security reasons, this should |
| 64 | +# be a dedicated group that is not shared by anything else (particularly |
| 65 | +# normal users). |
| 66 | +SB_GROUP=swtchbd |
| 67 | + |
| 68 | +# The $PATH that PHP will run with. |
| 69 | +SB_SAFE_PATH=/bin:/usr/bin:/usr/local/bin |
| 70 | + |
| 71 | +# Logfile |
| 72 | +SB_LOG_EXEC=/var/log/swexec |
Index: trunk/tools/switchboard2/Makefile |
— | — | @@ -0,0 +1,106 @@ |
| 2 | +include Makefile.config |
| 3 | + |
| 4 | +CPPFLAGS = |
| 5 | +INCLUDES = $(EXTRA_CPPFLAGS) |
| 6 | +CFLAGS = $(EXTRA_CFLAGS) |
| 7 | +CXXFLAGS = $(EXTRA_CXXFLAGS) |
| 8 | +LDFLAGS = $(EXTRA_LDFLAGS) |
| 9 | +LIBS = -llog4cxx -lboost_system$(BOOSTTAG) -lboost_signals$(BOOSTTAG) $(EXTRA_LIBS) |
| 10 | + |
| 11 | +CONFIG_FLAGS = \ |
| 12 | + -DPREFIX=\"$(PREFIX)\" \ |
| 13 | + -DCONFDIR=\"$(CONFDIR)\" \ |
| 14 | + -DPHP_BIN=\"$(PHP_BIN)\" \ |
| 15 | + -DSB_USER=\"$(SB_USER)\" \ |
| 16 | + -DSB_GROUP=\"$(SB_GROUP)\" \ |
| 17 | + |
| 18 | +SWEXEC_FLAGS = \ |
| 19 | + -DSB_UID_MIN=$(SB_UID_MIN) \ |
| 20 | + -DSB_GID_MIN=$(SB_GID_MIN) \ |
| 21 | + -DSB_USER=\"$(SB_USER)\" \ |
| 22 | + -DSB_GROUP=\"$(SB_GROUP)\" \ |
| 23 | + -DSB_SAFE_PATH=\"$(SB_SAFE_PATH)\" \ |
| 24 | + -DPHP_BIN=\"$(PHP_BIN)\" \ |
| 25 | + -DSB_LOG_EXEC=\"$(SB_LOG_EXEC)\" \ |
| 26 | + |
| 27 | +SWKILL_FLAGS = \ |
| 28 | + -DSB_UID_MIN=$(SB_UID_MIN) \ |
| 29 | + -DSB_GID_MIN=$(SB_GID_MIN) \ |
| 30 | + -DSB_USER=\"$(SB_USER)\" \ |
| 31 | + -DSB_LOG_EXEC=\"$(SB_LOG_EXEC)\" \ |
| 32 | + |
| 33 | +OBJS= \ |
| 34 | + main.o \ |
| 35 | + request_thread.o \ |
| 36 | + fcgi.o \ |
| 37 | + process.o \ |
| 38 | + config.o \ |
| 39 | + process_factory.o \ |
| 40 | + |
| 41 | +PROG= switchboard |
| 42 | +SWEXEC= swexec |
| 43 | +SWKILL= swkill |
| 44 | + |
| 45 | +all: $(PROG) $(SWEXEC) $(SWKILL) |
| 46 | + |
| 47 | +$(PROG): $(OBJS) |
| 48 | + $(CXX) $(CFLAGS) $(CXXFLAGS) $(LDFLAGS) $(OBJS) -o $(PROG) $(LIBS) |
| 49 | + |
| 50 | +$(SWEXEC): swexec.o |
| 51 | + $(CC) $(CFLAGS) $(LDFLAGS) swexec.o -o $(SWEXEC) $(SWEXEC_EXTRA_LIBS) |
| 52 | + |
| 53 | +$(SWKILL): swkill.o |
| 54 | + $(CC) $(CFLAGS) $(LDFLAGS) swkill.o -o $(SWKILL) |
| 55 | + |
| 56 | +.cc.o: |
| 57 | + $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(CONFIG_FLAGS) $(INCLUDES) $(CFLAGS) -c $< |
| 58 | + |
| 59 | +swexec.o: swexec.c |
| 60 | + $(CC) $(CPPFLAGS) $(SWEXEC_FLAGS) $(INCLUDES) $(CFLAGS) -c $< |
| 61 | + |
| 62 | +swkill.o: swkill.c |
| 63 | + $(CC) $(CPPFLAGS) $(SWKILL_FLAGS) $(INCLUDES) $(CFLAGS) -c $< |
| 64 | + |
| 65 | +install: |
| 66 | + $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX) |
| 67 | + $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/lib |
| 68 | + $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/lib/switchboard |
| 69 | + $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin |
| 70 | + $(INSTALL) -d -m 0755 $(DESTDIR)$(CONFDIR) |
| 71 | + $(INSTALL) -o root -g $(SB_GROUP) -m 0755 switchboard $(DESTDIR)$(PREFIX)/bin |
| 72 | + $(INSTALL) -o root -g $(SB_GROUP) -m 04710 $(SWEXEC) $(DESTDIR)$(PREFIX)/lib/switchboard |
| 73 | + $(INSTALL) -o root -g $(SB_GROUP) -m 04710 $(SWKILL) $(DESTDIR)$(PREFIX)/lib/switchboard |
| 74 | + $(INSTALL) -o root -g bin -m 0644 log.conf.example $(DESTDIR)$(CONFDIR) |
| 75 | + $(INSTALL) -o root -g bin -m 0644 main.conf.example $(DESTDIR)$(CONFDIR) |
| 76 | + $(INSTALL) -o root -g bin -m 0644 initlog.conf $(DESTDIR)$(CONFDIR) |
| 77 | + |
| 78 | +clean: |
| 79 | + rm -f $(OBJS) $(PROG) $(SWEXEC) $(SWKILL) swexec.o swkill.o |
| 80 | + |
| 81 | +Makefile.config: |
| 82 | + @echo You must create Makefile.config before you can build switchboard |
| 83 | + @exit 1 |
| 84 | + |
| 85 | +.SUFFIXES: .c .cc .o |
| 86 | +.KEEP_STATE: |
| 87 | +.PHONY: clean |
| 88 | + |
| 89 | +gccdepend: |
| 90 | + sed '/^# Do not delete this line -- make depend requires it/,$$d' < Makefile > Makefile.new |
| 91 | + echo '# Do not delete this line -- make depend requires it' >> Makefile.new |
| 92 | + $(CXX) $(CPPFLAGS) $(CONFIG_FLAGS) $(INCLUDES) -MM $(OBJS:.o=.cc) >>Makefile.new |
| 93 | + $(CXX) $(CPPFLAGS) $(SWEXEC_FLAGS) $(INCLUDES) -MM swexec.c >>Makefile.new |
| 94 | + $(CXX) $(CPPFLAGS) $(SWKILL_FLAGS) $(INCLUDES) -MM swkill.c >>Makefile.new |
| 95 | + mv -f Makefile.new Makefile |
| 96 | + |
| 97 | +# Do not delete this line -- make depend requires it |
| 98 | +main.o: main.cc fcgi.h request_thread.h process.h config.h version.h |
| 99 | +request_thread.o: request_thread.cc request_thread.h fcgi.h process.h \ |
| 100 | + process_factory.h util.h |
| 101 | +fcgi.o: fcgi.cc fcgi.h |
| 102 | +process.o: process.cc process.h |
| 103 | +config.o: config.cc config.h |
| 104 | +process_factory.o: process_factory.cc process_factory.h process.h \ |
| 105 | + config.h util.h |
| 106 | +swexec.o: swexec.c |
| 107 | +swkill.o: swkill.c |