2 * Copyright 2000, International Business Machines Corporation and others.
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
11 * Implements most of the client side web authentication protocol
17 #include "apache_afs_utils.h"
18 #include "apache_afs_cache.h"
20 #include "AFS_component_version_number.c"
21 #include "apache_api.h"
23 #define afsassert(str) if(!(str)) { fprintf(stderr, "afs module: assertion failed:%s\t%d\n",__FILE__,__LINE__) ; return SERVER_ERROR; }
27 #define APACHEAFS_MAX_PATH 1024 /* Maximum path length */
28 #define APACHEAFS_USERNAME_MAX 64 /* Maximum username length */
29 #define APACHEAFS_PASSWORD_MAX 64 /* Maximum password length */
30 #define APACHEAFS_CELLNAME_MAX 64 /* Maximum cellname length */
31 #define APACHEAFS_MOUNTPTLEN_MAX 64 /* Max mountpoint length */
34 #define MAX(A,B) ((A)>(B)?(A):(B))
37 #define MIN(A,B) ((A)<(B)?(A):(B))
41 static int setCellAuthHeader(request_rec * r);
43 /* Module name for logging stuff */
44 extern const char module_name[];
46 /* file descriptors for the pipes for communication with weblog */
51 /* lock file descriptor */
55 /* do we have an authentication field */
56 static int haveAuth = 0;
58 /* local cache stuff */
59 static int cache_initialized;
61 /* we need the defult cell in several places */
62 static char global_default_cell[APACHEAFS_CELLNAME_MAX];
64 /* buffers to keep track of the last authenticated user */
65 static char lastUser[APACHEAFS_USERNAME_MAX];
66 static char lastCell[APACHEAFS_CELLNAME_MAX];
67 static char lastCksum[SHA_HASH_BYTES];
69 /* do I have my own PAG */
70 static int doneSETPAG = 0;
73 * Parse the authorization header for username and password
76 parse_authhdr(request_rec * r, char *user, char *passwd, char *cell,
81 const char *auth_line = TABLE_GET(r->headers_in, "Authorization");
83 if ((r == NULL) || (auth_line == NULL) || (user == NULL)
84 || (passwd == NULL) || (cell == NULL)) {
85 LOG_REASON("AFSAUTH_CLIENT: NULL request record, auth_line, cell,"
86 "user or passwd while parsing authentication header",
96 * check for basic authentication
99 (GETWORD(r->pool, (const char **)&auth_line, ' '), "basic", 6) != 0) {
100 /* Client tried to authenticate using some other auth scheme */
102 ("AFSAUTH_CLIENT:client used other than Basic authentication"
103 "scheme", r->uri, r);
108 * Username and password are base64 encoded
110 t = UUDECODE(r->pool, auth_line);
113 LOG_REASON("AFSAUTH_CLIENT:uudecode failed", r->uri, r);
118 * Format is user@cell:passwd. The user, cell or passwd may be missing
120 r->connection->user = GETWORD_NULLS(r->pool, (const char **)&t, ':');
121 r->connection->auth_type = "Basic";
124 p = r->connection->user;
126 for (i = 0; *p != '@' && *p != '\0'; p++, i++) {
131 for (i = 0, p++; *p != '\0'; p++, i++) {
137 if (cell[0] == '\0') {
138 strcpy(cell, defaultCell);
144 * send a buffer to the weblog process over the pipe. Used for sending
145 * authentication credentials to weblog
148 sendBuffer(char *buf, int len)
151 if (write(writePipe, buf, len) != len) {
153 ("%s: Error writing to pipe - %s", module_name,
161 * packs user credentials into a buffer seperated by newlines and
162 * sends them to weblog
165 sendTo_afsAuthenticator(char *user, char *passwd, char *cell, char *type)
174 sprintf(buf, "%s\n%s\n%s\n%s", type, user, cell, passwd);
175 return sendBuffer(buf, strlen(buf));
179 * reads the response from weblog over the pipe
182 recvFrom_afsAuthenticator(char *buf)
187 n = read(readPipe, buf, MAXBUFF);
190 ("%s: Error reading from pipe - %s", module_name,
197 #ifndef NO_AFSAPACHE_CACHE
199 * check local cache for the token associated with user crds.
202 check_Cache(char *user, char *passwd, char *cell, char *tokenBuf)
204 char cksum[SHA_HASH_BYTES]; /* for sha checksum for caching */
206 /* look up local cache - function in apache_afs_cach.c */
207 weblog_login_checksum(user, cell, passwd, cksum);
208 return weblog_login_lookup(user, cell, cksum, &tokenBuf[0]);
212 * put the token and the user credentials in the local cache
215 updateCache(char *user, char *passwd, char *cell, char *tokenBuf,
218 long expires = 0, testExpires = 0;
219 char cksum[SHA_HASH_BYTES]; /* for sha checksum for caching */
221 /* put the token in local cache with the expiration date */
222 expires = getExpiration(tokenBuf);
225 ("%s: Error getting expiration time for cache. Expires %d",
226 module_name, expires));
230 weblog_login_checksum(user, cell, passwd, cksum);
232 if (cacheExpiration == 0) {
233 weblog_login_store(user, cell, cksum, &tokenBuf[0], sizeof(tokenBuf),
236 testExpires = cacheExpiration + time(NULL);
237 weblog_login_store(user, cell, cksum, &tokenBuf[0], sizeof(tokenBuf),
238 MIN(expires, testExpires));
242 #endif /* NO_APACHEAFS_CACHE */
246 * locking routines to provide exclusive access to the pipes
249 start_lock(int fd, int cmd, int type)
254 lock.l_whence = SEEK_SET;
256 return (fcntl(fd, cmd, &lock));
260 test_lock(int fd, int type)
265 lock.l_whence = SEEK_SET;
268 if (fcntl(fd, F_GETLK, &lock) < 0) {
271 if (lock.l_type == F_UNLCK) {
272 return 0; /* not locked */
274 return (lock.l_pid); /* return pid of locking process */
278 #define Read_lock(fd) \
279 start_lock(fd, F_SETLK, F_RDLCK)
280 #define Readw_lock(fd) \
281 start_lock(fd, F_SETLKW, F_RDLCK)
282 #define Write_lock(fd) \
283 start_lock(fd, F_SETLK, F_WRLCK)
284 #define Writew_lock(fd) \
285 start_lock(fd, F_SETLKW, F_WRLCK)
287 start_lock(fd, F_SETLK, F_UNLCK)
288 #define Is_readlock(fd) \
289 test_lock(fd, F_RDLCK)
290 #define Is_writelock(fd) \
291 test_lock(fd, F_WRLCK)
294 * communication between this process and weblog - sends user credentials
295 * over a shared pipe (mutex provided using locks) and recieves either a
296 * token or an error message
299 request_Authentication(char *user, char *passwd, char *cell, char *type,
300 char *tokenbuf, char *reason)
314 * lock the pipe before beginning communication or in case of AIX it is an
315 * error to attempt to lock a pipe or FIFO (EINVAL) therefore we have to create
316 * a temporary file and use that fd instead
324 while ((pid = Is_writelock(lockfd)) != 0) {
327 ("%s: pid:%d Error locking pipe - %s", module_name,
328 getpid(), strerror(errno)));
332 ("%s: pid %d waiting for lock held by pid %d", module_name,
336 if (Write_lock(lockfd) == -1) {
338 ("%s: pid:%d Error write lock - %s. Retrying with WriteW",
339 module_name, getpid(), strerror(errno)));
340 if (Writew_lock(lockfd) == -1) {
342 ("%s: pid:%d Error write lock - %s", module_name, getpid(),
348 if (sendTo_afsAuthenticator(user, passwd, cell, type) == -1) {
350 afslog(5, ("%s: Error sending authentication info", module_name));
354 len = recvFrom_afsAuthenticator(tokenbuf);
356 /* release the lock */
357 if (Unlock(lockfd)) {
358 afslog(5, ("%s: pid:%d Error unlocking", module_name, getpid()));
363 if (strncmp(tokenbuf, "FAILURE", 7) == 0) {
365 strncpy(reason, temp, len);
373 * pioctl setting token
376 setToken(char *tokenBuf, int tokenLen)
384 * set the primary flag only if we haven't done a SETPAG previoulsy
385 * by flipping this bit
389 /* skip over the secret token */
391 memcpy(&i, temp, sizeof(afs_int32));
392 temp += (i + sizeof(afs_int32));
394 /* skip over the clear token */
395 memcpy(&i, temp, sizeof(afs_int32));
396 temp += (i + sizeof(afs_int32));
399 memcpy(&i, temp, sizeof(afs_int32));
401 memcpy(temp, &i, sizeof(afs_int32));
402 temp += sizeof(afs_int32);
410 return do_pioctl(tokenBuf, tokenLen, tokenBuf, tokenLen, VIOCSETTOK, NULL,
415 * Get the token for the primary cell from the cache manager for this
416 * process. Primary cell is the cell at the first index (index 0)
419 getToken(char *buf, int bufsize)
421 /* get just the ONE token for this PAG from cache manager */
423 memcpy((void *)buf, (void *)&i, sizeof(afs_int32));
424 return do_pioctl(buf, sizeof(afs_int32), buf, bufsize, VIOCGETTOK, NULL,
430 * discard all authentication information for this PAG ie. this process
435 return do_pioctl(0, 0, 0, 0, VIOCUNPAG, NULL, 0);
440 * Does the following things:
441 * Checks whether there is a Basic Authentication header - obtains creds.
442 * Checks local cache for the token associated with the user creds.
443 * - if no token in cache - obtains token from weblog using pipes
444 * - sets the token and returns appropriate return code
445 * Return values: OK, SERVER_ERROR, AUTH_REQUIRED, FORBIDDEN
448 authenticateUser(request_rec * r, char *defaultCell, int cacheExpiration,
451 char user[APACHEAFS_USERNAME_MAX];
452 char passwd[APACHEAFS_PASSWORD_MAX];
453 char cell[APACHEAFS_CELLNAME_MAX];
454 char tokenbuf[MAXBUFF];
455 char cksum[SHA_HASH_BYTES];
458 const char *auth_line;
459 char reason[MAXBUFF]; /* if authentication failed - this is why */
460 char err_msg[MAXBUFF];
465 afsassert(defaultCell);
468 auth_line = TABLE_GET(r->headers_in, "Authorization");
470 if (strcmp(global_default_cell, defaultCell)) {
471 strcpy(global_default_cell, defaultCell);
474 memset(user, 0, APACHEAFS_USERNAME_MAX);
475 memset(passwd, 0, APACHEAFS_PASSWORD_MAX);
476 memset(cell, 0, APACHEAFS_CELLNAME_MAX);
478 if (auth_line == NULL) { /* No Authorization field - we don't do anything */
480 * No Authorization field recieved - that's fine by us.
481 * go ahead and attempt to service the request and if we get
482 * back FORBIDDEN then we'll take care of it then
484 afslog(15, ("%s: No authline recieved", module_name));
487 memset(lastUser, 0, APACHEAFS_USERNAME_MAX);
488 memset(lastCell, 0, APACHEAFS_CELLNAME_MAX);
489 memset(lastCksum, 0, SHA_HASH_BYTES);
492 ("%s: pid:%d No Authorization field. Unlogging ...",
493 module_name, getpid()));
496 "%s: Error unlogging from AFS cell - rc: %d, errno:%d",
497 module_name, rc, errno);
498 LOG_REASON(err_msg, r->uri, r);
505 * We should get here only if there IS an Authorization field
508 if ((rc = parse_authhdr(r, user, passwd, cell, defaultCell)) != 0) {
509 sprintf(err_msg, "%s: Error parsing Authorization Header rc:%d",
511 LOG_REASON(err_msg, r->uri, r);
512 return rc; /* SERVER ERROR */
516 * should get here only after obtaining the username and password and cell
517 * check to make sure anyway
519 if ((user[0] == '\0') || (cell[0] == '\0') || (passwd[0] == '\0')) {
521 ("%s: pid:%d No username or password or cell. Unlogging.",
522 module_name, getpid()));
525 memset(lastUser, 0, APACHEAFS_USERNAME_MAX);
526 memset(lastCell, 0, APACHEAFS_CELLNAME_MAX);
527 memset(lastCksum, 0, SHA_HASH_BYTES);
531 "%s: Error unlogging from AFS cell - rc: %d, errno:%d",
532 module_name, rc, errno);
533 LOG_REASON(err_msg, r->uri, r);
536 setCellAuthHeader(r);
537 return AUTH_REQUIRED;
540 fprintf(stderr, "Cell:%s\tUser:%s\tPasswd:%s\n", cell, user, passwd);
544 * compare with previous username/cell/cksum - update it
548 weblog_login_checksum(user, cell, passwd, cksum);
551 strcpy(lastUser, user);
552 strcpy(lastCksum, cksum);
553 strcpy(lastCell, cell);
555 if (strcmp(user, lastUser) || strcmp(cell, lastCell)
556 || strcmp(cksum, lastCksum)) {
558 * unlog the old user from the cell if a new username/passwd is recievd
563 ("%s: pid:%d\tUnlogging user %s from cell%s", module_name,
564 getpid(), lastUser, lastCell));
565 afslog(25, ("%s:New user:%s\t New Cell:%s", module_name, user, cell));
566 afslog(25, ("%s:Trying to get URL:%s", module_name, r->uri));
567 afslog(25, ("%s: Unlogging ....", module_name));
571 "%s: Error unlogging from AFS cell - rc: %d, errno:%d",
572 module_name, rc, errno);
573 LOG_REASON(err_msg, r->uri, r);
576 /* change lastUser to this user */
577 strcpy(lastUser, user);
578 strcpy(lastCksum, cksum);
579 strcpy(lastCell, cell);
582 /* strcmp checksums - ie. did the user change */
583 #ifndef NO_AFSAPACHE_CACHE
584 if (!cache_initialized) {
586 cache_initialized = 1;
589 /* have to check local cache for this name/passwd */
591 rc = check_Cache(user, passwd, cell, tokenbuf);
593 /* if found then just send the request without going through
594 * weblog - this means the user has already been authenticated
595 * once and we have a valid token just need to set it -
596 * only if it is different from the token already set. No need to
597 * even unlog because this token is set for the entire PAG which
598 * of course consists of just this child process
601 ("%s: pid:%d found user %s's token (expires:%d) in cache",
602 module_name, getpid(), user,
603 (getExpiration(tokenbuf) - time(NULL))));
605 /* if the user changed then set this token else leave it since it should
608 /* set this token obtained from the local cache */
610 ("%s:pid:%d\t Setting cached token", module_name,
612 if (setToken(tokenbuf, rc)) {
614 ("%s: pid:%d Failed to set token obtained from cache."
615 "rc:%d errno:%d Token Expiration:%d", module_name,
617 (getExpiration(tokenbuf) - time(NULL))));
619 parseToken(tokenbuf);
622 * BUG WORKAROUND: sometimes we fail while setting token
623 * with errno ESRCH indicating the named cell
624 * in the last field of the token is not recognized - but that's
625 * not quite true according to parseToken()!! Possibly corrupted
626 * tokens from the cache?
627 * Anyway we just get a new token from weblog
631 } /* if userChanged */
633 /* if this is a child process getting the request for the first time
634 * then there's no way this guy's got a token for us in which case
635 * getToken should fail with EDOM and that means we should set the token
636 * first and maybe set a static variable saying we have set a token?
639 if (getToken(temp, sizeof(temp))) {
641 /* try setting the cached token */
642 if (setToken(tokenbuf, rc)) {
644 * same bug workaround here. ie. go to weblog if setting
645 * the cached token fails.
648 "%s: pid:%d Failed to set cached token."
649 "errno:%d rc:%d", module_name, getpid(),
651 LOG_REASON(err_msg, r->uri, r);
655 /* and again for any getToken failure other than EDOM */
657 "%s: Failed to get token: errno:%d rc:%d",
658 module_name, errno, rc);
659 LOG_REASON(err_msg, r->uri, r);
662 } /* so we already have a token set since the gettoken succeeded */
665 /* to set the REMOTE_USER environment variable */
666 strcpy(r->connection->user, user);
670 * else - request afs_Authenticator's for it and update local cache
671 * then go about serving the request URI
675 #endif /* NO_AFSAPACHE_CACHE */
677 rc = request_Authentication(user, passwd, cell, type, tokenbuf,
680 /* we got back a token from weblog */
681 /* set the token with setToken */
682 if (setToken(tokenbuf, rc)) {
684 "%s: Failed to set token given by weblog. errno:%d",
686 LOG_REASON(err_msg, r->uri, r);
690 system("/usr/afsws/bin/tokens");
693 #ifndef NO_AFSAPACHE_CACHE
694 /* update local cache */
695 if (updateCache(user, passwd, cell, tokenbuf, cacheExpiration)) {
696 sprintf(err_msg, "%s: Error updating cache", module_name);
697 LOG_REASON(err_msg, r->uri, r);
701 ("%s: pid:%d\t put user:%s tokens in cache", module_name,
703 #endif /* NO_AFSAPACHE_CACHE */
705 /* now we've got a token, updated the cache and set it so we should
706 * have no problems accessing AFS files - however if we do then
707 * we handle it in afs_accessCheck() when the error comes back
710 /* to set the REMOTE_USER environment variable to the username */
711 strcpy(r->connection->user, user);
713 } else if (rc == -2) {
715 ":%s: AFS authentication failed for %s@%s because %s",
716 module_name, user, cell, reason);
717 LOG_REASON(err_msg, r->uri, r);
718 setCellAuthHeader(r);
719 return AUTH_REQUIRED;
720 } else if (rc == -1) {
721 sprintf(err_msg, "%s: Error readiong from pipe. errno:%d",
723 LOG_REASON(err_msg, r->uri, r);
729 * unknown error from weblog - this should not occur
730 * if afs_Authenticator can't authenticate you, then return FORBIDDEN
733 "%s: AFS could not authenticate user %s in cell %s."
734 "Returning FORBIDDEN", module_name, user, cell);
735 LOG_REASON(err_msg, r->uri, r);
738 #ifndef NO_AFSAPACHE_CACHE
741 /* should never get here */
742 LOG_REASON("AFS Authentication: WE SHOULD NEVER GET HERE", r->uri, r);
748 * pioctl call to get the cell name hosting the object specified by path.
749 * returns 0 if successful -1 if failure. Assumes memory has been allocated
750 * for cell. Used to set the www-authenticate header.
753 get_cellname_from_path(char *apath, char *cell)
760 rc = do_pioctl(NULL, 0, cell, APACHEAFS_CELLNAME_MAX, VIOC_FILE_CELL_NAME,
764 ("%s: Error getting cell from path %s. errno:%d rc:%d",
765 module_name, apath, errno, rc));
768 ("%s: Obtained cell %s from path %s", module_name, cell,
775 * obtains the path to the file requested and sets things up to
776 * call get_cell_by_name.
777 * TODO: These could well be combined into one single function.
780 getcellname(request_rec * r, char *buf)
788 rc = get_cellname_from_path(r->filename, buf);
791 sprintf(path, "%s/%s", DOCUMENT_ROOT(r), r->uri);
792 rc = get_cellname_from_path(path, buf);
798 * Returns a part of the url upto the second slash in the buf
801 geturi(request_rec * r, char *buf)
812 memset(buf, 0, APACHEAFS_CELLNAME_MAX);
813 pos = strchr(r->uri, '/');
816 for (i = 0; i < max; i++) {
817 end = strchr(pos, '/');
819 int len = strlen(pos) - strlen(end);
821 strncat(buf, pos, len);
823 ("%s: Getting URI upto second slash buf:%s",
826 end = strchr(pos, '/');
842 * This function recursively parses buf and places the output in msg
843 * Eg. <%c%uUnknown> gets translated to the cellname that the file
844 * resides in, failing which the first part of the uri failing which the
848 parseAuthName_int(request_rec * r, char *buf, char *msg)
854 char blank[APACHEAFS_CELLNAME_MAX];
860 memset(blank, 0, sizeof(blank));
862 ("%s: Parsing Authorization Required reply. buf:%s", module_name,
865 pos = strchr(buf, '<');
869 end = strchr(pos, '>');
872 ("%s:Parse error for AUTH_REQUIRED reply - mismatched <",
874 fprintf(stderr, "Parse Error: mismatched <\n");
875 strncpy(msg, buf, strlen(buf) - len);
876 afslog(0, ("%s: msg:%s", msg));
884 rc = getcellname(r, blank);
886 strncpy(msg, buf, strlen(buf) - len);
894 rc = geturi(r, blank);
896 strncpy(msg, buf, strlen(buf) - len);
904 if (global_default_cell != NULL) {
905 strncpy(msg, buf, strlen(buf) - len);
906 strcat(msg, global_default_cell);
912 strncpy(msg, buf, strlen(buf) - len);
917 memset(msg, 0, 1024);
918 parseAuthName_int(r, buf, msg);
921 strncpy(msg, buf, strlen(buf) - len);
922 strncat(msg, pos, strlen(pos) - strlen(end) - 1);
930 * Parses the entire auth_name string - ie. takes care of multiple
934 parseAuthName(request_rec * r, char *buf)
943 memset(msg, 0, sizeof(msg));
945 pos = strchr(buf, '<');
946 while (pos != NULL) {
947 rc = parseAuthName_int(r, buf, msg);
951 ("%s: Failed to parse Auth Name. buf:%s", module_name,
956 memset(msg, 0, sizeof(msg));
957 pos = strchr(buf, '<');
960 ("%s: Parsing WWW Auth required reply. final message:%s",
967 * Set the www-authenticate header - this is the login prompt the users see
970 setCellAuthHeader(request_rec * r)
978 name = (char *)get_afs_authprompt(r);
981 rc = parseAuthName(r, buf);
985 TABLE_SET(r->err_headers_out, "WWW-Authenticate",
986 PSTRCAT(r->pool, "Basic realm=\"", buf, "\"", NULL));
992 * Checks if we have some authentication credentials, if we do returns
993 * FORBIDDEN and if we don't then returns AUTH_REQUIRED with the appropriate
994 * www-authenticate header. Should be called if we can't access a file because
995 * permission is denied.
998 forbToAuthReqd(request_rec * r)
1003 setCellAuthHeader(r);
1004 return AUTH_REQUIRED;