#define EFL_NET_SOCKET_SSL_PROTECTED 1 #define EFL_IO_READER_PROTECTED 1 #define EFL_IO_WRITER_PROTECTED 1 #define EFL_IO_CLOSER_PROTECTED 1 #define EFL_NET_SOCKET_PROTECTED 1 #ifdef HAVE_CONFIG_H # include #endif #include "Ecore.h" #include "Ecore_Con.h" #include "ecore_con_private.h" typedef struct _Efl_Net_Ssl_Conn Efl_Net_Ssl_Conn; /** * Setups the SSL context * * Update the given lists, removing invalid entries. If all entries * failed in a list, return EINVAL. * * @internal */ static Eina_Error efl_net_ssl_conn_setup(Efl_Net_Ssl_Conn *conn, Eina_Bool is_dialer, Efl_Net_Socket *sock, Efl_Net_Ssl_Context *context); /** * Cleans up the SSL associated to this context. * @internal */ static void efl_net_ssl_conn_teardown(Efl_Net_Ssl_Conn *conn); /** * Send data to remote peer. * * This should be called once handshake is finished, otherwise it may * lead the handshake to fail. * * @param slice[inout] takes the amount of bytes to write and * source memory, will store written length in slice->len. * * @internal */ static Eina_Error efl_net_ssl_conn_write(Efl_Net_Ssl_Conn *conn, Eina_Slice *slice); /** * Receive data from remote peer. * * This should be called once handshake is finished, otherwise it may * lead the handshake to fail. * * Note that even if the socket 'can_read', eventually it couldn't * decipher a byte and it will return slice->len == 0 with EAGAIN as * error. * * @param slice[inout] takes the amount of bytes to read and * destination memory, will store read length in slice->len. * * @internal */ static Eina_Error efl_net_ssl_conn_read(Efl_Net_Ssl_Conn *conn, Eina_Rw_Slice *slice); /** * Attempt to finish the handshake. * * This should not block, if it's not finished yet, just set done = * false. * * Errors, such as failed handshake, should be returned as Eina_Error. * * @internal */ static Eina_Error efl_net_ssl_conn_handshake(Efl_Net_Ssl_Conn *conn, Eina_Bool *done); /** * Configure how to verify peer. * * @internal */ static Eina_Error efl_net_ssl_conn_verify_mode_set(Efl_Net_Ssl_Conn *conn, Efl_Net_Ssl_Verify_Mode verify_mode); /** * Configure whenever to check for hostname. * * @internal */ static Eina_Error efl_net_ssl_conn_hostname_verify_set(Efl_Net_Ssl_Conn *conn, Eina_Bool hostname_verify); /** * Overrides the hostname to use. * * @note duplicate hostname if needed! * * @internal */ static Eina_Error efl_net_ssl_conn_hostname_override_set(Efl_Net_Ssl_Conn *conn, const char *hostname); #if HAVE_OPENSSL # include "efl_net_ssl_conn-openssl.c" #else # include "efl_net_ssl_conn-none.c" #endif #define MY_CLASS EFL_NET_SOCKET_SSL_CLASS typedef struct _Efl_Net_Socket_Ssl_Data { Eo *sock; Efl_Net_Ssl_Context *context; const char *hostname_override; Efl_Net_Ssl_Conn ssl_conn; Efl_Net_Ssl_Verify_Mode verify_mode; Eina_Bool hostname_verify; Eina_Bool did_handshake; Eina_Bool torndown; Eina_Bool can_read; Eina_Bool eos; Eina_Bool can_write; } Efl_Net_Socket_Ssl_Data; static void efl_net_socket_ssl_sock_eos(void *data, const Efl_Event *event EINA_UNUSED) { Eo *o = data; efl_io_reader_eos_set(o, EINA_TRUE); } static void efl_net_socket_ssl_handshake_try(Eo *o, Efl_Net_Socket_Ssl_Data *pd) { Eina_Error err; if (pd->torndown) return; DBG("SSL=%p handshake...", o); err = efl_net_ssl_conn_handshake(&pd->ssl_conn, &pd->did_handshake); if (err) { WRN("SSL=%p failed handshake: %s", o, eina_error_msg_get(err)); efl_event_callback_call(o, EFL_NET_SOCKET_SSL_EVENT_SSL_ERROR, &err); efl_io_closer_close(o); return; } if (!pd->did_handshake) return; DBG("SSL=%p finished handshake", o); efl_io_reader_can_read_set(o, efl_io_reader_can_read_get(pd->sock)); efl_io_writer_can_write_set(o, efl_io_writer_can_write_get(pd->sock)); efl_event_callback_call(o, EFL_NET_SOCKET_SSL_EVENT_SSL_READY, NULL); } static void efl_net_socket_ssl_sock_can_read_changed(void *data, const Efl_Event *event EINA_UNUSED) { Eo *o = data; Efl_Net_Socket_Ssl_Data *pd = efl_data_scope_get(o, MY_CLASS); efl_ref(o); /* we're emitting callbacks then continuing the workflow */ if (!efl_io_reader_can_read_get(pd->sock)) goto end; if (pd->did_handshake) efl_io_reader_can_read_set(o, EINA_TRUE); else efl_net_socket_ssl_handshake_try(o, pd); end: efl_unref(o); } static void efl_net_socket_ssl_sock_can_write_changed(void *data, const Efl_Event *event EINA_UNUSED) { Eo *o = data; Efl_Net_Socket_Ssl_Data *pd = efl_data_scope_get(o, MY_CLASS); efl_ref(o); /* we're emitting callbacks then continuing the workflow */ if (!efl_io_writer_can_write_get(pd->sock)) goto end; if (pd->did_handshake) efl_io_writer_can_write_set(o, EINA_TRUE); else efl_net_socket_ssl_handshake_try(o, pd); end: efl_unref(o); } static void efl_net_socket_ssl_sock_closed(void *data, const Efl_Event *event EINA_UNUSED) { Eo *o = data; efl_event_callback_call(o, EFL_IO_CLOSER_EVENT_CLOSED, NULL); } static void efl_net_socket_ssl_sock_del(void *data, const Efl_Event *event EINA_UNUSED) { Eo *o = data; Efl_Net_Socket_Ssl_Data *pd = efl_data_scope_get(o, MY_CLASS); pd->sock = NULL; pd->torndown = EINA_TRUE; efl_net_ssl_conn_teardown(&pd->ssl_conn); } EFL_CALLBACKS_ARRAY_DEFINE(efl_net_socket_ssl_sock_cbs, {EFL_IO_READER_EVENT_EOS, efl_net_socket_ssl_sock_eos}, {EFL_IO_READER_EVENT_CAN_READ_CHANGED, efl_net_socket_ssl_sock_can_read_changed}, {EFL_IO_WRITER_EVENT_CAN_WRITE_CHANGED, efl_net_socket_ssl_sock_can_write_changed}, {EFL_IO_CLOSER_EVENT_CLOSED, efl_net_socket_ssl_sock_closed}, {EFL_EVENT_DEL, efl_net_socket_ssl_sock_del}); static void efl_net_socket_ssl_sock_resolved(void *data, const Efl_Event *event EINA_UNUSED) { Eo *o = data; Efl_Net_Socket_Ssl_Data *pd = efl_data_scope_get(o, MY_CLASS); if (pd->torndown) return; efl_event_callback_call(o, EFL_NET_DIALER_EVENT_DIALER_RESOLVED, NULL); } static void efl_net_socket_ssl_sock_connected(void *data, const Efl_Event *event EINA_UNUSED) { Eo *o = data; Efl_Net_Socket_Ssl_Data *pd = efl_data_scope_get(o, MY_CLASS); Eina_Error err; if (pd->torndown) return; efl_ref(o); /* we're emitting callbacks then continuing the workflow */ err = efl_net_ssl_conn_handshake(&pd->ssl_conn, &pd->did_handshake); if (err) { WRN("SSL=%p failed handshake: %s", o, eina_error_msg_get(err)); efl_io_closer_close(o); } efl_unref(o); } EFL_CALLBACKS_ARRAY_DEFINE(efl_net_socket_ssl_sock_dialer_cbs, {EFL_NET_DIALER_EVENT_DIALER_RESOLVED, efl_net_socket_ssl_sock_resolved}, {EFL_NET_DIALER_EVENT_DIALER_CONNECTED, efl_net_socket_ssl_sock_connected}); static void _efl_net_socket_ssl_context_del(void *data, const Efl_Event *event EINA_UNUSED) { Eo *o = data; Efl_Net_Socket_Ssl_Data *pd = efl_data_scope_get(o, MY_CLASS); pd->context = NULL; } EOLIAN static void _efl_net_socket_ssl_adopt(Eo *o, Efl_Net_Socket_Ssl_Data *pd, Efl_Net_Socket *sock, Efl_Net_Ssl_Context *context) { Eina_Error err; char *tmp = NULL; const char *hostname; Eina_Bool is_dialer; EINA_SAFETY_ON_TRUE_RETURN(pd->sock != NULL); EINA_SAFETY_ON_FALSE_RETURN(efl_isa(sock, EFL_NET_SOCKET_INTERFACE)); EINA_SAFETY_ON_FALSE_RETURN(efl_isa(context, EFL_NET_SSL_CONTEXT_CLASS)); EINA_SAFETY_ON_TRUE_RETURN(efl_finalized_get(o)); is_dialer = efl_isa(sock, EFL_NET_DIALER_INTERFACE); err = efl_net_ssl_conn_setup(&pd->ssl_conn, is_dialer, sock, context); if (err) { ERR("ssl=%p failed to adopt socket (is_dialer=%d) sock=%p", o, is_dialer, sock); return; } pd->context = efl_ref(context); efl_event_callback_add(context, EFL_EVENT_DEL, _efl_net_socket_ssl_context_del, o); DBG("ssl=%p adopted socket (is_dialer=%d) sock=%p, ssl_conn=%p", o, is_dialer, sock, &pd->ssl_conn); if (pd->hostname_verify == 0xff) pd->hostname_verify = efl_net_ssl_context_hostname_verify_get(context); hostname = pd->hostname_override; if (!hostname) hostname = pd->hostname_override = eina_stringshare_ref(efl_net_ssl_context_hostname_get(context)); if (!hostname) { const char *remote_address = (is_dialer ? efl_net_dialer_address_dial_get(sock) : efl_net_socket_address_remote_get(sock)); if (remote_address) { const char *host, *port; tmp = strdup(remote_address); EINA_SAFETY_ON_NULL_RETURN(tmp); if (efl_net_ip_port_split(tmp, &host, &port)) hostname = host; } } if ((uint8_t)pd->verify_mode == 0xff) pd->verify_mode = efl_net_ssl_context_verify_mode_get(context); efl_net_ssl_conn_verify_mode_set(&pd->ssl_conn, pd->verify_mode); efl_net_ssl_conn_hostname_verify_set(&pd->ssl_conn, pd->hostname_verify); efl_net_ssl_conn_hostname_override_set(&pd->ssl_conn, hostname); free(tmp); pd->sock = efl_ref(sock); efl_event_callback_array_add(sock, efl_net_socket_ssl_sock_cbs(), o); if (efl_isa(sock, EFL_NET_DIALER_INTERFACE)) efl_event_callback_array_add(sock, efl_net_socket_ssl_sock_dialer_cbs(), o); efl_net_socket_ssl_sock_can_read_changed(o, NULL); efl_net_socket_ssl_sock_can_write_changed(o, NULL); if (efl_io_closer_closed_get(sock)) efl_event_callback_call(o, EFL_IO_CLOSER_EVENT_CLOSED, NULL); } EOLIAN static Eina_Bool _efl_net_socket_ssl_adopted_get(const Eo *o EINA_UNUSED, Efl_Net_Socket_Ssl_Data *pd, Efl_Net_Socket **sock, Efl_Net_Ssl_Context **context) { if (sock) *sock = pd->sock; if (context) *context = pd->context; return !!pd->sock; } static Efl_Net_Ssl_Verify_Mode _efl_net_socket_ssl_verify_mode_get(const Eo *o EINA_UNUSED, Efl_Net_Socket_Ssl_Data *pd) { return pd->verify_mode; } static void _efl_net_socket_ssl_verify_mode_set(Eo *o EINA_UNUSED, Efl_Net_Socket_Ssl_Data *pd, Efl_Net_Ssl_Verify_Mode verify_mode) { pd->verify_mode = verify_mode; if (!efl_finalized_get(o)) return; EINA_SAFETY_ON_TRUE_RETURN(pd->torndown); efl_net_ssl_conn_verify_mode_set(&pd->ssl_conn, pd->verify_mode); } static Eina_Bool _efl_net_socket_ssl_hostname_verify_get(const Eo *o EINA_UNUSED, Efl_Net_Socket_Ssl_Data *pd) { return pd->hostname_verify; } static void _efl_net_socket_ssl_hostname_verify_set(Eo *o EINA_UNUSED, Efl_Net_Socket_Ssl_Data *pd, Eina_Bool hostname_verify) { pd->hostname_verify = hostname_verify; if (!efl_finalized_get(o)) return; EINA_SAFETY_ON_TRUE_RETURN(pd->torndown); efl_net_ssl_conn_hostname_verify_set(&pd->ssl_conn, pd->hostname_verify); } static const char * _efl_net_socket_ssl_hostname_override_get(const Eo *o EINA_UNUSED, Efl_Net_Socket_Ssl_Data *pd) { return pd->hostname_override; } static void _efl_net_socket_ssl_hostname_override_set(Eo *o EINA_UNUSED, Efl_Net_Socket_Ssl_Data *pd, const char* hostname_override) { char *tmp = NULL; const char *hostname; eina_stringshare_replace(&pd->hostname_override, hostname_override); if (!efl_finalized_get(o)) return; EINA_SAFETY_ON_TRUE_RETURN(pd->torndown); hostname = pd->hostname_override; if (!hostname) { const char *remote_address = (efl_isa(pd->sock, EFL_NET_DIALER_INTERFACE) ? efl_net_dialer_address_dial_get(pd->sock) : efl_net_socket_address_remote_get(pd->sock)); if (remote_address) { const char *host, *port; tmp = strdup(remote_address); EINA_SAFETY_ON_NULL_RETURN(tmp); if (efl_net_ip_port_split(tmp, &host, &port)) hostname = host; } } efl_net_ssl_conn_hostname_override_set(&pd->ssl_conn, hostname); free(tmp); } EOLIAN static Efl_Object * _efl_net_socket_ssl_efl_object_finalize(Eo *o, Efl_Net_Socket_Ssl_Data *pd EINA_UNUSED) { Eina_Error err; o = efl_finalize(efl_super(o, MY_CLASS)); if (!o) return NULL; if (!pd->sock) { ERR("no Efl.Net.Socket was adopted by this SSL=%p", o); return NULL; } if (efl_isa(pd->sock, EFL_NET_DIALER_INTERFACE)) { if (!efl_net_dialer_connected_get(pd->sock)) return o; } err = efl_net_ssl_conn_handshake(&pd->ssl_conn, &pd->did_handshake); if (err) { WRN("SSL=%p failed handshake", o); return NULL; } return o; } EOLIAN static Eo * _efl_net_socket_ssl_efl_object_constructor(Eo *o, Efl_Net_Socket_Ssl_Data *pd) { pd->hostname_verify = 0xff; pd->verify_mode = 0xff; return efl_constructor(efl_super(o, MY_CLASS)); } EOLIAN static void _efl_net_socket_ssl_efl_object_invalidate(Eo *o, Efl_Net_Socket_Ssl_Data *pd EINA_UNUSED) { if (efl_io_closer_close_on_invalidate_get(o) && (!efl_io_closer_closed_get(o))) { efl_event_freeze(o); efl_io_closer_close(o); efl_event_thaw(o); } pd->torndown = EINA_TRUE; efl_net_ssl_conn_teardown(&pd->ssl_conn); if (pd->sock) { efl_event_callback_array_del(pd->sock, efl_net_socket_ssl_sock_cbs(), o); if (efl_isa(pd->sock, EFL_NET_DIALER_INTERFACE)) efl_event_callback_array_del(pd->sock, efl_net_socket_ssl_sock_dialer_cbs(), o); efl_unref(pd->sock); pd->sock = NULL; } if (pd->context) { efl_event_callback_del(pd->context, EFL_EVENT_DEL, _efl_net_socket_ssl_context_del, o); efl_unref(pd->context); pd->context = NULL; } efl_invalidate(efl_super(o, MY_CLASS)); } EOLIAN static void _efl_net_socket_ssl_efl_object_destructor(Eo *o, Efl_Net_Socket_Ssl_Data *pd) { eina_stringshare_replace(&pd->hostname_override, NULL); efl_destructor(efl_super(o, MY_CLASS)); } EOLIAN static Eina_Error _efl_net_socket_ssl_efl_io_closer_close(Eo *o, Efl_Net_Socket_Ssl_Data *pd) { EINA_SAFETY_ON_NULL_RETURN_VAL(pd->sock, EBADF); efl_io_reader_can_read_set(o, EINA_FALSE); efl_io_reader_eos_set(o, EINA_TRUE); pd->torndown = EINA_TRUE; efl_net_ssl_conn_teardown(&pd->ssl_conn); if (efl_io_closer_closed_get(pd->sock)) return 0; return efl_io_closer_close(pd->sock); } EOLIAN static Eina_Bool _efl_net_socket_ssl_efl_io_closer_closed_get(const Eo *o EINA_UNUSED, Efl_Net_Socket_Ssl_Data *pd) { return (!pd->sock) || efl_io_closer_closed_get(pd->sock); } EOLIAN static Eina_Error _efl_net_socket_ssl_efl_io_reader_read(Eo *o, Efl_Net_Socket_Ssl_Data *pd, Eina_Rw_Slice *rw_slice) { Eina_Error err; EINA_SAFETY_ON_NULL_RETURN_VAL(rw_slice, EINVAL); EINA_SAFETY_ON_TRUE_RETURN_VAL(pd->torndown, EBADF); if (!pd->did_handshake) { rw_slice->mem = NULL; rw_slice->len = 0; return EAGAIN; } err = efl_net_ssl_conn_read(&pd->ssl_conn, rw_slice); if (rw_slice->len == 0) { efl_io_reader_can_read_set(o, EINA_FALSE); if (err == 0) efl_io_reader_eos_set(o, EINA_TRUE); } return err; } EOLIAN static void _efl_net_socket_ssl_efl_io_reader_can_read_set(Eo *o, Efl_Net_Socket_Ssl_Data *pd, Eina_Bool can_read) { EINA_SAFETY_ON_NULL_RETURN(pd->sock); if (pd->can_read == can_read) return; pd->can_read = can_read; efl_event_callback_call(o, EFL_IO_READER_EVENT_CAN_READ_CHANGED, &can_read); } EOLIAN static Eina_Bool _efl_net_socket_ssl_efl_io_reader_can_read_get(const Eo *o EINA_UNUSED, Efl_Net_Socket_Ssl_Data *pd) { return pd->can_read; } EOLIAN static Eina_Bool _efl_net_socket_ssl_efl_io_reader_eos_get(const Eo *o EINA_UNUSED, Efl_Net_Socket_Ssl_Data *pd) { return pd->eos; } EOLIAN static void _efl_net_socket_ssl_efl_io_reader_eos_set(Eo *o, Efl_Net_Socket_Ssl_Data *pd, Eina_Bool is_eos) { EINA_SAFETY_ON_NULL_RETURN(pd->sock); if (pd->eos == is_eos) return; pd->eos = is_eos; if (is_eos) efl_event_callback_call(o, EFL_IO_READER_EVENT_EOS, NULL); } EOLIAN static Eina_Error _efl_net_socket_ssl_efl_io_writer_write(Eo *o, Efl_Net_Socket_Ssl_Data *pd, Eina_Slice *ro_slice, Eina_Slice *remaining) { Eina_Error err; EINA_SAFETY_ON_NULL_RETURN_VAL(ro_slice, EINVAL); EINA_SAFETY_ON_TRUE_RETURN_VAL(pd->torndown, EBADF); if (!pd->did_handshake) { if (remaining) *remaining = *ro_slice; ro_slice->mem = NULL; ro_slice->len = 0; return EAGAIN; } if (remaining) *remaining = *ro_slice; err = efl_net_ssl_conn_write(&pd->ssl_conn, ro_slice); if (remaining) { remaining->bytes += ro_slice->len; remaining->len -= ro_slice->len; } if (ro_slice->len == 0) efl_io_writer_can_write_set(o, EINA_FALSE); return err; } EOLIAN static void _efl_net_socket_ssl_efl_io_writer_can_write_set(Eo *o, Efl_Net_Socket_Ssl_Data *pd, Eina_Bool can_write) { EINA_SAFETY_ON_NULL_RETURN(pd->sock); if (pd->can_write == can_write) return; pd->can_write = can_write; efl_event_callback_call(o, EFL_IO_WRITER_EVENT_CAN_WRITE_CHANGED, &can_write); } EOLIAN static Eina_Bool _efl_net_socket_ssl_efl_io_writer_can_write_get(const Eo *o EINA_UNUSED, Efl_Net_Socket_Ssl_Data *pd) { return pd->can_write; } EOLIAN static Eina_Bool _efl_net_socket_ssl_efl_io_closer_close_on_exec_set(Eo *o EINA_UNUSED, Efl_Net_Socket_Ssl_Data *pd, Eina_Bool close_on_exec) { if (pd->sock) efl_io_closer_close_on_exec_set(pd->sock, close_on_exec); return EINA_TRUE; } EOLIAN static Eina_Bool _efl_net_socket_ssl_efl_io_closer_close_on_exec_get(const Eo *o EINA_UNUSED, Efl_Net_Socket_Ssl_Data *pd) { return pd->sock && efl_io_closer_close_on_exec_get(pd->sock); } EOLIAN static void _efl_net_socket_ssl_efl_io_closer_close_on_invalidate_set(Eo *o EINA_UNUSED, Efl_Net_Socket_Ssl_Data *pd, Eina_Bool close_on_invalidate) { if (pd->sock) efl_io_closer_close_on_invalidate_set(pd->sock, close_on_invalidate); } EOLIAN static Eina_Bool _efl_net_socket_ssl_efl_io_closer_close_on_invalidate_get(const Eo *o EINA_UNUSED, Efl_Net_Socket_Ssl_Data *pd) { return pd->sock && efl_io_closer_close_on_invalidate_get(pd->sock); } EOLIAN static const char * _efl_net_socket_ssl_efl_net_socket_address_local_get(const Eo *o EINA_UNUSED, Efl_Net_Socket_Ssl_Data *pd) { if (!pd->sock) return "unbound"; return efl_net_socket_address_local_get(pd->sock); } EOLIAN static const char * _efl_net_socket_ssl_efl_net_socket_address_remote_get(const Eo *o EINA_UNUSED, Efl_Net_Socket_Ssl_Data *pd) { if (!pd->sock) return "unbound"; return efl_net_socket_address_remote_get(pd->sock); } #include "efl_net_socket_ssl.eo.c"