openafs/src/afsweb/apache_afs_plugin.c
Derrick Brashear 872bc94f8c reindent-20030715
FIXES 1774

thanks to nneul@umr.edu for providing a script to do this.
gnu indent 2.2.9 options:
-npro -nbad -bap -nbc -bbo -br -ce -cdw -brs -ncdb -cp1 -ncs -di2 -ndj -nfc1
-nfca -i4 -lp -npcs -nprs -psl -sc -nsob -ts8

====================
This delta was composed from multiple commits as part of the CVS->Git migration.
The checkin message with each commit was inconsistent.
The following are the additional commit messages.
====================
FIXES 1774

fix subst mistake
2003-07-16 00:28:24 +00:00

612 lines
16 KiB
C

/*
* Copyright 2000, International Business Machines Corporation and others.
* All Rights Reserved.
*
* This software has been released under the terms of the IBM Public
* License. For details, see the LICENSE file in the top-level source
* directory or online at http://www.openafs.org/dl/license10.html
*/
/* Apache plugin for AFS authentication - should be archived to libapacheafs.a
* contains calls to functions in apache_afs_client.o - and is the intermediary
* between the module that plugs into apache's source code and the
* apache_afs_client. Shares global variables with the module and the client.
*/
/*
*/
#include "apache_api.h"
#define afslog(level,str) if (level <= afsDebugLevel) (afsLogError str)
#define afsassert(str) if(!(str)) { fprintf(stderr, "afs module: assertion failed:%s\t%d\n",__FILE__,__LINE__) ; return SERVER_ERROR; }
#define AFS_AUTHTYPE "AFS"
#define AFS_DFS_AUTHTYPE "AFS-DFS"
#define ERRSTRLEN 1024
#include <sys/types.h>
#include <sys/stat.h>
/* Global vars */
u_long afsDebugLevel;
const char module_name[] = "AFS Authentication Module";
typedef struct {
char defaultCell[64];
u_long cacheExpiration;
} apache_afs_glob;
static apache_afs_glob *afs_str;
/* Global file descriptors for pipes */
int readPipe, writePipe;
#ifdef AIX
/* Global temp lock file descriptor */
int tempfd;
/*
* Create a temporary file and unlink it using the file descriptor for locking
* as a means of synchronization for providing exclusive access to the pipe
* for communicating with the weblog process
*/
static int
create_temp_file()
{
char tmpFileName[L_tmpnam];
int lockfd = 0;
tmpnam(tmpFileName);
unlink(tmpFileName);
lockfd = open(tmpFileName, O_RDWR | O_CREAT);
if (lockfd < 0) {
perror("afs_plugin:Error creating temp file:");
return lockfd;
}
unlink(tmpFileName);
return lockfd;
}
#endif
/*
* Initialization: start up the weblog process. Open the pipes and pass their
* file descriptors to the weblog process
*/
void
afs_plugin_init(int tokenExpiration, char *weblogPath, char *error_fname,
char *pf, char *cell, char *dir, int exp, char *loc,
int shutdown)
{
int childpid;
int pipe1[2], pipe2[2];
char weblogarg1[32];
char weblogarg2[32];
char weblogarg3[32];
char weblogarg4[32];
FILE *fp; /* for pid_fname */
char *afs_weblog_pidfile;
char *httpd_pid_fname = (char *)malloc(strlen(pf) + 1);
if (httpd_pid_fname == NULL) {
fprintf(stderr,
"%s: malloc failed - out of memory while allocating space for httpd_pid_fname\n",
module_name);
exit(-1);
}
strcpy(httpd_pid_fname, pf);
afs_weblog_pidfile = (char *)malloc(strlen(httpd_pid_fname) + 5);
if (httpd_pid_fname == NULL) {
fprintf(stderr,
"%s: malloc failed - out of memory while allocating space for afs_weblog_pidfile\n",
module_name);
exit(-1);
}
sprintf(afs_weblog_pidfile, "%s.afs", httpd_pid_fname);
if (do_setpag()) {
fprintf(stderr, "%s:Failed to set pag Error:%d\n", module_name,
errno);
exit(-1);
}
afs_str = (apache_afs_glob *) malloc(sizeof(apache_afs_glob));
if (afs_str == NULL) {
fprintf(stderr, "%s:malloc failed for afs_str\n", module_name);
exit(-1);
}
if (cell)
strcpy(afs_str->defaultCell, cell);
else {
fprintf(stderr, "%s: NULL argument in cell\n", module_name);
exit(-1);
}
afs_str->cacheExpiration = exp;
afslog(5,
("Default Cell:%s\nCache Expiration:%d\nDebugLevel:%d",
afs_str->defaultCell, afs_str->cacheExpiration, afsDebugLevel));
#ifdef AIX
/* Get a temp file fd for locking */
tempfd = create_temp_file();
if (tempfd < 0) {
fprintf(stderr, "%s: Error creating temp file", module_name);
exit(-1);
}
#endif
if (pipe(pipe1) < 0 || pipe(pipe2) < 0) {
fprintf(stderr, "%s: Error creating pipes - %s", module_name,
strerror(errno));
exit(-1);
}
if ((childpid = fork()) < 0) {
fprintf(stderr, "%s: Error forking - %s", module_name,
strerror(errno));
exit(-1);
} else if (childpid > 0) { /* parent */
close(pipe1[0]);
close(pipe2[1]);
readPipe = pipe2[0];
writePipe = pipe1[1];
} else { /* child */
close(pipe1[1]);
close(pipe2[0]);
fp = fopen(afs_weblog_pidfile, "w");
if (fp == NULL) {
perror("fopen");
fprintf(stderr, "%s: Error opening pidfile:%s - %s\n",
module_name, afs_weblog_pidfile, strerror(errno));
close(pipe1[0]);
close(pipe2[1]);
exit(-1);
}
fprintf(fp, "%ld\n", (long)getpid());
fclose(fp);
free(afs_weblog_pidfile);
sprintf(weblogarg1, "%d", pipe1[0]);
sprintf(weblogarg2, "%d", pipe2[1]);
sprintf(weblogarg3, "%d", afs_str->cacheExpiration);
sprintf(weblogarg4, "%d", tokenExpiration);
sleep(5);
execlp(weblogPath, "weblog_starter", weblogPath, error_fname,
weblogarg1, weblogarg2, weblogarg3, weblogarg4, NULL);
fprintf(stderr, "%s: Error executing %s - %s\n", module_name,
weblogPath, strerror(errno));
perror("execlp");
close(pipe1[0]);
close(pipe2[1]);
fclose(fp);
/* exit by sending a SIGTERM to the httpd process (how to get the pid?)
* since at this point the pid file is outdated and only if we sleep for
* a while to allow httpd_main to put it's pid in the pidfile can we
* attempt to send it a SIGTERM for graceful shuttdown)
* so that everything is brought down - if you want to bring everything
* down!! Else continue with httpd without AFS authentication.
*/
/*#ifdef SHUTDOWN_IF_AFS_FAILS in afs_module.c */
if (shutdown) {
#define KILL_TIME_WAIT 1
#define MAX_KILL_ATTEMPTS 3
int attempts = 0;
fp = fopen(httpd_pid_fname, "r");
fscanf(fp, "%d", &childpid);
fclose(fp);
killagain:
sleep(KILL_TIME_WAIT);
if (kill(childpid, SIGTERM) == -1) {
if ((errno == ESRCH) && (attempts < MAX_KILL_ATTEMPTS)) {
attempts++;
fprintf(stderr,
"%s:SIGTERM to process:%d FAILED attempt:%d\nRetrying "
" for %d more attempts every %d seconds\n",
module_name, childpid, attempts,
(MAX_KILL_ATTEMPTS - attempts), KILL_TIME_WAIT);
goto killagain;
}
} else {
fprintf(stderr, "%s:Shutdown complete ...\n", module_name);
}
if (attempts >= MAX_KILL_ATTEMPTS) {
fprintf(stderr,
"%s:httpd is still running-AFS authentication will fail "
"because weblog startup failed\n", module_name);
}
exit(0);
} else {
fprintf(stderr,
"%s:httpd running - AFS Authentication will not work! "
"Weblog startup failure", module_name);
exit(-1);
}
}
}
/*
* Returns HTTP error codes based on the result of a stat error
*/
static int
sort_stat_error(request_rec * r)
{
int status = 0;
switch (errno) {
case ENOENT:
status = HTTP_NOT_FOUND;
break;
case EACCES:
status = FORBIDDEN;
break;
case ENOLINK:
status = HTTP_NOT_FOUND;
break;
case ENODEV:
status = HTTP_NOT_FOUND;
break;
default:
{
char error[ERRSTRLEN];
sprintf(error, "%s: stat error: %s", module_name,
strerror(errno));
status = SERVER_ERROR;
LOG_REASON(error, r->uri, r);
break;
}
}
return status;
}
/*
* recursively stats the path to see where we're going wrong and
* chops off the path_info part of it -
* Returns OK or an HTTP status code
* Called if we get a ENOTDIR from the first stab at statting the
* entire path - so we assume that we have some PATH_INFO and try to
* chop it off the end and return the path itself
* Side effects on request_rec
- sets the filename field
- sets the path_info field
*/
static int
remove_path_info(request_rec * r, char *path, struct stat *buf)
{
char *cp;
char *end;
char *last_cp = NULL;
int rc = 0;
afsassert(r);
afsassert(path);
afsassert(buf);
end = &path[strlen(path)];
/* Advance over trailing slashes ... NOT part of filename */
for (cp = end; cp > path && cp[-1] == '/'; --cp)
continue;
while (cp > path) {
/* See if the pathname ending here exists... */
*cp = '\0';
errno = 0;
rc = stat(path, buf);
if (cp != end)
*cp = '/';
if (!rc) {
if (S_ISDIR(buf->st_mode) && last_cp) {
buf->st_mode = 0; /* No such file... */
cp = last_cp;
}
r->path_info = pstrdup(r->pool, cp);
*cp = '\0';
return OK;
}
else if (errno == ENOENT || errno == ENOTDIR) {
last_cp = cp;
while (--cp > path && *cp != '/')
continue;
while (cp > path && cp[-1] == '/')
--cp;
} else if (errno != EACCES) {
/*
* this would be really bad since we checked the entire path
* earlier and got ENOTDIR instead of EACCES - so why are we getting
* it now? Anyway, we ought to return FORBIDDEN
*/
return HTTP_FORBIDDEN;
}
}
r->filename = pstrdup(r->pool, path);
return OK;
}
/*
* Checks to see if actual access to the URL is permitted or not
* stats the URI first, if failure returns FORBIDDEN, if allowed then
* checks to see if it is a file, dir or LINK (TEST), and accordingly does more
*/
static int
can_access(request_rec * r)
{
int rc;
char *doc_root = (char *)DOCUMENT_ROOT(r);
struct stat buf;
char path[MAX_STRING_LEN];
afsassert(r->uri);
afsassert(doc_root);
if (r->filename) {
afslog(10, ("%s: Found r->filename:%s", module_name, r->filename));
sprintf(path, "%s", r->filename);
} else {
afslog(10,
("%s: Composing path from doc_root:%s and r->uri:%s",
module_name, doc_root, r->uri));
sprintf(path, "%s/%s", doc_root, r->uri);
afslog(10, ("%s: Path:%s", module_name, path));
}
rc = stat(path, &buf);
if (rc == -1) {
afslog(2,
("%s: pid:%d\tpath:%s\tstat error:%s", module_name, getpid(),
path, strerror(errno)));
/*
* if the requested method is an HTTP PUT and the file does not
* exist then well, we'll get a stat error but we shouldn't return
* an error - we should try and open the file in append mode and then
* see if we get a permission denied error
*/
if ((strncmp(r->method, "PUT", 3) == 0) && (errno == ENOENT)) {
FILE *f = fopen(path, "a");
if (f == NULL) {
if (errno == EACCES) {
afslog(2,
("%s: Either AFS acls or other permissions forbid write"
" access to file %s for user %s", module_name,
path,
r->connection->user ? r->connection->
user : "UNKNOWN"));
return FORBIDDEN;
} else {
log_reason
("afs_module: Error checking file for PUT method",
r->uri, r);
return SERVER_ERROR;
}
}
} else if (errno == ENOTDIR) {
/*
* maybe the special case of CGI PATH_INFO to be translated to
* PATH_TRANSLATED - check each component of this path
* and stat it to see what portion of the url is actually
* the path and discard the rest for our purposes.
*/
rc = remove_path_info(r, path, &buf);
afslog(10,
("%s:remove_path_info returned %d path:%s", module_name,
rc, path));
if (rc)
return rc;
} else {
return sort_stat_error(r);
}
}
/*
* If we get here then we have something - either a file or a directory
*/
else {
if (S_IFREG == (buf.st_mode & S_IFMT)) {
/* regular file */
FILE *f;
char permissions[] = { 'r', '\0', '\0', '\0' }; /* room for +, etc... */
if ((strncmp(r->method, "PUT", 3) == 0)) {
strcpy(permissions, "a");
}
if (!(f = fopen(path, permissions))) {
if (errno == EACCES) {
afslog(2,
("%s: Either AFS acls or other permissions"
" forbid access to file %s for user %s",
module_name, path,
r->connection->user ? r->connection->
user : "UNKNOWN"));
return FORBIDDEN;
} else {
char error[ERRSTRLEN];
sprintf(error,
"%s: Error checking file %s for permissions:%s",
module_name, path, strerror(errno));
log_reason(error, r->uri, r);
return SERVER_ERROR;
}
}
fclose(f);
return OK;
}
if (S_IFDIR == (buf.st_mode & S_IFMT)) {
/* path is a directory */
if (r->uri[strlen(r->uri) - 1] != '/') {
/* if we don't have a trailing slash, return REDIRECT */
char *ifile;
if (r->args != NULL) {
ifile =
PSTRCAT(r->pool, escape_uri(r->pool, r->uri), "/",
"?", r->args, NULL);
} else {
ifile =
PSTRCAT(r->pool, escape_uri(r->pool, r->uri), "/",
NULL);
}
TABLE_SET(r->headers_out, "Location", ifile);
return REDIRECT;
} else {
DIR *d;
if (!(d = opendir(path))) {
if (errno == EACCES) {
afslog(2,
("%s: Error accessing dir %s - %s",
module_name, path, strerror(errno)));
return FORBIDDEN;
} else {
char error[ERRSTRLEN];
sprintf(error, "%s: opendir failed with Error:%s",
module_name, strerror(errno));
log_reason(error, r, r->uri);
return SERVER_ERROR;
}
}
closedir(d);
return OK;
}
}
}
}
/*
* Logs requests which led to a FORBIDDEN return code provided a token
* existed. Added feature (SetAFSAccessLog) in beta2
*/
static int
log_Access_Error(request_rec * r)
{
if (FIND_LINKED_MODULE("afs_module.c") != NULL) {
char err_msg[1024];
int rc = 0;
int len = 0;
extern int logfd;
if (r->connection->user)
sprintf(err_msg,
"[%s] AFS ACL's deny permission to "
"user: %s for URI:%s\n", GET_TIME(), r->connection->user,
r->uri);
else
sprintf(err_msg,
"[%s] AFS ACL's deny permission to user - for URI:%s\n",
GET_TIME(), r->uri);
len = strlen(err_msg);
rc = write(logfd, err_msg, len);
if (rc != len) {
afslog(0,
("%s: Error logging message:%s - %s", module_name, err_msg,
strerror(errno)));
return -1;
}
return rc;
}
}
/*
* The interface - hook to obtain an AFS token if needed based on the
* result of a stat (returned by can_access) or an open file
*/
int
afs_auth_internal(request_rec * r, char *cell)
{
afsassert(r);
afsassert(r->uri);
afsassert(cell);
if (FIND_LINKED_MODULE("afs_module.c") != NULL) {
int rc, status;
char *type;
static int haveToken = 1; /* assume that we always have a token */
extern int logAccessErrors;
/*
* Get afs_authtype directive value for that directory or location
*/
type = (char *)get_afsauthtype(r);
/*
* UserDir (tilde) support
*/
#ifndef APACHE_1_3
if (FIND_LINKED_MODULE("mod_userdir.c") != NULL) {
rc = translate_userdir(r);
if ((rc != OK) && (rc != DECLINED)) {
LOG_REASON("afs_module: Failure while translating userdir",
r->uri, r);
return rc;
}
}
#endif
afslog(20, ("%s: pid:%d r->uri:%s", module_name, getpid(), r->uri));
if (type)
afslog(20, ("%s: AFSAuthType: %s", module_name, type));
else
afslog(20, ("%s: AFSAuthType NULL", module_name));
/* if AuthType is not AFS, then unlog any existing tokens and DECLINE */
if (type == NULL) {
if (haveToken)
unlog();
return DECLINED;
}
if ((strcasecmp(type, AFS_AUTHTYPE))
&& (strcasecmp(type, AFS_DFS_AUTHTYPE))) {
if (haveToken)
unlog();
afslog(10,
("%s: Error unknown AFSAuthType:%s returning DECLINED",
module_name, type));
return DECLINED;
}
if (cell)
status =
authenticateUser(r, cell, afs_str->cacheExpiration, type);
else
status =
authenticateUser(r, afs_str->defaultCell,
afs_str->cacheExpiration, type);
if (status != OK) {
afslog(10, ("%s: Returning status %d", module_name, status));
return status;
}
/* can we access this URL? */
rc = can_access(r);
if (rc == OK) {
return DECLINED;
}
if (rc == REDIRECT) {
return REDIRECT;
}
if (rc == FORBIDDEN) {
rc = forbToAuthReqd(r);
if (rc == FORBIDDEN) {
if (logAccessErrors) {
log_Access_Error(r);
}
}
return rc;
}
return DECLINED;
}
}