2010-01-19 04:56:37 +03:00
/*-*- Mode: C; c-basic-offset: 8 -*-*/
2010-02-03 15:03:47 +03:00
/***
This file is part of systemd .
Copyright 2010 Lennart Poettering
systemd 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 .
systemd 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 systemd ; If not , see < http : //www.gnu.org/licenses/>.
* * */
2010-01-19 04:56:37 +03:00
# include <assert.h>
# include <string.h>
# include <unistd.h>
# include <errno.h>
# include <stdlib.h>
# include <arpa/inet.h>
# include <stdio.h>
2010-01-23 05:35:54 +03:00
# include <net/if.h>
2010-02-12 04:02:14 +03:00
# include <sys/types.h>
# include <sys/stat.h>
2010-01-19 04:56:37 +03:00
# include "macro.h"
# include "util.h"
# include "socket-util.h"
2010-01-23 05:35:54 +03:00
int socket_address_parse ( SocketAddress * a , const char * s ) {
2010-01-19 04:56:37 +03:00
int r ;
char * e , * n ;
unsigned u ;
assert ( a ) ;
assert ( s ) ;
2010-01-24 02:39:29 +03:00
zero ( * a ) ;
2010-01-23 05:35:54 +03:00
a - > type = SOCK_STREAM ;
2010-01-19 04:56:37 +03:00
if ( * s = = ' [ ' ) {
/* IPv6 in [x:.....:z]:p notation */
if ( ! ( e = strchr ( s + 1 , ' ] ' ) ) )
return - EINVAL ;
if ( ! ( n = strndup ( s + 1 , e - s - 1 ) ) )
return - ENOMEM ;
errno = 0 ;
if ( inet_pton ( AF_INET6 , n , & a - > sockaddr . in6 . sin6_addr ) < = 0 ) {
free ( n ) ;
return errno ! = 0 ? - errno : - EINVAL ;
}
free ( n ) ;
e + + ;
if ( * e ! = ' : ' )
return - EINVAL ;
e + + ;
if ( ( r = safe_atou ( e , & u ) ) < 0 )
return r ;
if ( u < = 0 | | u > 0xFFFF )
return - EINVAL ;
a - > sockaddr . in6 . sin6_family = AF_INET6 ;
a - > sockaddr . in6 . sin6_port = htons ( ( uint16_t ) u ) ;
a - > size = sizeof ( struct sockaddr_in6 ) ;
} else if ( * s = = ' / ' ) {
/* AF_UNIX socket */
size_t l ;
l = strlen ( s ) ;
if ( l > = sizeof ( a - > sockaddr . un . sun_path ) )
return - EINVAL ;
a - > sockaddr . un . sun_family = AF_UNIX ;
memcpy ( a - > sockaddr . un . sun_path , s , l ) ;
a - > size = sizeof ( sa_family_t ) + l + 1 ;
2010-01-28 06:50:28 +03:00
} else if ( * s = = ' @ ' ) {
2010-01-19 04:56:37 +03:00
/* Abstract AF_UNIX socket */
size_t l ;
l = strlen ( s + 1 ) ;
if ( l > = sizeof ( a - > sockaddr . un . sun_path ) - 1 )
return - EINVAL ;
a - > sockaddr . un . sun_family = AF_UNIX ;
memcpy ( a - > sockaddr . un . sun_path + 1 , s + 1 , l ) ;
a - > size = sizeof ( struct sockaddr_un ) ;
} else {
if ( ( e = strchr ( s , ' : ' ) ) ) {
2010-01-23 05:35:54 +03:00
if ( ( r = safe_atou ( e + 1 , & u ) ) < 0 )
return r ;
if ( u < = 0 | | u > 0xFFFF )
return - EINVAL ;
2010-01-19 04:56:37 +03:00
if ( ! ( n = strndup ( s , e - s ) ) )
return - ENOMEM ;
2010-01-23 05:35:54 +03:00
/* IPv4 in w.x.y.z:p notation? */
if ( ( r = inet_pton ( AF_INET , n , & a - > sockaddr . in4 . sin_addr ) ) < 0 ) {
2010-01-19 04:56:37 +03:00
free ( n ) ;
2010-01-23 05:35:54 +03:00
return - errno ;
2010-01-19 04:56:37 +03:00
}
2010-01-23 05:35:54 +03:00
if ( r > 0 ) {
/* Gotcha, it's a traditional IPv4 address */
free ( n ) ;
2010-01-19 04:56:37 +03:00
2010-01-23 05:35:54 +03:00
a - > sockaddr . in4 . sin_family = AF_INET ;
a - > sockaddr . in4 . sin_port = htons ( ( uint16_t ) u ) ;
a - > size = sizeof ( struct sockaddr_in ) ;
} else {
unsigned idx ;
2010-01-19 04:56:37 +03:00
2010-01-27 06:31:52 +03:00
if ( strlen ( n ) > IF_NAMESIZE - 1 ) {
free ( n ) ;
return - EINVAL ;
}
2010-01-23 05:35:54 +03:00
/* Uh, our last resort, an interface name */
idx = if_nametoindex ( n ) ;
free ( n ) ;
2010-01-24 00:56:47 +03:00
if ( idx = = 0 )
2010-01-23 05:35:54 +03:00
return - EINVAL ;
2010-01-19 04:56:37 +03:00
2010-01-23 05:35:54 +03:00
a - > sockaddr . in6 . sin6_family = AF_INET6 ;
a - > sockaddr . in6 . sin6_port = htons ( ( uint16_t ) u ) ;
a - > sockaddr . in6 . sin6_scope_id = idx ;
2010-01-24 00:56:47 +03:00
a - > sockaddr . in6 . sin6_addr = in6addr_any ;
2010-01-23 05:35:54 +03:00
a - > size = sizeof ( struct sockaddr_in6 ) ;
2010-01-27 06:31:52 +03:00
2010-01-23 05:35:54 +03:00
}
2010-01-19 04:56:37 +03:00
} else {
/* Just a port */
if ( ( r = safe_atou ( s , & u ) ) < 0 )
return r ;
if ( u < = 0 | | u > 0xFFFF )
return - EINVAL ;
a - > sockaddr . in6 . sin6_family = AF_INET6 ;
a - > sockaddr . in6 . sin6_port = htons ( ( uint16_t ) u ) ;
2010-01-24 00:56:47 +03:00
a - > sockaddr . in6 . sin6_addr = in6addr_any ;
2010-01-19 04:56:37 +03:00
a - > size = sizeof ( struct sockaddr_in6 ) ;
}
}
return 0 ;
}
2010-01-23 05:35:54 +03:00
int socket_address_verify ( const SocketAddress * a ) {
2010-01-19 04:56:37 +03:00
assert ( a ) ;
2010-01-23 05:35:54 +03:00
switch ( socket_address_family ( a ) ) {
2010-01-19 04:56:37 +03:00
case AF_INET :
if ( a - > size ! = sizeof ( struct sockaddr_in ) )
return - EINVAL ;
if ( a - > sockaddr . in4 . sin_port = = 0 )
return - EINVAL ;
return 0 ;
case AF_INET6 :
if ( a - > size ! = sizeof ( struct sockaddr_in6 ) )
return - EINVAL ;
if ( a - > sockaddr . in6 . sin6_port = = 0 )
return - EINVAL ;
return 0 ;
case AF_UNIX :
if ( a - > size < sizeof ( sa_family_t ) )
return - EINVAL ;
if ( a - > size > sizeof ( sa_family_t ) ) {
if ( a - > sockaddr . un . sun_path [ 0 ] = = 0 ) {
/* abstract */
if ( a - > size ! = sizeof ( struct sockaddr_un ) )
return - EINVAL ;
} else {
char * e ;
/* path */
if ( ! ( e = memchr ( a - > sockaddr . un . sun_path , 0 , sizeof ( a - > sockaddr . un . sun_path ) ) ) )
return - EINVAL ;
if ( a - > size ! = sizeof ( sa_family_t ) + ( e - a - > sockaddr . un . sun_path ) + 1 )
return - EINVAL ;
}
}
return 0 ;
default :
return - EAFNOSUPPORT ;
}
}
2010-01-23 05:35:54 +03:00
int socket_address_print ( const SocketAddress * a , char * * p ) {
2010-01-19 04:56:37 +03:00
int r ;
assert ( a ) ;
assert ( p ) ;
2010-01-23 05:35:54 +03:00
if ( ( r = socket_address_verify ( a ) ) < 0 )
2010-01-19 04:56:37 +03:00
return r ;
2010-01-23 05:35:54 +03:00
switch ( socket_address_family ( a ) ) {
2010-01-19 04:56:37 +03:00
case AF_INET : {
char * ret ;
if ( ! ( ret = new ( char , INET_ADDRSTRLEN + 1 + 5 + 1 ) ) )
return - ENOMEM ;
if ( ! inet_ntop ( AF_INET , & a - > sockaddr . in4 . sin_addr , ret , INET_ADDRSTRLEN ) ) {
free ( ret ) ;
return - errno ;
}
sprintf ( strchr ( ret , 0 ) , " :%u " , ntohs ( a - > sockaddr . in4 . sin_port ) ) ;
* p = ret ;
return 0 ;
}
case AF_INET6 : {
char * ret ;
if ( ! ( ret = new ( char , 1 + INET6_ADDRSTRLEN + 2 + 5 + 1 ) ) )
return - ENOMEM ;
ret [ 0 ] = ' [ ' ;
if ( ! inet_ntop ( AF_INET6 , & a - > sockaddr . in6 . sin6_addr , ret + 1 , INET6_ADDRSTRLEN ) ) {
free ( ret ) ;
return - errno ;
}
sprintf ( strchr ( ret , 0 ) , " ]:%u " , ntohs ( a - > sockaddr . in6 . sin6_port ) ) ;
* p = ret ;
return 0 ;
}
case AF_UNIX : {
char * ret ;
if ( a - > size < = sizeof ( sa_family_t ) ) {
if ( ! ( ret = strdup ( " <unamed> " ) ) )
return - ENOMEM ;
} else if ( a - > sockaddr . un . sun_path [ 0 ] = = 0 ) {
/* abstract */
/* FIXME: We assume we can print the
* socket path here and that it hasn ' t
* more than one NUL byte . That is
* actually an invalid assumption */
if ( ! ( ret = new ( char , sizeof ( a - > sockaddr . un . sun_path ) + 1 ) ) )
return - ENOMEM ;
2010-01-28 06:50:28 +03:00
ret [ 0 ] = ' @ ' ;
2010-01-19 04:56:37 +03:00
memcpy ( ret + 1 , a - > sockaddr . un . sun_path + 1 , sizeof ( a - > sockaddr . un . sun_path ) - 1 ) ;
ret [ sizeof ( a - > sockaddr . un . sun_path ) ] = 0 ;
} else {
if ( ! ( ret = strdup ( a - > sockaddr . un . sun_path ) ) )
return - ENOMEM ;
}
* p = ret ;
return 0 ;
}
default :
return - EINVAL ;
}
}
2010-02-12 04:02:14 +03:00
int socket_address_listen (
const SocketAddress * a ,
int backlog ,
SocketAddressBindIPv6Only only ,
const char * bind_to_device ,
mode_t directory_mode ,
mode_t socket_mode ,
int * ret ) {
2010-01-27 06:31:52 +03:00
int r , fd , one ;
2010-01-19 04:56:37 +03:00
assert ( a ) ;
2010-01-24 00:56:47 +03:00
assert ( ret ) ;
2010-01-19 04:56:37 +03:00
2010-01-23 05:35:54 +03:00
if ( ( r = socket_address_verify ( a ) ) < 0 )
2010-01-19 04:56:37 +03:00
return r ;
2010-01-23 05:35:54 +03:00
if ( ( fd = socket ( socket_address_family ( a ) , a - > type | SOCK_NONBLOCK | SOCK_CLOEXEC , 0 ) ) < 0 )
2010-01-19 04:56:37 +03:00
return - errno ;
2010-01-23 05:35:54 +03:00
if ( socket_address_family ( a ) = = AF_INET6 & & only ! = SOCKET_ADDRESS_DEFAULT ) {
int flag = only = = SOCKET_ADDRESS_IPV6_ONLY ;
2010-01-27 06:31:52 +03:00
if ( setsockopt ( fd , IPPROTO_IPV6 , IPV6_V6ONLY , & flag , sizeof ( flag ) ) < 0 )
goto fail ;
2010-01-23 05:35:54 +03:00
}
2010-01-27 06:31:52 +03:00
if ( bind_to_device )
if ( setsockopt ( fd , SOL_SOCKET , SO_BINDTODEVICE , bind_to_device , strlen ( bind_to_device ) + 1 ) < 0 )
goto fail ;
one = 1 ;
if ( setsockopt ( fd , SOL_SOCKET , SO_REUSEADDR , & one , sizeof ( one ) ) < 0 )
goto fail ;
2010-02-12 04:02:14 +03:00
if ( socket_address_family ( a ) = = AF_UNIX & & a - > sockaddr . un . sun_path [ 0 ] ! = 0 ) {
mode_t old_mask ;
/* Create parents */
mkdir_parents ( a - > sockaddr . un . sun_path , directory_mode ) ;
/* Enforce the right access mode for the socket*/
old_mask = umask ( ~ socket_mode ) ;
/* Include the original umask in our mask */
umask ( ~ socket_mode | old_mask ) ;
r = bind ( fd , & a - > sockaddr . sa , a - > size ) ;
if ( r < 0 & & errno = = EADDRINUSE ) {
/* Unlink and try again */
unlink ( a - > sockaddr . un . sun_path ) ;
r = bind ( fd , & a - > sockaddr . sa , a - > size ) ;
}
umask ( old_mask ) ;
} else
r = bind ( fd , & a - > sockaddr . sa , a - > size ) ;
if ( r < 0 )
2010-01-27 06:31:52 +03:00
goto fail ;
2010-01-19 04:56:37 +03:00
if ( a - > type = = SOCK_STREAM )
2010-01-27 06:31:52 +03:00
if ( listen ( fd , backlog ) < 0 )
goto fail ;
2010-01-19 04:56:37 +03:00
2010-01-24 00:56:47 +03:00
* ret = fd ;
2010-01-19 04:56:37 +03:00
return 0 ;
2010-01-27 06:31:52 +03:00
fail :
r = - errno ;
close_nointr ( fd ) ;
return r ;
2010-01-19 04:56:37 +03:00
}