2005-03-28 05:00:39 +04:00
/*
Socket wrapper library . Passes all socket communication over
unix domain sockets if the environment variable SOCKET_WRAPPER_DIR
is set .
Copyright ( C ) Jelmer Vernooij 2005
This program is free software ; you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation ; either version 2 of the License , or
( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program ; if not , write to the Free Software
Foundation , Inc . , 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*/
2005-06-26 03:38:03 +04:00
# ifdef _SAMBA_BUILD_
2005-03-28 05:00:39 +04:00
# include "includes.h"
2005-06-26 04:20:22 +04:00
# undef SOCKET_WRAPPER
2005-03-28 05:00:39 +04:00
# include "system/network.h"
2005-06-10 16:21:46 +04:00
# include "system/filesys.h"
2005-03-28 05:00:39 +04:00
# else
# include <sys/types.h>
2005-06-10 16:21:46 +04:00
# include <sys/stat.h>
2005-03-28 05:00:39 +04:00
# include <sys/socket.h>
# include <errno.h>
# include <sys/un.h>
# include <netinet/in.h>
2005-03-31 03:28:15 +04:00
# include <netinet/tcp.h>
2005-03-28 05:00:39 +04:00
# include <stdlib.h>
# include <unistd.h>
# include <string.h>
# include <stdio.h>
# endif
2005-06-26 03:38:03 +04:00
# include "dlinklist.h"
2005-03-28 05:00:39 +04:00
/* LD_PRELOAD doesn't work yet, so REWRITE_CALLS is all we support
* for now */
# define REWRITE_CALLS
# ifdef REWRITE_CALLS
# define real_accept accept
# define real_connect connect
# define real_bind bind
# define real_getpeername getpeername
# define real_getsockname getsockname
# define real_getsockopt getsockopt
# define real_setsockopt setsockopt
# define real_recvfrom recvfrom
# define real_sendto sendto
# define real_socket socket
# define real_close close
# endif
2005-07-21 12:42:17 +04:00
/* we need to use a very terse format here as IRIX 6.4 silently
truncates names to 16 chars , so if we use a longer name then we
can ' t tell which port a packet came from with recvfrom ( )
with this format we have 8 chars left for the directory name
*/
2005-11-04 11:02:20 +03:00
# define SOCKET_FORMAT "%u_%05u"
2005-07-21 12:42:17 +04:00
2005-03-31 03:28:15 +04:00
static struct sockaddr * sockaddr_dup ( const void * data , socklen_t len )
2005-03-28 05:00:39 +04:00
{
struct sockaddr * ret = ( struct sockaddr * ) malloc ( len ) ;
memcpy ( ret , data , len ) ;
return ret ;
}
struct socket_info
{
int fd ;
int domain ;
int type ;
int protocol ;
2005-03-31 16:40:12 +04:00
int bound ;
2005-03-28 05:00:39 +04:00
char * path ;
2005-03-31 16:40:12 +04:00
char * tmp_path ;
2005-03-28 05:00:39 +04:00
struct sockaddr * myname ;
socklen_t myname_len ;
struct sockaddr * peername ;
socklen_t peername_len ;
struct socket_info * prev , * next ;
2005-03-31 16:40:12 +04:00
} ;
static struct socket_info * sockets = NULL ;
2005-03-28 05:00:39 +04:00
2005-07-21 17:45:07 +04:00
static const char * socket_wrapper_dir ( void )
{
const char * s = getenv ( " SOCKET_WRAPPER_DIR " ) ;
if ( s = = NULL ) {
return NULL ;
}
if ( strncmp ( s , " ./ " , 2 ) = = 0 ) {
s + = 2 ;
}
return s ;
}
2005-03-28 05:00:39 +04:00
static int convert_un_in ( const struct sockaddr_un * un , struct sockaddr_in * in , socklen_t * len )
{
unsigned int prt ;
const char * p ;
2005-03-31 03:28:15 +04:00
int type ;
2005-03-28 05:00:39 +04:00
if ( ( * len ) < sizeof ( struct sockaddr_in ) ) {
return 0 ;
}
in - > sin_family = AF_INET ;
2005-06-10 16:21:46 +04:00
in - > sin_port = htons ( 1025 ) ; /* Default to 1025 */
p = strrchr ( un - > sun_path , ' / ' ) ;
2005-03-28 05:00:39 +04:00
if ( p ) p + + ; else p = un - > sun_path ;
2005-07-21 12:42:17 +04:00
if ( sscanf ( p , SOCKET_FORMAT , & type , & prt ) = = 2 ) {
2005-03-28 05:00:39 +04:00
in - > sin_port = htons ( prt ) ;
}
2005-03-31 16:40:12 +04:00
in - > sin_addr . s_addr = htonl ( INADDR_LOOPBACK ) ;
2005-03-28 05:00:39 +04:00
* len = sizeof ( struct sockaddr_in ) ;
return 0 ;
}
2005-06-10 16:21:46 +04:00
static int convert_in_un ( struct socket_info * si , const struct sockaddr_in * in , struct sockaddr_un * un )
2005-03-28 05:00:39 +04:00
{
2005-06-10 16:21:46 +04:00
int type = si - > type ;
2005-03-28 05:00:39 +04:00
uint16_t prt = ntohs ( in - > sin_port ) ;
2005-06-10 16:21:46 +04:00
if ( prt = = 0 ) {
struct stat st ;
/* handle auto-allocation of ephemeral ports */
prt = 5000 ;
do {
2005-07-21 12:42:17 +04:00
snprintf ( un - > sun_path , sizeof ( un - > sun_path ) , " %s/ " SOCKET_FORMAT ,
2005-07-21 17:45:07 +04:00
socket_wrapper_dir ( ) , type , + + prt ) ;
2005-06-10 16:21:46 +04:00
} while ( stat ( un - > sun_path , & st ) = = 0 & & prt < 10000 ) ;
( ( struct sockaddr_in * ) si - > myname ) - > sin_port = htons ( prt ) ;
}
2005-07-21 12:42:17 +04:00
snprintf ( un - > sun_path , sizeof ( un - > sun_path ) , " %s/ " SOCKET_FORMAT ,
2005-07-21 17:45:07 +04:00
socket_wrapper_dir ( ) , type , prt ) ;
2005-03-28 05:00:39 +04:00
return 0 ;
}
static struct socket_info * find_socket_info ( int fd )
{
struct socket_info * i ;
for ( i = sockets ; i ; i = i - > next ) {
if ( i - > fd = = fd )
return i ;
}
return NULL ;
}
2005-06-10 16:21:46 +04:00
static int sockaddr_convert_to_un ( struct socket_info * si , const struct sockaddr * in_addr , socklen_t in_len ,
2005-03-28 05:00:39 +04:00
struct sockaddr_un * out_addr )
{
if ( ! out_addr )
return 0 ;
2005-03-31 04:43:26 +04:00
out_addr - > sun_family = AF_UNIX ;
2005-03-31 03:28:15 +04:00
2005-03-28 05:00:39 +04:00
switch ( in_addr - > sa_family ) {
case AF_INET :
2005-06-10 16:21:46 +04:00
return convert_in_un ( si , ( const struct sockaddr_in * ) in_addr , out_addr ) ;
2005-03-31 04:43:26 +04:00
case AF_UNIX :
2005-03-28 05:00:39 +04:00
memcpy ( out_addr , in_addr , sizeof ( * out_addr ) ) ;
return 0 ;
default :
break ;
}
errno = EAFNOSUPPORT ;
return - 1 ;
}
2005-03-31 03:28:15 +04:00
static int sockaddr_convert_from_un ( const struct socket_info * si ,
2005-03-31 16:40:12 +04:00
const struct sockaddr_un * in_addr ,
socklen_t un_addrlen ,
int family ,
struct sockaddr * out_addr ,
socklen_t * out_len )
2005-03-28 05:00:39 +04:00
{
2005-03-31 16:40:12 +04:00
if ( out_addr = = NULL | | out_len = = NULL )
2005-03-28 05:00:39 +04:00
return 0 ;
2005-03-31 16:40:12 +04:00
if ( un_addrlen = = 0 ) {
* out_len = 0 ;
return 0 ;
}
2005-03-28 05:00:39 +04:00
switch ( family ) {
case AF_INET :
return convert_un_in ( in_addr , ( struct sockaddr_in * ) out_addr , out_len ) ;
2005-03-31 04:43:26 +04:00
case AF_UNIX :
2005-03-28 05:00:39 +04:00
memcpy ( out_addr , in_addr , sizeof ( * in_addr ) ) ;
* out_len = sizeof ( * in_addr ) ;
return 0 ;
default :
break ;
}
errno = EAFNOSUPPORT ;
return - 1 ;
}
int swrap_socket ( int domain , int type , int protocol )
{
struct socket_info * si ;
int fd ;
2005-07-21 17:45:07 +04:00
if ( ! socket_wrapper_dir ( ) ) {
2005-03-28 05:00:39 +04:00
return real_socket ( domain , type , protocol ) ;
}
2005-03-31 04:43:26 +04:00
fd = real_socket ( AF_UNIX , type , 0 ) ;
2005-03-28 05:00:39 +04:00
2005-03-31 16:40:12 +04:00
if ( fd = = - 1 ) return - 1 ;
2005-03-28 05:00:39 +04:00
2005-03-31 16:40:12 +04:00
si = calloc ( 1 , sizeof ( struct socket_info ) ) ;
2005-03-28 05:00:39 +04:00
si - > domain = domain ;
si - > type = type ;
si - > protocol = protocol ;
si - > fd = fd ;
DLIST_ADD ( sockets , si ) ;
return si - > fd ;
}
int swrap_accept ( int s , struct sockaddr * addr , socklen_t * addrlen )
{
struct socket_info * parent_si , * child_si ;
int fd ;
socklen_t un_addrlen = sizeof ( struct sockaddr_un ) ;
struct sockaddr_un un_addr ;
int ret ;
parent_si = find_socket_info ( s ) ;
if ( ! parent_si ) {
return real_accept ( s , addr , addrlen ) ;
}
2005-07-21 12:42:17 +04:00
memset ( & un_addr , 0 , sizeof ( un_addr ) ) ;
2005-03-28 05:00:39 +04:00
ret = real_accept ( s , ( struct sockaddr * ) & un_addr , & un_addrlen ) ;
2005-03-31 16:40:12 +04:00
if ( ret = = - 1 ) return ret ;
2005-03-28 05:00:39 +04:00
fd = ret ;
2005-03-31 16:40:12 +04:00
ret = sockaddr_convert_from_un ( parent_si , & un_addr , un_addrlen ,
parent_si - > domain , addr , addrlen ) ;
if ( ret = = - 1 ) return ret ;
2005-03-28 05:00:39 +04:00
child_si = malloc ( sizeof ( struct socket_info ) ) ;
memset ( child_si , 0 , sizeof ( * child_si ) ) ;
child_si - > fd = fd ;
2005-06-19 16:34:59 +04:00
child_si - > bound = 1 ;
2005-03-28 05:00:39 +04:00
2005-06-19 16:34:59 +04:00
child_si - > myname_len = parent_si - > myname_len ;
child_si - > myname = sockaddr_dup ( parent_si - > myname , parent_si - > myname_len ) ;
2005-03-28 05:00:39 +04:00
2005-06-10 16:21:46 +04:00
child_si - > peername_len = * addrlen ;
child_si - > peername = sockaddr_dup ( addr , * addrlen ) ;
DLIST_ADD ( sockets , child_si ) ;
2005-03-28 05:00:39 +04:00
return fd ;
}
2005-06-10 16:21:46 +04:00
/* using sendto() or connect() on an unbound socket would give the
recipient no way to reply , as unlike UDP and TCP , a unix domain
socket can ' t auto - assign emphemeral port numbers , so we need to
assign it here */
static int swrap_auto_bind ( struct socket_info * si )
{
struct sockaddr_un un_addr ;
struct sockaddr_in in ;
int i ;
un_addr . sun_family = AF_UNIX ;
for ( i = 0 ; i < 1000 ; i + + ) {
snprintf ( un_addr . sun_path , sizeof ( un_addr . sun_path ) ,
2005-07-21 17:45:07 +04:00
" %s/ " SOCKET_FORMAT , socket_wrapper_dir ( ) ,
2005-06-10 16:21:46 +04:00
SOCK_DGRAM , i + 10000 ) ;
if ( bind ( si - > fd , ( struct sockaddr * ) & un_addr ,
sizeof ( un_addr ) ) = = 0 ) {
si - > tmp_path = strdup ( un_addr . sun_path ) ;
si - > bound = 1 ;
break ;
}
}
if ( i = = 1000 ) {
return - 1 ;
}
memset ( & in , 0 , sizeof ( in ) ) ;
in . sin_family = AF_INET ;
in . sin_port = htons ( i ) ;
in . sin_addr . s_addr = htonl ( INADDR_LOOPBACK ) ;
si - > myname_len = sizeof ( in ) ;
si - > myname = sockaddr_dup ( & in , si - > myname_len ) ;
si - > bound = 1 ;
return 0 ;
}
2005-03-28 05:00:39 +04:00
int swrap_connect ( int s , const struct sockaddr * serv_addr , socklen_t addrlen )
{
int ret ;
struct sockaddr_un un_addr ;
struct socket_info * si = find_socket_info ( s ) ;
if ( ! si ) {
return real_connect ( s , serv_addr , addrlen ) ;
}
2005-03-31 16:40:12 +04:00
/* only allow pseudo loopback connections */
2005-05-01 23:08:35 +04:00
if ( serv_addr - > sa_family = = AF_INET & &
( ( const struct sockaddr_in * ) serv_addr ) - > sin_addr . s_addr ! =
2005-03-31 16:40:12 +04:00
htonl ( INADDR_LOOPBACK ) ) {
errno = ENETUNREACH ;
return - 1 ;
}
2005-06-10 16:21:46 +04:00
if ( si - > bound = = 0 & & si - > domain ! = AF_UNIX ) {
ret = swrap_auto_bind ( si ) ;
if ( ret = = - 1 ) return - 1 ;
}
2005-03-31 03:28:15 +04:00
ret = sockaddr_convert_to_un ( si , ( const struct sockaddr * ) serv_addr , addrlen , & un_addr ) ;
2005-03-31 16:40:12 +04:00
if ( ret = = - 1 ) return - 1 ;
2005-03-28 05:00:39 +04:00
2005-03-31 16:40:12 +04:00
ret = real_connect ( s , ( struct sockaddr * ) & un_addr ,
sizeof ( struct sockaddr_un ) ) ;
2005-03-28 05:00:39 +04:00
2005-03-31 16:40:12 +04:00
if ( ret = = 0 ) {
2005-03-28 05:00:39 +04:00
si - > peername_len = addrlen ;
2005-03-31 03:28:15 +04:00
si - > peername = sockaddr_dup ( serv_addr , addrlen ) ;
2005-03-28 05:00:39 +04:00
}
return ret ;
}
int swrap_bind ( int s , const struct sockaddr * myaddr , socklen_t addrlen )
{
int ret ;
struct sockaddr_un un_addr ;
struct socket_info * si = find_socket_info ( s ) ;
if ( ! si ) {
return real_bind ( s , myaddr , addrlen ) ;
}
2005-06-10 16:21:46 +04:00
si - > myname_len = addrlen ;
si - > myname = sockaddr_dup ( myaddr , addrlen ) ;
if ( myaddr - > sa_family = = AF_INET & &
( ( const struct sockaddr_in * ) myaddr ) - > sin_addr . s_addr = = 0 ) {
( ( struct sockaddr_in * ) si - > myname ) - > sin_addr . s_addr =
htonl ( INADDR_LOOPBACK ) ;
}
2005-03-31 03:28:15 +04:00
ret = sockaddr_convert_to_un ( si , ( const struct sockaddr * ) myaddr , addrlen , & un_addr ) ;
2005-03-31 16:40:12 +04:00
if ( ret = = - 1 ) return - 1 ;
2005-03-28 05:00:39 +04:00
unlink ( un_addr . sun_path ) ;
2005-03-31 16:40:12 +04:00
ret = real_bind ( s , ( struct sockaddr * ) & un_addr ,
sizeof ( struct sockaddr_un ) ) ;
2005-03-28 05:00:39 +04:00
2005-03-31 16:40:12 +04:00
if ( ret = = 0 ) {
si - > bound = 1 ;
2005-03-28 05:00:39 +04:00
}
return ret ;
}
int swrap_getpeername ( int s , struct sockaddr * name , socklen_t * addrlen )
{
struct socket_info * si = find_socket_info ( s ) ;
if ( ! si ) {
return real_getpeername ( s , name , addrlen ) ;
}
if ( ! si - > peername )
{
errno = ENOTCONN ;
return - 1 ;
}
memcpy ( name , si - > peername , si - > peername_len ) ;
* addrlen = si - > peername_len ;
return 0 ;
}
int swrap_getsockname ( int s , struct sockaddr * name , socklen_t * addrlen )
{
struct socket_info * si = find_socket_info ( s ) ;
if ( ! si ) {
2005-04-06 14:06:08 +04:00
return real_getsockname ( s , name , addrlen ) ;
2005-03-28 05:00:39 +04:00
}
memcpy ( name , si - > myname , si - > myname_len ) ;
* addrlen = si - > myname_len ;
return 0 ;
}
int swrap_getsockopt ( int s , int level , int optname , void * optval , socklen_t * optlen )
{
struct socket_info * si = find_socket_info ( s ) ;
if ( ! si ) {
return real_getsockopt ( s , level , optname , optval , optlen ) ;
}
2005-03-31 03:28:15 +04:00
if ( level = = SOL_SOCKET ) {
return real_getsockopt ( s , level , optname , optval , optlen ) ;
}
switch ( si - > domain ) {
2005-03-31 04:43:26 +04:00
case AF_UNIX :
2005-03-31 03:28:15 +04:00
return real_getsockopt ( s , level , optname , optval , optlen ) ;
default :
2005-03-28 05:00:39 +04:00
errno = ENOPROTOOPT ;
return - 1 ;
}
}
int swrap_setsockopt ( int s , int level , int optname , const void * optval , socklen_t optlen )
{
struct socket_info * si = find_socket_info ( s ) ;
if ( ! si ) {
return real_setsockopt ( s , level , optname , optval , optlen ) ;
}
2005-03-31 03:28:15 +04:00
if ( level = = SOL_SOCKET ) {
return real_setsockopt ( s , level , optname , optval , optlen ) ;
}
switch ( si - > domain ) {
2005-03-31 04:43:26 +04:00
case AF_UNIX :
2005-03-31 03:28:15 +04:00
return real_setsockopt ( s , level , optname , optval , optlen ) ;
case AF_INET :
/* Silence some warnings */
# ifdef TCP_NODELAY
if ( optname = = TCP_NODELAY )
return 0 ;
# endif
default :
2005-03-28 05:00:39 +04:00
errno = ENOPROTOOPT ;
return - 1 ;
}
}
ssize_t swrap_recvfrom ( int s , void * buf , size_t len , int flags , struct sockaddr * from , socklen_t * fromlen )
{
struct sockaddr_un un_addr ;
2005-03-31 16:40:12 +04:00
socklen_t un_addrlen = sizeof ( un_addr ) ;
2005-03-28 05:00:39 +04:00
int ret ;
struct socket_info * si = find_socket_info ( s ) ;
if ( ! si ) {
return real_recvfrom ( s , buf , len , flags , from , fromlen ) ;
}
2005-07-21 12:42:17 +04:00
/* irix 6.4 forgets to null terminate the sun_path string :-( */
memset ( & un_addr , 0 , sizeof ( un_addr ) ) ;
2005-03-28 05:00:39 +04:00
ret = real_recvfrom ( s , buf , len , flags , ( struct sockaddr * ) & un_addr , & un_addrlen ) ;
2005-03-31 16:40:12 +04:00
if ( ret = = - 1 )
2005-03-28 05:00:39 +04:00
return ret ;
2005-03-31 16:40:12 +04:00
if ( sockaddr_convert_from_un ( si , & un_addr , un_addrlen ,
si - > domain , from , fromlen ) = = - 1 ) {
return - 1 ;
}
2005-03-28 05:00:39 +04:00
return ret ;
}
2005-06-10 16:21:46 +04:00
2005-03-28 05:00:39 +04:00
ssize_t swrap_sendto ( int s , const void * buf , size_t len , int flags , const struct sockaddr * to , socklen_t tolen )
{
struct sockaddr_un un_addr ;
int ret ;
struct socket_info * si = find_socket_info ( s ) ;
if ( ! si ) {
return real_sendto ( s , buf , len , flags , to , tolen ) ;
}
2005-06-10 16:21:46 +04:00
if ( si - > bound = = 0 & & si - > domain ! = AF_UNIX ) {
ret = swrap_auto_bind ( si ) ;
if ( ret = = - 1 ) return - 1 ;
2005-03-31 16:40:12 +04:00
}
2005-03-31 03:28:15 +04:00
ret = sockaddr_convert_to_un ( si , to , tolen , & un_addr ) ;
2005-03-31 16:40:12 +04:00
if ( ret = = - 1 ) return - 1 ;
2005-03-28 05:00:39 +04:00
ret = real_sendto ( s , buf , len , flags , ( struct sockaddr * ) & un_addr , sizeof ( un_addr ) ) ;
2005-03-31 16:40:12 +04:00
2005-03-28 05:00:39 +04:00
return ret ;
}
int swrap_close ( int fd )
{
struct socket_info * si = find_socket_info ( fd ) ;
if ( si ) {
DLIST_REMOVE ( sockets , si ) ;
free ( si - > path ) ;
free ( si - > myname ) ;
free ( si - > peername ) ;
2005-03-31 16:40:12 +04:00
if ( si - > tmp_path ) {
unlink ( si - > tmp_path ) ;
free ( si - > tmp_path ) ;
}
2005-03-28 05:00:39 +04:00
free ( si ) ;
}
return real_close ( fd ) ;
}