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
;