rxkad-krb5-improvements-20060222
[openafs.git] / src / rxkad / rxkad_server.c
1 /*
2  * Copyright 2000, International Business Machines Corporation and others.
3  * All Rights Reserved.
4  * 
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
8  */
9
10 /* The rxkad security object.  Authentication using a DES-encrypted
11  * Kerberos-style ticket.  These are the server-only routines. */
12
13
14 #include <afsconfig.h>
15 #include <afs/param.h>
16
17 RCSID
18     ("$Header$");
19
20 #include <afs/stds.h>
21 #include <sys/types.h>
22 #include <time.h>
23 #ifdef AFS_NT40_ENV
24 #include <winsock2.h>
25 #else
26 #include <netinet/in.h>
27 #endif
28 #ifdef HAVE_STRING_H
29 #include <string.h>
30 #else
31 #ifdef HAVE_STRINGS_H
32 #include <strings.h>
33 #endif
34 #endif
35 #include <rx/rx.h>
36 #include <rx/xdr.h>
37 #include <des.h>
38 #include <afs/afsutil.h>
39 #include <des/stats.h>
40 #include "private_data.h"
41 #define XPRT_RXKAD_SERVER
42
43 /*
44  * This can be set to allow alternate ticket decoding.
45  * Currently only used by the AFS/DFS protocol translator to recognize
46  * Kerberos V5 tickets. The actual code to do that is provided externally.
47  */
48 afs_int32(*rxkad_AlternateTicketDecoder) ();
49
50 static struct rx_securityOps rxkad_server_ops = {
51     rxkad_Close,
52     rxkad_NewConnection,
53     rxkad_PreparePacket,        /* once per packet creation */
54     0,                          /* send packet (once per retrans) */
55     rxkad_CheckAuthentication,
56     rxkad_CreateChallenge,
57     rxkad_GetChallenge,
58     0,
59     rxkad_CheckResponse,
60     rxkad_CheckPacket,          /* check data packet */
61     rxkad_DestroyConnection,
62     rxkad_GetStats,
63     0,                          /* spare 1 */
64     0,                          /* spare 2 */
65     0,                          /* spare 3 */
66 };
67 extern afs_uint32 rx_MyMaxSendSize;
68
69 /* Miscellaneous random number routines that use the fcrypt module and the
70  * timeofday. */
71
72 static fc_KeySchedule random_int32_schedule;
73
74 #ifdef AFS_PTHREAD_ENV
75 /*
76  * This mutex protects the following global variables:
77  * random_int32_schedule
78  * seed
79  */
80
81 #include <assert.h>
82 pthread_mutex_t rxkad_random_mutex;
83 #define LOCK_RM assert(pthread_mutex_lock(&rxkad_random_mutex)==0)
84 #define UNLOCK_RM assert(pthread_mutex_unlock(&rxkad_random_mutex)==0)
85 #else
86 #define LOCK_RM
87 #define UNLOCK_RM
88 #endif /* AFS_PTHREAD_ENV */
89
90 static void
91 init_random_int32(void)
92 {
93     struct timeval key;
94
95     gettimeofday(&key, NULL);
96     LOCK_RM;
97     fc_keysched((struct ktc_encryptionKey*)&key, random_int32_schedule);
98     UNLOCK_RM;
99 }
100
101 static afs_int32
102 get_random_int32(void)
103 {
104     static struct timeval seed;
105     afs_int32 rc;
106
107     LOCK_RM;
108     fc_ecb_encrypt(&seed, &seed, random_int32_schedule, ENCRYPT);
109     rc = seed.tv_sec;
110     UNLOCK_RM;
111     return rc;
112 }
113
114 /* Called with four parameters.  The first is the level of encryption, as
115    defined in the rxkad.h file.  The second and third are a rock and a
116    procedure that is called with the key version number that accompanies the
117    ticket and returns a pointer to the server's decryption key.  The fourth
118    argument, if not NULL, is a pointer to a function that will be called for
119    every new connection with the name, instance and cell of the client.  The
120    routine should return zero if the user is NOT acceptible to the server.  If
121    this routine is not supplied, the server can call rxkad_GetServerInfo with
122    the rx connection pointer passed to the RPC routine to obtain information
123    about the client. */
124
125 /*
126   rxkad_level      level;               * minimum level *
127   char            *get_key_rock;        * rock for get_key implementor *
128   int            (*get_key)();          * passed kvno & addr(key) to fill *
129   int            (*user_ok)();          * passed name, inst, cell => bool *
130 */
131
132 struct rx_securityClass *
133 rxkad_NewServerSecurityObject(rxkad_level level, char *get_key_rock,
134                               int (*get_key) (char *get_key_rock, int kvno,
135                                               struct ktc_encryptionKey *
136                                               serverKey),
137                               int (*user_ok) (char *name, char *instance,
138                                               char *cell, afs_int32 kvno))
139 {
140     struct rx_securityClass *tsc;
141     struct rxkad_sprivate *tsp;
142     int size;
143
144     if (!get_key)
145         return 0;
146
147     size = sizeof(struct rx_securityClass);
148     tsc = (struct rx_securityClass *)osi_Alloc(size);
149     memset(tsc, 0, size);
150     tsc->refCount = 1;          /* caller has one reference */
151     tsc->ops = &rxkad_server_ops;
152     size = sizeof(struct rxkad_sprivate);
153     tsp = (struct rxkad_sprivate *)osi_Alloc(size);
154     memset(tsp, 0, size);
155     tsc->privateData = (char *)tsp;
156
157     tsp->type |= rxkad_server;  /* so can identify later */
158     tsp->level = level;         /* level of encryption */
159     tsp->get_key_rock = get_key_rock;
160     tsp->get_key = get_key;     /* to get server ticket */
161     tsp->user_ok = user_ok;     /* to inform server of client id. */
162     init_random_int32();
163
164     INC_RXKAD_STATS(serverObjects);
165     return tsc;
166 }
167
168 /* server: called to tell if a connection authenticated properly */
169
170 int
171 rxkad_CheckAuthentication(struct rx_securityClass *aobj,
172                           struct rx_connection *aconn)
173 {
174     struct rxkad_sconn *sconn;
175
176     /* first make sure the object exists */
177     if (!aconn->securityData)
178         return RXKADINCONSISTENCY;
179
180     sconn = (struct rxkad_sconn *)aconn->securityData;
181     return !sconn->authenticated;
182 }
183
184 /* server: put the current challenge in the connection structure for later use
185    by packet sender */
186
187 int
188 rxkad_CreateChallenge(struct rx_securityClass *aobj,
189                       struct rx_connection *aconn)
190 {
191     struct rxkad_sconn *sconn;
192     struct rxkad_sprivate *tsp;
193
194     sconn = (struct rxkad_sconn *)aconn->securityData;
195     sconn->challengeID = get_random_int32();
196     sconn->authenticated = 0;   /* conn unauth. 'til we hear back */
197     /* initialize level from object's minimum acceptable level */
198     tsp = (struct rxkad_sprivate *)aobj->privateData;
199     sconn->level = tsp->level;
200     return 0;
201 }
202
203 /* server: fill in a challenge in the packet */
204
205 int
206 rxkad_GetChallenge(struct rx_securityClass *aobj, struct rx_connection *aconn,
207                    struct rx_packet *apacket)
208 {
209     struct rxkad_sconn *sconn;
210     char *challenge;
211     int challengeSize;
212     struct rxkad_v2Challenge c_v2;      /* version 2 */
213     struct rxkad_oldChallenge c_old;    /* old style */
214
215     sconn = (struct rxkad_sconn *)aconn->securityData;
216     if (rx_IsUsingPktCksum(aconn))
217         sconn->cksumSeen = 1;
218
219     if (sconn->cksumSeen) {
220         memset(&c_v2, 0, sizeof(c_v2));
221         c_v2.version = htonl(RXKAD_CHALLENGE_PROTOCOL_VERSION);
222         c_v2.challengeID = htonl(sconn->challengeID);
223         c_v2.level = htonl((afs_int32) sconn->level);
224         c_v2.spare = 0;
225         challenge = (char *)&c_v2;
226         challengeSize = sizeof(c_v2);
227     } else {
228         memset(&c_old, 0, sizeof(c_old));
229         c_old.challengeID = htonl(sconn->challengeID);
230         c_old.level = htonl((afs_int32) sconn->level);
231         challenge = (char *)&c_old;
232         challengeSize = sizeof(c_old);
233     }
234     if (rx_MyMaxSendSize < challengeSize)
235         return RXKADPACKETSHORT;        /* not enough space */
236
237     rx_packetwrite(apacket, 0, challengeSize, challenge);
238     rx_SetDataSize(apacket, challengeSize);
239     sconn->tried = 1;
240     INC_RXKAD_STATS(challengesSent);
241     return 0;
242 }
243
244 /* server: process a response to a challenge packet */
245 /* XXX this does some copying of data in and out of the packet, but I'll bet it
246  * could just do it in place, especially if I used rx_Pullup...
247  */
248 int
249 rxkad_CheckResponse(struct rx_securityClass *aobj,
250                     struct rx_connection *aconn, struct rx_packet *apacket)
251 {
252     struct rxkad_sconn *sconn;
253     struct rxkad_sprivate *tsp;
254     struct ktc_encryptionKey serverKey;
255     struct rxkad_oldChallengeResponse oldr;     /* response format */
256     struct rxkad_v2ChallengeResponse v2r;
257     afs_int32 tlen;             /* ticket len */
258     afs_int32 kvno;             /* key version of ticket */
259     char tix[MAXKTCTICKETLEN];
260     afs_int32 incChallengeID;
261     rxkad_level level;
262     int code;
263     /* ticket contents */
264     struct ktc_principal client;
265     struct ktc_encryptionKey sessionkey;
266     afs_int32 host;
267     afs_uint32 start;
268     afs_uint32 end;
269     unsigned int pos;
270     struct rxkad_serverinfo *rock;
271
272     sconn = (struct rxkad_sconn *)aconn->securityData;
273     tsp = (struct rxkad_sprivate *)aobj->privateData;
274
275     if (sconn->cksumSeen) {
276         /* expect v2 response, leave fields in v2r in network order for cksum
277          * computation which follows decryption. */
278         if (rx_GetDataSize(apacket) < sizeof(v2r))
279             return RXKADPACKETSHORT;
280         rx_packetread(apacket, 0, sizeof(v2r), &v2r);
281         pos = sizeof(v2r);
282         /* version == 2 */
283         /* ignore spare */
284         kvno = ntohl(v2r.kvno);
285         tlen = ntohl(v2r.ticketLen);
286         if (rx_GetDataSize(apacket) < sizeof(v2r) + tlen)
287             return RXKADPACKETSHORT;
288     } else {
289         /* expect old format response */
290         if (rx_GetDataSize(apacket) < sizeof(oldr))
291             return RXKADPACKETSHORT;
292         rx_packetread(apacket, 0, sizeof(oldr), &oldr);
293         pos = sizeof(oldr);
294
295         kvno = ntohl(oldr.kvno);
296         tlen = ntohl(oldr.ticketLen);
297         if (rx_GetDataSize(apacket) != sizeof(oldr) + tlen)
298             return RXKADPACKETSHORT;
299     }
300     if ((tlen < MINKTCTICKETLEN) || (tlen > MAXKTCTICKETLEN))
301         return RXKADTICKETLEN;
302
303     rx_packetread(apacket, pos, tlen, tix);     /* get ticket */
304
305     /*
306      * We allow the ticket to be optionally decoded by an alternate
307      * ticket decoder, if the function variable
308      * rxkad_AlternateTicketDecoder is set. That function should
309      * return a code of -1 if it wants the ticket to be decoded by
310      * the standard decoder.
311      */
312     if (rxkad_AlternateTicketDecoder) {
313         code =
314             rxkad_AlternateTicketDecoder(kvno, tix, tlen, client.name,
315                                          client.instance, client.cell,
316                                          &sessionkey, &host, &start, &end);
317         if (code && code != -1) {
318             return code;
319         }
320     } else {
321         code = -1;              /* No alternate ticket decoder present */
322     }
323
324     /*
325      * If the alternate decoder is not present, or returns -1, then
326      * assume the ticket is of the default style.
327      */
328     if (code == -1 && ((kvno == RXKAD_TKT_TYPE_KERBEROS_V5)
329         || (kvno == RXKAD_TKT_TYPE_KERBEROS_V5_ENCPART_ONLY))) {
330         code =
331             tkt_DecodeTicket5(tix, tlen, tsp->get_key, tsp->get_key_rock,
332                               kvno, client.name, client.instance, client.cell,
333                               &sessionkey, &host, &start, &end);
334         if (code)
335             return code;
336     }
337
338     /*
339      * If the alternate decoder/kerberos 5 decoder is not present, or
340      * returns -1, then assume the ticket is of the default style.
341      */
342     if (code == -1) {
343         /* get ticket's key */
344         code = (*tsp->get_key) (tsp->get_key_rock, kvno, &serverKey);
345         if (code)
346             return RXKADUNKNOWNKEY;     /* invalid kvno */
347         code =
348             tkt_DecodeTicket(tix, tlen, &serverKey, client.name,
349                              client.instance, client.cell, &sessionkey, &host,
350                              &start, &end);
351         if (code)
352             return RXKADBADTICKET;
353     }
354     code = tkt_CheckTimes(start, end, time(0));
355     if (code == -1)
356         return RXKADEXPIRED;
357     else if (code <= 0)
358         return RXKADNOAUTH;
359
360     code = fc_keysched(&sessionkey, sconn->keysched);
361     if (code)
362         return RXKADBADKEY;
363     memcpy(sconn->ivec, &sessionkey, sizeof(sconn->ivec));
364
365     if (sconn->cksumSeen) {
366         /* using v2 response */
367         afs_uint32 cksum;       /* observed cksum */
368         struct rxkad_endpoint endpoint; /* connections endpoint */
369         int i;
370         afs_uint32 xor[2];
371
372         memcpy(xor, sconn->ivec, 2 * sizeof(afs_int32));
373         fc_cbc_encrypt(&v2r.encrypted, &v2r.encrypted, sizeof(v2r.encrypted),
374                        sconn->keysched, xor, DECRYPT);
375         cksum = rxkad_CksumChallengeResponse(&v2r);
376         if (cksum != v2r.encrypted.endpoint.cksum)
377             return RXKADSEALEDINCON;
378         (void)rxkad_SetupEndpoint(aconn, &endpoint);
379         v2r.encrypted.endpoint.cksum = 0;
380         if (memcmp(&endpoint, &v2r.encrypted.endpoint, sizeof(endpoint)) != 0)
381             return RXKADSEALEDINCON;
382         for (i = 0; i < RX_MAXCALLS; i++) {
383             v2r.encrypted.callNumbers[i] =
384                 ntohl(v2r.encrypted.callNumbers[i]);
385             if (v2r.encrypted.callNumbers[i] < 0)
386                 return RXKADSEALEDINCON;
387         }
388
389         (void)rxi_SetCallNumberVector(aconn, v2r.encrypted.callNumbers);
390         incChallengeID = ntohl(v2r.encrypted.incChallengeID);
391         level = ntohl(v2r.encrypted.level);
392     } else {
393         /* expect old format response */
394         fc_ecb_encrypt(&oldr.encrypted, &oldr.encrypted, sconn->keysched,
395                        DECRYPT);
396         incChallengeID = ntohl(oldr.encrypted.incChallengeID);
397         level = ntohl(oldr.encrypted.level);
398     }
399     if (incChallengeID != sconn->challengeID + 1)
400         return RXKADOUTOFSEQUENCE;      /* replay attempt */
401     if ((level < sconn->level) || (level > rxkad_crypt))
402         return RXKADLEVELFAIL;
403     sconn->level = level;
404     rxkad_SetLevel(aconn, sconn->level);
405     INC_RXKAD_STATS(responses[rxkad_LevelIndex(sconn->level)]);
406     /* now compute endpoint-specific info used for computing 16 bit checksum */
407     rxkad_DeriveXORInfo(aconn, sconn->keysched, sconn->ivec, sconn->preSeq);
408
409     /* otherwise things are ok */
410     sconn->expirationTime = end;
411     sconn->authenticated = 1;
412
413     if (tsp->user_ok) {
414         code = tsp->user_ok(client.name, client.instance, client.cell, kvno);
415         if (code)
416             return RXKADNOAUTH;
417     } else {                    /* save the info for later retreival */
418         int size = sizeof(struct rxkad_serverinfo);
419         rock = (struct rxkad_serverinfo *)osi_Alloc(size);
420         memset(rock, 0, size);
421         rock->kvno = kvno;
422         memcpy(&rock->client, &client, sizeof(rock->client));
423         sconn->rock = rock;
424     }
425     return 0;
426 }
427
428 /* return useful authentication info about a server-side connection */
429
430 afs_int32
431 rxkad_GetServerInfo(struct rx_connection * aconn, rxkad_level * level,
432                     afs_uint32 * expiration, char *name, char *instance,
433                     char *cell, afs_int32 * kvno)
434 {
435     struct rxkad_sconn *sconn;
436
437     sconn = (struct rxkad_sconn *)aconn->securityData;
438     if (sconn && sconn->authenticated && sconn->rock
439         && (time(0) < sconn->expirationTime)) {
440         if (level)
441             *level = sconn->level;
442         if (expiration)
443             *expiration = sconn->expirationTime;
444         if (name)
445             strcpy(name, sconn->rock->client.name);
446         if (instance)
447             strcpy(instance, sconn->rock->client.instance);
448         if (cell)
449             strcpy(cell, sconn->rock->client.cell);
450         if (kvno)
451             *kvno = sconn->rock->kvno;
452         return 0;
453     } else
454         return RXKADNOAUTH;
455 }