[891] in Kerberos-V5-bugs

home help back first fref pref prev next nref lref last post

v4 compatibility kdc doesn't deal properly with long lifetimes

daemon@ATHENA.MIT.EDU (John G. Myers)
Fri Oct 21 19:20:36 1994

Date: Fri, 21 Oct 1994 19:21:40 -0400 (EDT)
From: "John G. Myers" <jgm+@cmu.edu>
To: krb5-bugs@MIT.EDU

The v4 compatibility kdc doesn't know about the lookup table for
long v4 lifetime values.  If a user has a 24-hour lifetime limit, the v4 
side of the kdc will happily grant tgt's with infinite lifetime.

Patch fixes this.  lifetime.c and a long-lifetime-aware rd_req.c are 
included for folks who don't have the long-lifetime patch applied to 
their v4 library.

*** kerberos_v4.c~	Thu Sep 15 00:24:05 1994
--- kerberos_v4.c	Fri Oct 21 19:13:57 1994
***************
*** 595,601 ****
      krb5_boolean more5;		/* are there more? */
      C_Block k;
      short toggle = 0;
-     int v4_time;
      unsigned long *date;
      char* text;
      struct tm *tp;
--- 595,600 ----
***************
*** 659,666 ****
      /* convert v5's entries struct to v4's Principal struct:
       * v5's time-unit for lifetimes is 1 sec, while v4 uses 5 minutes.
       */
!     v4_time = (entries.max_life + MIN5 - 1) / MIN5;
!     principal->max_life = v4_time > HR21 ? HR21 : (unsigned char) v4_time;
      principal->exp_date = (unsigned long) entries.expiration;
      principal->mod_date = (unsigned long) entries.mod_date;
      principal->attributes = 0;
--- 658,664 ----
      /* convert v5's entries struct to v4's Principal struct:
       * v5's time-unit for lifetimes is 1 sec, while v4 uses 5 minutes.
       */
!     principal->max_life = krb_time_to_life(0, entries.max_life);
      principal->exp_date = (unsigned long) entries.expiration;
      principal->mod_date = (unsigned long) entries.mod_date;
      principal->attributes = 0;
***************
*** 938,944 ****
  	    }
  	    /* Bound requested lifetime with service and user */
  	    lifetime = min(req_life,
! 	      (ad->life - ((kerb_time.tv_sec - ad->time_sec) / 300)));
  	    lifetime = min(lifetime, ((u_long) s_name_data.max_life));
  
  	    /* unseal server's key from master key */
--- 936,943 ----
  	    }
  	    /* Bound requested lifetime with service and user */
  	    lifetime = min(req_life,
! 	      krb_time_to_life(kerb_time.tv_sec,
! 			       krb_life_to_time(ad->time_sec, ad->life)));
  	    lifetime = min(lifetime, ((u_long) s_name_data.max_life));
  
  	    /* unseal server's key from master key */
*** null	Fri Oct 21 19:16:44 1994
--- lifetime.c	Fri Oct 21 19:05:45 1994
***************
*** 0 ****
--- 1,142 ----
+ /*
+  * Ticket lifetime.  This defines the table used to lookup lifetime
+  * for the fixed part of rande of the one byte lifetime field.  Values
+  * less than 0x80 are intrpreted as the number of 5 minute intervals.
+  * Values from 0x80 to 0xBF should be looked up in this table.  The
+  * value of 0x80 is the same using both methods: 10 and two-thirds
+  * hours .  The lifetime of 0xBF is 30 days.  The intervening values
+  * of have a fixed ratio of roughly 1.06914.  The value 0xFF is
+  * defined to mean a ticket has no expiration time.  This should be
+  * used advisedly since individual servers may impose defacto
+  * upperbounds on ticket lifetimes.
+  */
+ 
+ #define TKTLIFENUMFIXED 64
+ #define TKTLIFEMINFIXED 0x80
+ #define TKTLIFEMAXFIXED 0xBF
+ #define TKTLIFENOEXPIRE 0xFF
+ #define MAXTKTLIFETIME	(30*24*3600)	/* 30 days */
+ #ifndef NEVERDATE
+ #define NEVERDATE ((unsigned long)-1L)
+ #endif
+ 
+ static int tkt_lifetimes[TKTLIFENUMFIXED] = {
+     38400,				/* 10.67 hours, 0.44 days */ 
+     41055,				/* 11.40 hours, 0.48 days */ 
+     43894,				/* 12.19 hours, 0.51 days */ 
+     46929,				/* 13.04 hours, 0.54 days */ 
+     50174,				/* 13.94 hours, 0.58 days */ 
+     53643,				/* 14.90 hours, 0.62 days */ 
+     57352,				/* 15.93 hours, 0.66 days */ 
+     61318,				/* 17.03 hours, 0.71 days */ 
+     65558,				/* 18.21 hours, 0.76 days */ 
+     70091,				/* 19.47 hours, 0.81 days */ 
+     74937,				/* 20.82 hours, 0.87 days */ 
+     80119,				/* 22.26 hours, 0.93 days */ 
+     85658,				/* 23.79 hours, 0.99 days */ 
+     91581,				/* 25.44 hours, 1.06 days */ 
+     97914,				/* 27.20 hours, 1.13 days */ 
+     104684,				/* 29.08 hours, 1.21 days */ 
+     111922,				/* 31.09 hours, 1.30 days */ 
+     119661,				/* 33.24 hours, 1.38 days */ 
+     127935,				/* 35.54 hours, 1.48 days */ 
+     136781,				/* 37.99 hours, 1.58 days */ 
+     146239,				/* 40.62 hours, 1.69 days */ 
+     156350,				/* 43.43 hours, 1.81 days */ 
+     167161,				/* 46.43 hours, 1.93 days */ 
+     178720,				/* 49.64 hours, 2.07 days */ 
+     191077,				/* 53.08 hours, 2.21 days */ 
+     204289,				/* 56.75 hours, 2.36 days */ 
+     218415,				/* 60.67 hours, 2.53 days */ 
+     233517,				/* 64.87 hours, 2.70 days */ 
+     249664,				/* 69.35 hours, 2.89 days */ 
+     266926,				/* 74.15 hours, 3.09 days */ 
+     285383,				/* 79.27 hours, 3.30 days */ 
+     305116,				/* 84.75 hours, 3.53 days */ 
+     326213,				/* 90.61 hours, 3.78 days */ 
+     348769,				/* 96.88 hours, 4.04 days */ 
+     372885,				/* 103.58 hours, 4.32 days */ 
+     398668,				/* 110.74 hours, 4.61 days */ 
+     426234,				/* 118.40 hours, 4.93 days */ 
+     455705,				/* 126.58 hours, 5.27 days */ 
+     487215,				/* 135.34 hours, 5.64 days */ 
+     520904,				/* 144.70 hours, 6.03 days */ 
+     556921,				/* 154.70 hours, 6.45 days */ 
+     595430,				/* 165.40 hours, 6.89 days */ 
+     636601,				/* 176.83 hours, 7.37 days */ 
+     680618,				/* 189.06 hours, 7.88 days */ 
+     727680,				/* 202.13 hours, 8.42 days */ 
+     777995,				/* 216.11 hours, 9.00 days */ 
+     831789,				/* 231.05 hours, 9.63 days */ 
+     889303,				/* 247.03 hours, 10.29 days */ 
+     950794,				/* 264.11 hours, 11.00 days */ 
+     1016537,				/* 282.37 hours, 11.77 days */ 
+     1086825,				/* 301.90 hours, 12.58 days */ 
+     1161973,				/* 322.77 hours, 13.45 days */ 
+     1242318,				/* 345.09 hours, 14.38 days */ 
+     1328218,				/* 368.95 hours, 15.37 days */ 
+     1420057,				/* 394.46 hours, 16.44 days */ 
+     1518247,				/* 421.74 hours, 17.57 days */ 
+     1623226,				/* 450.90 hours, 18.79 days */ 
+     1735464,				/* 482.07 hours, 20.09 days */ 
+     1855462,				/* 515.41 hours, 21.48 days */ 
+     1983758,				/* 551.04 hours, 22.96 days */ 
+     2120925,				/* 589.15 hours, 24.55 days */ 
+     2267576,				/* 629.88 hours, 26.25 days */ 
+     2424367,				/* 673.44 hours, 28.06 days */ 
+     2592000};				/* 720.00 hours, 30.00 days */ 
+ 
+ /*
+  * krb_life_to_time - takes a start time and a Kerberos standard
+  * lifetime char and returns the corresponding end time.  There are
+  * four simple cases to be handled.  The first is a life of 0xff,
+  * meaning no expiration, and results in an end time of 0xffffffff.
+  * The second is when life is less than the values covered by the
+  * table.  In this case, the end time is the start time plus the
+  * number of 5 minute intervals specified by life.  The third case
+  * returns start plus the MAXTKTLIFETIME if life is greater than
+  * TKTLIFEMAXFIXED.  The last case, uses the life value (minus
+  * TKTLIFEMINFIXED) as an index into the table to extract the lifetime
+  * in seconds, which is added to start to produce the end time.
+  */
+ unsigned long krb_life_to_time(start, life)
+ unsigned long start;
+ int life;
+ {
+     life = (unsigned char) life;
+     if (life == TKTLIFENOEXPIRE) return NEVERDATE;
+     if (life < TKTLIFEMINFIXED) return start + life*5*60;
+     if (life > TKTLIFEMAXFIXED) return start + MAXTKTLIFETIME;
+     return start + tkt_lifetimes[life - TKTLIFEMINFIXED];
+ }
+ 
+ /*
+  * krb_time_to_life - takes start and end times for the ticket and
+  * returns a Kerberos standard lifetime char, possibily using the
+  * tkt_lifetimes table for lifetimes above 127*5 minutes.  First, the
+  * special case of (end == NEVERDATE) is handled to mean no
+  * expiration.  Then negative lifetimes and those greater than the
+  * maximum ticket lifetime are rejected.  Then lifetimes less than the
+  * first table entry are handled by rounding the requested lifetime
+  * *up* to the next 5 minute interval.  The final step is to search
+  * the table for the smallest entry *greater than or equal* to the
+  * requested entry.
+  */
+ int krb_time_to_life(start, end)
+ unsigned long start;
+ unsigned long end;
+ {
+     long lifetime;
+     int i;
+ 
+     if (end == NEVERDATE) return TKTLIFENOEXPIRE;
+     lifetime = end - start;
+     if (lifetime > MAXTKTLIFETIME || lifetime <= 0) return 0;
+     if (lifetime < tkt_lifetimes[0]) return (lifetime + 5*60 - 1)/(5*60);
+     for (i=0; i<TKTLIFENUMFIXED; i++) {
+ 	if (lifetime <= tkt_lifetimes[i]) {
+ 	    return i+TKTLIFEMINFIXED;
+ 	}
+     }
+     return 0;
+ }
*** null	Fri Oct 21 19:16:44 1994
--- rd_req.c	Fri Oct 21 19:06:59 1994
***************
*** 0 ****
--- 1,336 ----
+ /*
+  * $Source: /mit/kerberos/src/lib/krb/RCS/rd_req.c,v $
+  * $Author: jtkohl $
+  *
+  * Copyright 1985, 1986, 1987, 1988 by the Massachusetts Institute
+  * of Technology.
+  *
+  * For copying and distribution information, please see the file
+  * <mit-copyright.h>.
+  */
+ 
+ #ifndef lint
+ static char *rcsid_rd_req_c =
+ "$Header: rd_req.c,v 4.16 89/03/22 14:52:06 jtkohl Exp $";
+ #endif /* lint */
+ 
+ #include <mit-copyright.h>
+ #include <des.h>
+ #include <krb.h>
+ #include <prot.h>
+ #include <sys/time.h>
+ #include <strings.h>
+ 
+ extern int krb_ap_req_debug;
+ extern unsigned long krb_life_to_time();
+ 
+ static struct timeval t_local = { 0, 0 };
+ 
+ /*
+  * Keep the following information around for subsequent calls
+  * to this routine by the same server using the same key.
+  */
+ 
+ static Key_schedule serv_key;	/* Key sched to decrypt ticket */
+ static C_Block ky;              /* Initialization vector */
+ static int st_kvno;		/* version number for this key */
+ static char st_rlm[REALM_SZ];	/* server's realm */
+ static char st_nam[ANAME_SZ];	/* service name */
+ static char st_inst[INST_SZ];	/* server's instance */
+ 
+ /*
+  * This file contains two functions.  krb_set_key() takes a DES
+  * key or password string and returns a DES key (either the original
+  * key, or the password converted into a DES key) and a key schedule
+  * for it.
+  *
+  * krb_rd_req() reads an authentication request and returns information
+  * about the identity of the requestor, or an indication that the
+  * identity information was not authentic.
+  */
+ 
+ /*
+  * krb_set_key() takes as its first argument either a DES key or a
+  * password string.  The "cvt" argument indicates how the first
+  * argument "key" is to be interpreted: if "cvt" is null, "key" is
+  * taken to be a DES key; if "cvt" is non-null, "key" is taken to
+  * be a password string, and is converted into a DES key using
+  * string_to_key().  In either case, the resulting key is returned
+  * in the external static variable "ky".  A key schedule is
+  * generated for "ky" and returned in the external static variable
+  * "serv_key".
+  *
+  * This routine returns the return value of des_key_sched.
+  *
+  * krb_set_key() needs to be in the same .o file as krb_rd_req() so that
+  * the key set by krb_set_key() is available in private storage for
+  * krb_rd_req().
+  */
+ 
+ int
+ krb_set_key(key,cvt)
+     char *key;
+     int cvt;
+ {
+ #ifdef NOENCRYPTION
+     bzero(ky, sizeof(ky));
+     return KSUCCESS;
+ #else /* Encrypt */
+     if (cvt)
+         string_to_key(key,ky);
+     else
+         bcopy(key,(char *)ky,8);
+     return(des_key_sched(ky,serv_key));
+ #endif /* NOENCRYPTION */
+ }
+ 
+ 
+ /*
+  * krb_rd_req() takes an AUTH_MSG_APPL_REQUEST or
+  * AUTH_MSG_APPL_REQUEST_MUTUAL message created by krb_mk_req(),
+  * checks its integrity and returns a judgement as to the requestor's
+  * identity.
+  *
+  * The "authent" argument is a pointer to the received message.
+  * The "service" and "instance" arguments name the receiving server,
+  * and are used to get the service's ticket to decrypt the ticket
+  * in the message, and to compare against the server name inside the
+  * ticket.  "from_addr" is the network address of the host from which
+  * the message was received; this is checked against the network
+  * address in the ticket.  If "from_addr" is zero, the check is not
+  * performed.  "ad" is an AUTH_DAT structure which is
+  * filled in with information about the sender's identity according
+  * to the authenticator and ticket sent in the message.  Finally,
+  * "fn" contains the name of the file containing the server's key.
+  * (If "fn" is NULL, the server's key is assumed to have been set
+  * by krb_set_key().  If "fn" is the null string ("") the default
+  * file KEYFILE, defined in "krb.h", is used.)
+  *
+  * krb_rd_req() returns RD_AP_OK if the authentication information
+  * was genuine, or one of the following error codes (defined in
+  * "krb.h"):
+  *
+  *	RD_AP_VERSION		- wrong protocol version number
+  *	RD_AP_MSG_TYPE		- wrong message type
+  *	RD_AP_UNDEC		- couldn't decipher the message
+  *	RD_AP_INCON		- inconsistencies found
+  *	RD_AP_BADD		- wrong network address
+  *	RD_AP_TIME		- client time (in authenticator)
+  *				  too far off server time
+  *	RD_AP_NYV		- Kerberos time (in ticket) too
+  *				  far off server time
+  *	RD_AP_EXP		- ticket expired
+  *
+  * For the message format, see krb_mk_req().
+  *
+  * Mutual authentication is not implemented.
+  */
+ 
+ krb_rd_req(authent,service,instance,from_addr,ad,fn)
+     register KTEXT authent;	/* The received message */
+     char *service;		/* Service name */
+     char *instance;		/* Service instance */
+     long from_addr;		/* Net address of originating host */
+     AUTH_DAT *ad;		/* Structure to be filled in */
+     char *fn;			/* Filename to get keys from */
+ {
+     static KTEXT_ST ticket;     /* Temp storage for ticket */
+     static KTEXT tkt = &ticket;
+     static KTEXT_ST req_id_st;  /* Temp storage for authenticator */
+     register KTEXT req_id = &req_id_st;
+ 
+     char realm[REALM_SZ];	/* Realm of issuing kerberos */
+     static Key_schedule seskey_sched; /* Key sched for session key */
+     unsigned char skey[KKEY_SZ]; /* Session key from ticket */
+     char sname[SNAME_SZ];	/* Service name from ticket */
+     char iname[INST_SZ];	/* Instance name from ticket */
+     char r_aname[ANAME_SZ];	/* Client name from authenticator */
+     char r_inst[INST_SZ];	/* Client instance from authenticator */
+     char r_realm[REALM_SZ];	/* Client realm from authenticator */
+     unsigned int r_time_ms;     /* Fine time from authenticator */
+     unsigned long r_time_sec;   /* Coarse time from authenticator */
+     register char *ptr;		/* For stepping through */
+     unsigned long delta_t;      /* Time in authenticator - local time */
+     long tkt_age;		/* Age of ticket */
+     static int swap_bytes;	/* Need to swap bytes? */
+     static int mutual;		/* Mutual authentication requested? */
+     static unsigned char s_kvno;/* Version number of the server's key
+ 				 * Kerberos used to encrypt ticket */
+     int status;
+ 
+     if (authent->length <= 0)
+ 	return(RD_AP_MODIFIED);
+ 
+     ptr = (char *) authent->dat;
+ 
+     /* get msg version, type and byte order, and server key version */
+ 
+     /* check version */
+     if (KRB_PROT_VERSION != (unsigned int) *ptr++)
+         return(RD_AP_VERSION);
+ 
+     /* byte order */
+     swap_bytes = 0;
+     if ((*ptr & 1) != HOST_BYTE_ORDER)
+         swap_bytes++;
+ 
+     /* check msg type */
+     mutual = 0;
+     switch (*ptr++ & ~1) {
+     case AUTH_MSG_APPL_REQUEST:
+         break;
+     case AUTH_MSG_APPL_REQUEST_MUTUAL:
+         mutual++;
+         break;
+     default:
+         return(RD_AP_MSG_TYPE);
+     }
+ 
+ #ifdef lint
+     /* XXX mutual is set but not used; why??? */
+     /* this is a crock to get lint to shut up */
+     if (mutual)
+         mutual = 0;
+ #endif /* lint */
+     s_kvno = *ptr++;		/* get server key version */
+     (void) strcpy(realm,ptr);   /* And the realm of the issuing KDC */
+     ptr += strlen(ptr) + 1;     /* skip the realm "hint" */
+ 
+     /*
+      * If "fn" is NULL, key info should already be set; don't
+      * bother with ticket file.  Otherwise, check to see if we
+      * already have key info for the given server and key version
+      * (saved in the static st_* variables).  If not, go get it
+      * from the ticket file.  If "fn" is the null string, use the
+      * default ticket file.
+      */
+     if (fn && (strcmp(st_nam,service) || strcmp(st_inst,instance) ||
+                strcmp(st_rlm,realm) || (st_kvno != s_kvno))) {
+         if (*fn == 0) fn = KEYFILE;
+         st_kvno = s_kvno;
+ #ifndef NOENCRYPTION
+         if (read_service_key(service,instance,realm,(int) s_kvno,
+                             fn,(char *)skey))
+             return(RD_AP_UNDEC);
+         if (status = krb_set_key((char *)skey,0))
+ 	    return(status);
+ #endif /* !NOENCRYPTION */
+         (void) strcpy(st_rlm,realm);
+         (void) strcpy(st_nam,service);
+         (void) strcpy(st_inst,instance);
+     }
+ 
+     /* Get ticket from authenticator */
+     tkt->length = (int) *ptr++;
+     if ((tkt->length + (ptr+1 - (char *) authent->dat)) > authent->length)
+ 	return(RD_AP_MODIFIED);
+     bcopy(ptr+1,(char *)(tkt->dat),tkt->length);
+ 
+     if (krb_ap_req_debug)
+         log("ticket->length: %d",tkt->length);
+ 
+ #ifndef NOENCRYPTION
+     /* Decrypt and take apart ticket */
+ #endif
+ 
+     if (decomp_ticket(tkt,&ad->k_flags,ad->pname,ad->pinst,ad->prealm,
+                       &(ad->address),ad->session, &(ad->life),
+                       &(ad->time_sec),sname,iname,ky,serv_key))
+         return(RD_AP_UNDEC);
+ 
+     if (krb_ap_req_debug) {
+         log("Ticket Contents.");
+         log(" Aname:   %s.%s",ad->pname,
+             ((int)*(ad->prealm) ? ad->prealm : "Athena"));
+         log(" Service: %s%s%s",sname,((int)*iname ? "." : ""),iname);
+     }
+ 
+     /* Extract the authenticator */
+     req_id->length = (int) *(ptr++);
+     if ((req_id->length + (ptr + tkt->length - (char *) authent->dat)) >
+ 	authent->length)
+ 	return(RD_AP_MODIFIED);
+     bcopy(ptr + tkt->length, (char *)(req_id->dat),req_id->length);
+ 
+ #ifndef NOENCRYPTION
+     /* And decrypt it with the session key from the ticket */
+     if (krb_ap_req_debug) log("About to decrypt authenticator");
+     key_sched(ad->session,seskey_sched);
+     pcbc_encrypt((C_Block *)req_id->dat,(C_Block *)req_id->dat,
+                  (long) req_id->length, seskey_sched,ad->session,DES_DECRYPT);
+     if (krb_ap_req_debug) log("Done.");
+ #endif /* NOENCRYPTION */
+ 
+ #define check_ptr() if ((ptr - (char *) req_id->dat) > req_id->length) return(RD_AP_MODIFIED);
+ 
+     ptr = (char *) req_id->dat;
+     (void) strcpy(r_aname,ptr);	/* Authentication name */
+     ptr += strlen(r_aname)+1;
+     check_ptr();
+     (void) strcpy(r_inst,ptr);	/* Authentication instance */
+     ptr += strlen(r_inst)+1;
+     check_ptr();
+     (void) strcpy(r_realm,ptr);	/* Authentication name */
+     ptr += strlen(r_realm)+1;
+     check_ptr();
+     bcopy(ptr,(char *)&ad->checksum,4);	/* Checksum */
+     ptr += 4;
+     check_ptr();
+     if (swap_bytes) swap_u_long(ad->checksum);
+     r_time_ms = *(ptr++);	/* Time (fine) */
+ #ifdef lint
+     /* XXX r_time_ms is set but not used.  why??? */
+     /* this is a crock to get lint to shut up */
+     if (r_time_ms)
+         r_time_ms = 0;
+ #endif /* lint */
+     check_ptr();
+     /* assume sizeof(r_time_sec) == 4 ?? */
+     bcopy(ptr,(char *)&r_time_sec,4); /* Time (coarse) */
+     if (swap_bytes) swap_u_long(r_time_sec);
+ 
+     /* Check for authenticity of the request */
+     if (krb_ap_req_debug)
+         log("Pname:   %s %s",ad->pname,r_aname);
+     if (strcmp(ad->pname,r_aname) != 0)
+         return(RD_AP_INCON);
+     if (strcmp(ad->pinst,r_inst) != 0)
+         return(RD_AP_INCON);
+     if (krb_ap_req_debug)
+         log("Realm:   %s %s",ad->prealm,r_realm);
+     if ((strcmp(ad->prealm,r_realm) != 0))
+         return(RD_AP_INCON);
+ 
+     if (krb_ap_req_debug)
+         log("Address: %d %d",ad->address,from_addr);
+     if (from_addr && (ad->address != from_addr))
+         return(RD_AP_BADD);
+ 
+     (void) gettimeofday(&t_local,(struct timezone *) 0);
+     delta_t = abs((int)(t_local.tv_sec - r_time_sec));
+     if (delta_t > CLOCK_SKEW) {
+         if (krb_ap_req_debug)
+             log("Time out of range: %d - %d = %d",
+                 t_local.tv_sec,r_time_sec,delta_t);
+         return(RD_AP_TIME);
+     }
+ 
+     /* Now check for expiration of ticket */
+ 
+     tkt_age = t_local.tv_sec - ad->time_sec;
+     if (krb_ap_req_debug)
+         log("Time: %d Issue Date: %d Diff: %d Life %x",
+             t_local.tv_sec,ad->time_sec,tkt_age,ad->life);
+ 
+     if (t_local.tv_sec < ad->time_sec) {
+         if ((ad->time_sec - t_local.tv_sec) > CLOCK_SKEW)
+             return(RD_AP_NYV);
+     }
+     else if (t_local.tv_sec > krb_life_to_time(ad->time_sec, ad->life))
+         return(RD_AP_EXP);
+ 
+     /* All seems OK */
+     ad->reply.length = 0;
+ 
+     return(RD_AP_OK);
+ }


home help back first fref pref prev next nref lref last post