/* XXX this does some copying of data in and out of the packet, but I'll bet it * could just do it in place, especially if I used rx_Pullup... */ int rxkad_CheckResponse(struct rx_securityClass *aobj, struct rx_connection *aconn, struct rx_packet *apacket) { struct rxkad_sconn *sconn; struct rxkad_sprivate *tsp; struct ktc_encryptionKey serverKey; struct rxkad_oldChallengeResponse oldr; /* response format */ struct rxkad_v2ChallengeResponse v2r; afs_int32 tlen; /* ticket len */ afs_int32 kvno; /* key version of ticket */ char tix[MAXKTCTICKETLEN]; afs_int32 incChallengeID; rxkad_level level; int code; /* ticket contents */ struct ktc_principal client; struct ktc_encryptionKey sessionkey; afs_int32 host; afs_uint32 start; afs_uint32 end; unsigned int pos; struct rxkad_serverinfo *rock; sconn = (struct rxkad_sconn *)aconn->securityData; tsp = (struct rxkad_sprivate *)aobj->privateData; if (sconn->cksumSeen) { /* expect v2 response, leave fields in v2r in network order for cksum * computation which follows decryption. */ if (rx_GetDataSize(apacket) < sizeof(v2r)) return RXKADPACKETSHORT; rx_packetread(apacket, 0, sizeof(v2r), &v2r); pos = sizeof(v2r); /* version == 2 */ /* ignore spare */ kvno = ntohl(v2r.kvno); tlen = ntohl(v2r.ticketLen); if (rx_GetDataSize(apacket) < sizeof(v2r) + tlen) return RXKADPACKETSHORT; } else { /* expect old format response */ if (rx_GetDataSize(apacket) < sizeof(oldr)) return RXKADPACKETSHORT; rx_packetread(apacket, 0, sizeof(oldr), &oldr); pos = sizeof(oldr); kvno = ntohl(oldr.kvno); tlen = ntohl(oldr.ticketLen); if (rx_GetDataSize(apacket) != sizeof(oldr) + tlen) return RXKADPACKETSHORT; } if ((tlen < MINKTCTICKETLEN) || (tlen > MAXKTCTICKETLEN)) return RXKADTICKETLEN; rx_packetread(apacket, pos, tlen, tix); /* get ticket */ /* * We allow the ticket to be optionally decoded by an alternate * ticket decoder, if the function variable * rxkad_AlternateTicketDecoder is set. That function should * return a code of -1 if it wants the ticket to be decoded by * the standard decoder. */ if (rxkad_AlternateTicketDecoder) { code = rxkad_AlternateTicketDecoder(kvno, tix, tlen, client.name, client.instance, client.cell, &sessionkey, &host, &start, &end); if (code && code != -1) { return code; } } else { code = -1; /* No alternate ticket decoder present */ } /* * If the alternate decoder is not present, or returns -1, then * assume the ticket is of the default style. */ if (code == -1 && ((kvno == RXKAD_TKT_TYPE_KERBEROS_V5) || (kvno == RXKAD_TKT_TYPE_KERBEROS_V5_ENCPART_ONLY))) { code = tkt_DecodeTicket5(tix, tlen, tsp->get_key, tsp->get_key_rock, kvno, client.name, client.instance, client.cell, &sessionkey, &host, &start, &end, tsp->flags & RXS_CONFIG_FLAGS_DISABLE_DOTCHECK); if (code) return code; } /* * If the alternate decoder/kerberos 5 decoder is not present, or * returns -1, then assume the ticket is of the default style. */ if (code == -1) { /* get ticket's key */ code = (*tsp->get_key) (tsp->get_key_rock, kvno, &serverKey); if (code) return RXKADUNKNOWNKEY; /* invalid kvno */ code = tkt_DecodeTicket(tix, tlen, &serverKey, client.name, client.instance, client.cell, &sessionkey, &host, &start, &end); if (code) return code; } code = tkt_CheckTimes(start, end, time(0)); if (code == 0) return RXKADNOAUTH; else if (code == -1) return RXKADEXPIRED; else if (code < -1) return RXKADBADTICKET; code = fc_keysched(&sessionkey, sconn->keysched); if (code) return RXKADBADKEY; memcpy(sconn->ivec, &sessionkey, sizeof(sconn->ivec)); if (sconn->cksumSeen) { /* using v2 response */ afs_uint32 cksum; /* observed cksum */ struct rxkad_endpoint endpoint; /* connections endpoint */ int i; afs_uint32 xor[2]; memcpy(xor, sconn->ivec, 2 * sizeof(afs_int32)); fc_cbc_encrypt(&v2r.encrypted, &v2r.encrypted, sizeof(v2r.encrypted), sconn->keysched, xor, DECRYPT); cksum = rxkad_CksumChallengeResponse(&v2r); if (cksum != v2r.encrypted.endpoint.cksum) return RXKADSEALEDINCON; (void)rxkad_SetupEndpoint(aconn, &endpoint); v2r.encrypted.endpoint.cksum = 0; if (memcmp(&endpoint, &v2r.encrypted.endpoint, sizeof(endpoint)) != 0) return RXKADSEALEDINCON; for (i = 0; i < RX_MAXCALLS; i++) { v2r.encrypted.callNumbers[i] = ntohl(v2r.encrypted.callNumbers[i]); if (v2r.encrypted.callNumbers[i] < 0) return RXKADSEALEDINCON; } (void)rxi_SetCallNumberVector(aconn, v2r.encrypted.callNumbers); incChallengeID = ntohl(v2r.encrypted.incChallengeID); level = ntohl(v2r.encrypted.level); } else { /* expect old format response */ fc_ecb_encrypt(&oldr.encrypted, &oldr.encrypted, sconn->keysched, DECRYPT); incChallengeID = ntohl(oldr.encrypted.incChallengeID); level = ntohl(oldr.encrypted.level); } if (incChallengeID != sconn->challengeID + 1) return RXKADOUTOFSEQUENCE; /* replay attempt */ if ((level < sconn->level) || (level > rxkad_crypt)) return RXKADLEVELFAIL; sconn->level = level; rxkad_SetLevel(aconn, sconn->level); INC_RXKAD_STATS(responses[rxkad_LevelIndex(sconn->level)]); /* now compute endpoint-specific info used for computing 16 bit checksum */ rxkad_DeriveXORInfo(aconn, sconn->keysched, sconn->ivec, sconn->preSeq); /* otherwise things are ok */ sconn->expirationTime = end; sconn->authenticated = 1; if (tsp->user_ok) { code = tsp->user_ok(client.name, client.instance, client.cell, kvno); if (code) return RXKADNOAUTH; } else { /* save the info for later retreival */ int size = sizeof(struct rxkad_serverinfo); rock = (struct rxkad_serverinfo *)osi_Alloc(size); memset(rock, 0, size); rock->kvno = kvno; memcpy(&rock->client, &client, sizeof(rock->client)); sconn->rock = rock; } return 0; }
static long RunCallTest(struct clientParms *parms, long host, struct rx_securityClass *sc, long si) { long code; #ifndef rx_GetPacketCksum code = RXKST_BADARGS; afs_com_err(whoami, code, "Older versions of Rx don't support Get/Set callNumber Vector procedures: can't run this CallTest"); return code; #else int i, ch; struct rx_connection *conn; long firstCall; afs_int32 callNumbers[RX_MAXCALLS]; long codes[RX_MAXCALLS]; long retCode = 0; /* ret. if nothing fatal goes wrong */ conn = rx_NewConnection(host, htons(RXKST_SERVICEPORT), RXKST_SERVICEID, sc, si); if (!conn) return RXKST_NEWCONNFAILED; /* First check the basic behaviour of call number handling */ code = rxi_GetCallNumberVector(conn, callNumbers); if (code) return code; for (i = 0; i < RX_MAXCALLS; i++) { if (callNumbers[i] != 0) { fprintf(stderr, "Connection's initial call numbers not zero. call[%d] = %d\n", i, callNumbers[i]); return RXKST_BADCALLNUMBERS; } } code = FastCall(conn); if (code) return code; code = rxi_GetCallNumberVector(conn, callNumbers); if (code) return code; firstCall = callNumbers[0]; code = FastCall(conn); if (code) return code; code = rxi_GetCallNumberVector(conn, callNumbers); if (code) return code; if ((callNumbers[0] != firstCall + 1) && ((firstCall == 1) || (firstCall == 2))) { /* The call number after the first call should be one or, more likely, * two (if the call is still DALLYing). Between first and second call, * the call number should have incremented by one. */ fprintf(stderr, "Connection's first channel call number not one. call[%d] = %d\n", 0, callNumbers[0]); return RXKST_BADCALLNUMBERS; } for (i = 1; i < RX_MAXCALLS; i++) { if (callNumbers[i] != 0) { fprintf(stderr, "Connection's other channel call numbers not zero. call[%d] = %d\n", i, callNumbers[i]); return RXKST_BADCALLNUMBERS; } } code = MakeMultiChannelCall(conn, 1, 0, codes); if (code) return code; /* Now try to resend a call that's already been executed by finding a * non-zero call number on a channel other than zero and decrementing it by * one. This should appear to the server as a retransmitted call. Since * this is behaving as a broken client different strange behaviors may be * exhibited by different servers. If the response packet to the original * call is discarded by the time the "retransmitted" call arrives (perhaps * due to high server or client load) there is no way for the server to * respond at all. Further, it seems, that under some cases the connection * will be kept alive indefinitely even though the server has discarded the * "retransmitted" call and is making no effort to reexecute the call. To * handle these, accept either a timeout (-1) or and INCFAILED error here, * also set the connenction HardDeadTime to punt after a reasonable * interval. */ /* short dead time since may we expect some trouble */ rx_SetConnHardDeadTime(conn, 30); code = rxi_GetCallNumberVector(conn, callNumbers); if (code) return code; for (ch = 1; ch < RX_MAXCALLS; ch++) if (callNumbers[ch] > 1) { callNumbers[ch]--; code = rxi_SetCallNumberVector(conn, callNumbers); if (code) return code; break; } if (ch >= RX_MAXCALLS) /* didn't find any? all DALLYing? */ return RXKST_BADCALLNUMBERS; code = MakeMultiChannelCall(conn, 1, RXKST_INCFAILED, codes); code = CheckCallFailure(conn, codes, code, "retransmitted call"); if (code && !retCode) retCode = code; /* Get a fresh connection, becasue if the above failed as it should the * connection is dead. */ rx_DestroyConnection(conn); conn = rx_NewConnection(host, htons(RXKST_SERVICEPORT), RXKST_SERVICEID, sc, si); if (!conn) return RXKST_NEWCONNFAILED; /* Similarly, but decrement call number by two which should be completely * unmistakeable as a broken or malicious client. */ /* short dead time since may we expect some trouble */ rx_SetConnHardDeadTime(conn, 30); code = MakeMultiChannelCall(conn, 2, 0, codes); if (code) return code; code = rxi_GetCallNumberVector(conn, callNumbers); if (code) return code; for (ch = 1; ch < RX_MAXCALLS; ch++) if (callNumbers[ch] > 2) { callNumbers[ch] -= 2; code = rxi_SetCallNumberVector(conn, callNumbers); break; } if (ch >= RX_MAXCALLS) /* didn't find any? all DALLYing? */ return RXKST_BADCALLNUMBERS; code = MakeMultiChannelCall(conn, 1, -1, codes); code = CheckCallFailure(conn, codes, code, "duplicate call"); if (code && !retCode) retCode = code; rx_DestroyConnection(conn); conn = rx_NewConnection(host, htons(RXKST_SERVICEPORT), RXKST_SERVICEID, sc, si); if (!conn) return RXKST_NEWCONNFAILED; /* Next, without waiting for the server to discard its state, we will check * to see if the Challenge/Response protocol correctly informs the server * of the client's callNumber state. We do this by artificially increasing * the call numbers of a new connection for all channels beyond zero, * making a call on channel zero, then resetting the call number for the * unused channels back to zero, then making calls on all channels. */ code = rxi_GetCallNumberVector(conn, callNumbers); if (code) return code; for (i = 0; i < RX_MAXCALLS; i++) { if (callNumbers[i] != 0) return RXKST_BADCALLNUMBERS; callNumbers[i] = 51; /* an arbitrary value... */ } code = rxi_SetCallNumberVector(conn, callNumbers); if (code) return code; code = FastCall(conn); /* use channel 0 */ if (code) return code; code = rxi_GetCallNumberVector(conn, callNumbers); if (code) return code; if (callNumbers[0] != 52) return RXKST_BADCALLNUMBERS; for (i = 1; i < RX_MAXCALLS; i++) { if (callNumbers[i] != 51) return RXKST_BADCALLNUMBERS; callNumbers[i] = 37; /* back up a ways */ } code = rxi_SetCallNumberVector(conn, callNumbers); if (code) return code; /* now try calls on all channels... */ code = MakeMultiChannelCall(conn, 1, -1, codes); code = CheckCallFailure(conn, codes, code, "alternate channel call replay"); if (code && !retCode) retCode = code; rx_DestroyConnection(conn); return retCode; #endif /* rx_GetPacketCksum */ }