/* * $Id: fcgi_util.c,v 1.32 2007/09/23 16:33:29 robs Exp $ */ #include "fcgi.h" #ifdef WIN32 #pragma warning( disable : 4100 ) #elif defined(APACHE2) #include #include #include #include #if APR_HAVE_ARPA_INET_H #include #endif #include "unixd.h" #endif uid_t fcgi_util_get_server_uid(const server_rec * const s) { #if defined(WIN32) return (uid_t) 0; #elif defined(APACHE2) /* the main server's uid */ return ap_user_id; #else /* the vhost's uid */ return s->server_uid; #endif } uid_t fcgi_util_get_server_gid(const server_rec * const s) { #if defined(WIN32) return (uid_t) 0; #elif defined(APACHE2) /* the main server's gid */ return ap_group_id; #else /* the vhost's gid */ return s->server_gid; #endif } /******************************************************************************* * Compute printable MD5 hash. Pool p is used for scratch as well as for * allocating the hash - use temp storage, and dup it if you need to keep it. */ char * fcgi_util_socket_hash_filename(pool *p, const char *path, const char *user, const char *group) { char *buf = ap_pstrcat(p, path, user, group, NULL); /* Canonicalize the path (remove "//", ".", "..") */ ap_getparents(buf); return ap_md5(p, (unsigned char *)buf); } /******************************************************************************* * Concat src1 and src2 using the approprate path seperator for the platform. */ static char * make_full_path(pool *a, const char *src1, const char *src2) { #ifdef WIN32 register int x; char * p ; char * q ; x = strlen(src1); if (x == 0) { p = ap_pstrcat(a, "\\", src2, NULL); } else if (src1[x - 1] != '\\' && src1[x - 1] != '/') { p = ap_pstrcat(a, src1, "\\", src2, NULL); } else { p = ap_pstrcat(a, src1, src2, NULL); } q = p ; while (*q) { if (*q == '/') { *q = '\\' ; } ++q; } return p ; #else return ap_make_full_path(a, src1, src2); #endif } /******************************************************************************* * Return absolute path to file in either "regular" FCGI socket directory or * the dynamic directory. Result is allocated in pool p. */ const char * fcgi_util_socket_make_path_absolute(pool * const p, const char *const file, const int dynamic) { #ifdef APACHE2 if (ap_os_is_path_absolute(p, (char *) file)) #else if (ap_os_is_path_absolute(file)) #endif { return file; } else { const char * parent_dir = dynamic ? fcgi_dynamic_dir : fcgi_socket_dir; return (const char *) make_full_path(p, parent_dir, file); } } #ifndef WIN32 /******************************************************************************* * Build a Domain Socket Address structure, and calculate its size. * The error message is allocated from the pool p. If you don't want the * struct sockaddr_un also allocated from p, pass it preallocated (!=NULL). */ const char * fcgi_util_socket_make_domain_addr(pool *p, struct sockaddr_un **socket_addr, int *socket_addr_len, const char *socket_path) { int socket_pathLen = strlen(socket_path); if (socket_pathLen >= sizeof((*socket_addr)->sun_path)) { return ap_pstrcat(p, "path \"", socket_path, "\" is too long for a Domain socket", NULL); } if (*socket_addr == NULL) *socket_addr = ap_pcalloc(p, sizeof(struct sockaddr_un)); else memset(*socket_addr, 0, sizeof(struct sockaddr_un)); (*socket_addr)->sun_family = AF_UNIX; strcpy((*socket_addr)->sun_path, socket_path); *socket_addr_len = SUN_LEN(*socket_addr); return NULL; } #endif /******************************************************************************* * Convert a hostname or IP address string to an in_addr struct. */ static int convert_string_to_in_addr(const char * const hostname, struct in_addr * const addr) { struct hostent *hp; int count; addr->s_addr = inet_addr((char *)hostname); #if !defined(INADDR_NONE) && defined(APACHE2) #define INADDR_NONE APR_INADDR_NONE #endif if (addr->s_addr == INADDR_NONE) { if ((hp = gethostbyname((char *)hostname)) == NULL) return -1; memcpy((char *) addr, hp->h_addr, hp->h_length); count = 0; while (hp->h_addr_list[count] != 0) count++; return count; } return 1; } /******************************************************************************* * Build an Inet Socket Address structure, and calculate its size. * The error message is allocated from the pool p. If you don't want the * struct sockaddr_in also allocated from p, pass it preallocated (!=NULL). */ const char * fcgi_util_socket_make_inet_addr(pool *p, struct sockaddr_in **socket_addr, int *socket_addr_len, const char *host, unsigned short port) { if (*socket_addr == NULL) *socket_addr = ap_pcalloc(p, sizeof(struct sockaddr_in)); else memset(*socket_addr, 0, sizeof(struct sockaddr_in)); (*socket_addr)->sin_family = AF_INET; (*socket_addr)->sin_port = htons(port); /* Get an in_addr represention of the host */ if (host != NULL) { if (convert_string_to_in_addr(host, &(*socket_addr)->sin_addr) != 1) { return ap_pstrcat(p, "failed to resolve \"", host, "\" to exactly one IP address", NULL); } } else { (*socket_addr)->sin_addr.s_addr = htonl(INADDR_ANY); } *socket_addr_len = sizeof(struct sockaddr_in); return NULL; } /******************************************************************************* * Determine if a process with uid/gid can access a file with mode permissions. */ const char * fcgi_util_check_access(pool *tp, const char * const path, const struct stat *statBuf, const int mode, const uid_t uid, const gid_t gid) { struct stat myStatBuf; if (statBuf == NULL) { if (stat(path, &myStatBuf) < 0) return ap_psprintf(tp, "stat(%s) failed: %s", path, strerror(errno)); statBuf = &myStatBuf; } #ifndef WIN32 /* If the uid owns the file, check the owner bits */ if (uid == statBuf->st_uid) { if (mode & R_OK && !(statBuf->st_mode & S_IRUSR)) return "read not allowed by owner"; if (mode & W_OK && !(statBuf->st_mode & S_IWUSR)) return "write not allowed by owner"; if (mode & X_OK && !(statBuf->st_mode & S_IXUSR)) return "execute not allowed by owner"; return NULL; } #else if (mode & _S_IREAD && !(statBuf->st_mode & _S_IREAD)) return "read not allowed"; if (mode & _S_IWRITE && !(statBuf->st_mode & _S_IWRITE)) return "write not allowed"; /* I don't think this works on FAT, but since I don't know how to check.. * if (mode & _S_IEXEC && !(statBuf->st_mode & _S_IEXEC)) * return "execute not allowed"; */ #endif #if !defined(__EMX__) && !defined(WIN32) /* If the gid is same as the file's group, check the group bits */ if (gid == statBuf->st_gid) { if (mode & R_OK && !(statBuf->st_mode & S_IRGRP)) return "read not allowed by group"; if (mode & W_OK && !(statBuf->st_mode & S_IWGRP)) return "write not allowed by group"; if (mode & X_OK && !(statBuf->st_mode & S_IXGRP)) return "execute not allowed by group"; return NULL; } /* Get the user membership for the file's group. If the * uid is a member, check the group bits. */ { const struct group * const gr = getgrgid(statBuf->st_gid); const struct passwd * const pw = getpwuid(uid); if (gr != NULL && pw != NULL) { char **user = gr->gr_mem; for ( ; *user != NULL; user++) { if (strcmp(*user, pw->pw_name) == 0) { if (mode & R_OK && !(statBuf->st_mode & S_IRGRP)) return "read not allowed by group"; if (mode & W_OK && !(statBuf->st_mode & S_IWGRP)) return "write not allowed by group"; if (mode & X_OK && !(statBuf->st_mode & S_IXGRP)) return "execute not allowed by group"; return NULL; } } } } /* That just leaves the other bits.. */ if (mode & R_OK && !(statBuf->st_mode & S_IROTH)) return "read not allowed"; if (mode & W_OK && !(statBuf->st_mode & S_IWOTH)) return "write not allowed"; if (mode & X_OK && !(statBuf->st_mode & S_IXOTH)) return "execute not allowed"; #endif return NULL; } /******************************************************************************* * Find a FastCGI server with a matching fs_path, and if fcgi_wrapper is * enabled with matching uid and gid. */ fcgi_server * fcgi_util_fs_get_by_id(const char *ePath, uid_t uid, gid_t gid) { char path[FCGI_MAXPATH]; fcgi_server *s; /* @@@ This should now be done in the loop below */ ap_cpystrn(path, ePath, FCGI_MAXPATH); ap_no2slash(path); for (s = fcgi_servers; s != NULL; s = s->next) { int i; const char *fs_path = s->fs_path; for (i = 0; fs_path[i] && path[i]; ++i) { if (fs_path[i] != path[i]) { break; } } if (fs_path[i]) { continue; } if (path[i] == '\0' || path[i] == '/') { if (fcgi_wrapper == NULL || (uid == s->uid && gid == s->gid)) return s; } } return NULL; } /******************************************************************************* * Find a FastCGI server with a matching fs_path, and if fcgi_wrapper is * enabled with matching user and group. */ fcgi_server * fcgi_util_fs_get(const char *ePath, const char *user, const char *group) { char path[FCGI_MAXPATH]; fcgi_server *s; ap_cpystrn(path, ePath, FCGI_MAXPATH); ap_no2slash(path); for (s = fcgi_servers; s != NULL; s = s->next) { if (strcmp(s->fs_path, path) == 0) { if (fcgi_wrapper == NULL) return s; if (strcmp(user, s->user) == 0 && (user[0] == '~' || strcmp(group, s->group) == 0)) { return s; } } } return NULL; } const char * fcgi_util_fs_is_path_ok(pool * const p, const char * const fs_path, struct stat *finfo) { const char *err; if (finfo == NULL) { finfo = (struct stat *)ap_palloc(p, sizeof(struct stat)); if (stat(fs_path, finfo) < 0) return ap_psprintf(p, "stat(%s) failed: %s", fs_path, strerror(errno)); } if (finfo->st_mode == 0) return ap_psprintf(p, "script not found or unable to stat()"); if (S_ISDIR(finfo->st_mode)) return ap_psprintf(p, "script is a directory!"); /* Let the wrapper determine what it can and can't execute */ if (! fcgi_wrapper) { #ifdef WIN32 err = fcgi_util_check_access(p, fs_path, finfo, _S_IEXEC, fcgi_user_id, fcgi_group_id); #else err = fcgi_util_check_access(p, fs_path, finfo, X_OK, fcgi_user_id, fcgi_group_id); #endif if (err) { return ap_psprintf(p, "access for server (uid %ld, gid %ld) not allowed: %s", (long)fcgi_user_id, (long)fcgi_group_id, err); } } return NULL; } /******************************************************************************* * Allocate a new FastCGI server record from pool p with default values. */ fcgi_server * fcgi_util_fs_new(pool *p) { fcgi_server *s = (fcgi_server *) ap_pcalloc(p, sizeof(fcgi_server)); /* Initialize anything who's init state is not zeroizzzzed */ s->listenQueueDepth = FCGI_DEFAULT_LISTEN_Q; s->appConnectTimeout = FCGI_DEFAULT_APP_CONN_TIMEOUT; s->idle_timeout = FCGI_DEFAULT_IDLE_TIMEOUT; s->initStartDelay = DEFAULT_INIT_START_DELAY; s->restartDelay = FCGI_DEFAULT_RESTART_DELAY; s->minServerLife = FCGI_DEFAULT_MIN_SERVER_LIFE; s->restartOnExit = FALSE; s->directive = APP_CLASS_UNKNOWN; s->processPriority = FCGI_DEFAULT_PRIORITY; s->envp = &fcgi_empty_env; #ifdef WIN32 s->listenFd = (int) INVALID_HANDLE_VALUE; #else s->listenFd = -2; #endif return s; } /******************************************************************************* * Add the server to the linked list of FastCGI servers. */ void fcgi_util_fs_add(fcgi_server *s) { s->next = fcgi_servers; fcgi_servers = s; } /******************************************************************************* * Configure uid, gid, user, group, username for wrapper. */ const char * fcgi_util_fs_set_uid_n_gid(pool *p, fcgi_server *s, uid_t uid, gid_t gid) { #ifndef WIN32 struct passwd *pw; struct group *gr; if (fcgi_wrapper == NULL) return NULL; if (uid == 0 || gid == 0) { return "invalid uid or gid, see the -user and -group options"; } s->uid = uid; pw = getpwuid(uid); if (pw == NULL) { return ap_psprintf(p, "getpwuid() couldn't determine the username for uid '%ld', " "you probably need to modify the User directive: %s", (long)uid, strerror(errno)); } s->user = ap_pstrdup(p, pw->pw_name); s->username = s->user; s->gid = gid; gr = getgrgid(gid); if (gr == NULL) { return ap_psprintf(p, "getgrgid() couldn't determine the group name for gid '%ld', " "you probably need to modify the Group directive: %s", (long)gid, strerror(errno)); } s->group = ap_pstrdup(p, gr->gr_name); #endif /* !WIN32 */ return NULL; } /******************************************************************************* * Allocate an array of ServerProcess records. */ ServerProcess * fcgi_util_fs_create_procs(pool *p, int num) { int i; ServerProcess *proc = (ServerProcess *)ap_pcalloc(p, sizeof(ServerProcess) * num); for (i = 0; i < num; i++) { #ifdef WIN32 proc[i].handle = INVALID_HANDLE_VALUE; proc[i].terminationEvent = INVALID_HANDLE_VALUE; #endif proc[i].pid = 0; proc[i].state = FCGI_READY_STATE; } return proc; } int fcgi_util_ticks(struct timeval * tv) { #ifdef WIN32 /* millisecs is sufficent granularity */ DWORD millis = GetTickCount(); tv->tv_sec = millis / 1000; tv->tv_usec = (millis % 1000) * 1000; return 0; #else return gettimeofday(tv, NULL); #endif }