/* * Copyright (C), 2000-2007 by the monit project group. * All Rights Reserved. * * 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 3 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, see . */ #include "config.h" #ifdef HAVE_SYS_TYPES_H #include #endif #ifdef HAVE_STRING_H #include #endif #ifdef HAVE_SYS_SOCKET_H #include #endif #ifdef HAVE_STRINGS_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #include "net.h" #include "ssl.h" #include "monitor.h" #include "socket.h" /** * Implementation of the socket interface. * * @author Jan-Henrik Haukeland, * @version \$Id: socket.c,v 1.63 2007/10/02 13:58:49 hauk Exp $ * @file */ /* ------------------------------------------------------------- Definitions */ #define TYPE_LOCAL 0 #define TYPE_ACCEPT 1 #define RBUFFER_SIZE 1024 struct Socket_T { int port; int type; int socket; char *host; Port_T Port; int timeout; int connection_type; ssl_connection *ssl; ssl_server_connection *sslserver; int length; int offset; unsigned char buffer[RBUFFER_SIZE+1]; }; /* -------------------------------------------------------------- Prototypes */ static int fill(Socket_T S, int timeout); /* ------------------------------------------------------------------ Public */ /** * Create a new Socket opened against host:port. The returned Socket * is a connected socket. This method can be used to create either TCP * or UDP sockets and the type parameter is used to select the socket * type. If the use_ssl parameter is TRUE the socket is created using * SSL. Only TCP sockets may use SSL. * @param host The remote host to open the Socket against. The host * may be a hostname found in the DNS or an IP address string. * @param port The port number to connect to * @param type The socket type to use (SOCKET_TCP or SOCKET_UPD) * @param use_ssl If TRUE the socket is created supporting SSL * @param timeout The timeout value in seconds * @return The connected Socket or NULL if an error occurred */ Socket_T socket_new(const char *host, int port, int type, int use_ssl, int timeout) { Ssl_T ssl; ssl.use_ssl= use_ssl; ssl.version= SSL_VERSION_AUTO; ssl.certmd5= NULL; return socket_create_t(host, port, type, ssl, timeout); } /** * Factory method for creating a new Socket from a monit Port object * @param port The port object to create a socket from * @return The connected Socket or NULL if an error occurred */ Socket_T socket_create(void *port) { int s; Port_T p= port; ASSERT(port); if((s= create_generic_socket(p)) != -1) { Socket_T S= NULL; NEW(S); S->socket= s; S->length= 0; S->offset= 0; S->type= p->type; S->port= p->port; S->timeout= p->timeout; S->connection_type= TYPE_LOCAL; if(p->family==AF_UNIX) { S->host= xstrdup(LOCALHOST); } else { S->host= xstrdup(p->hostname); } if(p->SSL.use_ssl && !socket_switch2ssl(S, p->SSL)) { socket_free(&S); return NULL; } S->Port= port; return S; } return NULL; } /** * Create a new Socket opened against host:port with an explicit * ssl value for connect and read. Otherwise, same as socket_new() * @param host The remote host to open the Socket against. The host * may be a hostname found in the DNS or an IP address string. * @param port The port number to connect to * @param type The socket type to use (SOCKET_TCP or SOCKET_UPD) * @param ssl Options for SSL * @param timeout The timeout value in seconds * @return The connected Socket or NULL if an error occurred */ Socket_T socket_create_t(const char *host, int port, int type, Ssl_T ssl, int timeout) { int s; int proto= type==SOCKET_UDP?SOCK_DGRAM:SOCK_STREAM; ASSERT(host); ASSERT((type==SOCKET_UDP)||(type==SOCKET_TCP)); if(ssl.use_ssl) { ASSERT(type==SOCKET_TCP); } ASSERT(timeout>0); if((s= create_socket(host, port, proto, timeout)) != -1) { Socket_T S= NULL; NEW(S); S->socket= s; S->length= 0; S->offset= 0; S->port= port; S->type= proto; S->timeout= timeout; S->host= xstrdup(host); S->connection_type= TYPE_LOCAL; if(ssl.use_ssl && !socket_switch2ssl(S, ssl)) { socket_free(&S); return NULL; } return S; } return NULL; } /** * Factory method for creating a Socket object from an accepted * socket. The given socket must be a socket created from accept(2). * If the sslserver context is non-null the socket will support * ssl. This method does only support TCP sockets. * @param socket The accepted socket * @param remote_host The remote host from where the socket connection * originated * @param port The localhost port number from where the connection * arrived. * @param sslserver A ssl server connection context, may be NULL * @return A Socket or NULL if an error occurred */ Socket_T socket_create_a(int socket, const char *remote_host, int port, void *sslserver) { Socket_T S; ASSERT(socket>=0); ASSERT(remote_host); NEW(S); S->length= 0; S->offset= 0; S->port= port; S->socket= socket; S->type= SOCK_STREAM; S->timeout= NET_TIMEOUT; S->host= xstrdup(remote_host); S->connection_type= TYPE_ACCEPT; if(sslserver) { S->sslserver= sslserver; if(! (S->ssl= insert_accepted_ssl_socket(S->sslserver))) { goto ssl_error; } if(! embed_accepted_ssl_socket(S->ssl, S->socket)) { goto ssl_error; } } return S; ssl_error: socket_free(&S); return NULL; } /** * Destroy a Socket object. Close the socket and release allocated * resources. * @param S A Socket object reference */ void socket_free(Socket_T *S) { ASSERT(S && *S); #ifdef HAVE_OPENSSL if((*S)->ssl && (*S)->ssl->handler) { if((*S)->connection_type==TYPE_LOCAL) { close_ssl_socket((*S)->ssl); delete_ssl_socket((*S)->ssl); } else if((*S)->connection_type==TYPE_ACCEPT && (*S)->sslserver) { close_accepted_ssl_socket((*S)->sslserver, (*S)->ssl); } } #endif close_socket((*S)->socket); FREE((*S)->host); FREE(*S); } /* ------------------------------------------------------------ Properties */ /** * Returns TRUE if the socket is ready for i|o * @param S A Socket object * @return TRUE if the socket is ready otherwise FALSE */ int socket_is_ready(Socket_T S) { ASSERT(S); switch(S->type) { case SOCK_STREAM: return check_socket(S->socket); case SOCK_DGRAM: return check_udp_socket(S->socket); default: break; } return FALSE; } /** * Get the underlying socket descriptor * @param S A Socket object * @return The socket descriptor */ int socket_get_socket(Socket_T S) { ASSERT(S); return S->socket; } /** * Get the type of this socket. * @param S A Socket object * @return The socket type */ int socket_get_type(Socket_T S) { ASSERT(S); return S->type; } /** * Get the Port object used to create this socket. If no Port object * was used this method returns NULL. * @param S A Socket object * @return The Port object or NULL */ void *socket_get_Port(Socket_T S) { ASSERT(S); return S->Port; } /** * Get the remote port number the socket is connected to * @param S A Socket object * @return The remote host's port number */ int socket_get_remote_port(Socket_T S) { ASSERT(S); return S->port; } /** * Get the remote host this socket is connected to. The host is either * a host name in DNS or an IP address string. * @param S A Socket object * @return The remote host */ const char *socket_get_remote_host(Socket_T S) { ASSERT(S); return S->host; } /* ---------------------------------------------------------------- Public */ /** * Switches a connected socket to ssl. * @param S The already connected socket * @param ssl Options for ssl * @return TRUE if ssl is ready otherwise FALSE */ int socket_switch2ssl(Socket_T S, Ssl_T ssl) { if(! (S->ssl= new_ssl_connection(NULL, ssl.version))) return FALSE; if(! embed_ssl_socket(S->ssl, S->socket)) return FALSE; if(ssl.certmd5 && !check_ssl_md5sum(S->ssl, ssl.certmd5)) { LogError("md5sum of certificate does not match!"); return FALSE; } return TRUE; } /** * Writes a character string. Use this function to send text based * messages to a client. * @param S A Socket_T object * @param m A String to send to the client * @return The bytes sent or -1 if an error occured */ int socket_print(Socket_T S, const char *m, ...) { int n; long l; va_list ap; char *buf= NULL; ASSERT(S); ASSERT(m); va_start(ap, m); buf= Util_formatString(m, ap, &l); va_end(ap); n= socket_write(S, buf, l); FREE(buf); return n; } /** * Write size bytes from the buffer b. * @param S A Socket_T object * @param b The data to be written * @param size The size of the data in b * @return The bytes sent or -1 if an error occured */ int socket_write(Socket_T S, void *b, int size) { int n= 0; void *p= b; ASSERT(S); /* Clear any extra data read from the server */ socket_reset(S); while(size > 0) { if(S->ssl) { n= send_ssl_socket(S->ssl, p, size, S->timeout); } else { if(S->type==SOCK_DGRAM) n= udp_write(S->socket, p, size, S->timeout); else n= sock_write(S->socket, p, size, S->timeout); } if(n <= 0) break; p+= n; size-= n; } if(n < 0) { /* No write or a partial write is an error */ return -1; } return (int)(p - b); } /** * Read a single byte. The byte is returned as an int in the range 0 * to 255. * @param S A Socket_T object * @return The byte read, or -1 if the end of the stream has been reached */ int socket_read_byte(Socket_T S) { ASSERT(S); if(S->offset >= S->length) { if(fill(S, S->timeout) <= 0) return -1; } return S->buffer[S->offset++]; } /** * Reads size bytes and stores them into the byte buffer pointed to by b. * @param S A Socket_T object * @param b A Byte buffer * @param size The size of the buffer b * @return The bytes read or -1 if an error occured */ int socket_read(Socket_T S, void *b, int size) { int c; unsigned char *p= b; ASSERT(S); while((size-- > 0) && ((c= socket_read_byte(S)) >= 0)) { *p++= c; } return (long)p - (long)b; } /** * Reads in at most one less than size characters and * stores them into the buffer pointed to by s. Reading stops after * an EOF or a newline. If a newline is read, it is stored into the * buffer. A '\0' is stored after the last character in the buffer. * @param S A Socket_T object * @param s A character buffer to store the string in * @param size The size of the string buffer, s * @return s on success, and NULL on error or when end of file occurs * while no characters have been read. */ char *socket_readln(Socket_T S, char *s, int size) { int c; unsigned char *p= (unsigned char *)s; ASSERT(S); while(--size && ((c= socket_read_byte(S)) > 0)) { *p++= c; if(c == '\n' || c == '\0') break; } *p= 0; if(*s) return s; return NULL; } /** * Clears any data that exists in the input buffer * @param S A Socket_T object */ void socket_reset(Socket_T S) { ASSERT(S); /* Throw away any pending incomming data */ while(fill(S, 0) > 0); S->offset= 0; S->length= 0; } /* --------------------------------------------------------------- Private */ /* * Fill the internal buffer. If an error occurs or if the read * operation timed out -1 is returned. * @param S A Socket object * @param timeout The number of seconds to wait for data to be read * @return TRUE (the length of data read) or -1 if an error occured */ static int fill(Socket_T S, int timeout) { int n; S->offset= 0; S->length= 0; /* Optimizing, assuming a request/response pattern and that a udp_write was issued before we are called, we don't have to wait for data */ if(S->type==SOCK_DGRAM) timeout= 0; /* Read as much as we can, but only block on the first read */ while(RBUFFER_SIZE > S->length) { if(S->ssl) { n= recv_ssl_socket(S->ssl, S->buffer + S->length, RBUFFER_SIZE-S->length, timeout); } else { n= sock_read(S->socket, S->buffer + S->length, RBUFFER_SIZE-S->length, timeout); } timeout= 0; if(n > 0) { S->length+= n; continue; } else if(n < 0) { if(errno == EAGAIN || errno == EWOULDBLOCK || S->type==SOCK_DGRAM) break; return -1; } else break; } return S->length; }