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