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
75 static int parse_authhdr(request_rec *r,
83 const char *auth_line = TABLE_GET (r->headers_in, "Authorization");
85 if ((r == NULL) || (auth_line == NULL) || (user == NULL) ||
86 (passwd == NULL) || (cell == NULL)) {
87 LOG_REASON ("AFSAUTH_CLIENT: NULL request record, auth_line, cell,"
88 "user or passwd while parsing authentication header",
98 * check for basic authentication
100 if (strncasecmp(GETWORD(r->pool,(const char **)&auth_line,' '),
102 /* Client tried to authenticate using some other auth scheme */
103 LOG_REASON ("AFSAUTH_CLIENT:client used other than Basic authentication"
104 "scheme", r->uri, r);
109 * Username and password are base64 encoded
111 t = UUDECODE (r->pool, auth_line);
114 LOG_REASON ("AFSAUTH_CLIENT:uudecode failed", r->uri, r);
119 * Format is user@cell:passwd. The user, cell or passwd may be missing
121 r->connection->user = GETWORD_NULLS (r->pool, (const char **)&t, ':');
122 r->connection->auth_type = "Basic";
125 p = r->connection->user;
127 for ( i = 0 ; *p != '@' && *p != '\0' ; p++ , i++) {
132 for (i = 0 , p++ ; *p != '\0' ; p++ , i++) {
138 if (cell[0] == '\0') {
139 strcpy(cell, defaultCell);
145 * send a buffer to the weblog process over the pipe. Used for sending
146 * authentication credentials to weblog
148 static int sendBuffer(char *buf, int len)
151 if (write(writePipe, buf, len) != len) {
152 afslog(5, ("%s: Error writing to pipe - %s", module_name, strerror(errno)));
159 * packs user credentials into a buffer seperated by newlines and
160 * sends them to weblog
162 static int sendTo_afsAuthenticator(char *user, char *passwd,
163 char *cell, char *type)
172 sprintf(buf, "%s\n%s\n%s\n%s", type, user, cell, passwd);
173 return sendBuffer(buf, strlen(buf));
177 * reads the response from weblog over the pipe
179 static int recvFrom_afsAuthenticator(char *buf)
184 n = read(readPipe, buf, MAXBUFF);
186 afslog(5, ("%s: Error reading from pipe - %s", module_name, strerror(errno)));
192 #ifndef NO_AFSAPACHE_CACHE
194 * check local cache for the token associated with user crds.
196 static int check_Cache(char *user, char *passwd, char *cell, char *tokenBuf)
198 char cksum[SHA_HASH_BYTES]; /* for sha checksum for caching */
200 /* look up local cache - function in apache_afs_cach.c */
201 weblog_login_checksum(user, cell, passwd, cksum);
202 return weblog_login_lookup(user, cell, cksum, &tokenBuf[0]);
206 * put the token and the user credentials in the local cache
208 static int updateCache(char *user,
214 long expires = 0, testExpires = 0;
215 char cksum[SHA_HASH_BYTES]; /* for sha checksum for caching */
217 /* put the token in local cache with the expiration date */
218 expires = getExpiration(tokenBuf);
220 afslog(5, ("%s: Error getting expiration time for cache. Expires %d",
221 module_name, expires));
225 weblog_login_checksum(user, cell, passwd, cksum);
227 if (cacheExpiration == 0) {
228 weblog_login_store(user, cell, cksum, &tokenBuf[0], sizeof(tokenBuf),expires);
231 testExpires=cacheExpiration+time(NULL);
232 weblog_login_store(user, cell, cksum, &tokenBuf[0],sizeof(tokenBuf),
233 MIN(expires,testExpires));
237 #endif /* NO_APACHEAFS_CACHE */
241 * locking routines to provide exclusive access to the pipes
243 static int start_lock(int fd, int cmd, int type)
248 lock.l_whence = SEEK_SET;
250 return (fcntl(fd, cmd, &lock));
253 static int test_lock(int fd, int type)
258 lock.l_whence = SEEK_SET;
261 if (fcntl(fd, F_GETLK, &lock) < 0) {
264 if (lock.l_type == F_UNLCK) {
265 return 0; /* not locked */
267 return (lock.l_pid); /* return pid of locking process */
271 #define Read_lock(fd) \
272 start_lock(fd, F_SETLK, F_RDLCK)
273 #define Readw_lock(fd) \
274 start_lock(fd, F_SETLKW, F_RDLCK)
275 #define Write_lock(fd) \
276 start_lock(fd, F_SETLK, F_WRLCK)
277 #define Writew_lock(fd) \
278 start_lock(fd, F_SETLKW, F_WRLCK)
280 start_lock(fd, F_SETLK, F_UNLCK)
281 #define Is_readlock(fd) \
282 test_lock(fd, F_RDLCK)
283 #define Is_writelock(fd) \
284 test_lock(fd, F_WRLCK)
287 * communication between this process and weblog - sends user credentials
288 * over a shared pipe (mutex provided using locks) and recieves either a
289 * token or an error message
291 static int request_Authentication(char *user, char *passwd,
292 char *cell, char *type,
293 char *tokenbuf, char *reason)
307 * lock the pipe before beginning communication or in case of AIX it is an
308 * error to attempt to lock a pipe or FIFO (EINVAL) therefore we have to create
309 * a temporary file and use that fd instead
317 while ((pid = Is_writelock(lockfd)) != 0) {
319 afslog(5, ("%s: pid:%d Error locking pipe - %s",
320 module_name, getpid(), strerror(errno)));
323 afslog(40, ("%s: pid %d waiting for lock held by pid %d",
324 module_name, getpid(), pid));
327 if(Write_lock(lockfd) == -1) {
328 afslog(5, ("%s: pid:%d Error write lock - %s. Retrying with WriteW",
329 module_name, getpid(), strerror(errno)));
330 if (Writew_lock(lockfd) == -1) {
331 afslog(5, ("%s: pid:%d Error write lock - %s",
332 module_name, getpid(), strerror(errno)));
337 if (sendTo_afsAuthenticator(user, passwd, cell, type) == -1) {
339 afslog(5, ("%s: Error sending authentication info", module_name));
343 len = recvFrom_afsAuthenticator(tokenbuf);
345 /* release the lock */
346 if (Unlock(lockfd)) {
347 afslog(5, ("%s: pid:%d Error unlocking", module_name, getpid()));
352 if (strncmp(tokenbuf,"FAILURE",7)==0) {
354 strncpy(reason, temp, len);
362 * pioctl setting token
364 static int setToken(char *tokenBuf, int tokenLen)
372 * set the primary flag only if we haven't done a SETPAG previoulsy
373 * by flipping this bit
377 /* skip over the secret token */
379 bcopy(temp, &i, sizeof(afs_int32));
380 temp += (i + sizeof(afs_int32));
382 /* skip over the clear token */
383 bcopy(temp, &i, sizeof(afs_int32));
384 temp += (i + sizeof(afs_int32));
387 bcopy(temp, &i, sizeof(afs_int32));
389 bcopy(&i, temp, sizeof(afs_int32));
390 temp += sizeof(afs_int32);
398 return do_pioctl(tokenBuf, tokenLen, tokenBuf, tokenLen, VIOCSETTOK, NULL, 0);
402 * Get the token for the primary cell from the cache manager for this
403 * process. Primary cell is the cell at the first index (index 0)
405 static int getToken(char *buf, int bufsize)
407 /* get just the ONE token for this PAG from cache manager */
409 memcpy((void *)buf, (void *)&i, sizeof(afs_int32));
410 return do_pioctl(buf, sizeof(afs_int32), buf, bufsize,
416 * discard all authentication information for this PAG ie. this process
420 return do_pioctl(0, 0, 0,0, VIOCUNPAG, NULL, 0);
425 * Does the following things:
426 * Checks whether there is a Basic Authentication header - obtains creds.
427 * Checks local cache for the token associated with the user creds.
428 * - if no token in cache - obtains token from weblog using pipes
429 * - sets the token and returns appropriate return code
430 * Return values: OK, SERVER_ERROR, AUTH_REQUIRED, FORBIDDEN
432 int authenticateUser(request_rec *r, char *defaultCell,
433 int cacheExpiration, char *type)
435 char user[APACHEAFS_USERNAME_MAX];
436 char passwd[APACHEAFS_PASSWORD_MAX];
437 char cell[APACHEAFS_CELLNAME_MAX];
438 char tokenbuf[MAXBUFF];
439 char cksum[SHA_HASH_BYTES];
442 const char *auth_line;
443 char reason[MAXBUFF]; /* if authentication failed - this is why */
444 char err_msg[MAXBUFF];
449 afsassert(defaultCell);
452 auth_line = TABLE_GET (r->headers_in, "Authorization");
454 if (strcmp(global_default_cell, defaultCell)) {
455 strcpy(global_default_cell, defaultCell);
458 bzero(user,APACHEAFS_USERNAME_MAX);
459 bzero(passwd,APACHEAFS_PASSWORD_MAX);
460 bzero(cell,APACHEAFS_CELLNAME_MAX);
462 if (auth_line == NULL) { /* No Authorization field - we don't do anything */
464 * No Authorization field recieved - that's fine by us.
465 * go ahead and attempt to service the request and if we get
466 * back FORBIDDEN then we'll take care of it then
468 afslog(15, ("%s: No authline recieved", module_name));
471 bzero(lastUser, APACHEAFS_USERNAME_MAX);
472 bzero(lastCell, APACHEAFS_CELLNAME_MAX);
473 bzero(lastCksum, SHA_HASH_BYTES);
475 afslog(25, ("%s: pid:%d No Authorization field. Unlogging ...",
476 module_name, getpid()));
478 sprintf(err_msg, "%s: Error unlogging from AFS cell - rc: %d, errno:%d",
479 module_name, rc, errno);
480 LOG_REASON(err_msg, r->uri, r);
487 * We should get here only if there IS an Authorization field
490 if ((rc = parse_authhdr(r, user, passwd, cell, defaultCell)) != 0) {
491 sprintf(err_msg, "%s: Error parsing Authorization Header rc:%d",
493 LOG_REASON(err_msg, r->uri, r);
494 return rc; /* SERVER ERROR */
498 * should get here only after obtaining the username and password and cell
499 * check to make sure anyway
501 if ((user[0] =='\0') || (cell[0] == '\0') || (passwd[0] =='\0')) {
502 afslog(15, ("%s: pid:%d No username or password or cell. Unlogging.",
503 module_name, getpid()));
506 bzero(lastUser, APACHEAFS_USERNAME_MAX);
507 bzero(lastCell, APACHEAFS_CELLNAME_MAX);
508 bzero(lastCksum, SHA_HASH_BYTES);
511 sprintf(err_msg, "%s: Error unlogging from AFS cell - rc: %d, errno:%d",
512 module_name, rc, errno);
513 LOG_REASON(err_msg, r->uri, r);
516 setCellAuthHeader(r);
517 return AUTH_REQUIRED;
521 fprintf(stderr, "Cell:%s\tUser:%s\tPasswd:%s\n", cell, user, passwd);
525 * compare with previous username/cell/cksum - update it
529 weblog_login_checksum(user, cell, passwd, cksum);
532 strcpy(lastUser, user);
533 strcpy (lastCksum, cksum);
534 strcpy(lastCell, cell);
536 if (strcmp(user, lastUser) ||
537 strcmp(cell, lastCell) ||
538 strcmp(cksum, lastCksum)) {
540 * unlog the old user from the cell if a new username/passwd is recievd
544 afslog(25, ("%s: pid:%d\tUnlogging user %s from cell%s",
545 module_name, getpid(), lastUser, lastCell));
546 afslog(25, ("%s:New user:%s\t New Cell:%s", module_name, user, cell));
547 afslog(25, ("%s:Trying to get URL:%s", module_name, r->uri));
548 afslog(25, ("%s: Unlogging ....", module_name));
551 sprintf(err_msg, "%s: Error unlogging from AFS cell - rc: %d, errno:%d",
552 module_name, rc, errno);
553 LOG_REASON(err_msg, r->uri, r);
556 /* change lastUser to this user */
557 strcpy(lastUser, user);
558 strcpy (lastCksum, cksum);
559 strcpy(lastCell, cell);
560 } /* strcmp checksums - ie. did the user change */
563 #ifndef NO_AFSAPACHE_CACHE
564 if (!cache_initialized) {
566 cache_initialized = 1;
569 /* have to check local cache for this name/passwd */
571 rc = check_Cache(user, passwd, cell, tokenbuf);
573 /* if found then just send the request without going through
574 * weblog - this means the user has already been authenticated
575 * once and we have a valid token just need to set it -
576 * only if it is different from the token already set. No need to
577 * even unlog because this token is set for the entire PAG which
578 * of course consists of just this child process
580 afslog(35, ("%s: pid:%d found user %s's token (expires:%d) in cache",
581 module_name, getpid(), user, (getExpiration(tokenbuf)-time(NULL))));
583 /* if the user changed then set this token else leave it since it should
586 /* set this token obtained from the local cache */
587 afslog(15,("%s:pid:%d\t Setting cached token", module_name, getpid()));
588 if(setToken(tokenbuf, rc)) {
589 afslog(5, ("%s: pid:%d Failed to set token obtained from cache."
590 "rc:%d errno:%d Token Expiration:%d", module_name, getpid(),
591 rc, errno, (getExpiration(tokenbuf)-time(NULL))));
593 parseToken(tokenbuf);
596 * BUG WORKAROUND: sometimes we fail while setting token
597 * with errno ESRCH indicating the named cell
598 * in the last field of the token is not recognized - but that's
599 * not quite true according to parseToken()!! Possibly corrupted
600 * tokens from the cache?
601 * Anyway we just get a new token from weblog
605 } /* if userChanged */
607 /* if this is a child process getting the request for the first time
608 * then there's no way this guy's got a token for us in which case
609 * getToken should fail with EDOM and that means we should set the token
610 * first and maybe set a static variable saying we have set a token?
613 if(getToken(temp, sizeof(temp))) {
615 /* try setting the cached token */
616 if(setToken(tokenbuf,rc)) {
618 * same bug workaround here. ie. go to weblog if setting
619 * the cached token fails.
621 sprintf(err_msg,"%s: pid:%d Failed to set cached token."
622 "errno:%d rc:%d", module_name, getpid(), errno, rc);
623 LOG_REASON(err_msg, r->uri, r);
628 /* and again for any getToken failure other than EDOM */
629 sprintf(err_msg,"%s: Failed to get token: errno:%d rc:%d",
630 module_name, errno, rc);
631 LOG_REASON(err_msg, r->uri, r);
634 } /* so we already have a token set since the gettoken succeeded */
637 /* to set the REMOTE_USER environment variable */
638 strcpy(r->connection->user,user);
642 * else - request afs_Authenticator's for it and update local cache
643 * then go about serving the request URI
647 #endif /* NO_AFSAPACHE_CACHE */
649 rc = request_Authentication(user, passwd, cell, type, tokenbuf,reason);
651 /* we got back a token from weblog */
652 /* set the token with setToken */
653 if(setToken(tokenbuf, rc)) {
654 sprintf(err_msg, "%s: Failed to set token given by weblog. errno:%d",
656 LOG_REASON(err_msg, r->uri, r);
661 system("/usr/afsws/bin/tokens");
664 #ifndef NO_AFSAPACHE_CACHE
665 /* update local cache */
666 if (updateCache(user, passwd, cell, tokenbuf,cacheExpiration)) {
667 sprintf(err_msg, "%s: Error updating cache", module_name);
668 LOG_REASON(err_msg, r->uri, r);
671 afslog(15, ("%s: pid:%d\t put user:%s tokens in cache",
672 module_name, getpid(), user));
673 #endif /* NO_AFSAPACHE_CACHE */
675 /* now we've got a token, updated the cache and set it so we should
676 * have no problems accessing AFS files - however if we do then
677 * we handle it in afs_accessCheck() when the error comes back
680 /* to set the REMOTE_USER environment variable to the username */
681 strcpy(r->connection->user,user);
685 sprintf(err_msg, ":%s: AFS authentication failed for %s@%s because %s",
686 module_name, user, cell, reason);
687 LOG_REASON(err_msg, r->uri, r);
688 setCellAuthHeader(r);
689 return AUTH_REQUIRED;
692 sprintf(err_msg, "%s: Error readiong from pipe. errno:%d", module_name, errno);
693 LOG_REASON(err_msg, r->uri, r);
699 * unknown error from weblog - this should not occur
700 * if afs_Authenticator can't authenticate you, then return FORBIDDEN
702 sprintf(err_msg, "%s: AFS could not authenticate user %s in cell %s."
703 "Returning FORBIDDEN", module_name, user, cell);
704 LOG_REASON(err_msg, r->uri, r);
707 #ifndef NO_AFSAPACHE_CACHE
710 /* should never get here */
711 LOG_REASON("AFS Authentication: WE SHOULD NEVER GET HERE", r->uri, r );
717 * pioctl call to get the cell name hosting the object specified by path.
718 * returns 0 if successful -1 if failure. Assumes memory has been allocated
719 * for cell. Used to set the www-authenticate header.
721 static int get_cellname_from_path(char *apath, char *cell)
728 rc = do_pioctl(NULL, 0, cell, APACHEAFS_CELLNAME_MAX,
729 VIOC_FILE_CELL_NAME, apath, 1);
731 afslog(30, ("%s: Error getting cell from path %s. errno:%d rc:%d",
732 module_name, apath, errno, rc));
734 afslog(30, ("%s: Obtained cell %s from path %s", module_name, cell, apath));
740 * obtains the path to the file requested and sets things up to
741 * call get_cell_by_name.
742 * TODO: These could well be combined into one single function.
744 static int getcellname(request_rec *r, char *buf)
752 rc = get_cellname_from_path(r->filename, buf);
756 sprintf(path,"%s/%s",DOCUMENT_ROOT(r), r->uri);
757 rc = get_cellname_from_path(path, buf);
763 * Returns a part of the url upto the second slash in the buf
765 static int geturi(request_rec *r, char *buf)
776 bzero(buf, APACHEAFS_CELLNAME_MAX);
777 pos = strchr(r->uri,'/');
780 for (i=0; i<max; i++) {
781 end = strchr(pos,'/');
783 int len = strlen(pos) - strlen(end);
785 strncat(buf,pos,len);
786 afslog(35, ("%s: Getting URI upto second slash buf:%s", module_name, buf));
788 end = strchr(pos,'/');
806 * This function recursively parses buf and places the output in msg
807 * Eg. <%c%uUnknown> gets translated to the cellname that the file
808 * resides in, failing which the first part of the uri failing which the
811 static int parseAuthName_int(request_rec *r, char *buf, char *msg)
817 char blank[APACHEAFS_CELLNAME_MAX];
823 bzero(blank, sizeof(blank));
824 afslog(50, ("%s: Parsing Authorization Required reply. buf:%s", module_name, buf));
826 pos = strchr(buf, '<');
830 end = strchr(pos, '>');
832 afslog(0,("%s:Parse error for AUTH_REQUIRED reply - mismatched <", module_name));
833 fprintf(stderr,"Parse Error: mismatched <\n");
834 strncpy(msg, buf, strlen(buf)-len);
835 afslog(0, ("%s: msg:%s", msg));
843 rc = getcellname(r, blank);
845 strncpy(msg, buf, strlen(buf)-len);
853 rc = geturi(r, blank);
855 strncpy(msg, buf, strlen(buf)-len);
863 if (global_default_cell != NULL) {
864 strncpy(msg, buf, strlen(buf)-len);
865 strcat(msg, global_default_cell);
871 strncpy(msg, buf, strlen(buf)-len);
877 parseAuthName_int(r, buf, msg);
881 strncpy(msg, buf, strlen(buf)-len);
882 strncat(msg, pos, strlen(pos)-strlen(end)-1);
890 * Parses the entire auth_name string - ie. takes care of multiple
893 static int parseAuthName(request_rec *r, char *buf)
902 bzero(msg, sizeof(msg));
905 while (pos != NULL) {
906 rc = parseAuthName_int(r, buf, msg);
909 afslog(35, ("%s: Failed to parse Auth Name. buf:%s", module_name, buf));
913 bzero(msg, sizeof(msg));
914 pos = strchr(buf, '<');
916 afslog(50, ("%s: Parsing WWW Auth required reply. final message:%s",
923 * Set the www-authenticate header - this is the login prompt the users see
925 static int setCellAuthHeader(request_rec *r)
933 name = (char *)get_afs_authprompt(r);
936 rc = parseAuthName(r, buf);
941 TABLE_SET (r->err_headers_out, "WWW-Authenticate",
942 PSTRCAT(r->pool, "Basic realm=\"", buf, "\"", NULL));
948 * Checks if we have some authentication credentials, if we do returns
949 * FORBIDDEN and if we don't then returns AUTH_REQUIRED with the appropriate
950 * www-authenticate header. Should be called if we can't access a file because
951 * permission is denied.
953 int forbToAuthReqd(request_rec *r)
959 setCellAuthHeader(r);
960 return AUTH_REQUIRED;