7a1c291283bbb32386c1ccb201c4db1f91231974
[dragonfly.git] / lib / libc / rpc / svc_auth_des.c
1
2 /*
3  * Copyright (c) 1988 by Sun Microsystems, Inc.
4  *
5  * @(#)svcauth_des.c    2.3 89/07/11 4.0 RPCSRC; from 1.15 88/02/08 SMI
6  * $FreeBSD: src/lib/libc/rpc/svc_auth_des.c,v 1.9 2002/03/22 23:18:37 obrien Exp $
7  */
8
9 /*-
10  * Copyright (c) 2009, Sun Microsystems, Inc.
11  * All rights reserved.
12  *
13  * Redistribution and use in source and binary forms, with or without 
14  * modification, are permitted provided that the following conditions are met:
15  * - Redistributions of source code must retain the above copyright notice, 
16  *   this list of conditions and the following disclaimer.
17  * - Redistributions in binary form must reproduce the above copyright notice, 
18  *   this list of conditions and the following disclaimer in the documentation 
19  *   and/or other materials provided with the distribution.
20  * - Neither the name of Sun Microsystems, Inc. nor the names of its 
21  *   contributors may be used to endorse or promote products derived 
22  *   from this software without specific prior written permission.
23  * 
24  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
25  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
26  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
27  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
28  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
29  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
30  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
31  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
32  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
33  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
34  * POSSIBILITY OF SUCH DAMAGE.
35  */
36
37 /*
38  * svcauth_des.c, server-side des authentication
39  *
40  * We insure for the service the following:
41  * (1) The timestamp microseconds do not exceed 1 million.
42  * (2) The timestamp plus the window is less than the current time.
43  * (3) The timestamp is not less than the one previously
44  *     seen in the current session.
45  *
46  * It is up to the server to determine if the window size is
47  * too small .
48  *
49  */
50
51 #include "namespace.h"
52 #include "reentrant.h"
53 #include <string.h>
54 #include <stdlib.h>
55 #include <stdio.h>
56 #include <unistd.h>
57 #include <rpc/des_crypt.h>
58 #include <sys/param.h>
59 #include <netinet/in.h>
60 #include <rpc/types.h>
61 #include <rpc/xdr.h>
62 #include <rpc/auth.h>
63 #include <rpc/auth_des.h>
64 #include <rpc/svc.h>
65 #include <rpc/rpc_msg.h>
66 #include <rpc/svc_auth.h>
67 #include "libc_private.h"
68
69 extern int key_decryptsession_pk(const char *, netobj *, des_block *);
70
71 #define debug(msg)       printf("svcauth_des: %s\n", msg) 
72
73 #define USEC_PER_SEC ((u_long) 1000000L)
74 #define BEFORE(t1, t2) timercmp(t1, t2, <)
75
76 /*
77  * LRU cache of conversation keys and some other useful items.
78  */
79 #define AUTHDES_CACHESZ 64
80 struct cache_entry {
81         des_block key;          /* conversation key */
82         char *rname;            /* client's name */
83         u_int window;           /* credential lifetime window */
84         struct timeval laststamp;       /* detect replays of creds */
85         char *localcred;        /* generic local credential */
86 };
87 static struct cache_entry *authdes_cache/* [AUTHDES_CACHESZ] */;
88 static short *authdes_lru/* [AUTHDES_CACHESZ] */;
89
90 static void cache_init(void);   /* initialize the cache */
91 static short cache_spot(des_block *, char *, struct timeval *);
92                                 /* find an entry in the cache */
93 static void cache_ref(short sid);       /* note that sid was ref'd */
94
95 static void invalidate(char *); /* invalidate entry in cache */
96
97 /*
98  * cache statistics 
99  */
100 static struct {
101         u_long ncachehits;      /* times cache hit, and is not replay */
102         u_long ncachereplays;   /* times cache hit, and is replay */
103         u_long ncachemisses;    /* times cache missed */
104 } svcauthdes_stats;
105
106 /*
107  * Service side authenticator for AUTH_DES
108  */
109 enum auth_stat
110 _svcauth_des(struct svc_req *rqst, struct rpc_msg *msg)
111 {
112
113         long *ixdr;
114         des_block cryptbuf[2];
115         struct authdes_cred *cred;
116         struct authdes_verf verf;
117         int status;
118         struct cache_entry *entry;
119         short sid = 0;
120         des_block *sessionkey;
121         des_block ivec;
122         u_int window;
123         struct timeval timestamp;
124         u_long namelen;
125         struct area {
126                 struct authdes_cred area_cred;
127                 char area_netname[MAXNETNAMELEN+1];
128         } *area;
129
130         if (authdes_cache == NULL) {
131                 cache_init();
132         }
133
134         area = (struct area *)rqst->rq_clntcred;
135         cred = (struct authdes_cred *)&area->area_cred;
136
137         /*
138          * Get the credential
139          */
140         ixdr = (long *)msg->rm_call.cb_cred.oa_base;
141         cred->adc_namekind = IXDR_GET_ENUM(ixdr, enum authdes_namekind);
142         switch (cred->adc_namekind) {
143         case ADN_FULLNAME:
144                 namelen = IXDR_GET_U_LONG(ixdr);
145                 if (namelen > MAXNETNAMELEN) {
146                         return (AUTH_BADCRED);
147                 }
148                 cred->adc_fullname.name = area->area_netname;
149                 bcopy((char *)ixdr, cred->adc_fullname.name, 
150                         (u_int)namelen);
151                 cred->adc_fullname.name[namelen] = 0;
152                 ixdr += (RNDUP(namelen) / BYTES_PER_XDR_UNIT);
153                 cred->adc_fullname.key.key.high = (u_long)*ixdr++;
154                 cred->adc_fullname.key.key.low = (u_long)*ixdr++;
155                 cred->adc_fullname.window = (u_long)*ixdr++;
156                 break;
157         case ADN_NICKNAME:
158                 cred->adc_nickname = (u_long)*ixdr++;
159                 break;
160         default:
161                 return (AUTH_BADCRED);  
162         }
163
164         /*
165          * Get the verifier
166          */
167         ixdr = (long *)msg->rm_call.cb_verf.oa_base;
168         verf.adv_xtimestamp.key.high = (u_long)*ixdr++;
169         verf.adv_xtimestamp.key.low =  (u_long)*ixdr++;
170         verf.adv_int_u = (u_long)*ixdr++;
171
172
173         /*
174          * Get the conversation key
175          */
176         if (cred->adc_namekind == ADN_FULLNAME) {
177                 netobj pkey;
178                 char pkey_data[1024];
179
180                 sessionkey = &cred->adc_fullname.key;
181                 if (! getpublickey(cred->adc_fullname.name, pkey_data)) {
182                         debug("getpublickey");
183                         return(AUTH_BADCRED);
184                 }
185                 pkey.n_bytes = pkey_data;
186                 pkey.n_len = strlen(pkey_data) + 1;
187                 if (key_decryptsession_pk(cred->adc_fullname.name, &pkey,
188                                        sessionkey) < 0) {
189                         debug("decryptsessionkey");
190                         return (AUTH_BADCRED); /* key not found */
191                 }
192         } else { /* ADN_NICKNAME */     
193                 sid = (short)cred->adc_nickname;
194                 if (sid < 0 || sid >= AUTHDES_CACHESZ) {
195                         debug("bad nickname");
196                         return (AUTH_BADCRED);  /* garbled credential */
197                 }
198                 sessionkey = &authdes_cache[sid].key;
199         }
200
201
202         /*
203          * Decrypt the timestamp
204          */
205         cryptbuf[0] = verf.adv_xtimestamp; 
206         if (cred->adc_namekind == ADN_FULLNAME) {
207                 cryptbuf[1].key.high = cred->adc_fullname.window;
208                 cryptbuf[1].key.low = verf.adv_winverf;
209                 ivec.key.high = ivec.key.low = 0;       
210                 status = cbc_crypt((char *)sessionkey, (char *)cryptbuf,
211                         2*sizeof(des_block), DES_DECRYPT | DES_HW, 
212                         (char *)&ivec);
213         } else {
214                 status = ecb_crypt((char *)sessionkey, (char *)cryptbuf,
215                         sizeof(des_block), DES_DECRYPT | DES_HW);
216         }
217         if (DES_FAILED(status)) {
218                 debug("decryption failure");
219                 return (AUTH_FAILED);   /* system error */
220         }
221
222         /*
223          * XDR the decrypted timestamp
224          */
225         ixdr = (long *)cryptbuf;
226         timestamp.tv_sec = IXDR_GET_LONG(ixdr);
227         timestamp.tv_usec = IXDR_GET_LONG(ixdr);
228
229         /*
230          * Check for valid credentials and verifiers.
231          * They could be invalid because the key was flushed
232          * out of the cache, and so a new session should begin.
233          * Be sure and send AUTH_REJECTED{CRED, VERF} if this is the case.
234          */
235         {
236                 struct timeval current;
237                 int nick;
238                 int winverf;
239
240                 if (cred->adc_namekind == ADN_FULLNAME) {
241                         window = IXDR_GET_U_LONG(ixdr);
242                         winverf = IXDR_GET_U_LONG(ixdr);
243                         if (winverf != window - 1) {
244                                 debug("window verifier mismatch");
245                                 return (AUTH_BADCRED);  /* garbled credential */
246                         }
247                         sid = cache_spot(sessionkey, cred->adc_fullname.name, 
248                             &timestamp);
249                         if (sid < 0) {
250                                 debug("replayed credential");
251                                 return (AUTH_REJECTEDCRED);     /* replay */
252                         }
253                         nick = 0;
254                 } else {        /* ADN_NICKNAME */
255                         window = authdes_cache[sid].window;
256                         nick = 1;
257                 }
258
259                 if ((u_long)timestamp.tv_usec >= USEC_PER_SEC) {
260                         debug("invalid usecs");
261                         /* cached out (bad key), or garbled verifier */
262                         return (nick ? AUTH_REJECTEDVERF : AUTH_BADVERF);
263                 }
264                 if (nick && BEFORE(&timestamp, 
265                                    &authdes_cache[sid].laststamp)) {
266                         debug("timestamp before last seen");
267                         return (AUTH_REJECTEDVERF);     /* replay */
268                 }
269                 gettimeofday(&current, NULL);
270                 current.tv_sec -= window;       /* allow for expiration */
271                 if (!BEFORE(&current, &timestamp)) {
272                         debug("timestamp expired");
273                         /* replay, or garbled credential */
274                         return (nick ? AUTH_REJECTEDVERF : AUTH_BADCRED);
275                 }
276         }
277
278         /*
279          * Set up the reply verifier
280          */
281         verf.adv_nickname = (u_long)sid;
282
283         /*
284          * xdr the timestamp before encrypting
285          */
286         ixdr = (long *)cryptbuf;
287         IXDR_PUT_LONG(ixdr, timestamp.tv_sec - 1);
288         IXDR_PUT_LONG(ixdr, timestamp.tv_usec);
289
290         /*       
291          * encrypt the timestamp
292          */
293         status = ecb_crypt((char *)sessionkey, (char *)cryptbuf,
294             sizeof(des_block), DES_ENCRYPT | DES_HW);
295         if (DES_FAILED(status)) {
296                 debug("encryption failure");
297                 return (AUTH_FAILED);   /* system error */
298         }
299         verf.adv_xtimestamp = cryptbuf[0];
300
301         /*
302          * Serialize the reply verifier, and update rqst
303          */
304         ixdr = (long *)msg->rm_call.cb_verf.oa_base;
305         *ixdr++ = (long)verf.adv_xtimestamp.key.high;
306         *ixdr++ = (long)verf.adv_xtimestamp.key.low;
307         *ixdr++ = (long)verf.adv_int_u;
308
309         rqst->rq_xprt->xp_verf.oa_flavor = AUTH_DES;
310         rqst->rq_xprt->xp_verf.oa_base = msg->rm_call.cb_verf.oa_base;
311         rqst->rq_xprt->xp_verf.oa_length = 
312                 (char *)ixdr - msg->rm_call.cb_verf.oa_base;
313
314         /*
315          * We succeeded, commit the data to the cache now and
316          * finish cooking the credential.
317          */
318         entry = &authdes_cache[sid];
319         entry->laststamp = timestamp;
320         cache_ref(sid);
321         if (cred->adc_namekind == ADN_FULLNAME) {
322                 cred->adc_fullname.window = window;
323                 cred->adc_nickname = (u_long)sid;       /* save nickname */
324                 if (entry->rname != NULL) {
325                         mem_free(entry->rname, strlen(entry->rname) + 1);
326                 }
327                 entry->rname = (char *)mem_alloc((u_int)strlen(cred->adc_fullname.name)
328                                          + 1);
329                 if (entry->rname != NULL) {
330                         strcpy(entry->rname, cred->adc_fullname.name);
331                 } else {
332                         debug("out of memory");
333                 }
334                 entry->key = *sessionkey;
335                 entry->window = window;
336                 invalidate(entry->localcred); /* mark any cached cred invalid */
337         } else { /* ADN_NICKNAME */
338                 /*
339                  * nicknames are cooked into fullnames
340                  */     
341                 cred->adc_namekind = ADN_FULLNAME;
342                 cred->adc_fullname.name = entry->rname;
343                 cred->adc_fullname.key = entry->key;
344                 cred->adc_fullname.window = entry->window;
345         }
346         return (AUTH_OK);       /* we made it!*/
347 }
348
349
350 /*
351  * Initialize the cache
352  */
353 static void
354 cache_init(void)
355 {
356         int i;
357
358         authdes_cache = (struct cache_entry *)
359                 mem_alloc(sizeof(struct cache_entry) * AUTHDES_CACHESZ);        
360         bzero((char *)authdes_cache, 
361                 sizeof(struct cache_entry) * AUTHDES_CACHESZ);
362
363         authdes_lru = (short *)mem_alloc(sizeof(short) * AUTHDES_CACHESZ);
364         /*
365          * Initialize the lru list
366          */
367         for (i = 0; i < AUTHDES_CACHESZ; i++) {
368                 authdes_lru[i] = i;
369         }
370 }
371
372
373 /*
374  * Find the lru victim
375  */
376 static short
377 cache_victim(void)
378 {
379         return (authdes_lru[AUTHDES_CACHESZ-1]);
380 }
381
382 /*
383  * Note that sid was referenced
384  */
385 static void
386 cache_ref(short sid)
387 {
388         int i;
389         short curr;
390         short prev;
391
392         prev = authdes_lru[0];
393         authdes_lru[0] = sid;
394         for (i = 1; prev != sid; i++) {
395                 curr = authdes_lru[i];
396                 authdes_lru[i] = prev;
397                 prev = curr;
398         }
399 }
400
401
402 /*
403  * Find a spot in the cache for a credential containing
404  * the items given.  Return -1 if a replay is detected, otherwise
405  * return the spot in the cache.
406  */
407 static short
408 cache_spot(des_block *key, char *name, struct timeval *timestamp)
409 {
410         struct cache_entry *cp;
411         int i;
412         u_long hi;
413
414         hi = key->key.high;
415         for (cp = authdes_cache, i = 0; i < AUTHDES_CACHESZ; i++, cp++) {
416                 if (cp->key.key.high == hi && 
417                     cp->key.key.low == key->key.low &&
418                     cp->rname != NULL &&
419                     bcmp(cp->rname, name, strlen(name) + 1) == 0) {
420                         if (BEFORE(timestamp, &cp->laststamp)) {
421                                 svcauthdes_stats.ncachereplays++;
422                                 return (-1); /* replay */
423                         }
424                         svcauthdes_stats.ncachehits++;
425                         return (i);     /* refresh */
426                 }
427         }
428         svcauthdes_stats.ncachemisses++;
429         return (cache_victim());        /* new credential */
430 }
431
432
433 #if defined(sun) || defined(vax) || \
434     defined(__FreeBSD__) || defined(__DragonFly__)
435 /*
436  * Local credential handling stuff.
437  * NOTE: bsd unix dependent.
438  * Other operating systems should put something else here.
439  */
440 #define UNKNOWN         -2      /* grouplen, if cached cred is unknown user */
441 #define INVALID         -1      /* grouplen, if cache entry is invalid */
442
443 struct bsdcred {
444         short uid;              /* cached uid */
445         short gid;              /* cached gid */
446         short grouplen; /* length of cached groups */
447         short groups[NGROUPS];  /* cached groups */
448 };
449
450 /*
451  * Map a des credential into a unix cred.
452  * We cache the credential here so the application does
453  * not have to make an rpc call every time to interpret
454  * the credential.
455  */
456 int
457 authdes_getucred(struct authdes_cred *adc, uid_t *uid, gid_t *gid,
458                  int *grouplen, gid_t *groups)
459 {
460         unsigned sid;
461         int i;
462         uid_t i_uid;    
463         gid_t i_gid;
464         int i_grouplen;
465         struct bsdcred *cred;
466
467         sid = adc->adc_nickname;
468         if (sid >= AUTHDES_CACHESZ) {
469                 debug("invalid nickname");
470                 return (0);
471         }
472         cred = (struct bsdcred *)authdes_cache[sid].localcred;
473         if (cred == NULL) {
474                 cred = (struct bsdcred *)mem_alloc(sizeof(struct bsdcred));
475                 authdes_cache[sid].localcred = (char *)cred;
476                 cred->grouplen = INVALID;
477         }
478         if (cred->grouplen == INVALID) {
479                 /*
480                  * not in cache: lookup
481                  */
482                 if (!netname2user(adc->adc_fullname.name, &i_uid, &i_gid, 
483                         &i_grouplen, groups))
484                 {
485                         debug("unknown netname");
486                         cred->grouplen = UNKNOWN;       /* mark as lookup up, but not found */
487                         return (0);
488                 }
489                 debug("missed ucred cache");
490                 *uid = cred->uid = i_uid;
491                 *gid = cred->gid = i_gid;
492                 *grouplen = cred->grouplen = i_grouplen;
493                 for (i = i_grouplen - 1; i >= 0; i--) {
494                         cred->groups[i] = groups[i]; /* int to short */
495                 }
496                 return (1);
497         } else if (cred->grouplen == UNKNOWN) {
498                 /*
499                  * Already lookup up, but no match found
500                  */     
501                 return (0);
502         }
503
504         /*
505          * cached credentials
506          */
507         *uid = cred->uid;
508         *gid = cred->gid;
509         *grouplen = cred->grouplen;
510         for (i = cred->grouplen - 1; i >= 0; i--) {
511                 groups[i] = cred->groups[i];    /* short to int */
512         }
513         return (1);
514 }
515
516 static void
517 invalidate(char *cred)
518 {
519         if (cred == NULL) {
520                 return;
521         }
522         ((struct bsdcred *)cred)->grouplen = INVALID;
523 }
524 #endif
525