2005-04-17 02:20:36 +04:00
# include <linux/types.h>
# include <linux/sched.h>
# include <linux/module.h>
# include <linux/sunrpc/types.h>
# include <linux/sunrpc/xdr.h>
# include <linux/sunrpc/svcsock.h>
# include <linux/sunrpc/svcauth.h>
# include <linux/err.h>
# include <linux/seq_file.h>
# include <linux/hash.h>
2005-06-23 11:09:02 +04:00
# include <linux/string.h>
2006-10-04 13:15:50 +04:00
# include <net/sock.h>
2005-04-17 02:20:36 +04:00
# define RPCDBG_FACILITY RPCDBG_AUTH
/*
* AUTHUNIX and AUTHNULL credentials are both handled here .
* AUTHNULL is treated just like AUTHUNIX except that the uid / gid
* are always nobody ( - 2 ) . i . e . we do the same IP address checks for
* AUTHNULL as for AUTHUNIX , and that is done here .
*/
struct unix_domain {
struct auth_domain h ;
int addr_changes ;
/* other stuff later */
} ;
2006-03-27 13:14:59 +04:00
extern struct auth_ops svcauth_unix ;
2005-04-17 02:20:36 +04:00
struct auth_domain * unix_domain_find ( char * name )
{
2006-03-27 13:14:59 +04:00
struct auth_domain * rv ;
struct unix_domain * new = NULL ;
rv = auth_domain_lookup ( name , NULL ) ;
while ( 1 ) {
2006-03-27 13:15:11 +04:00
if ( rv ) {
if ( new & & rv ! = & new - > h )
auth_domain_put ( & new - > h ) ;
if ( rv - > flavour ! = & svcauth_unix ) {
auth_domain_put ( rv ) ;
return NULL ;
}
2006-03-27 13:14:59 +04:00
return rv ;
}
new = kmalloc ( sizeof ( * new ) , GFP_KERNEL ) ;
if ( new = = NULL )
return NULL ;
kref_init ( & new - > h . ref ) ;
new - > h . name = kstrdup ( name , GFP_KERNEL ) ;
new - > h . flavour = & svcauth_unix ;
new - > addr_changes = 0 ;
rv = auth_domain_lookup ( name , & new - > h ) ;
2005-04-17 02:20:36 +04:00
}
}
static void svcauth_unix_domain_release ( struct auth_domain * dom )
{
struct unix_domain * ud = container_of ( dom , struct unix_domain , h ) ;
kfree ( dom - > name ) ;
kfree ( ud ) ;
}
/**************************************************
* cache for IP address to unix_domain
* as needed by AUTH_UNIX
*/
# define IP_HASHBITS 8
# define IP_HASHMAX (1<<IP_HASHBITS)
# define IP_HASHMASK (IP_HASHMAX-1)
struct ip_map {
struct cache_head h ;
char m_class [ 8 ] ; /* e.g. "nfsd" */
struct in_addr m_addr ;
struct unix_domain * m_client ;
int m_add_change ;
} ;
static struct cache_head * ip_table [ IP_HASHMAX ] ;
2006-03-27 13:15:09 +04:00
static void ip_map_put ( struct kref * kref )
2005-04-17 02:20:36 +04:00
{
2006-03-27 13:15:09 +04:00
struct cache_head * item = container_of ( kref , struct cache_head , ref ) ;
2005-04-17 02:20:36 +04:00
struct ip_map * im = container_of ( item , struct ip_map , h ) ;
2006-03-27 13:15:09 +04:00
if ( test_bit ( CACHE_VALID , & item - > flags ) & &
! test_bit ( CACHE_NEGATIVE , & item - > flags ) )
auth_domain_put ( & im - > m_client - > h ) ;
kfree ( im ) ;
2005-04-17 02:20:36 +04:00
}
2006-01-06 11:09:49 +03:00
# if IP_HASHBITS == 8
/* hash_long on a 64 bit machine is currently REALLY BAD for
* IP addresses in reverse - endian ( i . e . on a little - endian machine ) .
* So use a trivial but reliable hash instead
*/
static inline int hash_ip ( unsigned long ip )
{
int hash = ip ^ ( ip > > 16 ) ;
return ( hash ^ ( hash > > 8 ) ) & 0xff ;
}
# endif
2006-03-27 13:15:02 +04:00
static int ip_map_match ( struct cache_head * corig , struct cache_head * cnew )
2005-04-17 02:20:36 +04:00
{
2006-03-27 13:15:02 +04:00
struct ip_map * orig = container_of ( corig , struct ip_map , h ) ;
struct ip_map * new = container_of ( cnew , struct ip_map , h ) ;
return strcmp ( orig - > m_class , new - > m_class ) = = 0
& & orig - > m_addr . s_addr = = new - > m_addr . s_addr ;
2005-04-17 02:20:36 +04:00
}
2006-03-27 13:15:02 +04:00
static void ip_map_init ( struct cache_head * cnew , struct cache_head * citem )
2005-04-17 02:20:36 +04:00
{
2006-03-27 13:15:02 +04:00
struct ip_map * new = container_of ( cnew , struct ip_map , h ) ;
struct ip_map * item = container_of ( citem , struct ip_map , h ) ;
2005-04-17 02:20:36 +04:00
strcpy ( new - > m_class , item - > m_class ) ;
new - > m_addr . s_addr = item - > m_addr . s_addr ;
}
2006-03-27 13:15:02 +04:00
static void update ( struct cache_head * cnew , struct cache_head * citem )
2005-04-17 02:20:36 +04:00
{
2006-03-27 13:15:02 +04:00
struct ip_map * new = container_of ( cnew , struct ip_map , h ) ;
struct ip_map * item = container_of ( citem , struct ip_map , h ) ;
2006-03-27 13:14:59 +04:00
kref_get ( & item - > m_client - > h . ref ) ;
2005-04-17 02:20:36 +04:00
new - > m_client = item - > m_client ;
new - > m_add_change = item - > m_add_change ;
}
2006-03-27 13:15:02 +04:00
static struct cache_head * ip_map_alloc ( void )
{
struct ip_map * i = kmalloc ( sizeof ( * i ) , GFP_KERNEL ) ;
if ( i )
return & i - > h ;
else
return NULL ;
}
2005-04-17 02:20:36 +04:00
static void ip_map_request ( struct cache_detail * cd ,
struct cache_head * h ,
char * * bpp , int * blen )
{
char text_addr [ 20 ] ;
struct ip_map * im = container_of ( h , struct ip_map , h ) ;
2006-09-27 09:29:38 +04:00
__be32 addr = im - > m_addr . s_addr ;
2005-04-17 02:20:36 +04:00
snprintf ( text_addr , 20 , " %u.%u.%u.%u " ,
ntohl ( addr ) > > 24 & 0xff ,
ntohl ( addr ) > > 16 & 0xff ,
ntohl ( addr ) > > 8 & 0xff ,
ntohl ( addr ) > > 0 & 0xff ) ;
qword_add ( bpp , blen , im - > m_class ) ;
qword_add ( bpp , blen , text_addr ) ;
( * bpp ) [ - 1 ] = ' \n ' ;
}
2006-03-27 13:15:02 +04:00
static struct ip_map * ip_map_lookup ( char * class , struct in_addr addr ) ;
static int ip_map_update ( struct ip_map * ipm , struct unix_domain * udom , time_t expiry ) ;
2005-04-17 02:20:36 +04:00
static int ip_map_parse ( struct cache_detail * cd ,
char * mesg , int mlen )
{
/* class ipaddress [domainname] */
/* should be safe just to use the start of the input buffer
* for scratch : */
char * buf = mesg ;
int len ;
int b1 , b2 , b3 , b4 ;
char c ;
2006-03-27 13:15:02 +04:00
char class [ 8 ] ;
struct in_addr addr ;
int err ;
struct ip_map * ipmp ;
2005-04-17 02:20:36 +04:00
struct auth_domain * dom ;
time_t expiry ;
if ( mesg [ mlen - 1 ] ! = ' \n ' )
return - EINVAL ;
mesg [ mlen - 1 ] = 0 ;
/* class */
2006-03-27 13:15:02 +04:00
len = qword_get ( & mesg , class , sizeof ( class ) ) ;
2005-04-17 02:20:36 +04:00
if ( len < = 0 ) return - EINVAL ;
/* ip address */
len = qword_get ( & mesg , buf , mlen ) ;
if ( len < = 0 ) return - EINVAL ;
if ( sscanf ( buf , " %u.%u.%u.%u%c " , & b1 , & b2 , & b3 , & b4 , & c ) ! = 4 )
return - EINVAL ;
expiry = get_expiry ( & mesg ) ;
if ( expiry = = 0 )
return - EINVAL ;
/* domainname, or empty for NEGATIVE */
len = qword_get ( & mesg , buf , mlen ) ;
if ( len < 0 ) return - EINVAL ;
if ( len ) {
dom = unix_domain_find ( buf ) ;
if ( dom = = NULL )
return - ENOENT ;
} else
dom = NULL ;
2006-03-27 13:15:02 +04:00
addr . s_addr =
2005-04-17 02:20:36 +04:00
htonl ( ( ( ( ( ( b1 < < 8 ) | b2 ) < < 8 ) | b3 ) < < 8 ) | b4 ) ;
2006-03-27 13:15:02 +04:00
ipmp = ip_map_lookup ( class , addr ) ;
if ( ipmp ) {
err = ip_map_update ( ipmp ,
container_of ( dom , struct unix_domain , h ) ,
expiry ) ;
2005-04-17 02:20:36 +04:00
} else
2006-03-27 13:15:02 +04:00
err = - ENOMEM ;
2005-04-17 02:20:36 +04:00
if ( dom )
auth_domain_put ( dom ) ;
2006-03-27 13:15:02 +04:00
2005-04-17 02:20:36 +04:00
cache_flush ( ) ;
2006-03-27 13:15:02 +04:00
return err ;
2005-04-17 02:20:36 +04:00
}
static int ip_map_show ( struct seq_file * m ,
struct cache_detail * cd ,
struct cache_head * h )
{
struct ip_map * im ;
struct in_addr addr ;
char * dom = " -no-domain- " ;
if ( h = = NULL ) {
seq_puts ( m , " #class IP domain \n " ) ;
return 0 ;
}
im = container_of ( h , struct ip_map , h ) ;
/* class addr domain */
addr = im - > m_addr ;
if ( test_bit ( CACHE_VALID , & h - > flags ) & &
! test_bit ( CACHE_NEGATIVE , & h - > flags ) )
dom = im - > m_client - > h . name ;
seq_printf ( m , " %s %d.%d.%d.%d %s \n " ,
im - > m_class ,
2006-09-27 09:30:23 +04:00
ntohl ( addr . s_addr ) > > 24 & 0xff ,
ntohl ( addr . s_addr ) > > 16 & 0xff ,
ntohl ( addr . s_addr ) > > 8 & 0xff ,
ntohl ( addr . s_addr ) > > 0 & 0xff ,
2005-04-17 02:20:36 +04:00
dom
) ;
return 0 ;
}
struct cache_detail ip_map_cache = {
2005-09-07 02:17:08 +04:00
. owner = THIS_MODULE ,
2005-04-17 02:20:36 +04:00
. hash_size = IP_HASHMAX ,
. hash_table = ip_table ,
. name = " auth.unix.ip " ,
. cache_put = ip_map_put ,
. cache_request = ip_map_request ,
. cache_parse = ip_map_parse ,
. cache_show = ip_map_show ,
2006-03-27 13:15:02 +04:00
. match = ip_map_match ,
. init = ip_map_init ,
. update = update ,
. alloc = ip_map_alloc ,
2005-04-17 02:20:36 +04:00
} ;
2006-03-27 13:15:02 +04:00
static struct ip_map * ip_map_lookup ( char * class , struct in_addr addr )
{
struct ip_map ip ;
struct cache_head * ch ;
strcpy ( ip . m_class , class ) ;
ip . m_addr = addr ;
ch = sunrpc_cache_lookup ( & ip_map_cache , & ip . h ,
hash_str ( class , IP_HASHBITS ) ^
hash_ip ( ( unsigned long ) addr . s_addr ) ) ;
if ( ch )
return container_of ( ch , struct ip_map , h ) ;
else
return NULL ;
}
2005-04-17 02:20:36 +04:00
2006-03-27 13:15:02 +04:00
static int ip_map_update ( struct ip_map * ipm , struct unix_domain * udom , time_t expiry )
{
struct ip_map ip ;
struct cache_head * ch ;
ip . m_client = udom ;
ip . h . flags = 0 ;
if ( ! udom )
set_bit ( CACHE_NEGATIVE , & ip . h . flags ) ;
else {
ip . m_add_change = udom - > addr_changes ;
/* if this is from the legacy set_client system call,
* we need m_add_change to be one higher
*/
if ( expiry = = NEVER )
ip . m_add_change + + ;
}
ip . h . expiry_time = expiry ;
ch = sunrpc_cache_update ( & ip_map_cache ,
& ip . h , & ipm - > h ,
hash_str ( ipm - > m_class , IP_HASHBITS ) ^
hash_ip ( ( unsigned long ) ipm - > m_addr . s_addr ) ) ;
if ( ! ch )
return - ENOMEM ;
2006-03-27 13:15:09 +04:00
cache_put ( ch , & ip_map_cache ) ;
2006-03-27 13:15:02 +04:00
return 0 ;
}
2005-04-17 02:20:36 +04:00
int auth_unix_add_addr ( struct in_addr addr , struct auth_domain * dom )
{
struct unix_domain * udom ;
2006-03-27 13:15:02 +04:00
struct ip_map * ipmp ;
2005-04-17 02:20:36 +04:00
2006-03-27 13:14:59 +04:00
if ( dom - > flavour ! = & svcauth_unix )
2005-04-17 02:20:36 +04:00
return - EINVAL ;
udom = container_of ( dom , struct unix_domain , h ) ;
2006-03-27 13:15:02 +04:00
ipmp = ip_map_lookup ( " nfsd " , addr ) ;
2005-04-17 02:20:36 +04:00
2006-03-27 13:15:02 +04:00
if ( ipmp )
return ip_map_update ( ipmp , udom , NEVER ) ;
else
2005-04-17 02:20:36 +04:00
return - ENOMEM ;
}
int auth_unix_forget_old ( struct auth_domain * dom )
{
struct unix_domain * udom ;
2006-03-27 13:14:59 +04:00
if ( dom - > flavour ! = & svcauth_unix )
2005-04-17 02:20:36 +04:00
return - EINVAL ;
udom = container_of ( dom , struct unix_domain , h ) ;
udom - > addr_changes + + ;
return 0 ;
}
struct auth_domain * auth_unix_lookup ( struct in_addr addr )
{
2006-10-02 13:17:43 +04:00
struct ip_map * ipm ;
2005-04-17 02:20:36 +04:00
struct auth_domain * rv ;
2006-03-27 13:15:02 +04:00
ipm = ip_map_lookup ( " nfsd " , addr ) ;
2005-04-17 02:20:36 +04:00
if ( ! ipm )
return NULL ;
if ( cache_check ( & ip_map_cache , & ipm - > h , NULL ) )
return NULL ;
if ( ( ipm - > m_client - > addr_changes - ipm - > m_add_change ) > 0 ) {
if ( test_and_set_bit ( CACHE_NEGATIVE , & ipm - > h . flags ) = = 0 )
auth_domain_put ( & ipm - > m_client - > h ) ;
rv = NULL ;
} else {
rv = & ipm - > m_client - > h ;
2006-03-27 13:14:59 +04:00
kref_get ( & rv - > ref ) ;
2005-04-17 02:20:36 +04:00
}
2006-03-27 13:15:09 +04:00
cache_put ( & ipm - > h , & ip_map_cache ) ;
2005-04-17 02:20:36 +04:00
return rv ;
}
void svcauth_unix_purge ( void )
{
cache_purge ( & ip_map_cache ) ;
}
2006-10-04 13:15:50 +04:00
static inline struct ip_map *
ip_map_cached_get ( struct svc_rqst * rqstp )
{
struct ip_map * ipm = rqstp - > rq_sock - > sk_info_authunix ;
if ( ipm ! = NULL ) {
if ( ! cache_valid ( & ipm - > h ) ) {
/*
* The entry has been invalidated since it was
* remembered , e . g . by a second mount from the
* same IP address .
*/
rqstp - > rq_sock - > sk_info_authunix = NULL ;
cache_put ( & ipm - > h , & ip_map_cache ) ;
return NULL ;
}
cache_get ( & ipm - > h ) ;
}
return ipm ;
}
static inline void
ip_map_cached_put ( struct svc_rqst * rqstp , struct ip_map * ipm )
{
struct svc_sock * svsk = rqstp - > rq_sock ;
if ( svsk - > sk_sock - > type = = SOCK_STREAM & & svsk - > sk_info_authunix = = NULL )
svsk - > sk_info_authunix = ipm ; /* newly cached, keep the reference */
else
cache_put ( & ipm - > h , & ip_map_cache ) ;
}
void
svcauth_unix_info_release ( void * info )
{
struct ip_map * ipm = info ;
cache_put ( & ipm - > h , & ip_map_cache ) ;
}
2005-04-17 02:20:36 +04:00
static int
svcauth_unix_set_client ( struct svc_rqst * rqstp )
{
2006-03-27 13:15:02 +04:00
struct ip_map * ipm ;
2005-04-17 02:20:36 +04:00
rqstp - > rq_client = NULL ;
if ( rqstp - > rq_proc = = 0 )
return SVC_OK ;
2006-10-04 13:15:50 +04:00
ipm = ip_map_cached_get ( rqstp ) ;
if ( ipm = = NULL )
ipm = ip_map_lookup ( rqstp - > rq_server - > sv_program - > pg_class ,
rqstp - > rq_addr . sin_addr ) ;
2005-04-17 02:20:36 +04:00
if ( ipm = = NULL )
return SVC_DENIED ;
switch ( cache_check ( & ip_map_cache , & ipm - > h , & rqstp - > rq_chandle ) ) {
default :
BUG ( ) ;
case - EAGAIN :
return SVC_DROP ;
case - ENOENT :
return SVC_DENIED ;
case 0 :
rqstp - > rq_client = & ipm - > m_client - > h ;
2006-03-27 13:14:59 +04:00
kref_get ( & rqstp - > rq_client - > ref ) ;
2006-10-04 13:15:50 +04:00
ip_map_cached_put ( rqstp , ipm ) ;
2005-04-17 02:20:36 +04:00
break ;
}
return SVC_OK ;
}
static int
2006-09-27 09:29:38 +04:00
svcauth_null_accept ( struct svc_rqst * rqstp , __be32 * authp )
2005-04-17 02:20:36 +04:00
{
struct kvec * argv = & rqstp - > rq_arg . head [ 0 ] ;
struct kvec * resv = & rqstp - > rq_res . head [ 0 ] ;
struct svc_cred * cred = & rqstp - > rq_cred ;
cred - > cr_group_info = NULL ;
rqstp - > rq_client = NULL ;
if ( argv - > iov_len < 3 * 4 )
return SVC_GARBAGE ;
if ( svc_getu32 ( argv ) ! = 0 ) {
dprintk ( " svc: bad null cred \n " ) ;
* authp = rpc_autherr_badcred ;
return SVC_DENIED ;
}
2006-09-27 09:28:46 +04:00
if ( svc_getu32 ( argv ) ! = htonl ( RPC_AUTH_NULL ) | | svc_getu32 ( argv ) ! = 0 ) {
2005-04-17 02:20:36 +04:00
dprintk ( " svc: bad null verf \n " ) ;
* authp = rpc_autherr_badverf ;
return SVC_DENIED ;
}
/* Signal that mapping to nobody uid/gid is required */
cred - > cr_uid = ( uid_t ) - 1 ;
cred - > cr_gid = ( gid_t ) - 1 ;
cred - > cr_group_info = groups_alloc ( 0 ) ;
if ( cred - > cr_group_info = = NULL )
return SVC_DROP ; /* kmalloc failure - client must retry */
/* Put NULL verifier */
2006-09-27 09:28:46 +04:00
svc_putnl ( resv , RPC_AUTH_NULL ) ;
svc_putnl ( resv , 0 ) ;
2005-04-17 02:20:36 +04:00
return SVC_OK ;
}
static int
svcauth_null_release ( struct svc_rqst * rqstp )
{
if ( rqstp - > rq_client )
auth_domain_put ( rqstp - > rq_client ) ;
rqstp - > rq_client = NULL ;
if ( rqstp - > rq_cred . cr_group_info )
put_group_info ( rqstp - > rq_cred . cr_group_info ) ;
rqstp - > rq_cred . cr_group_info = NULL ;
return 0 ; /* don't drop */
}
struct auth_ops svcauth_null = {
. name = " null " ,
. owner = THIS_MODULE ,
. flavour = RPC_AUTH_NULL ,
. accept = svcauth_null_accept ,
. release = svcauth_null_release ,
. set_client = svcauth_unix_set_client ,
} ;
static int
2006-09-27 09:29:38 +04:00
svcauth_unix_accept ( struct svc_rqst * rqstp , __be32 * authp )
2005-04-17 02:20:36 +04:00
{
struct kvec * argv = & rqstp - > rq_arg . head [ 0 ] ;
struct kvec * resv = & rqstp - > rq_res . head [ 0 ] ;
struct svc_cred * cred = & rqstp - > rq_cred ;
u32 slen , i ;
int len = argv - > iov_len ;
cred - > cr_group_info = NULL ;
rqstp - > rq_client = NULL ;
if ( ( len - = 3 * 4 ) < 0 )
return SVC_GARBAGE ;
svc_getu32 ( argv ) ; /* length */
svc_getu32 ( argv ) ; /* time stamp */
2006-09-27 09:28:46 +04:00
slen = XDR_QUADLEN ( svc_getnl ( argv ) ) ; /* machname length */
2005-04-17 02:20:36 +04:00
if ( slen > 64 | | ( len - = ( slen + 3 ) * 4 ) < 0 )
goto badcred ;
2006-09-27 09:29:38 +04:00
argv - > iov_base = ( void * ) ( ( __be32 * ) argv - > iov_base + slen ) ; /* skip machname */
2005-04-17 02:20:36 +04:00
argv - > iov_len - = slen * 4 ;
2006-09-27 09:28:46 +04:00
cred - > cr_uid = svc_getnl ( argv ) ; /* uid */
cred - > cr_gid = svc_getnl ( argv ) ; /* gid */
slen = svc_getnl ( argv ) ; /* gids length */
2005-04-17 02:20:36 +04:00
if ( slen > 16 | | ( len - = ( slen + 2 ) * 4 ) < 0 )
goto badcred ;
cred - > cr_group_info = groups_alloc ( slen ) ;
if ( cred - > cr_group_info = = NULL )
return SVC_DROP ;
for ( i = 0 ; i < slen ; i + + )
2006-09-27 09:28:46 +04:00
GROUP_AT ( cred - > cr_group_info , i ) = svc_getnl ( argv ) ;
2005-04-17 02:20:36 +04:00
2006-09-27 09:28:46 +04:00
if ( svc_getu32 ( argv ) ! = htonl ( RPC_AUTH_NULL ) | | svc_getu32 ( argv ) ! = 0 ) {
2005-04-17 02:20:36 +04:00
* authp = rpc_autherr_badverf ;
return SVC_DENIED ;
}
/* Put NULL verifier */
2006-09-27 09:28:46 +04:00
svc_putnl ( resv , RPC_AUTH_NULL ) ;
svc_putnl ( resv , 0 ) ;
2005-04-17 02:20:36 +04:00
return SVC_OK ;
badcred :
* authp = rpc_autherr_badcred ;
return SVC_DENIED ;
}
static int
svcauth_unix_release ( struct svc_rqst * rqstp )
{
/* Verifier (such as it is) is already in place.
*/
if ( rqstp - > rq_client )
auth_domain_put ( rqstp - > rq_client ) ;
rqstp - > rq_client = NULL ;
if ( rqstp - > rq_cred . cr_group_info )
put_group_info ( rqstp - > rq_cred . cr_group_info ) ;
rqstp - > rq_cred . cr_group_info = NULL ;
return 0 ;
}
struct auth_ops svcauth_unix = {
. name = " unix " ,
. owner = THIS_MODULE ,
. flavour = RPC_AUTH_UNIX ,
. accept = svcauth_unix_accept ,
. release = svcauth_unix_release ,
. domain_release = svcauth_unix_domain_release ,
. set_client = svcauth_unix_set_client ,
} ;