Fix spelling of "privilege". Andrew Benham [adsb@bigfoot.com]
[mod_fastcgi.git] / fcgi_util.c
blob4b3a658d4669d60bc6ad91b83f46ccd415f78531
1 /*
2 * $Id: fcgi_util.c,v 1.16 2000/11/12 15:13:24 robs Exp $
3 */
5 #include "fcgi.h"
7 /*******************************************************************************
8 * Compute printable MD5 hash. Pool p is used for scratch as well as for
9 * allocating the hash - use temp storage, and dup it if you need to keep it.
11 char *
12 fcgi_util_socket_hash_filename(pool *p, const char *path,
13 const char *user, const char *group)
15 char *buf = ap_pstrcat(p, path, user, group, NULL);
17 /* Canonicalize the path (remove "//", ".", "..") */
18 ap_getparents(buf);
20 return ap_md5(p, (unsigned char *)buf);
23 /*******************************************************************************
24 * Return absolute path to file in either "regular" FCGI socket directory or
25 * the dynamic directory. Result is allocated in pool p.
27 const char *
28 fcgi_util_socket_make_path_absolute(pool * const p,
29 const char *const file, const int dynamic)
31 if (ap_os_is_path_absolute(file))
33 return file;
35 else
37 const char * parent_dir = dynamic ? fcgi_dynamic_dir : fcgi_socket_dir;
38 return (const char *) ap_make_full_path(p, parent_dir, file);
42 /*******************************************************************************
43 * Allocate a new string from pool p with the name of a Unix/Domain socket's
44 * lock file (used by dynamic only).
46 const char *
47 fcgi_util_socket_get_lock_filename(pool *p, const char *socket_path)
49 return ap_pstrcat(p, socket_path, ".lock", NULL);
52 #ifndef WIN32
53 /*******************************************************************************
54 * Build a Domain Socket Address structure, and calculate its size.
55 * The error message is allocated from the pool p. If you don't want the
56 * struct sockaddr_un also allocated from p, pass it preallocated (!=NULL).
58 const char *
59 fcgi_util_socket_make_domain_addr(pool *p, struct sockaddr_un **socket_addr,
60 int *socket_addr_len, const char *socket_path)
62 int socket_pathLen = strlen(socket_path);
64 if (socket_pathLen >= sizeof((*socket_addr)->sun_path)) {
65 return ap_pstrcat(p, "path \"", socket_path,
66 "\" is too long for a Domain socket", NULL);
69 if (*socket_addr == NULL)
70 *socket_addr = ap_pcalloc(p, sizeof(struct sockaddr_un));
71 else
72 memset(*socket_addr, 0, sizeof(struct sockaddr_un));
74 (*socket_addr)->sun_family = AF_UNIX;
75 strcpy((*socket_addr)->sun_path, socket_path);
77 *socket_addr_len = SUN_LEN(*socket_addr);
78 return NULL;
80 #endif
82 /*******************************************************************************
83 * Convert a hostname or IP address string to an in_addr struct.
85 static int
86 convert_string_to_in_addr(const char * const hostname, struct in_addr * const addr)
88 struct hostent *hp;
89 int count;
91 addr->s_addr = inet_addr((char *)hostname);
93 if (addr->s_addr == INADDR_NONE) {
94 if ((hp = gethostbyname((char *)hostname)) == NULL)
95 return -1;
97 memcpy((char *) addr, hp->h_addr, hp->h_length);
98 count = 0;
99 while (hp->h_addr_list[count] != 0)
100 count++;
102 return count;
104 return 1;
108 /*******************************************************************************
109 * Build an Inet Socket Address structure, and calculate its size.
110 * The error message is allocated from the pool p. If you don't want the
111 * struct sockaddr_in also allocated from p, pass it preallocated (!=NULL).
113 const char *
114 fcgi_util_socket_make_inet_addr(pool *p, struct sockaddr_in **socket_addr,
115 int *socket_addr_len, const char *host, unsigned short port)
117 if (*socket_addr == NULL)
118 *socket_addr = ap_pcalloc(p, sizeof(struct sockaddr_in));
119 else
120 memset(*socket_addr, 0, sizeof(struct sockaddr_in));
122 (*socket_addr)->sin_family = AF_INET;
123 (*socket_addr)->sin_port = htons(port);
125 /* Get an in_addr represention of the host */
126 if (host != NULL) {
127 if (convert_string_to_in_addr(host, &(*socket_addr)->sin_addr) != 1) {
128 return ap_pstrcat(p, "failed to resolve \"", host,
129 "\" to exactly one IP address", NULL);
131 } else {
132 (*socket_addr)->sin_addr.s_addr = htonl(INADDR_ANY);
135 *socket_addr_len = sizeof(struct sockaddr_in);
136 return NULL;
139 /*******************************************************************************
140 * Determine if a process with uid/gid can access a file with mode permissions.
142 const char *
143 fcgi_util_check_access(pool *tp,
144 const char * const path, const struct stat *statBuf,
145 const int mode, const uid_t uid, const gid_t gid)
147 if (statBuf == NULL) {
148 static struct stat staticStatBuf;
150 if (stat(path, &staticStatBuf) < 0)
151 return ap_psprintf(tp, "stat() failed: %s", strerror(errno));
152 statBuf = &staticStatBuf;
155 #ifndef WIN32
156 /* If the uid owns the file, check the owner bits */
157 if (uid == statBuf->st_uid) {
158 if (mode & R_OK && !(statBuf->st_mode & S_IRUSR))
159 return "read not allowed by owner";
160 if (mode & W_OK && !(statBuf->st_mode & S_IWUSR))
161 return "write not allowed by owner";
162 if (mode & X_OK && !(statBuf->st_mode & S_IXUSR))
163 return "execute not allowed by owner";
164 return NULL;
166 #else
167 if (mode & _S_IREAD && !(statBuf->st_mode & _S_IREAD))
168 return "read not allowed";
169 if (mode & _S_IWRITE && !(statBuf->st_mode & _S_IWRITE))
170 return "write not allowed";
172 // I don't think this works on FAT, but since I don't know how to check..
173 // if (mode & _S_IEXEC && !(statBuf->st_mode & _S_IEXEC))
174 // return "execute not allowed";
175 #endif
177 #if !defined(__EMX__) && !defined(WIN32)
178 /* If the gid is same as the file's group, check the group bits */
179 if (gid == statBuf->st_gid) {
180 if (mode & R_OK && !(statBuf->st_mode & S_IRGRP))
181 return "read not allowed by group";
182 if (mode & W_OK && !(statBuf->st_mode & S_IWGRP))
183 return "write not allowed by group";
184 if (mode & X_OK && !(statBuf->st_mode & S_IXGRP))
185 return "execute not allowed by group";
186 return NULL;
189 /* Get the user membership for the file's group. If the
190 * uid is a member, check the group bits. */
192 const struct group * const gr = getgrgid(statBuf->st_gid);
193 const struct passwd * const pw = getpwuid(uid);
195 if (gr != NULL && pw != NULL) {
196 char **user = gr->gr_mem;
197 for ( ; *user != NULL; user++) {
198 if (strcmp(*user, pw->pw_name) == 0) {
199 if (mode & R_OK && !(statBuf->st_mode & S_IRGRP))
200 return "read not allowed by group";
201 if (mode & W_OK && !(statBuf->st_mode & S_IWGRP))
202 return "write not allowed by group";
203 if (mode & X_OK && !(statBuf->st_mode & S_IXGRP))
204 return "execute not allowed by group";
205 return NULL;
211 /* That just leaves the other bits.. */
212 if (mode & R_OK && !(statBuf->st_mode & S_IROTH))
213 return "read not allowed";
214 if (mode & W_OK && !(statBuf->st_mode & S_IWOTH))
215 return "write not allowed";
216 if (mode & X_OK && !(statBuf->st_mode & S_IXOTH))
217 return "execute not allowed";
218 #endif
220 return NULL;
224 /*******************************************************************************
225 * Find a FastCGI server with a matching fs_path, and if fcgi_wrapper is
226 * enabled with matching uid and gid.
228 fcgi_server *
229 fcgi_util_fs_get_by_id(const char *ePath, uid_t uid, gid_t gid)
231 char path[FCGI_MAXPATH];
232 fcgi_server *s;
234 /* @@@ This should now be done in the loop below */
235 ap_cpystrn(path, ePath, FCGI_MAXPATH);
236 ap_no2slash(path);
238 for (s = fcgi_servers; s != NULL; s = s->next) {
239 int i;
240 const char *fs_path = s->fs_path;
241 for (i = 0; fs_path[i] && path[i]; ++i) {
242 if (fs_path[i] != path[i]) {
243 break;
246 if (fs_path[i]) {
247 continue;
249 if (path[i] == '\0' || path[i] == '/') {
250 if (fcgi_wrapper == NULL || (uid == s->uid && gid == s->gid))
251 return s;
254 return NULL;
257 /*******************************************************************************
258 * Find a FastCGI server with a matching fs_path, and if fcgi_wrapper is
259 * enabled with matching user and group.
261 fcgi_server *
262 fcgi_util_fs_get(const char *ePath, const char *user, const char *group)
264 char path[FCGI_MAXPATH];
265 fcgi_server *s;
267 ap_cpystrn(path, ePath, FCGI_MAXPATH);
268 ap_no2slash(path);
270 for (s = fcgi_servers; s != NULL; s = s->next) {
271 if (strcmp(s->fs_path, path) == 0) {
272 if (fcgi_wrapper == NULL)
273 return s;
275 if (strcmp(user, s->user) == 0
276 && (user[0] == '~' || strcmp(group, s->group) == 0))
278 return s;
282 return NULL;
285 const char *
286 fcgi_util_fs_is_path_ok(pool * const p, const char * const fs_path,
287 struct stat *finfo, const uid_t uid, const gid_t gid)
289 const char *err;
291 /* If a wrapper is in use, let the wrapper determine what it can
292 * and can't execute */
293 if (fcgi_wrapper)
294 return NULL;
296 if (finfo == NULL) {
297 finfo = (struct stat *)ap_palloc(p, sizeof(struct stat));
298 if (stat(fs_path, finfo) < 0)
299 return ap_psprintf(p, "stat() failed: %s", strerror(errno));
302 /* No Parse Header scripts aren't allowed.
303 * @@@ Well... we really could quite easily */
304 if (strncmp(strrchr(fs_path, '/'), "/nph-", 5) == 0)
305 return ap_psprintf(p, "NPH scripts cannot be run as FastCGI");
307 if (finfo->st_mode == 0)
308 return ap_psprintf(p, "script not found or unable to stat()");
310 if (S_ISDIR(finfo->st_mode))
311 return ap_psprintf(p, "script is a directory!");
313 #ifdef WIN32
314 err = fcgi_util_check_access(p, fs_path, finfo, _S_IEXEC, fcgi_user_id, fcgi_group_id);
315 #else
316 err = fcgi_util_check_access(p, fs_path, finfo, X_OK, fcgi_user_id, fcgi_group_id);
317 #endif
318 if (err) {
319 return ap_psprintf(p,
320 "access for server (uid %ld, gid %ld) not allowed: %s",
321 (long)fcgi_user_id, (long)fcgi_group_id, err);
324 return NULL;
329 /*******************************************************************************
330 * Allocate a new FastCGI server record from pool p with default values.
332 fcgi_server *
333 fcgi_util_fs_new(pool *p)
335 fcgi_server *s = (fcgi_server *) ap_pcalloc(p, sizeof(fcgi_server));
337 /* Initialize anything who's init state is not zeroizzzzed */
338 s->listenQueueDepth = FCGI_DEFAULT_LISTEN_Q;
339 s->appConnectTimeout = FCGI_DEFAULT_APP_CONN_TIMEOUT;
340 s->idle_timeout = FCGI_DEFAULT_IDLE_TIMEOUT;
341 s->initStartDelay = DEFAULT_INIT_START_DELAY;
342 s->restartDelay = FCGI_DEFAULT_RESTART_DELAY;
343 s->restartOnExit = FALSE;
344 s->directive = APP_CLASS_UNKNOWN;
345 s->processPriority = FCGI_DEFAULT_PRIORITY;
346 #ifdef WIN32
347 s->listenFd = (int)INVALID_HANDLE_VALUE;
348 #else
349 s->listenFd = -2;
350 #endif
351 s->envp = &fcgi_empty_env;
353 return s;
356 /*******************************************************************************
357 * Add the server to the linked list of FastCGI servers.
359 void
360 fcgi_util_fs_add(fcgi_server *s)
362 s->next = fcgi_servers;
363 fcgi_servers = s;
366 /*******************************************************************************
367 * Configure uid, gid, user, group, username for wrapper.
369 const char *
370 fcgi_util_fs_set_uid_n_gid(pool *p, fcgi_server *s, uid_t uid, gid_t gid)
372 #ifndef WIN32
373 struct passwd *pw;
374 struct group *gr;
375 #endif
377 if (fcgi_wrapper == NULL)
378 return NULL;
380 #ifndef WIN32
381 s->uid = uid;
382 pw = getpwuid(uid);
383 if (pw == NULL) {
384 return ap_psprintf(p,
385 "getpwuid() couldn't determine the username for uid '%ld', "
386 "you probably need to modify the User directive: %s",
387 (long)uid, strerror(errno));
389 s->user = ap_pstrdup(p, pw->pw_name);
390 s->username = s->user;
392 s->gid = gid;
393 gr = getgrgid(gid);
394 if (gr == NULL) {
395 return ap_psprintf(p,
396 "getgrgid() couldn't determine the group name for gid '%ld', "
397 "you probably need to modify the Group directive: %s",
398 (long)gid, strerror(errno));
400 s->group = ap_pstrdup(p, gr->gr_name);
401 #endif
402 return NULL;
405 /*******************************************************************************
406 * Allocate an array of ServerProcess records.
408 ServerProcess *
409 fcgi_util_fs_create_procs(pool *p, int num)
411 int i;
412 ServerProcess *proc = (ServerProcess *)ap_pcalloc(p, sizeof(ServerProcess) * num);
414 for (i = 0; i < num; i++) {
415 #ifdef WIN32
416 proc[i].handle = INVALID_HANDLE_VALUE;
417 #endif
418 proc[i].pid = 0;
419 proc[i].state = STATE_READY;
421 return proc;
425 *----------------------------------------------------------------------
427 * fcgi_util_lock_fd
429 * Provide file locking via fcntl(2) function call. This
430 * code has been borrowed from Stevens "Advanced Unix
431 * Programming", page 370.
433 * Inputs:
434 * File descriptor to be locked, offsets within the file.
436 * Results:
437 * 0 on successful locking, -1 otherwise
439 * Side effects:
440 * File pointed to by file descriptor is locked or unlocked.
441 * Provided macros allow for both blocking and non-blocking
442 * behavior.
444 *----------------------------------------------------------------------
447 #ifndef WIN32
448 int
449 fcgi_util_lock_fd(int fd, int cmd, int type, off_t offset, int whence, off_t len)
451 int res = 0;
452 struct flock lock;
454 lock.l_type = type; /* F_RDLCK, F_WRLCK, F_UNLCK */
455 lock.l_start = offset; /* byte offset, relative to whence */
456 lock.l_whence = whence; /* SEEK_SET, SEET_CUR, SEEK_END */
457 lock.l_len = len; /* # of bytes, (0 indicates to EOF) */
459 /* Don't be fooled into thinking we've set a lock when we've
460 merely caught a signal. */
462 /* This is OK only if there is a hard_timeout() in effect! */
463 while ((res = fcntl(fd, cmd, &lock)) == -1 && errno == EINTR);
464 return res;
466 #endif
468 int fcgi_util_gettimeofday(struct timeval *Time) {
469 #ifdef WIN32
470 DWORD clock;
471 time_t t;
473 clock = GetTickCount();
475 t = time(NULL);
477 Time->tv_sec = t; //clock / 1000;
478 Time->tv_usec = (clock - Time->tv_sec) * 1000;
480 if (Time->tv_sec == (time_t)-1)
481 return -1;
482 else
483 return 0;
484 #else
485 return gettimeofday(Time, NULL);
486 #endif
489 #ifdef WIN32
492 * We allow only one writer at a time and a writer can proceed only
493 * when there are no readers.
495 * @@@ For named pipes we could lock only the process we're going to waste.
498 FcgiRWLock * fcgi_rdwr_create()
500 FcgiRWLock *newlock = NULL;
502 newlock = (FcgiRWLock *) malloc(sizeof(FcgiRWLock));
503 ap_assert(newlock);
505 newlock->write_lock = CreateEvent(NULL, FALSE, TRUE, NULL);
506 newlock->mutex = CreateMutex(NULL, FALSE, NULL);
507 newlock->counter = 0;
509 return newlock;
512 void fcgi_rdwr_destroy(FcgiRWLock *lock)
514 CloseHandle(lock->write_lock);
515 CloseHandle(lock->mutex);
516 free(lock);
519 int fcgi_rdwr_lock(FcgiRWLock *lock, int type)
521 if (type == WRITER)
523 WaitForSingleObject(lock->write_lock, INFINITE);
525 else
527 WaitForSingleObject(lock->mutex, INFINITE);
528 if (lock->counter == 0)
530 WaitForSingleObject(lock->write_lock, INFINITE);
532 ++lock->counter;
533 ReleaseMutex(lock->mutex);
536 return 0;
539 int fcgi_rdwr_unlock(FcgiRWLock *lock, int type)
541 if (type == WRITER)
543 SetEvent(lock->write_lock);
545 else
547 WaitForSingleObject(lock->mutex, INFINITE);
548 --lock->counter;
549 if (lock->counter == 0)
551 SetEvent(lock->write_lock);
553 ReleaseMutex(lock->mutex);
556 return 0;
559 int fcgi_rdwr_try_lock(FcgiRWLock *lock, int type)
561 DWORD rc;
563 if (type == WRITER)
565 rc = WaitForSingleObject(lock->write_lock, 0);
566 if (rc == WAIT_TIMEOUT || rc == WAIT_FAILED)
568 return -1;
571 else
573 ap_assert(0);
576 return 0;
579 #endif