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 the weblog binary which links with the AFS libraries and acts
12 * as the server for authenticating users for apache access to AFS. Code
13 * structure is based on klog.c. The communication with clients is done
14 * via pipes whose file descriptors are passed as command line arguments
15 * thus making it necessary for a common parent to start this process and
16 * the processes that will communicate with it for them to inherit the
17 * pipes. Also passed as a command line argument is a Silent flag (like klog)
18 * and a cache expiration flag which allows cache expiration times for
19 * tokens to be set for testing purposes
23 /* These two needed for rxgen output to work */
24 #include <afsconfig.h>
25 #include <afs/param.h>
31 #include <sys/types.h>
32 #include <sys/errno.h>
45 #include <afs/com_err.h>
47 #include <afs/cellconfig.h>
50 #include "weblog_errors.h"
53 #define MAX(A,B) ((A)>(B)?(A):(B))
56 #define MIN(A,B) ((A)<(B)?(A):(B))
59 #include "apache_afs_utils.h"
64 #include "apache_afs_cache.h"
66 /* the actual function that does all the work! */
70 static char **zero_argv;
72 /* pipes used for communicating with web server */
73 /* these are passed as command line args - defaults to stdin/stdout */
79 * now I know why this was necessary! - it's a hokie thing -
80 * the call to ka_UserAuthenticateGeneral doesn't compile otherwise
89 main(int argc, char **argv)
91 struct cmd_syndesc *ts;
96 * The following signal action for AIX is necessary so that in case of a
97 * crash (i.e. core is generated) we can include the user's data section
98 * in the core dump. Unfortunately, by default, only a partial core is
99 * generated which, in many cases, isn't too useful.
101 struct sigaction nsa;
103 sigemptyset(&nsa.sa_mask);
104 nsa.sa_handler = SIG_DFL;
105 nsa.sa_flags = SA_FULLDUMP;
106 sigaction(SIGABRT, &nsa, NULL);
107 sigaction(SIGSEGV, &nsa, NULL);
111 * we ignore SIGPIPE so that EPIPE is returned if there is no one reading
112 * data being written to the pipe
116 /* TODO - for AIX? */
119 sa.sa_handler = SIG_IGN;
120 sigaction(SIGPIPE, &sa, NULL);
126 ts = cmd_CreateSyntax(NULL, CommandProc, 0,
127 "obtain Kerberos authentication for web servers");
129 /* define the command line arguments */
132 #define aCACHEEXPIRATION 2
133 #define aTOKENEXPIRATION 3
136 cmd_AddParm(ts, "-readPipe", CMD_SINGLE, CMD_OPTIONAL, "inPipefd");
137 cmd_AddParm(ts, "-writePipe", CMD_SINGLE, CMD_OPTIONAL, "outPipefd");
138 cmd_AddParm(ts, "-cacheExpiration", CMD_SINGLE, CMD_OPTIONAL,
139 "local cache expiration times for tokens");
140 cmd_AddParm(ts, "-tokenExpiration", CMD_SINGLE, CMD_OPTIONAL,
141 "cache manager expiration time for tokens");
142 cmd_AddParm(ts, "-silent", CMD_FLAG, CMD_OPTIONAL, "silent operation");
144 code = cmd_Dispatch(argc, argv);
151 * send a buffer over the pipe
154 sendToken(int len, void *buf)
157 WEBLOGEXIT(NULLARGSERROR);
159 if (write(writePipe, buf, len) != len) {
161 perror("weblog: write to pipe error");
170 * read the incoming buffer from the pipe
174 readFromClient(char *buf)
178 WEBLOGEXIT(NULLARGSERROR);
180 n = read(readPipe, buf, MAXBUFF);
183 perror("weblog: pipe read error");
189 perror("weblog: zero bytes read from pipe");
197 * copies the string spereated by the sep into retbuf and returns the position
198 * from the beginning of the string that this string ends at so you can call
199 * it againword seperated by the sep character and give that value as th start
200 * parameter - used to parse incoming buffer from clients over the pipe
203 * NOTE - the space seperated credentials failed for passwds with spaces, thus
204 * we use newline for seperators instead
207 getNullSepWord(char *buf, char sep, char *retBuf, int start)
210 int len = strlen(buf) - start;
213 if ((buf == NULL) || (retBuf == NULL) || (start < 0)) {
214 fprintf(stderr, "weblog (getWordSep):NULL args\n");
218 while ((buf[start] != sep) && (ret <= len)) {
219 retBuf[ret] = buf[start];
229 * parses the NEWLINE seperated buffer giving the username, passwd and cell
230 * coming over the pipe from the clients and sets the variables accordingly
233 parseBuf(char *buf, char *user, char *pass, char *cell, char *type)
236 int start = 0, ret = 0;
238 if ((buf == NULL) || (user == NULL) || (pass == NULL) || (cell == NULL)
241 fprintf(stderr, "afs_Authenticator:parseBuf-an arg was NULL\n");
245 if ((ret = getNullSepWord(buf, '\n', type, start)) < 0) {
249 if ((ret = getNullSepWord(buf, '\n', user, start)) < 0) {
253 if ((ret = getNullSepWord(buf, '\n', cell, start)) < 0) {
257 if ((ret = getNullSepWord(buf, '\n', pass, start)) < 0) {
265 * Discard any authentication information held in trust by the Cache Manager
266 * for the calling process and all other processes in the same PAG
271 return do_pioctl(NULL, 0, NULL, 0, VIOCUNPAG, NULL, 0);
275 /* we can obtain a PAG by calling this system call */
282 #ifdef ENABLE_DCE_DLOG
284 * Attempt to use the dlog mechanism to get a DCE-DFS ticket into the AFS cache manager
290 #include <afs/afsutil.h>
293 * The un-decoded version of the encrypted portion of the kdc
294 * AS reply message (see Kerberos V5 spec).
296 typedef struct kdc_as_reply {
297 des_cblock session_key;
312 fprintf(stderr, "weblog: makeString - NULL argument\n");
317 fprintf(stderr, "weblog: makeString. strlen error\n");
320 new_string = (char *)malloc(len + 1);
321 if (new_string == NULL) {
322 fprintf(stderr, "weblog: Out of memory - malloc failed\n");
325 strncpy(new_string, sp, len);
330 * Store the returned credentials as an AFS "token" for the user
331 * "AFS ID <user_id>".
334 store_afs_token(unix_id, realm_p, tkt_type, ticket_p, ticket_len, session_key,
335 starttime, endtime, set_pag)
339 unsigned char *ticket_p;
341 des_cblock session_key;
346 struct ktc_token token;
347 struct ktc_principal client, server;
349 token.startTime = starttime;
350 token.endTime = endtime;
351 memcpy((char *)&token.sessionKey, session_key, sizeof(token.sessionKey));
352 token.kvno = tkt_type;
353 token.ticketLen = ticket_len;
354 if (ticket_len > MAXKTCTICKETLEN) {
356 "weblog: DCE ticket is too long (length %d)."
357 "Maximum length accepted by AFS cache manager is %d\n",
361 memcpy((char *)token.ticket, (char *)ticket_p, ticket_len);
363 sprintf(client.name, "AFS ID %d", unix_id);
364 strcpy(client.instance, "");
365 strcpy(client.cell, realm_p);
367 strcpy(server.name, "afs");
368 strcpy(server.instance, "");
369 strcpy(server.cell, realm_p);
372 (&server, &token, &client, set_pag ? AFS_SETTOK_SETPAG : 0));
377 make_string(s_p, length)
381 char *new_p = (char *)malloc(length + 1);
383 fprintf(stderr, "dlog: out of memory\n");
386 memcpy(new_p, s_p, length);
387 new_p[length] = '\0';
392 * Decode an asn.1 GeneralizedTime, turning it into a 32-bit Unix time.
393 * Format is fixed at YYYYMMDDHHMMSS plus a terminating "Z".
395 * NOTE: A test for this procedure is included at the end of this file.
398 decode_asn_time(buf, buflen, utime)
403 int year, month, day, hour, mina, sec;
405 static mdays[11] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30 };
409 || sscanf(buf, "%4d%2d%2d%2d%2d%2dZ", &year, &month, &day, &hour,
413 leapyear = month > 2 ? (year + 1) : year; /* Account for feb 29 if
414 * current year is a leap year */
415 for (days = 0, m = 0; m < month - 1; m++)
419 ((((((year - 1970) * 365 + (leapyear - 1970 + 1) / 4 + days + day -
420 1) * 24) + hour) * 60 + mina) * 60) + sec;
425 * A quick and (very) dirty ASN.1 decode of the ciphertext portion
426 * of the KDC AS reply message... good enough for a product with a short
427 * expected lifetime (this will work as long as the message format doesn't
432 * 1. The nonce is the only INTEGER with a tag of [2], at any nesting level.
433 * 2. The session key is the only OCTET STRING with length 8 and tag [1].
434 * 3. The authtime, starttime, and endtimes are the only Generalized Time
435 * strings with tags 5, 6, and 7, respectively.
436 * 4. The realm is the only General String with tag 9.
437 * 5. The tags, above, are presented in ascending order.
440 #define ASN_INTEGER 0x2
441 #define ASN_OCTET_STRING 0x4
442 #define ASN_TIME 0x18
443 #define ASN_GENERAL_STRING 0x1b
447 decode_reply(buf, buflen, reply_p)
448 unsigned char *buf; /* encoded ASN.1 string */
449 int buflen; /* length of encoded string */
450 kdc_as_reply_t *reply_p; /* result */
452 unsigned char *limit = buf + buflen;
455 char saw_kdc_rep = 0;
456 char saw_session_key = 0;
457 char saw_authtime = 0;
458 char saw_starttime = 0;
459 char saw_endtime = 0;
462 int context = -1; /* Initialize with invalid context */
464 reply_p->starttime = 0; /* This is optionally provided by kdc */
466 while (buf < limit) {
472 if ((op & 0x20) == 0) {
473 /* Primitive encoding */
475 return 1; /* Forget about long unspecified lengths */
478 if (buf + len > limit)
490 * Since non ANSI C doesn't recognize the "signed"
491 * type attribute for chars, we have to fiddle about
492 * to get sign extension (the sign bit is the top bit
493 * of the first byte).
495 #define SHIFTSIGN ((sizeof(afs_int32) - 1) * 8)
497 val = (afs_int32) (*buf++ << SHIFTSIGN) >> SHIFTSIGN;
500 val = (val << 8) | *buf++;
503 reply_p->nonce = val;
509 case ASN_OCTET_STRING:
510 if (context == 1 && len == sizeof(reply_p->session_key)) {
512 memcpy(reply_p->session_key, buf, len);
517 case ASN_GENERAL_STRING:
520 reply_p->realm = make_string(buf, len);
521 goto out; /* Best to terminate now, rather than
522 * continue--we don't know if the entire
523 * request is padded with zeroes, and if
524 * not there is a danger of misinterpreting
525 * an op-code (since the request may well
526 * be padded somewhat, for encryption purposes)
527 * This would work much better if we really
528 * tracked constructed type boundaries.
538 if (decode_asn_time(buf, len, &reply_p->authtime))
544 if (decode_asn_time(buf, len, &reply_p->starttime))
550 if (decode_asn_time(buf, len, &reply_p->endtime))
558 if ((op & 0xe0) == 0xa0) {
559 /* Remember last context label */
561 } else if ((op & 0x20) == 0) {
562 /* Skip primitive encodings we don't understand */
569 return !(saw_kdc_rep == 1 && saw_nonce == 1 && saw_session_key == 1
570 && saw_authtime == 1 && (saw_starttime == 1
571 || saw_starttime == 0)
572 && saw_endtime == 1 && saw_realm == 1);
577 * Attempt to obtain a DFS ticket
580 getDFScreds(char *name, char *realm, char *passwd, afs_uint32 lifetime,
583 extern ADK_GetTicket();
584 afs_int32 serverList[MAXSERVERS];
585 struct rx_connection *serverconns[MAXSERVERS];
586 struct ubik_client *ubik_handle = 0;
587 struct timeval now; /* current time */
588 afs_int32 nonce; /* Kerberos V5 "nonce" */
589 adk_error_ptr error_p; /* Error code from ktc intermediary */
590 adk_reply_ptr reply_p; /* reply from ktc intermediary */
591 des_cblock passwd_key; /* des key from user password */
592 des_key_schedule schedule; /* Key schedule from key */
593 kdc_as_reply_t kdcrep; /* Our own decoded version of
594 * ciphertext portion of kdc reply */
596 struct afsconf_dir *cdir; /* Open configuration structure */
598 struct afsconf_cell cellinfo; /* Info for specified cell */
600 if ((name == NULL) || (realm == NULL) || (passwd == NULL)) {
601 *reason = makeString("weblog: NULL Arguments to getDCEcreds");
605 cdir = afsconf_Open(AFSDIR_CLIENT_ETC_DIRPATH);
608 makeString("weblog: unable to read or open AFS client "
609 "configuration file");
614 * Resolve full name of cell and get server list.
616 code = afsconf_GetCellInfo(cdir, realm, 0, &cellinfo);
618 *reason = makeString("-- unable to get cell info");
622 if (strcmp(realm, cellinfo.name)) {
623 strncpy(realm, cellinfo.name, sizeof(realm) - 1);
624 realm[sizeof(realm) - 1] = '\0';
627 for (i = 0; i < cellinfo.numServers && i < MAXSERVERS; i++) {
628 serverList[i] = cellinfo.hostAddr[i].sin_addr.s_addr;
634 * Make connections to all the servers.
637 for (i = 0; i < MAXSERVERS; i++) {
638 if (!serverList[i]) {
643 rx_NewConnection(serverList[i], htons(ADK_PORT), ADK_SERVICE,
644 rxnull_NewClientSecurityObject(), 0);
648 * Initialize ubik client gizmo to randomize the calls for us.
650 ubik_ClientInit(serverconns, &ubik_handle);
652 * Come up with an acceptable nonce. V5 doc says that we just need
653 * time of day. Actually, for this app, I don't think anything
654 * is really needed. Better safe than sorry (although I wonder if
655 * it might have been better to encode the AFS ID in the nonce
656 * reply field--that's the one field that the intermediate server
657 * has total control over, and which can be securely transmitted
658 * back to the client).
660 gettimeofday(&now, 0);
665 * Ask our agent to get us a Kerberos V5 ticket.
667 reply_p = (adk_reply_ptr) 0;
668 error_p = (adk_error_ptr) 0;
669 code = ubik_ADK_GetTicket(ubik_handle, 0, /* Ubik flags */
670 name, /* IN: Principal: must be exact DCE principal */
671 nonce, /* IN: Input nonce */
672 lifetime, /* IN: lifetime */
673 &error_p, /* OUT: Error, if any */
674 &reply_p); /* OUT: KTC reply, if no error */
677 * Destroy Rx connections on the off-chance this will allow less state
678 * to be preserved at the server.
680 ubik_ClientDestroy(ubik_handle);
683 * Finalize Rx. This may allow connections at the server to wind down
690 * Check for simple communication failures.
693 *reason = makeString("-- failed to contact authentication service");
698 * Also check for DCE errors, which are interpreted for us by
701 if (error_p && error_p->code) {
702 *reason = (char *)makeString(error_p->data);
703 fprintf(stderr, "weblog error:error_p->data:%s\n", error_p->data);
709 * Make sure the reply was filled in.
714 ("weblog: unexpected error in server response; aborted");
720 * Convert the password into the appropriate key block, given
721 * the salt passed back from the ADK_GetTicket call, above. Destroy
724 if (strlen(passwd) + strlen(reply_p->salt) + 1 > BUFSIZ) {
726 makeString("weblog: unexpectedly long passwd/salt combination");
729 strcat(passwd, reply_p->salt);
730 des_string_to_key(passwd, passwd_key);
732 /* Destroy the password. */
733 memset(passwd, 0, strlen(passwd));
737 * Decrypt the private data returned by the DCE KDC, and forwarded
738 * to us by the translator.
740 code = des_key_sched(passwd_key, schedule);
743 des_cbc_encrypt(reply_p->private.adk_code_val,
744 reply_p->private.adk_code_val,
745 reply_p->private.adk_code_len, schedule,
746 passwd_key, DECRYPT);
750 (char *)makeString("-- unable to decrypt reply from the DCE KDC");
755 * Destroy the key block: it's no longer needed.
757 memset(schedule, 0, sizeof(schedule));
758 memset(passwd_key, 0, sizeof(passwd_key));
762 * Do a very quick and dirty ASN.1 decode of the relevant parts
763 * of the private data.
765 * The decrypted data contains a 12-byte header (confounder and CRC-32
766 * checksum). We choose to ignore this.
768 code = decode_reply(reply_p->private.adk_code_val + 12, /* Skip header */
769 reply_p->private.adk_code_len - 12, /* ditto */
772 if (code || kdcrep.nonce != nonce) {
774 (char *)makeString("weblog: DCE authentication failed -- "
775 "password is probably incorrect");
781 * Make an AFS token out of the ticket and session key, and install it
782 * in the cache manager.
785 store_afs_token(reply_p->unix_id, realm, reply_p->tktype,
786 reply_p->ticket.adk_code_val,
787 reply_p->ticket.adk_code_len, kdcrep.session_key,
788 kdcrep.starttime ? kdcrep.starttime : kdcrep.authtime,
793 makeString("weblog -- getDCEcreds:failed to store tickets");
798 #endif /* ENABLE_DCE_DLOG */
802 * The main procedure that waits in an infinite loop for data to
803 * arrive through a pipe from the httpds, authenticates the user and
804 * returns a token (or a failure message) over the pipe
806 CommandProc(as, arock)
808 struct cmd_syndesc *as;
811 char name[MAXKTCNAMELEN];
812 char cell[MAXKTCREALMLEN];
814 /* All the constant sizes for these arrays are taken from the code for klog */
816 char cksum[SHA_HASH_BYTES]; /* for sha checksum for caching */
817 afs_int32 expires = 0; /* for cache expiration */
818 afs_int32 cacheExpiration = 0; /* configurable cmd line parameter */
819 afs_int32 testExpires = 0; /* cacheExpiration + current time */
821 int authtype = 0; /* AFS or AFS-DFS Authentication */
823 int shutdown = 0; /* on getting shutdown from the pipe we GO */
824 int gotToken = 0; /* did we get a token from the cache manager */
825 afs_int32 i = 0; /* for getting primary token held by CM */
826 int dosetpag = 1; /* not used */
827 Date lifetime; /* requested ticket lifetime */
828 char tbuffer[MAXBUFF]; /* for pioctl ops + pipe transfers */
829 static char rn[] = "weblog"; /* Routine name */
830 static int Silent = 0; /* Don't want error messages */
831 afs_int32 password_expires = -1;
832 char *reason; /* string describing errors */
833 char type[10]; /* authentication type AFS or DFS */
835 /* blow away command line arguments */
836 for (i = 1; i < zero_argc; i++)
837 memset(zero_argv[i], 0, strlen(zero_argv[i]));
840 /* first determine quiet flag based on -silent switch */
841 Silent = (as->parms[aSILENT].items ? 1 : 0);
846 fprintf(stderr, "%s:ka_Init FAILED\n", rn);
851 /* Parse our arguments. */
852 if (as->parms[aREADPIPE].items)
853 /* there is a file descriptor instead of stdin */
854 readPipe = atoi(as->parms[aREADPIPE].items->data);
858 if (as->parms[aWRITEPIPE].items)
859 /* there is a file descriptor instead of stdout */
860 writePipe = atoi(as->parms[aWRITEPIPE].items->data);
864 if (as->parms[aCACHEEXPIRATION].items)
865 /* set configurable cache expiration time */
866 cacheExpiration = atoi(as->parms[aCACHEEXPIRATION].items->data);
868 if (as->parms[aTOKENEXPIRATION].items)
869 /* set configurable token lifetime */
870 lifetime = atoi(as->parms[aTOKENEXPIRATION].items->data);
873 * Initialize the cache for tokens
878 * discard any tokens held for this PAG -
879 * should we create a seperate PAG for weblog first? makeNewPAG does that
882 fprintf(stderr, "%s:Before MAKENEWPAG\n", rn);
884 fprintf(stderr, "\nWEBLOG: before PAG:\t");
888 fprintf(stderr, "WEBLOG: MakeNewPAG failed\n");
891 fprintf(stderr, "weblog:AFTER do_setpag,PAG:\t");
893 fprintf(stderr, "%s:After MAKENEWPAG\n", rn);
899 fprintf(stderr, "WEBLOG: UNLOG FAILED\n");
905 code = readFromClient(tbuffer);
907 tbuffer[code] = '\0';
908 code = parseBuf(tbuffer, name, passwd, cell, type);
910 fprintf(stderr, "weblog: parseBuf FAILED\n");
911 WEBLOGEXIT(PARSEERROR);
915 fprintf(stderr, "%s: readFromClient FAILED:%d...exiting\n", rn,
918 WEBLOGEXIT(PIPEREADERROR);
921 if (strcasecmp(type, "AFS") == 0) {
924 #ifdef ENABLE_DCE_DLOG
925 else if (strcasecmp(type, "AFS-DFS") == 0) {
928 #endif /* ENABLE_DCE_DLOG */
934 reason = (char *)malloc(sizeof(tbuffer));
936 "weblog: Unknown Authentication type:%s. AFS-DFS login "
937 "may not be enabled - check compile flags for ENABLE_DCE_DLOG",
942 memset((void *)&tbuffer, 0, sizeof(tbuffer));
944 /* look up local cache */
945 weblog_login_checksum(name, cell, passwd, cksum);
946 code = weblog_login_lookup(name, cell, cksum, &tbuffer[0]);
948 if (!code) { /* local cache lookup failed */
949 /* authenticate user */
952 fprintf(stderr, "WEBLOG GROUPCHECK BEFORE KA_AUTH\n");
958 ka_UserAuthenticateGeneral(KA_USERAUTH_VERSION + 0, name,
959 NULL, cell, passwd, lifetime,
960 &password_expires, 0, &reason);
962 #ifdef ENABLE_DCE_DLOG
963 else if (authtype == 2) {
965 code = getDFScreds(name, cell, passwd, lifetime, &reason);
968 printf("Code:%d\n", code);
971 printf("FAILURE:Reason:%s\n", reason);
975 #endif /* ENABLE_DCE_DLOG */
981 "weblog:Unable to authenticate to AFS because "
989 "WEBLOG:After ka_UserAuthenticateGeneral GroupCheck\n");
992 /* get just the ONE token for this PAG from cache manager */
994 memcpy((void *)&tbuffer[0], (void *)&i, sizeof(afs_int32));
996 do_pioctl(tbuffer, sizeof(afs_int32), tbuffer,
997 sizeof(tbuffer), VIOCGETTOK, NULL, 0);
999 fprintf(stderr, "weblog: failed to get token:%d\n", code);
1001 "FAILED TO GET TOKEN FROM CACHE MANAGER\n");
1006 hexDump(tbuffer, sizeof(tbuffer));
1007 parseToken(tbuffer);
1010 /* put the token in local cache with the expiration date/time */
1011 expires = getExpiration(tbuffer);
1014 fprintf(stderr, "Error getting expiration time\n");
1017 weblog_login_checksum(name, cell, passwd, cksum);
1018 if (cacheExpiration == 0) {
1019 weblog_login_store(name, cell, cksum, &tbuffer[0],
1020 sizeof(tbuffer), expires);
1022 testExpires = cacheExpiration + time(NULL);
1023 weblog_login_store(name, cell, cksum, &tbuffer[0],
1024 sizeof(tbuffer), MIN(expires,
1031 /* cache lookup succesful */
1033 fprintf(stderr, "WEBLOG: Cache lookup succesful\n");
1038 /* prepare the reply buffer with this token */
1042 /* respond with a reason why authentication failed */
1043 sprintf(tbuffer, "FAILURE:%s", reason);
1046 /* send response to client */
1047 code = sendToken(sizeof(tbuffer), tbuffer);
1050 fprintf(stderr, "sendToken FAILED\n");
1052 WEBLOGEXIT(PIPESENDERROR);
1054 /* unlog after every request unconditionally */
1057 fprintf(stderr, "WEBLOG: UNLOG FAILED\n");