Import Upstream version 1.8.5
[hcoop/debian/openafs.git] / src / afsweb / apache_afs_plugin.c
1 /*
2 * Copyright 2000, International Business Machines Corporation and others.
3 * All Rights Reserved.
4 *
5 * This software has been released under the terms of the IBM Public
6 * License. For details, see the LICENSE file in the top-level source
7 * directory or online at http://www.openafs.org/dl/license10.html
8 */
9
10 /* Apache plugin for AFS authentication - should be archived to libapacheafs.a
11 * contains calls to functions in apache_afs_client.o - and is the intermediary
12 * between the module that plugs into apache's source code and the
13 * apache_afs_client. Shares global variables with the module and the client.
14 */
15
16 /*
17 */
18
19 #include "apache_api.h"
20
21 #define afslog(level,str) if (level <= afsDebugLevel) (afsLogError str)
22 #define afsassert(str) if(!(str)) { fprintf(stderr, "afs module: assertion failed:%s\t%d\n",__FILE__,__LINE__) ; return SERVER_ERROR; }
23
24 #define AFS_AUTHTYPE "AFS"
25 #define AFS_DFS_AUTHTYPE "AFS-DFS"
26 #define ERRSTRLEN 1024
27
28
29 #include <sys/types.h>
30 #include <sys/stat.h>
31
32
33 /* Global vars */
34
35 u_long afsDebugLevel;
36 const char module_name[] = "AFS Authentication Module";
37
38 typedef struct {
39 char defaultCell[64];
40 u_long cacheExpiration;
41 } apache_afs_glob;
42
43 static apache_afs_glob *afs_str;
44
45 /* Global file descriptors for pipes */
46 int readPipe, writePipe;
47
48 #ifdef AIX
49 /* Global temp lock file descriptor */
50 int tempfd;
51
52 /*
53 * Create a temporary file and unlink it using the file descriptor for locking
54 * as a means of synchronization for providing exclusive access to the pipe
55 * for communicating with the weblog process
56 */
57 static int
58 create_temp_file()
59 {
60 char tmpFileName[L_tmpnam];
61 int lockfd = 0;
62
63 tmpnam(tmpFileName);
64 unlink(tmpFileName);
65
66 lockfd = open(tmpFileName, O_RDWR | O_CREAT);
67 if (lockfd < 0) {
68 perror("afs_plugin:Error creating temp file:");
69 return lockfd;
70 }
71 unlink(tmpFileName);
72 return lockfd;
73 }
74 #endif
75
76 /*
77 * Initialization: start up the weblog process. Open the pipes and pass their
78 * file descriptors to the weblog process
79 */
80 void
81 afs_plugin_init(int tokenExpiration, char *weblogPath, char *error_fname,
82 char *pf, char *cell, char *dir, int exp, char *loc,
83 int shutdown)
84 {
85 int childpid;
86 int pipe1[2], pipe2[2];
87 char weblogarg1[32];
88 char weblogarg2[32];
89 char weblogarg3[32];
90 char weblogarg4[32];
91
92 FILE *fp; /* for pid_fname */
93 char *afs_weblog_pidfile;
94 char *httpd_pid_fname = strdup(pf);
95 if (httpd_pid_fname == NULL) {
96 fprintf(stderr,
97 "%s: malloc failed - out of memory while allocating space for httpd_pid_fname\n",
98 module_name);
99 exit(-1);
100 }
101 if (asprintf(&afs_weblog_pidfile, "%s.afs", httpd_pid_fname) < 0)
102 afs_weblog_pidfile == NULL;
103 if (afs_weblog_pidfile == NULL) {
104 fprintf(stderr,
105 "%s: malloc failed - out of memory while allocating space for afs_weblog_pidfile\n",
106 module_name);
107 exit(-1);
108 }
109
110 if (do_setpag()) {
111 fprintf(stderr, "%s:Failed to set pag Error:%d\n", module_name,
112 errno);
113 exit(-1);
114 }
115
116 afs_str = (apache_afs_glob *) malloc(sizeof(apache_afs_glob));
117 if (afs_str == NULL) {
118 fprintf(stderr, "%s:malloc failed for afs_str\n", module_name);
119 exit(-1);
120 }
121
122 if (cell)
123 strcpy(afs_str->defaultCell, cell);
124 else {
125 fprintf(stderr, "%s: NULL argument in cell\n", module_name);
126 exit(-1);
127 }
128 afs_str->cacheExpiration = exp;
129
130 afslog(5,
131 ("Default Cell:%s\nCache Expiration:%d\nDebugLevel:%d",
132 afs_str->defaultCell, afs_str->cacheExpiration, afsDebugLevel));
133
134 #ifdef AIX
135 /* Get a temp file fd for locking */
136 tempfd = create_temp_file();
137 if (tempfd < 0) {
138 fprintf(stderr, "%s: Error creating temp file", module_name);
139 exit(-1);
140 }
141 #endif
142
143 if (pipe(pipe1) < 0 || pipe(pipe2) < 0) {
144 fprintf(stderr, "%s: Error creating pipes - %s", module_name,
145 strerror(errno));
146 exit(-1);
147 }
148 if ((childpid = fork()) < 0) {
149 fprintf(stderr, "%s: Error forking - %s", module_name,
150 strerror(errno));
151 exit(-1);
152 } else if (childpid > 0) { /* parent */
153 close(pipe1[0]);
154 close(pipe2[1]);
155 readPipe = pipe2[0];
156 writePipe = pipe1[1];
157 } else { /* child */
158 close(pipe1[1]);
159 close(pipe2[0]);
160 fp = fopen(afs_weblog_pidfile, "w");
161 if (fp == NULL) {
162 perror("fopen");
163 fprintf(stderr, "%s: Error opening pidfile:%s - %s\n",
164 module_name, afs_weblog_pidfile, strerror(errno));
165 close(pipe1[0]);
166 close(pipe2[1]);
167 exit(-1);
168 }
169 fprintf(fp, "%ld\n", (long)getpid());
170 fclose(fp);
171 free(afs_weblog_pidfile);
172 sprintf(weblogarg1, "%d", pipe1[0]);
173 sprintf(weblogarg2, "%d", pipe2[1]);
174 sprintf(weblogarg3, "%d", afs_str->cacheExpiration);
175 sprintf(weblogarg4, "%d", tokenExpiration);
176 sleep(5);
177 execlp(weblogPath, "weblog_starter", weblogPath, error_fname,
178 weblogarg1, weblogarg2, weblogarg3, weblogarg4, NULL);
179 fprintf(stderr, "%s: Error executing %s - %s\n", module_name,
180 weblogPath, strerror(errno));
181 perror("execlp");
182 close(pipe1[0]);
183 close(pipe2[1]);
184 fclose(fp);
185
186 /* exit by sending a SIGTERM to the httpd process (how to get the pid?)
187 * since at this point the pid file is outdated and only if we sleep for
188 * a while to allow httpd_main to put it's pid in the pidfile can we
189 * attempt to send it a SIGTERM for graceful shuttdown)
190 * so that everything is brought down - if you want to bring everything
191 * down!! Else continue with httpd without AFS authentication.
192 */
193 /*#ifdef SHUTDOWN_IF_AFS_FAILS in afs_module.c */
194 if (shutdown) {
195 #define KILL_TIME_WAIT 1
196 #define MAX_KILL_ATTEMPTS 3
197 int attempts = 0;
198 fp = fopen(httpd_pid_fname, "r");
199 fscanf(fp, "%d", &childpid);
200 fclose(fp);
201 killagain:
202 sleep(KILL_TIME_WAIT);
203 if (kill(childpid, SIGTERM) == -1) {
204 if ((errno == ESRCH) && (attempts < MAX_KILL_ATTEMPTS)) {
205 attempts++;
206 fprintf(stderr,
207 "%s:SIGTERM to process:%d FAILED attempt:%d\nRetrying "
208 " for %d more attempts every %d seconds\n",
209 module_name, childpid, attempts,
210 (MAX_KILL_ATTEMPTS - attempts), KILL_TIME_WAIT);
211 goto killagain;
212 }
213 } else {
214 fprintf(stderr, "%s:Shutdown complete ...\n", module_name);
215 }
216 if (attempts >= MAX_KILL_ATTEMPTS) {
217 fprintf(stderr,
218 "%s:httpd is still running-AFS authentication will fail "
219 "because weblog startup failed\n", module_name);
220 }
221 exit(0);
222 } else {
223 fprintf(stderr,
224 "%s:httpd running - AFS Authentication will not work! "
225 "Weblog startup failure", module_name);
226 exit(-1);
227 }
228 }
229 }
230
231 /*
232 * Returns HTTP error codes based on the result of a stat error
233 */
234 static int
235 sort_stat_error(request_rec * r)
236 {
237 int status = 0;
238 switch (errno) {
239 case ENOENT:
240 status = HTTP_NOT_FOUND;
241 break;
242
243 case EACCES:
244 status = FORBIDDEN;
245 break;
246
247 case ENOLINK:
248 status = HTTP_NOT_FOUND;
249 break;
250
251 case ENODEV:
252 status = HTTP_NOT_FOUND;
253 break;
254
255 default:
256 {
257 char error[ERRSTRLEN];
258 sprintf(error, "%s: stat error: %s", module_name,
259 strerror(errno));
260 status = SERVER_ERROR;
261 LOG_REASON(error, r->uri, r);
262 break;
263 }
264 }
265 return status;
266 }
267
268 /*
269 * recursively stats the path to see where we're going wrong and
270 * chops off the path_info part of it -
271 * Returns OK or an HTTP status code
272 * Called if we get a ENOTDIR from the first stab at statting the
273 * entire path - so we assume that we have some PATH_INFO and try to
274 * chop it off the end and return the path itself
275 * Side effects on request_rec
276 - sets the filename field
277 - sets the path_info field
278 */
279 static int
280 remove_path_info(request_rec * r, char *path, struct stat *buf)
281 {
282 char *cp;
283 char *end;
284 char *last_cp = NULL;
285 int rc = 0;
286
287 afsassert(r);
288 afsassert(path);
289 afsassert(buf);
290
291 end = &path[strlen(path)];
292
293 /* Advance over trailing slashes ... NOT part of filename */
294 for (cp = end; cp > path && cp[-1] == '/'; --cp)
295 continue;
296
297 while (cp > path) {
298 /* See if the pathname ending here exists... */
299 *cp = '\0';
300 errno = 0;
301 rc = stat(path, buf);
302 if (cp != end)
303 *cp = '/';
304
305 if (!rc) {
306 if (S_ISDIR(buf->st_mode) && last_cp) {
307 buf->st_mode = 0; /* No such file... */
308 cp = last_cp;
309 }
310 r->path_info = pstrdup(r->pool, cp);
311 *cp = '\0';
312 return OK;
313 }
314
315 else if (errno == ENOENT || errno == ENOTDIR) {
316 last_cp = cp;
317 while (--cp > path && *cp != '/')
318 continue;
319 while (cp > path && cp[-1] == '/')
320 --cp;
321 } else if (errno != EACCES) {
322 /*
323 * this would be really bad since we checked the entire path
324 * earlier and got ENOTDIR instead of EACCES - so why are we getting
325 * it now? Anyway, we ought to return FORBIDDEN
326 */
327 return HTTP_FORBIDDEN;
328 }
329 }
330 r->filename = pstrdup(r->pool, path);
331 return OK;
332 }
333
334 /*
335 * Checks to see if actual access to the URL is permitted or not
336 * stats the URI first, if failure returns FORBIDDEN, if allowed then
337 * checks to see if it is a file, dir or LINK (TEST), and accordingly does more
338 */
339 static int
340 can_access(request_rec * r)
341 {
342 int rc;
343 char *doc_root = (char *)DOCUMENT_ROOT(r);
344 struct stat buf;
345 char path[MAX_STRING_LEN];
346
347 afsassert(r->uri);
348 afsassert(doc_root);
349
350 if (r->filename) {
351 afslog(10, ("%s: Found r->filename:%s", module_name, r->filename));
352 sprintf(path, "%s", r->filename);
353 } else {
354 afslog(10,
355 ("%s: Composing path from doc_root:%s and r->uri:%s",
356 module_name, doc_root, r->uri));
357 sprintf(path, "%s/%s", doc_root, r->uri);
358 afslog(10, ("%s: Path:%s", module_name, path));
359 }
360 rc = stat(path, &buf);
361 if (rc == -1) {
362 afslog(2,
363 ("%s: pid:%d\tpath:%s\tstat error:%s", module_name, getpid(),
364 path, strerror(errno)));
365
366 /*
367 * if the requested method is an HTTP PUT and the file does not
368 * exist then well, we'll get a stat error but we shouldn't return
369 * an error - we should try and open the file in append mode and then
370 * see if we get a permission denied error
371 */
372 if ((strncmp(r->method, "PUT", 3) == 0) && (errno == ENOENT)) {
373 FILE *f = fopen(path, "a");
374 if (f == NULL) {
375 if (errno == EACCES) {
376 afslog(2,
377 ("%s: Either AFS acls or other permissions forbid write"
378 " access to file %s for user %s", module_name,
379 path,
380 r->connection->user ? r->connection->
381 user : "UNKNOWN"));
382 return FORBIDDEN;
383 } else {
384 log_reason
385 ("afs_module: Error checking file for PUT method",
386 r->uri, r);
387 return SERVER_ERROR;
388 }
389 }
390 } else if (errno == ENOTDIR) {
391 /*
392 * maybe the special case of CGI PATH_INFO to be translated to
393 * PATH_TRANSLATED - check each component of this path
394 * and stat it to see what portion of the url is actually
395 * the path and discard the rest for our purposes.
396 */
397 rc = remove_path_info(r, path, &buf);
398 afslog(10,
399 ("%s:remove_path_info returned %d path:%s", module_name,
400 rc, path));
401 if (rc)
402 return rc;
403 } else {
404 return sort_stat_error(r);
405 }
406 }
407 /*
408 * If we get here then we have something - either a file or a directory
409 */
410 else {
411 if (S_IFREG == (buf.st_mode & S_IFMT)) {
412 /* regular file */
413 FILE *f;
414 char permissions[] = { 'r', '\0', '\0', '\0' }; /* room for +, etc... */
415
416 if ((strncmp(r->method, "PUT", 3) == 0)) {
417 strcpy(permissions, "a");
418 }
419 if (!(f = fopen(path, permissions))) {
420 if (errno == EACCES) {
421 afslog(2,
422 ("%s: Either AFS acls or other permissions"
423 " forbid access to file %s for user %s",
424 module_name, path,
425 r->connection->user ? r->connection->
426 user : "UNKNOWN"));
427 return FORBIDDEN;
428 } else {
429 char error[ERRSTRLEN];
430 sprintf(error,
431 "%s: Error checking file %s for permissions:%s",
432 module_name, path, strerror(errno));
433 log_reason(error, r->uri, r);
434 return SERVER_ERROR;
435 }
436 }
437 fclose(f);
438 return OK;
439 }
440 if (S_IFDIR == (buf.st_mode & S_IFMT)) {
441 /* path is a directory */
442
443 if (r->uri[strlen(r->uri) - 1] != '/') {
444 /* if we don't have a trailing slash, return REDIRECT */
445 char *ifile;
446 if (r->args != NULL) {
447 ifile =
448 PSTRCAT(r->pool, escape_uri(r->pool, r->uri), "/",
449 "?", r->args, NULL);
450 } else {
451 ifile =
452 PSTRCAT(r->pool, escape_uri(r->pool, r->uri), "/",
453 NULL);
454 }
455 TABLE_SET(r->headers_out, "Location", ifile);
456 return REDIRECT;
457 } else {
458 DIR *d;
459 if (!(d = opendir(path))) {
460 if (errno == EACCES) {
461 afslog(2,
462 ("%s: Error accessing dir %s - %s",
463 module_name, path, strerror(errno)));
464 return FORBIDDEN;
465 } else {
466 char error[ERRSTRLEN];
467 sprintf(error, "%s: opendir failed with Error:%s",
468 module_name, strerror(errno));
469 log_reason(error, r, r->uri);
470 return SERVER_ERROR;
471 }
472 }
473 closedir(d);
474 return OK;
475 }
476 }
477 }
478 }
479
480
481 /*
482 * Logs requests which led to a FORBIDDEN return code provided a token
483 * existed. Added feature (SetAFSAccessLog) in beta2
484 */
485 static int
486 log_Access_Error(request_rec * r)
487 {
488 if (FIND_LINKED_MODULE("afs_module.c") != NULL) {
489 char err_msg[1024];
490 int rc = 0;
491 int len = 0;
492 extern int logfd;
493
494 if (r->connection->user)
495 sprintf(err_msg,
496 "[%s] AFS ACL's deny permission to "
497 "user: %s for URI:%s\n", GET_TIME(), r->connection->user,
498 r->uri);
499 else
500 sprintf(err_msg,
501 "[%s] AFS ACL's deny permission to user - for URI:%s\n",
502 GET_TIME(), r->uri);
503
504 len = strlen(err_msg);
505 rc = write(logfd, err_msg, len);
506
507 if (rc != len) {
508 afslog(0,
509 ("%s: Error logging message:%s - %s", module_name, err_msg,
510 strerror(errno)));
511 return -1;
512 }
513 return rc;
514 }
515 }
516
517 /*
518 * The interface - hook to obtain an AFS token if needed based on the
519 * result of a stat (returned by can_access) or an open file
520 */
521 int
522 afs_auth_internal(request_rec * r, char *cell)
523 {
524 afsassert(r);
525 afsassert(r->uri);
526 afsassert(cell);
527 if (FIND_LINKED_MODULE("afs_module.c") != NULL) {
528 int rc, status;
529 char *type;
530 static int haveToken = 1; /* assume that we always have a token */
531 extern int logAccessErrors;
532
533 /*
534 * Get afs_authtype directive value for that directory or location
535 */
536 type = (char *)get_afsauthtype(r);
537
538 /*
539 * UserDir (tilde) support
540 */
541 #ifndef APACHE_1_3
542 if (FIND_LINKED_MODULE("mod_userdir.c") != NULL) {
543 rc = translate_userdir(r);
544 if ((rc != OK) && (rc != DECLINED)) {
545 LOG_REASON("afs_module: Failure while translating userdir",
546 r->uri, r);
547 return rc;
548 }
549 }
550 #endif
551
552 afslog(20, ("%s: pid:%d r->uri:%s", module_name, getpid(), r->uri));
553 if (type)
554 afslog(20, ("%s: AFSAuthType: %s", module_name, type));
555 else
556 afslog(20, ("%s: AFSAuthType NULL", module_name));
557
558 /* if AuthType is not AFS, then unlog any existing tokens and DECLINE */
559 if (type == NULL) {
560 if (haveToken)
561 unlog();
562 return DECLINED;
563 }
564
565 if ((strcasecmp(type, AFS_AUTHTYPE))
566 && (strcasecmp(type, AFS_DFS_AUTHTYPE))) {
567 if (haveToken)
568 unlog();
569 afslog(10,
570 ("%s: Error unknown AFSAuthType:%s returning DECLINED",
571 module_name, type));
572 return DECLINED;
573 }
574
575 if (cell)
576 status =
577 authenticateUser(r, cell, afs_str->cacheExpiration, type);
578 else
579 status =
580 authenticateUser(r, afs_str->defaultCell,
581 afs_str->cacheExpiration, type);
582
583 if (status != OK) {
584 afslog(10, ("%s: Returning status %d", module_name, status));
585 return status;
586 }
587
588 /* can we access this URL? */
589 rc = can_access(r);
590
591 if (rc == OK) {
592 return DECLINED;
593 }
594
595 if (rc == REDIRECT) {
596 return REDIRECT;
597 }
598
599 if (rc == FORBIDDEN) {
600 rc = forbToAuthReqd(r);
601 if (rc == FORBIDDEN) {
602 if (logAccessErrors) {
603 log_Access_Error(r);
604 }
605 }
606 return rc;
607 }
608 return DECLINED;
609 }
610 }