/* * vim:ts=8:sw=3:sts=8:noexpandtab:cino=>5n-3f0^-2{2 */ #include #include #include #include #include #include #if USE_OPENSSL #include #endif #include "Ecore.h" #include "ecore_private.h" #include "Ecore_Con.h" #include "ecore_con_private.h" #ifdef HAVE_NETINET_IN_H # include #endif #ifdef HAVE_WINSOCK2_H # include #endif static void _ecore_con_cb_dns_lookup(void *data, struct hostent *he); static void _ecore_con_server_free(Ecore_Con_Server *svr); static void _ecore_con_client_free(Ecore_Con_Client *cl); static int _ecore_con_svr_handler(void *data, Ecore_Fd_Handler *fd_handler); static int _ecore_con_cl_handler(void *data, Ecore_Fd_Handler *fd_handler); static int _ecore_con_svr_cl_handler(void *data, Ecore_Fd_Handler *fd_handler); static void _ecore_con_server_flush(Ecore_Con_Server *svr); static void _ecore_con_client_flush(Ecore_Con_Client *cl); static void _ecore_con_event_client_add_free(void *data, void *ev); static void _ecore_con_event_client_del_free(void *data, void *ev); static void _ecore_con_event_client_data_free(void *data, void *ev); static void _ecore_con_event_server_add_free(void *data, void *ev); static void _ecore_con_event_server_del_free(void *data, void *ev); static void _ecore_con_event_server_data_free(void *data, void *ev); EAPI int ECORE_CON_EVENT_CLIENT_ADD = 0; EAPI int ECORE_CON_EVENT_CLIENT_DEL = 0; EAPI int ECORE_CON_EVENT_SERVER_ADD = 0; EAPI int ECORE_CON_EVENT_SERVER_DEL = 0; EAPI int ECORE_CON_EVENT_CLIENT_DATA = 0; EAPI int ECORE_CON_EVENT_SERVER_DATA = 0; static Ecore_List *servers = NULL; static int init_count = 0; #if USE_OPENSSL static int ssl_init_count = 0; #endif #define LENGTH_OF_SOCKADDR_UN(s) (strlen((s)->sun_path) + (size_t)(((struct sockaddr_un *)NULL)->sun_path)) #define LENGTH_OF_ABSTRACT_SOCKADDR_UN(s, path) (strlen(path) + 1 + (size_t)(((struct sockaddr_un *)NULL)->sun_path)) /** * @defgroup Ecore_Con_Lib_Group Ecore Connection Library Functions * * Utility functions that set up and shut down the Ecore Connection * library. */ /** * Initialises the Ecore_Con library. * @return Number of times the library has been initialised without being * shut down. * @ingroup Ecore_Con_Lib_Group */ EAPI int ecore_con_init(void) { if (++init_count != 1) return init_count; ecore_init(); ECORE_CON_EVENT_CLIENT_ADD = ecore_event_type_new(); ECORE_CON_EVENT_CLIENT_DEL = ecore_event_type_new(); ECORE_CON_EVENT_SERVER_ADD = ecore_event_type_new(); ECORE_CON_EVENT_SERVER_DEL = ecore_event_type_new(); ECORE_CON_EVENT_CLIENT_DATA = ecore_event_type_new(); ECORE_CON_EVENT_SERVER_DATA = ecore_event_type_new(); /* TODO Remember return value, if it fails, use gethostbyname() */ ecore_con_dns_init(); servers = ecore_list_new(); return init_count; } /** * Shuts down the Ecore_Con library. * @return Number of times the library has been initialised without being * shut down. * @ingroup Ecore_Con_Lib_Group */ EAPI int ecore_con_shutdown(void) { if (--init_count != 0) return init_count; while (!ecore_list_empty_is(servers)) _ecore_con_server_free(ecore_list_first_remove(servers)); ecore_list_destroy(servers); servers = NULL; ecore_con_dns_shutdown(); ecore_shutdown(); return init_count; } /** * @defgroup Ecore_Con_Server_Group Ecore Connection Server Functions * * Functions that operate on Ecore server objects. */ /** * Creates a server to listen for connections. * * The socket on which the server listens depends on the connection * type: * @li If @a compl_type is @c ECORE_CON_LOCAL_USER, the server will listen on * the Unix socket "~/.ecore/[name]/[port]". * @li If @a compl_type is @c ECORE_CON_LOCAL_SYSTEM, the server will listen * on Unix socket "/tmp/.ecore_service|[name]|[port]". * @li If @a compl_type is @c ECORE_CON_REMOTE_SYSTEM, the server will listen * on TCP port @c port. * * @param compl_type The connection type. * @param name Name to associate with the socket. It is used when * generating the socket name of a Unix socket. Though * it is not used for the TCP socket, it still needs to * be a valid character array. @c NULL will not be * accepted. * @param port Number to identify socket. When a Unix socket is used, * it becomes part of the socket name. When a TCP socket * is used, it is used as the TCP port. * @param data Data to associate with the created Ecore_Con_Server * object. * @return A new Ecore_Con_Server. * @ingroup Ecore_Con_Server_Group */ EAPI Ecore_Con_Server * ecore_con_server_add(Ecore_Con_Type compl_type, const char *name, int port, const void *data) { Ecore_Con_Server *svr; Ecore_Con_Type type; struct sockaddr_in socket_addr; struct sockaddr_un socket_unix; struct linger lin; char buf[4096]; if (port < 0) return NULL; /* local user socket: FILE: ~/.ecore/[name]/[port] */ /* local system socket: FILE: /tmp/.ecore_service|[name]|[port] */ /* remote system socket: TCP/IP: [name]:[port] */ svr = calloc(1, sizeof(Ecore_Con_Server)); if (!svr) return NULL; type = compl_type; #if USE_OPENSSL /* unset the SSL flag for the following checks */ type &= ECORE_CON_TYPE; #endif if ((type == ECORE_CON_LOCAL_USER) || (type == ECORE_CON_LOCAL_SYSTEM) || (type == ECORE_CON_LOCAL_ABSTRACT)) { const char *homedir; struct stat st; mode_t pmode, mask; int socket_unix_len; if (!name) goto error; mask = S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IWOTH | S_IXOTH; if (type == ECORE_CON_LOCAL_USER) { homedir = getenv("HOME"); if (!homedir) homedir = getenv("TMP"); if (!homedir) homedir = "/tmp"; mask = S_IRUSR | S_IWUSR | S_IXUSR; snprintf(buf, sizeof(buf), "%s/.ecore", homedir); if (stat(buf, &st) < 0) mkdir(buf, mask); snprintf(buf, sizeof(buf), "%s/.ecore/%s", homedir, name); if (stat(buf, &st) < 0) mkdir(buf, mask); snprintf(buf, sizeof(buf), "%s/.ecore/%s/%i", homedir, name, port); mask = S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IWOTH | S_IXOTH; } else if (type == ECORE_CON_LOCAL_SYSTEM) { mask = 0; if (name[0] == '/') { if (port >= 0) snprintf(buf, sizeof(buf), "%s|%i", name, port); else snprintf(buf, sizeof(buf), "%s", name); } else snprintf(buf, sizeof(buf), "/tmp/.ecore_service|%s|%i", name, port); } else if (type == ECORE_CON_LOCAL_ABSTRACT) strncpy(buf, name, sizeof(buf)); pmode = umask(mask); start: svr->fd = socket(AF_UNIX, SOCK_STREAM, 0); if (svr->fd < 0) { umask(pmode); goto error; } if (fcntl(svr->fd, F_SETFL, O_NONBLOCK) < 0) { umask(pmode); goto error; } if (fcntl(svr->fd, F_SETFD, FD_CLOEXEC) < 0) { umask(pmode); goto error; } lin.l_onoff = 1; lin.l_linger = 0; if (setsockopt(svr->fd, SOL_SOCKET, SO_LINGER, &lin, sizeof(struct linger)) < 0) { umask(pmode); goto error; } socket_unix.sun_family = AF_UNIX; if (type == ECORE_CON_LOCAL_ABSTRACT) { #ifdef HAVE_ABSTRACT_SOCKET /* . is a placeholder */ snprintf(socket_unix.sun_path, sizeof(socket_unix.sun_path), ".%s", name); /* first char null indicates abstract namespace */ socket_unix.sun_path[0] = '\0'; socket_unix_len = LENGTH_OF_ABSTRACT_SOCKADDR_UN(&socket_unix, name); #else fprintf(stderr, "Your system does not support abstract sockets!\n"); umask(pmode); goto error; #endif } else { strncpy(socket_unix.sun_path, buf, sizeof(socket_unix.sun_path)); socket_unix_len = LENGTH_OF_SOCKADDR_UN(&socket_unix); } if (bind(svr->fd, (struct sockaddr *)&socket_unix, socket_unix_len) < 0) { if (connect(svr->fd, (struct sockaddr *)&socket_unix, socket_unix_len) < 0) { if ((type == ECORE_CON_LOCAL_USER) || (type == ECORE_CON_LOCAL_SYSTEM)) { if (unlink(buf) < 0) { umask(pmode); goto error; } else goto start; } else { umask(pmode); goto error; } } else { umask(pmode); goto error; } } if (listen(svr->fd, 4096) < 0) { umask(pmode); goto error; } svr->path = strdup(buf); if (!svr->path) { umask(pmode); goto error; } svr->fd_handler = ecore_main_fd_handler_add(svr->fd, ECORE_FD_READ, _ecore_con_svr_handler, svr, NULL, NULL); umask(pmode); if (!svr->fd_handler) goto error; } else if (type == ECORE_CON_REMOTE_SYSTEM) { svr->fd = socket(AF_INET, SOCK_STREAM, 0); if (svr->fd < 0) goto error; if (fcntl(svr->fd, F_SETFL, O_NONBLOCK) < 0) goto error; if (fcntl(svr->fd, F_SETFD, FD_CLOEXEC) < 0) goto error; lin.l_onoff = 1; lin.l_linger = 0; if (setsockopt(svr->fd, SOL_SOCKET, SO_LINGER, &lin, sizeof(struct linger)) < 0) goto error; socket_addr.sin_family = AF_INET; socket_addr.sin_port = htons(port); socket_addr.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(svr->fd, (struct sockaddr *)&socket_addr, sizeof(struct sockaddr_in)) < 0) goto error; if (listen(svr->fd, 4096) < 0) goto error; svr->fd_handler = ecore_main_fd_handler_add(svr->fd, ECORE_FD_READ, _ecore_con_svr_handler, svr, NULL, NULL); if (!svr->fd_handler) goto error; } #if USE_OPENSSL if (compl_type & ECORE_CON_SSL) { if (!ssl_init_count) { SSL_library_init(); SSL_load_error_strings(); } ssl_init_count++; switch (compl_type & ECORE_CON_SSL) { case ECORE_CON_USE_SSL2: if (!(svr->ssl_ctx = SSL_CTX_new(SSLv2_client_method()))) goto error; break; case ECORE_CON_USE_SSL3: if (!(svr->ssl_ctx = SSL_CTX_new(SSLv3_client_method()))) goto error; break; case ECORE_CON_USE_TLS: if (!(svr->ssl_ctx = SSL_CTX_new(TLSv1_client_method()))) goto error; break; } if (!(svr->ssl = SSL_new(svr->ssl_ctx))) goto error; SSL_set_fd(svr->ssl, svr->fd); } #endif svr->name = strdup(name); if (!svr->name) goto error; svr->type = type; svr->port = port; svr->data = (void *)data; svr->created = 1; svr->reject_excess_clients = 0; svr->client_limit = -1; svr->clients = ecore_list_new(); svr->ppid = getpid(); ecore_list_append(servers, svr); ECORE_MAGIC_SET(svr, ECORE_MAGIC_CON_SERVER); return svr; error: if (svr->name) free(svr->name); if (svr->path) free(svr->path); if (svr->fd >= 0) close(svr->fd); if (svr->fd_handler) ecore_main_fd_handler_del(svr->fd_handler); if (svr->write_buf) free(svr->write_buf); if (svr->ip) free(svr->ip); #if USE_OPENSSL if (svr->ssl) SSL_free(svr->ssl); if (svr->ssl_ctx) SSL_CTX_free(svr->ssl_ctx); #endif free(svr); return NULL; } /** * Creates a server object to represent the server listening at the * given port. * * The socket to which the server connects depends on the connection type: * @li If @a compl_type is @c ECORE_CON_LOCAL_USER, the function will * connect to the server listening on the Unix socket * "~/.ecore/[name]/[port]". * @li If @a compl_type is @c ECORE_CON_LOCAL_SYSTEM, the function will * connect to the server listening on the Unix socket * "/tmp/.ecore_service|[name]|[port]". * @li If @a compl_type is @c ECORE_CON_REMOTE_SYSTEM, the function will * connect to the server listening on the TCP port "[name]:[port]". * * @param compl_type The connection type. * @param name Name used when determining what socket to connect to. * It is used to generate the socket name when the socket * is a Unix socket. It is used as the hostname when * connecting with a TCP socket. * @param port Number to identify the socket to connect to. Used when * generating the socket name for a Unix socket, or as the * TCP port when connecting to a TCP socket. * @param data Data to associate with the created Ecore_Con_Server * object. * @return A new Ecore_Con_Server. * @ingroup Ecore_Con_Server_Group */ EAPI Ecore_Con_Server * ecore_con_server_connect(Ecore_Con_Type compl_type, const char *name, int port, const void *data) { Ecore_Con_Server *svr; Ecore_Con_Type type; struct sockaddr_un socket_unix; int curstate = 0; char buf[4096]; if (!name) return NULL; /* local user socket: FILE: ~/.ecore/[name]/[port] */ /* local system socket: FILE: /tmp/.ecore_service|[name]|[port] */ /* remote system socket: TCP/IP: [name]:[port] */ svr = calloc(1, sizeof(Ecore_Con_Server)); if (!svr) return NULL; type = compl_type; #if USE_OPENSSL /* unset the SSL flag for the following checks */ type &= ECORE_CON_TYPE; #endif if ((type == ECORE_CON_REMOTE_SYSTEM) && (port < 0)) return NULL; if ((type == ECORE_CON_LOCAL_USER) || (type == ECORE_CON_LOCAL_SYSTEM) || (type == ECORE_CON_LOCAL_ABSTRACT)) { const char *homedir; int socket_unix_len; if (type == ECORE_CON_LOCAL_USER) { homedir = getenv("HOME"); if (!homedir) homedir = getenv("TMP"); if (!homedir) homedir = "/tmp"; snprintf(buf, sizeof(buf), "%s/.ecore/%s/%i", homedir, name, port); } else if (type == ECORE_CON_LOCAL_SYSTEM) { if (port < 0) { if (name[0] == '/') strncpy(buf, name, sizeof(buf)); else snprintf(buf, sizeof(buf), "/tmp/.ecore_service|%s", name); } else { if (name[0] == '/') snprintf(buf, sizeof(buf), "%s|%i", name, port); else snprintf(buf, sizeof(buf), "/tmp/.ecore_service|%s|%i", name, port); } } else if (type == ECORE_CON_LOCAL_ABSTRACT) strncpy(buf, name, sizeof(buf)); svr->fd = socket(AF_UNIX, SOCK_STREAM, 0); if (svr->fd < 0) goto error; if (fcntl(svr->fd, F_SETFL, O_NONBLOCK) < 0) goto error; if (fcntl(svr->fd, F_SETFD, FD_CLOEXEC) < 0) goto error; if (setsockopt(svr->fd, SOL_SOCKET, SO_REUSEADDR, &curstate, sizeof(curstate)) < 0) goto error; socket_unix.sun_family = AF_UNIX; if (type == ECORE_CON_LOCAL_ABSTRACT) { #ifdef HAVE_ABSTRACT_SOCKETS /* copy name insto sun_path, prefixed by null to indicate abstract namespace */ snprintf(socket_unix.sun_path, sizeof(socket_unix.sun_path), ".%s", name); socket_unix.sun_path[0] = '\0'; socket_unix_len = LENGTH_OF_ABSTRACT_SOCKADDR_UN(&socket_unix, name); #else fprintf(stderr, "Your system does not support abstract sockets!\n"); goto error; #endif } else { strncpy(socket_unix.sun_path, buf, sizeof(socket_unix.sun_path)); socket_unix_len = LENGTH_OF_SOCKADDR_UN(&socket_unix); } if (connect(svr->fd, (struct sockaddr *)&socket_unix, socket_unix_len) < 0) goto error; svr->path = strdup(buf); if (!svr->path) goto error; svr->fd_handler = ecore_main_fd_handler_add(svr->fd, ECORE_FD_READ, _ecore_con_cl_handler, svr, NULL, NULL); if (!svr->fd_handler) goto error; if (!svr->delete_me) { /* we got our server! */ Ecore_Con_Event_Server_Add *e; e = calloc(1, sizeof(Ecore_Con_Event_Server_Add)); if (e) { svr->event_count++; e->server = svr; ecore_event_add(ECORE_CON_EVENT_SERVER_ADD, e, _ecore_con_event_server_add_free, NULL); } } } svr->name = strdup(name); if (!svr->name) goto error; svr->type = compl_type; svr->port = port; svr->data = (void *)data; svr->created = 0; svr->reject_excess_clients = 0; svr->client_limit = -1; svr->clients = ecore_list_new(); ecore_list_append(servers, svr); ECORE_MAGIC_SET(svr, ECORE_MAGIC_CON_SERVER); if (type == ECORE_CON_REMOTE_SYSTEM) { if (!ecore_con_dns_lookup(svr->name, _ecore_con_cb_dns_lookup, svr)) goto error; } return svr; error: if (svr->name) free(svr->name); if (svr->path) free(svr->path); if (svr->fd >= 0) close(svr->fd); if (svr->fd_handler) ecore_main_fd_handler_del(svr->fd_handler); #if USE_OPENSSL if (svr->ssl) SSL_free(svr->ssl); if (svr->ssl_ctx) SSL_CTX_free(svr->ssl_ctx); #endif free(svr); return NULL; } /** * Closes the connection and frees the given server. * @param svr The given server. * @return Data associated with the server when it was created. * @ingroup Ecore_Con_Server_Group */ EAPI void * ecore_con_server_del(Ecore_Con_Server *svr) { void *data; if (!ECORE_MAGIC_CHECK(svr, ECORE_MAGIC_CON_SERVER)) { ECORE_MAGIC_FAIL(svr, ECORE_MAGIC_CON_SERVER, "ecore_con_server_del"); return NULL; } data = svr->data; svr->data = NULL; svr->delete_me = 1; if (svr->event_count > 0) { if (svr->fd_handler) { ecore_main_fd_handler_del(svr->fd_handler); svr->fd_handler = NULL; } } else { _ecore_con_server_free(svr); if (ecore_list_goto(servers, svr)) ecore_list_remove(servers); } return data; } /** * Retrieves the data associated with the given server. * @param svr The given server. * @return The associated data. * @ingroup Ecore_Con_Server_Group */ EAPI void * ecore_con_server_data_get(Ecore_Con_Server *svr) { if (!ECORE_MAGIC_CHECK(svr, ECORE_MAGIC_CON_SERVER)) { ECORE_MAGIC_FAIL(svr, ECORE_MAGIC_CON_SERVER, "ecore_con_server_data_get"); return NULL; } return svr->data; } /** * Retrieves whether the given server is currently connected. * @todo Check that this function does what the documenter believes it does. * @param svr The given server. * @return @c 1 if the server is connected. @c 0 otherwise. * @ingroup Ecore_Con_Server_Group */ EAPI int ecore_con_server_connected_get(Ecore_Con_Server *svr) { if (!ECORE_MAGIC_CHECK(svr, ECORE_MAGIC_CON_SERVER)) { ECORE_MAGIC_FAIL(svr, ECORE_MAGIC_CON_SERVER, "ecore_con_server_connected_get"); return 0; } if (svr->connecting) return 0; return 1; } /** * Retrieves the current list of clients. * @param svr The given server. * @return The list of clients on this server. * @ingroup Ecore_Con_Server_Group */ EAPI Ecore_List * ecore_con_server_clients_get(Ecore_Con_Server *svr) { if (!ECORE_MAGIC_CHECK(svr, ECORE_MAGIC_CON_SERVER)) { ECORE_MAGIC_FAIL(svr, ECORE_MAGIC_CON_SERVER, "ecore_con_server_clients_get"); return NULL; } return svr->clients; } /** * Sends the given data to the given server. * @param svr The given server. * @param data The given data. * @param size Length of the data, in bytes, to send. * @return The number of bytes sent. @c 0 will be returned if there is an * error. * @ingroup Ecore_Con_Server_Group */ EAPI int ecore_con_server_send(Ecore_Con_Server *svr, const void *data, int size) { if (!ECORE_MAGIC_CHECK(svr, ECORE_MAGIC_CON_SERVER)) { ECORE_MAGIC_FAIL(svr, ECORE_MAGIC_CON_SERVER, "ecore_con_server_send"); return 0; } if (svr->dead) return 0; if (!data) return 0; if (size < 1) return 0; if (svr->fd_handler) ecore_main_fd_handler_active_set(svr->fd_handler, ECORE_FD_READ | ECORE_FD_WRITE); if (svr->write_buf) { unsigned char *newbuf; newbuf = realloc(svr->write_buf, svr->write_buf_size + size); if (newbuf) svr->write_buf = newbuf; else return 0; memcpy(svr->write_buf + svr->write_buf_size, data, size); svr->write_buf_size += size; } else { svr->write_buf = malloc(size); if (!svr->write_buf) return 0; svr->write_buf_size = size; memcpy(svr->write_buf, data, size); } return size; } /** * Sets a limit on the number of clients that can be handled concurrently * by the given server, and a policy on what to do if excess clients try to * connect. * Beware that if you set this once ecore is already running, you may * already have pending CLIENT_ADD events in your event queue. Those * clients have already connected and will not be affected by this call. * Only clients subsequently trying to connect will be affected. * @param svr The given server. * @param client_limit The maximum number of clients to handle * concurrently. -1 means unlimited (default). 0 * effectively disables the server. * @param reject_excess_clients Set to 1 to automatically disconnect * excess clients as soon as they connect if you are * already handling client_limit clients. Set to 0 * (default) to just hold off on the "accept()" * system call until the number of active clients * drops. This causes the kernel to queue up to 4096 * connections (or your kernel's limit, whichever is * lower). * @ingroup Ecore_Con_Server_Group */ EAPI void ecore_con_server_client_limit_set(Ecore_Con_Server *svr, int client_limit, char reject_excess_clients) { if (!ECORE_MAGIC_CHECK(svr, ECORE_MAGIC_CON_SERVER)) { ECORE_MAGIC_FAIL(svr, ECORE_MAGIC_CON_SERVER, "ecore_con_server_client_limit_set"); return; } svr->client_limit = client_limit; svr->reject_excess_clients = reject_excess_clients; } /** * Gets the IP address of a server that has been connected to. * * @param svr The given server. * @return A pointer to an internal string that contains the IP address of * the connected server in the form "XXX.YYY.ZZZ.AAA" IP notation. * This string should not be modified or trusted to stay valid after * deletion for the @p svr object. If no IP is known NULL is returned. * @ingroup Ecore_Con_Server_Group */ EAPI char * ecore_con_server_ip_get(Ecore_Con_Server *svr) { if (!ECORE_MAGIC_CHECK(svr, ECORE_MAGIC_CON_SERVER)) { ECORE_MAGIC_FAIL(svr, ECORE_MAGIC_CON_SERVER, "ecore_con_server_ip_get"); return NULL; } return svr->ip; } /** * Flushes all pending data to the given server. Will return when done. * * @param svr The given server. * @ingroup Ecore_Con_Server_Group */ EAPI void ecore_con_server_flush(Ecore_Con_Server *svr) { if (!ECORE_MAGIC_CHECK(svr, ECORE_MAGIC_CON_SERVER)) { ECORE_MAGIC_FAIL(svr, ECORE_MAGIC_CON_SERVER, "ecore_con_server_flush"); return; } _ecore_con_server_flush(svr); } /** * @defgroup Ecore_Con_Client_Group Ecore Connection Client Functions * * Functions that operate on Ecore connection client objects. */ /** * Sends the given data to the given client. * @param cl The given client. * @param data The given data. * @param size Length of the data, in bytes, to send. * @return The number of bytes sent. @c 0 will be returned if there is an * error. * @ingroup Ecore_Con_Client_Group */ EAPI int ecore_con_client_send(Ecore_Con_Client *cl, const void *data, int size) { if (!ECORE_MAGIC_CHECK(cl, ECORE_MAGIC_CON_CLIENT)) { ECORE_MAGIC_FAIL(cl, ECORE_MAGIC_CON_CLIENT, "ecore_con_client_send"); return 0; } if (cl->dead) return 0; if (!data) return 0; if (size < 1) return 0; if (cl->fd_handler) ecore_main_fd_handler_active_set(cl->fd_handler, ECORE_FD_READ | ECORE_FD_WRITE); if (cl->buf) { unsigned char *newbuf; newbuf = realloc(cl->buf, cl->buf_size + size); if (newbuf) cl->buf = newbuf; else return 0; memcpy(cl->buf + cl->buf_size, data, size); cl->buf_size += size; } else { cl->buf = malloc(size); if (!cl->buf) return 0; cl->buf_size = size; memcpy(cl->buf, data, size); } return size; } /** * Retrieves the server representing the socket the client has * connected to. * @param cl The given client. * @return The server that the client connected to. * @ingroup Ecore_Con_Client_Group */ EAPI Ecore_Con_Server * ecore_con_client_server_get(Ecore_Con_Client *cl) { if (!ECORE_MAGIC_CHECK(cl, ECORE_MAGIC_CON_CLIENT)) { ECORE_MAGIC_FAIL(cl, ECORE_MAGIC_CON_CLIENT, "ecore_con_client_server_get"); return NULL; } return cl->server; } /** * Closes the connection and frees memory allocated to the given client. * @param cl The given client. * @return Data associated with the client. * @ingroup Ecore_Con_Client_Group */ EAPI void * ecore_con_client_del(Ecore_Con_Client *cl) { void *data; if (!ECORE_MAGIC_CHECK(cl, ECORE_MAGIC_CON_CLIENT)) { ECORE_MAGIC_FAIL(cl, ECORE_MAGIC_CON_CLIENT, "ecore_con_client_del"); return NULL; } data = cl->data; cl->data = NULL; cl->delete_me = 1; if (cl->event_count > 0) { if (cl->fd_handler) { ecore_main_fd_handler_del(cl->fd_handler); cl->fd_handler = NULL; } } else { if (ecore_list_goto(cl->server->clients, cl)) ecore_list_remove(cl->server->clients); _ecore_con_client_free(cl); } return data; } /** * Sets the data associated with the given client to @p data. * @param cl The given client. * @param data What to set the data to. * @ingroup Ecore_Con_Client_Group */ EAPI void ecore_con_client_data_set(Ecore_Con_Client *cl, const void *data) { if (!ECORE_MAGIC_CHECK(cl, ECORE_MAGIC_CON_CLIENT)) { ECORE_MAGIC_FAIL(cl, ECORE_MAGIC_CON_CLIENT, "ecore_con_client_data_set"); return; } cl->data = (void *)data; } /** * Retrieves the data associated with the given client. * @param cl The given client. * @return The data associated with @p cl. * @ingroup Ecore_Con_Client_Group */ EAPI void * ecore_con_client_data_get(Ecore_Con_Client *cl) { if (!ECORE_MAGIC_CHECK(cl, ECORE_MAGIC_CON_CLIENT)) { ECORE_MAGIC_FAIL(cl, ECORE_MAGIC_CON_CLIENT, "ecore_con_client_data_get"); return NULL; } return cl->data; } /** * Gets the IP address of a cleint that has connected. * * @param cl The given client. * @return A pointer to an internal string that contains the IP address of * the connected client in the form "XXX.YYY.ZZZ.AAA" IP notation. * This string should not be modified or trusted to stay valid after * deletion for the @p cl object. If no IP is known NULL is returned. * @ingroup Ecore_Con_Client_Group */ EAPI char * ecore_con_client_ip_get(Ecore_Con_Client *cl) { if (!ECORE_MAGIC_CHECK(cl, ECORE_MAGIC_CON_CLIENT)) { ECORE_MAGIC_FAIL(cl, ECORE_MAGIC_CON_CLIENT, "ecore_con_client_ip_get"); return NULL; } return cl->ip; } /** * Flushes all pending data to the given client. Will return when done. * * @param cl The given client. * @ingroup Ecore_Con_Client_Group */ EAPI void ecore_con_client_flush(Ecore_Con_Client *cl) { if (!ECORE_MAGIC_CHECK(cl, ECORE_MAGIC_CON_CLIENT)) { ECORE_MAGIC_FAIL(cl, ECORE_MAGIC_CON_CLIENT, "ecore_con_client_flush"); return; } _ecore_con_client_flush(cl); } /** * Returns if SSL support is available * @return 1 if SSL is available, 0 if it is not. * @ingroup Ecore_Con_Client_Group */ EAPI int ecore_con_ssl_available_get(void) { #if USE_OPENSSL return 1; #else return 0; #endif } static void _ecore_con_server_free(Ecore_Con_Server *svr) { double t_start, t; ECORE_MAGIC_SET(svr, ECORE_MAGIC_NONE); t_start = ecore_time_get(); while ((svr->write_buf) && (!svr->dead)) { _ecore_con_server_flush(svr); t = ecore_time_get(); if ((t - t_start) > 0.5) { printf("ECORE_CON: EEK - stuck in _ecore_con_server_free() trying\n" " to flush data out from the server, and have been for\n" " %1.1f seconds. This is taking too long. Aborting flush.\n", (t - t_start)); break; } } if (svr->write_buf) free(svr->write_buf); while (!ecore_list_empty_is(svr->clients)) _ecore_con_client_free(ecore_list_first_remove(svr->clients)); ecore_list_destroy(svr->clients); if ((svr->created) && (svr->path) && (svr->ppid == getpid())) unlink(svr->path); if (svr->fd >= 0) close(svr->fd); #if USE_OPENSSL if (svr->ssl) { SSL_shutdown(svr->ssl); SSL_free(svr->ssl); } if (svr->ssl_ctx) SSL_CTX_free(svr->ssl_ctx); #endif if (svr->name) free(svr->name); if (svr->path) free(svr->path); if (svr->ip) free(svr->ip); if (svr->fd_handler) ecore_main_fd_handler_del(svr->fd_handler); free(svr); } static void _ecore_con_client_free(Ecore_Con_Client *cl) { double t_start, t; ECORE_MAGIC_SET(cl, ECORE_MAGIC_NONE); t_start = ecore_time_get(); while ((cl->buf) && (!cl->dead)) { _ecore_con_client_flush(cl); t = ecore_time_get(); if ((t - t_start) > 0.5) { printf("ECORE_CON: EEK - stuck in _ecore_con_client_free() trying\n" " to flush data out from the client, and have been for\n" " %1.1f seconds. This is taking too long. Aborting flush.\n", (t - t_start)); break; } } if (cl->buf) free(cl->buf); if (cl->fd >= 0) close(cl->fd); if (cl->fd_handler) ecore_main_fd_handler_del(cl->fd_handler); if (cl->ip) free(cl->ip); free(cl); } static int _ecore_con_svr_handler(void *data, Ecore_Fd_Handler *fd_handler __UNUSED__) { Ecore_Con_Server *svr; int new_fd; struct sockaddr_in incoming; size_t size_in; svr = data; if (svr->dead) return 1; if (svr->delete_me) return 1; if ((svr->client_limit >= 0) && (!svr->reject_excess_clients)) { if (ecore_list_count(svr->clients) >= svr->client_limit) return 1; } /* a new client */ size_in = sizeof(struct sockaddr_in); new_fd = accept(svr->fd, (struct sockaddr *)&incoming, (socklen_t *)&size_in); if (new_fd >= 0) { Ecore_Con_Client *cl; char buf[64]; uint32_t ip; if ((svr->client_limit >= 0) && (svr->reject_excess_clients)) { close(new_fd); return 1; } cl = calloc(1, sizeof(Ecore_Con_Client)); if (!cl) { close(new_fd); return 1; } fcntl(new_fd, F_SETFL, O_NONBLOCK); fcntl(new_fd, F_SETFD, FD_CLOEXEC); cl->fd = new_fd; cl->server = svr; cl->fd_handler = ecore_main_fd_handler_add(cl->fd, ECORE_FD_READ, _ecore_con_svr_cl_handler, cl, NULL, NULL); ECORE_MAGIC_SET(cl, ECORE_MAGIC_CON_CLIENT); ecore_list_append(svr->clients, cl); if (!svr->path) { ip = incoming.sin_addr.s_addr; snprintf(buf, sizeof(buf), "%i.%i.%i.%i", (ip ) & 0xff, (ip >> 8 ) & 0xff, (ip >> 16) & 0xff, (ip >> 24) & 0xff); cl->ip = strdup(buf); } if (!cl->delete_me) { Ecore_Con_Event_Client_Add *e; e = calloc(1, sizeof(Ecore_Con_Event_Client_Add)); if (e) { cl->event_count++; e->client = cl; ecore_event_add(ECORE_CON_EVENT_CLIENT_ADD, e, _ecore_con_event_client_add_free, NULL); } } } return 1; } #if USE_OPENSSL /* Tries to connect an Ecore_Con_Server to an SSL host. * Returns 1 on success, -1 on fatal errors and 0 if the caller * should try again later. */ static int svr_try_connect_ssl(Ecore_Con_Server *svr) { int res, ssl_err, flag = 0; if (!ssl_init_count) { SSL_library_init(); SSL_load_error_strings(); } ssl_init_count++; res = SSL_connect(svr->ssl); if (res == 1) return 1; ssl_err = SSL_get_error(svr->ssl, res); if (ssl_err == SSL_ERROR_NONE) return 1; if (ssl_err == SSL_ERROR_WANT_READ) flag = ECORE_FD_READ; else if (ssl_err == SSL_ERROR_WANT_WRITE) flag = ECORE_FD_WRITE; else return -1; if (svr->fd_handler) { if (flag) ecore_main_fd_handler_active_set(svr->fd_handler, flag); } return 0; } #endif static void kill_server(Ecore_Con_Server *svr) { if (!svr->delete_me) { Ecore_Con_Event_Server_Del *e; e = calloc(1, sizeof(Ecore_Con_Event_Server_Del)); if (e) { svr->event_count++; e->server = svr; ecore_event_add(ECORE_CON_EVENT_SERVER_DEL, e, _ecore_con_event_server_del_free, NULL); } } svr->dead = 1; if (svr->fd_handler) ecore_main_fd_handler_del(svr->fd_handler); svr->fd_handler = NULL; } static void _ecore_con_cb_dns_lookup(void *data, struct hostent *he) { Ecore_Con_Server *svr; struct sockaddr_in socket_addr; int curstate = 0; char buf[64]; uint32_t ip; svr = data; if (!he) goto error; svr->fd = socket(AF_INET, SOCK_STREAM, 0); if (svr->fd < 0) goto error; if (fcntl(svr->fd, F_SETFL, O_NONBLOCK) < 0) goto error; if (fcntl(svr->fd, F_SETFD, FD_CLOEXEC) < 0) goto error; if (setsockopt(svr->fd, SOL_SOCKET, SO_REUSEADDR, &curstate, sizeof(curstate)) < 0) goto error; socket_addr.sin_family = AF_INET; socket_addr.sin_port = htons(svr->port); memcpy((struct in_addr *)&socket_addr.sin_addr, he->h_addr, sizeof(struct in_addr)); if (connect(svr->fd, (struct sockaddr *)&socket_addr, sizeof(struct sockaddr_in)) < 0) { if (errno != EINPROGRESS) goto error; svr->connecting = 1; svr->fd_handler = ecore_main_fd_handler_add(svr->fd, ECORE_FD_READ | ECORE_FD_WRITE, _ecore_con_cl_handler, svr, NULL, NULL); } else svr->fd_handler = ecore_main_fd_handler_add(svr->fd, ECORE_FD_READ, _ecore_con_cl_handler, svr, NULL, NULL); if (!svr->fd_handler) goto error; ip = socket_addr.sin_addr.s_addr; snprintf(buf, sizeof(buf), "%i.%i.%i.%i", (ip ) & 0xff, (ip >> 8 ) & 0xff, (ip >> 16) & 0xff, (ip >> 24) & 0xff); svr->ip = strdup(buf); #if USE_OPENSSL if (svr->type & ECORE_CON_SSL) { if (!ssl_init_count) { SSL_library_init(); SSL_load_error_strings(); } ssl_init_count++; switch (svr->type & ECORE_CON_SSL) { case ECORE_CON_USE_SSL2: if (!(svr->ssl_ctx = SSL_CTX_new(SSLv2_client_method()))) goto error; break; case ECORE_CON_USE_SSL3: if (!(svr->ssl_ctx = SSL_CTX_new(SSLv3_client_method()))) goto error; break; case ECORE_CON_USE_TLS: if (!(svr->ssl_ctx = SSL_CTX_new(TLSv1_client_method()))) goto error; break; } if (!(svr->ssl = SSL_new(svr->ssl_ctx))) goto error; SSL_set_fd(svr->ssl, svr->fd); } #endif return; error: kill_server(svr); } static int svr_try_connect_plain(Ecore_Con_Server *svr) { int so_err = 0; unsigned int size = sizeof(int); if (getsockopt(svr->fd, SOL_SOCKET, SO_ERROR, &so_err, &size) < 0) so_err = -1; if (so_err != 0) { /* we lost our server! */ kill_server(svr); } else { if (!svr->delete_me) { /* we got our server! */ Ecore_Con_Event_Server_Add *e; svr->connecting = 0; e = calloc(1, sizeof(Ecore_Con_Event_Server_Add)); if (e) { svr->event_count++; e->server = svr; ecore_event_add(ECORE_CON_EVENT_SERVER_ADD, e, _ecore_con_event_server_add_free, NULL); } } if (svr->fd_handler) { if (!svr->write_buf) ecore_main_fd_handler_active_set(svr->fd_handler, ECORE_FD_READ); } } return (!svr->dead); } /* returns 1 on success, 0 on failure */ static int svr_try_connect(Ecore_Con_Server *svr) { #if USE_OPENSSL if (!svr->ssl) { #endif return svr_try_connect_plain(svr); #if USE_OPENSSL } else switch (svr_try_connect_ssl(svr)) { case 1: return svr_try_connect_plain(svr); case -1: kill_server(svr); return 0; default: return 0; } #endif } static int _ecore_con_cl_handler(void *data, Ecore_Fd_Handler *fd_handler) { Ecore_Con_Server *svr; #if USE_OPENSSL int ssl_err = SSL_ERROR_NONE; #endif svr = data; if (svr->dead) return 1; if (svr->delete_me) return 1; if (ecore_main_fd_handler_active_get(fd_handler, ECORE_FD_READ)) { unsigned char *inbuf = NULL; int inbuf_num = 0; if (svr->connecting && !svr_try_connect(svr)) return 1; for (;;) { int num, lost_server; char buf[READBUFSIZ]; lost_server = 0; #if USE_OPENSSL if (!svr->ssl) { #endif if ((num = read(svr->fd, buf, READBUFSIZ)) < 1) { lost_server = ((errno == EIO) || (errno == EBADF) || (errno == EPIPE) || (errno == EINVAL) || (errno == ENOSPC) || (num == 0)); /* is num == 0 is right - when the server closes us * off we will get this (as this is called when select * tells us there is data to read!) */ } #if USE_OPENSSL } else { num = SSL_read(svr->ssl, buf, READBUFSIZ); if (num < 1) { ssl_err = SSL_get_error(svr->ssl, num); lost_server = (ssl_err == SSL_ERROR_ZERO_RETURN); if (ssl_err == SSL_ERROR_SYSCALL) { if (num == 0) lost_server = 1; else { lost_server = ((errno == EIO) || (errno == EBADF) || (errno == EPIPE) || (errno == EINVAL) || (errno == ENOSPC) || (errno == ECONNRESET)); } } } else ssl_err = SSL_ERROR_NONE; } #endif if (num < 1) { if (inbuf) { if (!svr->delete_me) { Ecore_Con_Event_Server_Data *e; e = calloc(1, sizeof(Ecore_Con_Event_Server_Data)); if (e) { svr->event_count++; e->server = svr; e->data = inbuf; e->size = inbuf_num; ecore_event_add(ECORE_CON_EVENT_SERVER_DATA, e, _ecore_con_event_server_data_free, NULL); } } } if (lost_server) { /* we lost our server! */ kill_server(svr); return 1; } break; } else { inbuf = realloc(inbuf, inbuf_num + num); memcpy(inbuf + inbuf_num, buf, num); inbuf_num += num; } } #if USE_OPENSSL if (svr->fd_handler) { if (svr->ssl && ssl_err == SSL_ERROR_WANT_READ) ecore_main_fd_handler_active_set(svr->fd_handler, ECORE_FD_READ); else if (svr->ssl && ssl_err == SSL_ERROR_WANT_WRITE) ecore_main_fd_handler_active_set(svr->fd_handler, ECORE_FD_WRITE); } #endif } else if (ecore_main_fd_handler_active_get(fd_handler, ECORE_FD_WRITE)) { if (svr->connecting && !svr_try_connect (svr)) return 1; _ecore_con_server_flush(svr); } return 1; } static int _ecore_con_svr_cl_handler(void *data, Ecore_Fd_Handler *fd_handler) { Ecore_Con_Client *cl; cl = data; if (cl->dead) return 1; if (cl->delete_me) return 1; if (ecore_main_fd_handler_active_get(fd_handler, ECORE_FD_READ)) { unsigned char *inbuf = NULL; int inbuf_num = 0; for (;;) { char buf[65536]; int num; errno = 0; num = read(cl->fd, buf, 65536); if (num < 1) { if (inbuf) { if (!cl->delete_me) { Ecore_Con_Event_Client_Data *e; e = calloc(1, sizeof(Ecore_Con_Event_Client_Data)); if (e) { cl->event_count++; e->client = cl; e->data = inbuf; e->size = inbuf_num; ecore_event_add(ECORE_CON_EVENT_CLIENT_DATA, e, _ecore_con_event_client_data_free, NULL); } } } if ((errno == EIO) || (errno == EBADF) || (errno == EPIPE) || (errno == EINVAL) || (errno == ENOSPC) || (num == 0)/* is num == 0 right? */) { if (!cl->delete_me) { /* we lost our client! */ Ecore_Con_Event_Client_Del *e; e = calloc(1, sizeof(Ecore_Con_Event_Client_Del)); if (e) { cl->event_count++; e->client = cl; ecore_event_add(ECORE_CON_EVENT_CLIENT_DEL, e, _ecore_con_event_client_del_free, NULL); } } cl->dead = 1; if (cl->fd_handler) ecore_main_fd_handler_del(cl->fd_handler); cl->fd_handler = NULL; } break; } else { inbuf = realloc(inbuf, inbuf_num + num); memcpy(inbuf + inbuf_num, buf, num); inbuf_num += num; } } } else if (ecore_main_fd_handler_active_get(fd_handler, ECORE_FD_WRITE)) _ecore_con_client_flush(cl); return 1; } static void _ecore_con_server_flush(Ecore_Con_Server *svr) { int count, num, lost_server = 0; #if USE_OPENSSL int ssl_err = SSL_ERROR_NONE; #endif if (!svr->write_buf) return; /* check whether we need to write anything at all. * we must not write zero bytes with SSL_write() since it * causes undefined behaviour */ if (svr->write_buf_size == svr->write_buf_offset) return; num = svr->write_buf_size - svr->write_buf_offset; #if USE_OPENSSL if (!svr->ssl) { #endif count = write(svr->fd, svr->write_buf + svr->write_buf_offset, num); if (count < 1) lost_server = (errno == EIO || errno == EBADF || errno == EPIPE || errno == EINVAL || errno == ENOSPC); #if USE_OPENSSL } else { count = SSL_write(svr->ssl, svr->write_buf + svr->write_buf_offset, num); if (count < 1) { ssl_err = SSL_get_error(svr->ssl, count); lost_server = (ssl_err == SSL_ERROR_ZERO_RETURN); } } #endif if (lost_server) { /* we lost our server! */ kill_server(svr); return; } if (count < 1) { #if USE_OPENSSL if (svr->fd_handler) { if (svr->ssl && ssl_err == SSL_ERROR_WANT_READ) ecore_main_fd_handler_active_set(svr->fd_handler, ECORE_FD_READ); else if (svr->ssl && ssl_err == SSL_ERROR_WANT_WRITE) ecore_main_fd_handler_active_set(svr->fd_handler, ECORE_FD_WRITE); } #endif return; } svr->write_buf_offset += count; if (svr->write_buf_offset >= svr->write_buf_size) { svr->write_buf_size = 0; svr->write_buf_offset = 0; free(svr->write_buf); svr->write_buf = NULL; if (svr->fd_handler) ecore_main_fd_handler_active_set(svr->fd_handler, ECORE_FD_READ); } } static void _ecore_con_client_flush(Ecore_Con_Client *cl) { int count, num; if (!cl->buf) return; num = cl->buf_size - cl->buf_offset; count = write(cl->fd, cl->buf + cl->buf_offset, num); if (count < 1) { if ((errno == EIO) || (errno == EBADF) || (errno == EPIPE) || (errno == EINVAL) || (errno == ENOSPC)) { if (!cl->delete_me) { /* we lost our client! */ Ecore_Con_Event_Client_Del *e; e = calloc(1, sizeof(Ecore_Con_Event_Client_Del)); if (e) { cl->event_count++; e->client = cl; ecore_event_add(ECORE_CON_EVENT_CLIENT_DEL, e, _ecore_con_event_client_del_free, NULL); } cl->dead = 1; if (cl->fd_handler) ecore_main_fd_handler_del(cl->fd_handler); cl->fd_handler = NULL; } } return; } cl->buf_offset += count; if (cl->buf_offset >= cl->buf_size) { cl->buf_size = 0; cl->buf_offset = 0; free(cl->buf); cl->buf = NULL; if (cl->fd_handler) ecore_main_fd_handler_active_set(cl->fd_handler, ECORE_FD_READ); } } static void _ecore_con_event_client_add_free(void *data __UNUSED__, void *ev) { Ecore_Con_Event_Client_Add *e; e = ev; e->client->event_count--; if ((e->client->event_count == 0) && (e->client->delete_me)) ecore_con_client_del(e->client); free(e); } static void _ecore_con_event_client_del_free(void *data __UNUSED__, void *ev) { Ecore_Con_Event_Client_Del *e; e = ev; e->client->event_count--; if ((e->client->event_count == 0) && (e->client->delete_me)) ecore_con_client_del(e->client); free(e); } static void _ecore_con_event_client_data_free(void *data __UNUSED__, void *ev) { Ecore_Con_Event_Client_Data *e; e = ev; e->client->event_count--; if (e->data) free(e->data); if ((e->client->event_count == 0) && (e->client->delete_me)) ecore_con_client_del(e->client); free(e); } static void _ecore_con_event_server_add_free(void *data __UNUSED__, void *ev) { Ecore_Con_Event_Server_Add *e; e = ev; e->server->event_count--; if ((e->server->event_count == 0) && (e->server->delete_me)) ecore_con_server_del(e->server); free(e); } static void _ecore_con_event_server_del_free(void *data __UNUSED__, void *ev) { Ecore_Con_Event_Server_Del *e; e = ev; e->server->event_count--; if ((e->server->event_count == 0) && (e->server->delete_me)) ecore_con_server_del(e->server); free(e); } static void _ecore_con_event_server_data_free(void *data __UNUSED__, void *ev) { Ecore_Con_Event_Server_Data *e; e = ev; e->server->event_count--; if (e->data) free(e->data); if ((e->server->event_count == 0) && (e->server->delete_me)) ecore_con_server_del(e->server); free(e); }