7bc3c54644065838f4e12f70e8e127164ce33fbb
[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     afs_weblog_pidfile = (char *)malloc(strlen(httpd_pid_fname) + 5);
102     if (httpd_pid_fname == NULL) {
103         fprintf(stderr,
104                 "%s: malloc failed - out of memory while allocating space for afs_weblog_pidfile\n",
105                 module_name);
106         exit(-1);
107     }
108     sprintf(afs_weblog_pidfile, "%s.afs", httpd_pid_fname);
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 }