/*
* 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 Library 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "socket.h"
static ErrType err_type = STD_ERROR;
static unsigned int g_use_ssl = 0;
static char *g_ssl_pathstore = NULL;
/*
* Tools functions
*/
const char * socket_strerror(int err) {
/* -----------------------------------------------------------------------------
* Get str error, that can be strerror or ssl error
*
* @param int, not used on OPENSSL_ERROR
* @return char *, a static buffer
* -------------------------------------------------------------------------- */
switch (err_type) {
case STD_ERROR:
return (const char *)strerror(err);
break;
case OPENSSL_ERROR:
#ifdef USE_SOCKET_SSL
err_type = STD_ERROR; /* Reinit to errno as it is the more frequent one */
return ERR_reason_error_string(ERR_get_error());
#endif
break;
}
return NULL;
}
void set_socket_ssl(unsigned int use) {
/* -----------------------------------------------------------------------------
* Toggle the variable g_use_ssl
*
* @param unsigned int
* -------------------------------------------------------------------------- */
g_use_ssl = use;
}
unsigned int get_socket_ssl() {
/* -----------------------------------------------------------------------------
* Get the variable g_use_ssl
*
* @return unsigned int
* -------------------------------------------------------------------------- */
return g_use_ssl;
}
void set_ssl_pathstore(char * pathstore) {
/* -----------------------------------------------------------------------------
* Toggle the variable g_ssl_pathstore
*
* @param char *
* !! The pointer must contain a dynamically memory allocate
* and must not be freed after
* -------------------------------------------------------------------------- */
if (g_ssl_pathstore != NULL)
free(g_ssl_pathstore);
g_ssl_pathstore = pathstore;
}
char * get_ssl_pathstore() {
/* -----------------------------------------------------------------------------
* Get the variable g_ssl_pathstore
*
* @return char *
* -------------------------------------------------------------------------- */
return g_ssl_pathstore;
}
#ifdef USE_SOCKET_SSL
int BIO_read_buffer(BIO *bio, void * buffer, int count) {
/* -----------------------------------------------------------------------------
* Read to buffer
*
* @param BIO *, the SSL BIO pointer to read on
* @param void *, the buffer where to store
* @param int, the max number of character to read
* @return int the number of character read or -1 if an error occurss
* -------------------------------------------------------------------------- */
int status = 0, n;
while(status < count) {
n = BIO_read(bio, (void *)&((char *)buffer)[status], count - status);
switch (n) {
case 0:
/* end-of-file (0) */
return n;
break;
case -1:
if (!BIO_should_retry(bio)) {
err_type = OPENSSL_ERROR;
return n;
}
break;
default:
status += n;
}
}
return status;
}
int BIO_readline(BIO *bio, char * buffer, int maxlen) {
/* -----------------------------------------------------------------------------
* Read line to buffer
*
* @param BIO *, the SSL BIO pointer to read on
* @param char *, the buffer where to store
* @param int, the max number of character to read
* @return int the number of character read
* -------------------------------------------------------------------------- */
int count = 0, status;
while(count < maxlen) {
/* Read one character */
if ((status = BIO_read_buffer(bio, buffer + count, 1)) <= 0) {
/* Nothing to read */
return count;
}
else {
if (buffer[count] == '\n'){
buffer[count + 1] = 0;
return count + 1;
}
}
count++;
}
buffer[count] = 0;
return 0;
}
char * X509_get_verify_error_string(unsigned int verify) {
/* -----------------------------------------------------------------------------
* Return an error string
*
* @param
* @param unsigned int, the verify error number, from SSL_get_verify_result()
* @return char *, error string
* -------------------------------------------------------------------------- */
static char verify_err_string[256];
verify_err_string[0] = '\0';
switch(verify) {
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
strlcpy(verify_err_string,
"unable to get issuer certificate",
sizeof(verify_err_string));
break;
case X509_V_ERR_UNABLE_TO_GET_CRL:
strlcpy(verify_err_string,
"unable to get certificate CRL",
sizeof(verify_err_string));
break;
case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE:
strlcpy(verify_err_string,
"nable to decrypt certificate's signature",
sizeof(verify_err_string));
break;
case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE:
strlcpy(verify_err_string,
"unable to decrypt CRL's signature",
sizeof(verify_err_string));
break;
case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY:
strlcpy(verify_err_string,
"unable to decode issuer public key",
sizeof(verify_err_string));
break;
case X509_V_ERR_CERT_SIGNATURE_FAILURE:
strlcpy(verify_err_string,
"certificate signature failure",
sizeof(verify_err_string));
break;
case X509_V_ERR_CRL_SIGNATURE_FAILURE:
strlcpy(verify_err_string,
"CRL signature failure",
sizeof(verify_err_string));
break;
case X509_V_ERR_CERT_NOT_YET_VALID:
strlcpy(verify_err_string,
"certificate is not yet valid",
sizeof(verify_err_string));
break;
case X509_V_ERR_CERT_HAS_EXPIRED:
strlcpy(verify_err_string,
"certificate has expired",
sizeof(verify_err_string));
break;
case X509_V_ERR_CRL_NOT_YET_VALID:
strlcpy(verify_err_string,
"CRL is not yet valid",
sizeof(verify_err_string));
break;
case X509_V_ERR_CRL_HAS_EXPIRED:
strlcpy(verify_err_string,
"CRL has expired",
sizeof(verify_err_string));
break;
case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
strlcpy(verify_err_string,
"format error in certificate's notBefore field",
sizeof(verify_err_string));
break;
case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
strlcpy(verify_err_string,
"format error in certificate's notAfter field",
sizeof(verify_err_string));
break;
case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD:
strlcpy(verify_err_string,
"format error in CRL's lastUpdate field",
sizeof(verify_err_string));
break;
case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD:
strlcpy(verify_err_string,
"format error in CRL's nextUpdate field",
sizeof(verify_err_string));
break;
case X509_V_ERR_OUT_OF_MEM:
strlcpy(verify_err_string,
"out of memory",
sizeof(verify_err_string));
break;
case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
strlcpy(verify_err_string,
"self signed certificate",
sizeof(verify_err_string));
break;
case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
strlcpy(verify_err_string,
"self signed certificate in certificate chain",
sizeof(verify_err_string));
break;
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
strlcpy(verify_err_string,
"unable to get issuer certificate locally",
sizeof(verify_err_string));
break;
case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE:
strlcpy(verify_err_string,
"unable to verify the first certificate",
sizeof(verify_err_string));
break;
case X509_V_ERR_CERT_CHAIN_TOO_LONG:
strlcpy(verify_err_string,
"certificate chain too long",
sizeof(verify_err_string));
break;
case X509_V_ERR_CERT_REVOKED:
strlcpy(verify_err_string,
"certificate revoked",
sizeof(verify_err_string));
break;
case X509_V_ERR_INVALID_CA:
strlcpy(verify_err_string,
"invalid CA certificate",
sizeof(verify_err_string));
break;
case X509_V_ERR_PATH_LENGTH_EXCEEDED:
strlcpy(verify_err_string,
"path length constraint exceeded",
sizeof(verify_err_string));
break;
case X509_V_ERR_INVALID_PURPOSE:
strlcpy(verify_err_string,
"unsupported certificate purpose",
sizeof(verify_err_string));
break;
case X509_V_ERR_CERT_UNTRUSTED:
strlcpy(verify_err_string,
"certificate not trusted",
sizeof(verify_err_string));
break;
case X509_V_ERR_CERT_REJECTED:
strlcpy(verify_err_string,
"certificate rejected",
sizeof(verify_err_string));
break;
case X509_V_ERR_SUBJECT_ISSUER_MISMATCH:
strlcpy(verify_err_string,
"subject issuer mismatch",
sizeof(verify_err_string));
break;
case X509_V_ERR_AKID_SKID_MISMATCH:
strlcpy(verify_err_string,
"authority and subject key identifier mismatch",
sizeof(verify_err_string));
break;
case X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH:
strlcpy(verify_err_string,
"authority and issuer serial number mismatch",
sizeof(verify_err_string));
break;
case X509_V_ERR_KEYUSAGE_NO_CERTSIGN:
strlcpy(verify_err_string,
"key usage does not include certificate signing",
sizeof(verify_err_string));
break;
case X509_V_ERR_APPLICATION_VERIFICATION:
strlcpy(verify_err_string,
"application verification failure",
sizeof(verify_err_string));
break;
}
return verify_err_string;
}
#endif
/*
* Server Socket functions
*/
ServerSocket *server_socket_new(const char * host, const char * port, const char * proto) {
/* -----------------------------------------------------------------------------
* Create a server socket
*
* @param const char *, the address to bind to
* @param const char *, port number or service name
* @param const char *, name protocol
* @return ServerSocket * or NULL
* -------------------------------------------------------------------------- */
struct sockaddr_in socketaddr;
int mastersock;
int trueval = TRUE;
ServerSocket * new_socket = NULL;
u_long ip_addr;
new_socket = (ServerSocket *)malloc(sizeof(ServerSocket));
if (new_socket == NULL) return NULL;
socketaddr_init(&socketaddr);
if (host == NULL) {
socketaddr.sin_addr.s_addr = INADDR_ANY;
new_socket->addr = NULL;
}
else {
if (isipv4(host)) {
ip_addr = inet_addr(host);
socketaddr.sin_addr.s_addr = ip_addr;
new_socket->addr = (char *)strdup(host);
if (new_socket->addr == NULL)
return NULL;
}
else {
if (!socketaddr_host(&socketaddr, host)) {
/* host cannot be resolved */
socketaddr.sin_addr.s_addr = INADDR_ANY;
new_socket->addr = NULL;
}
else {
new_socket->addr = (char *)strdup(host);
if (new_socket->addr == NULL)
return NULL;
}
}
}
if (port != NULL) {
socketaddr_service(&socketaddr, port, proto);
new_socket->port = (char *)strdup(port);
if (new_socket->port == NULL)
return NULL;
}
else
new_socket->port = NULL;
new_socket->proto = (char *)strdup(proto);
if (new_socket->proto == NULL)
return NULL;
mastersock = socket(PF_INET, prototype(proto), resolveproto(proto));
if (mastersock < 0) {
return NULL;
}
setsockopt(mastersock, SOL_SOCKET, SO_REUSEADDR, &trueval, sizeof(trueval));
if (bind(mastersock, (struct sockaddr *) &socketaddr, sizeof(socketaddr)) < 0) {
return NULL;
}
if (prototype(proto) == SOCK_STREAM) {
if (listen(mastersock, 5) < 0) {
return NULL;
}
}
new_socket->fd = mastersock;
return new_socket;
}
Socket *server_socket_accept(ServerSocket *socket) {
/* -----------------------------------------------------------------------------
* Do an accept() to the server socket
*
* @param ServerSocket *
* @return Socket *, client socket
* -------------------------------------------------------------------------- */
Socket * new_sock = NULL;
char port[5];
int sockid;
struct sockaddr_in socketaddr;
socklen_t size = sizeof(socketaddr);
if ((sockid = accept(socket->fd, (struct sockaddr *)&socketaddr, &size)) >= 0) {
new_sock = (Socket *)malloc(sizeof(Socket));
if (new_sock != NULL) {
new_sock->fd = sockid;
new_sock->host = (char *)strdup(inet_ntoa(socketaddr.sin_addr));
memset(&port, 0, 5);
snprintf(port, sizeof(port), "%d", ntohs(socketaddr.sin_port));
new_sock->port = (char *)strdup(port);
new_sock->proto = (char *)strdup("tcp");
new_sock->state = SOCK_CONNECTED;
return new_sock;
}
}
return NULL;
}
int server_socket_disconnect(ServerSocket *socket) {
/* -----------------------------------------------------------------------------
* Close a server socket
*
* @param ServerSocket *
* @return int
* -------------------------------------------------------------------------- */
if (close(socket->fd) == 0) {
return 1;
}
return 0;
}
int server_socket_destroy(ServerSocket *socket) {
/* -----------------------------------------------------------------------------
* Destroy a server socket
*
* @param ServerSocket *
* @return int
* -------------------------------------------------------------------------- */
server_socket_disconnect(socket);
if (socket->addr != NULL)
free(socket->addr);
free(socket->port);
free(socket->proto);
free(socket);
socket = NULL;
return 1;
}
/*
* Client Socket functions
*/
Socket *socket_new(const char *host, const char * port, const char * proto) {
/* -----------------------------------------------------------------------------
* Create a client socket
*
* @param const char *, the hostname or IP address
* @param const char *, the port number or service name
* @param const char *, name protocol
* @return Socket * or NULL
* -------------------------------------------------------------------------- */
int sockid = -1;
Socket * new_socket = NULL;
#ifdef USE_SOCKET_SSL
char *pathstore = NULL;
SSL *ssl;
#endif
new_socket = (Socket *)malloc(sizeof(Socket));
if (new_socket == NULL) return NULL;
new_socket->host = (char *)strdup(host);
if (new_socket->host == NULL) {
free(new_socket);
return NULL;
}
new_socket->port = (char *)strdup(port);
if (new_socket->port == NULL) {
free(new_socket->host);
free(new_socket);
return NULL;
}
new_socket->proto = (char *)strdup(proto);
if (new_socket->proto == NULL) {
free(new_socket->host);
free(new_socket->port);
free(new_socket);
return NULL;
}
new_socket->state = SOCK_DISCONNECTED;
new_socket->use_ssl = 0;
#ifdef USE_SOCKET_SSL
if (get_socket_ssl()) {
pathstore = get_ssl_pathstore();
if (pathstore != NULL) {
new_socket->ctx = SSL_CTX_new(SSLv23_client_method());
/* Load the Certificate Trust store */
if (!SSL_CTX_load_verify_locations(new_socket->ctx, pathstore, NULL)) {
err_type = OPENSSL_ERROR;
SSL_CTX_free(new_socket->ctx);
new_socket->ctx = NULL;
free(new_socket->host);
free(new_socket->port);
free(new_socket->proto);
free(new_socket);
return NULL;
}
/* Create the BIO structure */
new_socket->bio = BIO_new_ssl_connect(new_socket->ctx);
/* Auto responding in background, if the server requested another handshake */
BIO_get_ssl(new_socket->bio, &ssl);
SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);
new_socket->use_ssl = 1;
}
else {
logmsg(LOG_ERR, "an SSL certificate trust store must be specified.");
free(new_socket->host);
free(new_socket->port);
free(new_socket->proto);
free(new_socket);
return NULL;
}
}
else {
new_socket->bio = NULL;
new_socket->ctx = NULL;
#else
if (get_socket_ssl()) {
logmsg(LOG_ERR, "Warning : socket creation with SSL is requested but SSL "
"is not supported. Define the USE_SOCKET_SSL.");
}
#endif
sockid = socket(PF_INET, prototype(proto), resolveproto(proto));
if (sockid < 0) {
free(new_socket->host);
free(new_socket->port);
free(new_socket->proto);
free(new_socket);
return NULL;
}
#ifdef USE_SOCKET_SSL
}
#endif
new_socket->fd = sockid;
return new_socket;
}
int socket_bind(Socket *socket, const char *host, const char *port) {
/* -----------------------------------------------------------------------------
* Bind a socket to a particular port (and host)
*
* @param Socket *
* @param const char *, the ip / host address
* if NULL, INADDR_ANY will be used
* @param const char *, the port
* @return int
* -------------------------------------------------------------------------- */
struct sockaddr_in socketaddr;
u_long ip_addr;
socketaddr_init(&socketaddr);
socketaddr_service(&socketaddr, port, socket->proto);
if (host == NULL)
socketaddr.sin_addr.s_addr = INADDR_ANY;
else {
if (isipv4(host)) {
ip_addr = inet_addr(host);
socketaddr.sin_addr.s_addr = ip_addr;
}
else {
if (!socketaddr_host(&socketaddr, host))
/* host cannot be resolved */
socketaddr.sin_addr.s_addr = INADDR_ANY;
}
}
return bind(socket->fd, (struct sockaddr *)&socketaddr, sizeof(socketaddr));
}
int socket_connect(Socket *socket) {
/* -----------------------------------------------------------------------------
* Connect a client socket
*
* @param Socket *
* @return int (see SocketConnectStatus enum)
* -------------------------------------------------------------------------- */
struct sockaddr_in socketaddr;
int retcode = SOCK_OK;
#ifdef USE_SOCKET_SSL
SSL *ssl;
X509 *peerCertificate;
char commonName[512];
X509_NAME *name;
if ((socket->use_ssl)
&& (socket->bio != NULL)) {
if (isipv4(socket->host))
BIO_set_conn_ip(socket->bio, socket->host);
else
BIO_set_conn_hostname(socket->bio, socket->host);
BIO_set_conn_port(socket->bio, socket->port);
/* Verify the connection opened */
if (BIO_do_connect(socket->bio) <= 0) {
err_type = OPENSSL_ERROR;
return SOCK_ERR;
}
/* Verify the certificate validity
* Note that the connection can still be used
* The caller must chose to be paranoid and refuse the connection or not
*/
BIO_get_ssl(socket->bio, &ssl);
peerCertificate = SSL_get_peer_certificate(ssl);
if (peerCertificate != NULL) {
socket->ssl_verify = SSL_get_verify_result(ssl);
if (socket->ssl_verify != X509_V_OK) {
logmsg(LOG_ERR, "Warning : certificate cannot be verified with trust store : %s",
X509_get_verify_error_string(socket->ssl_verify));
retcode = SOCK_SSL_VERIFY_ERR;
}
name = X509_get_subject_name(peerCertificate);
X509_NAME_get_text_by_NID(name, NID_commonName, commonName, 512);
if(strncmp(commonName, socket->host, 512) != 0) {
logmsg(LOG_ERR, "Warning : peer certificate name verification failed for %s"
" : commonName mis-match : %s", socket->host, commonName);
retcode = SOCK_SSL_CN_MISMATCH_ERR;
}
}
else {
logmsg(LOG_ERR, "Warning : no peer certificate found");
retcode = SOCK_SSL_NO_PEER_CERT_ERR;
}
/* Get the file descriptor, useful for select() loop */
socket->fd = SSL_get_fd(ssl);
}
else {
#endif
socketaddr_init(&socketaddr);
socketaddr_service(&socketaddr, socket->port, socket->proto);
socketaddr_host(&socketaddr, socket->host);
if (connect(socket->fd, (struct sockaddr *)&socketaddr, sizeof(socketaddr)) < 0)
return SOCK_ERR;
#ifdef USE_SOCKET_SSL
}
#endif
socket->state = SOCK_CONNECTED;
return retcode;
}
int socket_disconnect(Socket *socket) {
/* -----------------------------------------------------------------------------
* Close a client socket
*
* @param Socket *
* @return int
* -------------------------------------------------------------------------- */
#ifdef USE_SOCKET_SSL
if ((socket->use_ssl)
&& (socket->bio != NULL)) {
BIO_reset(socket->bio);
socket->state = SOCK_DISCONNECTED;
return 1;
}
else {
#endif
if (close(socket->fd) == 0) {
socket->state = SOCK_DISCONNECTED;
return 1;
}
#ifdef USE_SOCKET_SSL
}
#endif
return 0;
}
int socket_destroy(Socket *socket) {
/* -----------------------------------------------------------------------------
* Destroy a client socket
*
* @param Socket *
* @return int
* -------------------------------------------------------------------------- */
socket_disconnect(socket);
free(socket->host);
free(socket->port);
free(socket->proto);
#ifdef USE_SOCKET_SSL
if ((socket->use_ssl)
&& (socket->bio != NULL)) {
BIO_free_all(socket->bio);
SSL_CTX_free(socket->ctx);
}
#endif
free(socket);
socket = NULL;
return 1;
}
int socket_readline(Socket *socket, char * buffer, int maxlen) {
/* -----------------------------------------------------------------------------
* Read line from a client socket
*
* @param Socket *
* @param char *, buffer to fill
* @param int, maxlen to read
* @return int, number of characters read
* -------------------------------------------------------------------------- */
#ifdef USE_SOCKET_SSL
if ((socket->use_ssl)
&& (socket->bio != NULL)) {
return BIO_readline(socket->bio, buffer, maxlen);
}
else {
#endif
return readline(socket->fd, buffer, maxlen);
#ifdef USE_SOCKET_SSL
}
#endif
}
int socket_read(Socket *socket, char * buffer, int maxlen) {
/* -----------------------------------------------------------------------------
* Read from a client socket
*
* @param Socket *
* @param char *, buffer to fill
* @param int, maxlen to read
* @return int, number of characters read
* -------------------------------------------------------------------------- */
#ifdef USE_SOCKET_SSL
if ((socket->use_ssl)
&& (socket->bio != NULL)) {
return BIO_read_buffer(socket->bio, buffer, maxlen);
}
else {
#endif
return read_buffer(socket->fd, buffer, maxlen);
#ifdef USE_SOCKET_SSL
}
#endif
}
int socket_write(Socket *socket, int flags, const char * str, ...) {
/* -----------------------------------------------------------------------------
* Write to client socket
*
* @param Socket *
* @param int, flags passed to send()
* @param const char *, printf format
* @return int, number of characters written
* -------------------------------------------------------------------------- */
va_list args;
char *result = NULL;
int retval;
va_start(args, str);
vasprintf(&result, str, args);
va_end(args);
if (result != NULL) {
#ifdef USE_SOCKET_SSL
if ((socket->use_ssl)
&& (socket->bio != NULL)) {
retval = BIO_write(socket->bio, result, strlen(result));
if (retval <= 0) {
if (!BIO_should_retry(socket->bio)) {
err_type = OPENSSL_ERROR;
}
}
}
else {
#endif
retval = send(socket->fd, result, strlen(result), flags);
free(result);
switch(errno) {
case EBADF:
case ENOTSOCK:
case EFAULT:
case EMSGSIZE:
case EAGAIN:
case ENOBUFS:
case EINTR:
case ENOMEM:
case EINVAL:
case EPIPE:
return -1;
break;
default:
return retval;
break;
}
#ifdef USE_SOCKET_SSL
}
#endif
}
return -1;
}
int socket_rawrite(Socket *socket, int flags, const char * data, int size) {
/* -----------------------------------------------------------------------------
* Write to client socket
*
* @param Socket *
* @param int, flags passed to send()
* @param const void *, data to write
* @param int, number of bytes to write
* @return int, number of characters written
* -------------------------------------------------------------------------- */
int retval;
#ifdef USE_SOCKET_SSL
if ((socket->use_ssl)
&& (socket->bio != NULL)) {
retval = BIO_write(socket->bio, data, size);
if (retval <= 0) {
if (!BIO_should_retry(socket->bio)) {
err_type = OPENSSL_ERROR;
}
}
}
else {
#endif
retval = send(socket->fd, data, size, flags);
switch(errno) {
case EBADF:
case ENOTSOCK:
case EFAULT:
case EMSGSIZE:
case EAGAIN:
case ENOBUFS:
case EINTR:
case ENOMEM:
case EINVAL:
case EPIPE:
return -1;
break;
default:
return retval;
break;
}
#ifdef USE_SOCKET_SSL
}
#endif
return -1;
}
syntax highlighted by Code2HTML, v. 0.9.1