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