Commit | Line | Data |
---|---|---|
805e021f CE |
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 | } |