#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include struct _patty_ax25_server { patty_list *ifaces; patty_dict *socks_by_fd, *socks_bound, *socks_pending_accept, *socks_pending_connect, *socks_established; int fd_max; fd_set fds_watch, fds_r, fds_w; }; int patty_ax25_server_init(patty_ax25_server *server) { memset(server, '\0', sizeof(*server)); if ((server->ifaces = patty_list_new()) == NULL) { goto error_list_new_ifaces; } if ((server->socks_by_fd = patty_dict_new()) == NULL) { goto error_dict_new_socks_by_fd; } if ((server->socks_bound = patty_dict_new()) == NULL) { goto error_dict_new_socks_bound; } if ((server->socks_pending_accept = patty_dict_new()) == NULL) { goto error_dict_new_socks_pending_accept; } if ((server->socks_pending_connect = patty_dict_new()) == NULL) { goto error_dict_new_socks_pending_connect; } if ((server->socks_established = patty_dict_new()) == NULL) { goto error_dict_new_socks_established; } return 0; error_dict_new_socks_established: patty_dict_destroy(server->socks_pending_connect); error_dict_new_socks_pending_connect: patty_dict_destroy(server->socks_pending_accept); error_dict_new_socks_pending_accept: patty_dict_destroy(server->socks_bound); error_dict_new_socks_bound: patty_dict_destroy(server->socks_by_fd); error_dict_new_socks_by_fd: patty_list_destroy(server->ifaces); error_list_new_ifaces: return -1; } static int destroy_if(patty_ax25_if *iface, void *ctx) { patty_ax25_if_destroy(iface); return 0; } void patty_ax25_server_stop(patty_ax25_server *server) { patty_dict_destroy(server->socks_established); patty_dict_destroy(server->socks_pending_connect); patty_dict_destroy(server->socks_pending_accept); patty_dict_destroy(server->socks_bound); patty_dict_destroy(server->socks_by_fd); patty_ax25_server_each_if(server, destroy_if, NULL); patty_list_destroy(server->ifaces); } static void sock_init(patty_ax25_sock *sock, enum patty_ax25_sock_type type) { memset(sock, '\0', sizeof(*sock)); sock->status = PATTY_AX25_SOCK_CLOSED; sock->mode = PATTY_AX25_SOCK_DM; sock->type = type; sock->n_maxlen = PATTY_AX25_FRAME_DEFAULT_MAXLEN; sock->n_window = PATTY_AX25_FRAME_DEFAULT_WINDOW; } static inline void hash_init(uint32_t *hash) { *hash = 0xffffffdf; } static inline void hash_byte(uint32_t *hash, uint8_t c) { *hash += c; *hash += (*hash << 10); *hash ^= (*hash >> 6); } static inline void hash_addr(uint32_t *hash, patty_ax25_addr *addr) { int i; for (i=0; icallsign[i] >> 1); } hash_byte(hash, PATTY_AX25_ADDRESS_SSID_NUMBER(addr->ssid)); } static inline void hash_end(uint32_t *hash) { *hash += (*hash << 3); *hash ^= (*hash >> 11); *hash += (*hash << 15); } static uint32_t hash_addrpair(patty_ax25_addr *local, patty_ax25_addr *remote) { uint32_t hash; hash_init(&hash); hash_addr(&hash, local); hash_addr(&hash, remote); hash_end(&hash); return hash; } static patty_ax25_sock *sock_by_fd(patty_dict *dict, int fd) { return patty_dict_get(dict, NULL + fd, sizeof(fd)); } static patty_ax25_sock *sock_by_addr(patty_dict *dict, patty_ax25_addr *addr) { patty_dict_slot *slot; uint32_t hash; hash_init(&hash); hash_addr(&hash, addr); hash_end(&hash); if ((slot = patty_dict_slot_find(dict, hash)) == NULL) { errno = EEXIST; goto error_dict_slot_find; } return (patty_ax25_sock *)slot->value; error_dict_slot_find: return NULL; } static patty_ax25_sock *sock_by_addrpair(patty_dict *dict, patty_ax25_addr *local, patty_ax25_addr *remote) { patty_dict_slot *slot; uint32_t hash = hash_addrpair(local, remote); if ((slot = patty_dict_slot_find(dict, hash)) == NULL) { errno = EEXIST; goto error_dict_slot_find; } return (patty_ax25_sock *)slot->value; error_dict_slot_find: return NULL; } static int sock_save_by_fd(patty_dict *dict, patty_ax25_sock *sock) { if (patty_dict_set(dict, NULL + sock->fd, sizeof(sock->fd), sock) == NULL) { goto error_dict_set; } return sock->fd; error_dict_set: return -1; } static int sock_save_by_addr(patty_dict *dict, patty_ax25_sock *sock, patty_ax25_addr *addr) { uint32_t hash; hash_init(&hash); hash_addr(&hash, addr); hash_end(&hash); if (patty_dict_set_with_hash(dict, addr, sizeof(*addr), sock, hash) == NULL) { goto error_dict_set_with_hash; } return 0; error_dict_set_with_hash: return -1; } static int sock_save_by_addrpair(patty_dict *dict, patty_ax25_sock *sock, patty_ax25_addr *local, patty_ax25_addr *remote) { uint32_t hash = hash_addrpair(local, remote); if (patty_dict_set_with_hash(dict, sock, sizeof(*sock), sock, hash) == NULL) { goto error_dict_set_with_hash; } return 0; error_dict_set_with_hash: return -1; } static int server_connect(patty_ax25_server *server, int socket, patty_ax25_addr *addr) { patty_ax25_sock *sock; if ((sock = sock_by_fd(server->socks_by_fd, socket)) == NULL) { goto error_sock_by_fd; } if (sock->remote.callsign[0] != '\0') { errno = EEXIST; goto error_exists; } memcpy(&sock->remote, addr, sizeof(*addr)); return 0; error_exists: error_sock_by_fd: return -1; } int patty_ax25_server_add_if(patty_ax25_server *server, patty_ax25_if *iface) { int fd; if ((fd = patty_kiss_tnc_fd(iface->tnc)) >= 0) { FD_SET(fd, &server->fds_watch); if (server->fd_max < fd + 1) { server->fd_max = fd + 1; } } if (patty_dict_set(server->socks_by_fd, NULL + fd, sizeof(fd), iface) == NULL) { goto error_dict_set; } if (patty_list_append(server->ifaces, iface) == NULL) { goto error_list_append; } return 0; error_list_append: error_dict_set: return -1; } int patty_ax25_server_delete_if(patty_ax25_server *server, patty_ax25_if *iface) { patty_list_item *item = server->ifaces->first; int fd, i = 0; while (item) { if (item->value == iface) { if (patty_list_splice(server->ifaces, i) == NULL) { goto error_list_splice; } return 0; } item = item->next; i++; } if ((fd = patty_kiss_tnc_fd(iface->tnc)) >= 0) { FD_CLR(fd, &server->fds_watch); if (server->fd_max == fd) { server->fd_max--; } if (patty_dict_delete(server->socks_by_fd, NULL + fd, sizeof(fd)) == NULL) { goto error_dict_delete; } } return 0; error_dict_delete: error_list_splice: return -1; } patty_ax25_if *patty_ax25_server_get_if(patty_ax25_server *server, const char *name) { patty_list_iterator *iter; patty_ax25_if *iface; if ((iter = patty_list_start(server->ifaces)) == NULL) { goto error_list_start; } while ((iface = patty_list_next(iter)) != NULL) { if (strncmp(iface->name, name, sizeof(iface->name)) == 0) { patty_list_finish(iter); return iface; } } patty_list_finish(iter); error_list_start: return NULL; } int patty_ax25_server_each_if(patty_ax25_server *server, int (*callback)(patty_ax25_if *, void *), void *ctx) { patty_list_iterator *iter; patty_ax25_if *iface; if ((iter = patty_list_start(server->ifaces)) == NULL) { goto error_list_start; } while ((iface = patty_list_next(iter)) != NULL) { if (callback(iface, ctx) < 0) { goto error_callback; } } patty_list_finish(iter); return 0; error_callback: patty_list_finish(iter); error_list_start: return -1; } static int server_socket(patty_ax25_server *server, int client) { patty_ax25_call_socket_request request; patty_ax25_call_socket_response response; patty_ax25_sock *sock; if ((sock = malloc(sizeof(*sock))) == NULL) { goto error_malloc_sock; } sock_init(sock, request.type); if ((sock->fd = open("/dev/null", O_RDWR)) < 0) { goto error_io; } if (read(client, &request, sizeof(request)) < 0) { goto error_io; } response.ret = sock->fd; response.eno = 0; if (sock_save_by_fd(server->socks_by_fd, sock) < 0) { response.ret = -1; response.eno = errno; goto error_sock_save_by_fd; } error_sock_save_by_fd: if (write(client, &response, sizeof(response)) < 0) { goto error_io; } return 0; error_io: free(sock); error_malloc_sock: return -1; } static int server_bind(patty_ax25_server *server, int client) { patty_ax25_call_bind_request request; patty_ax25_call_bind_response response; patty_ax25_sock *sock; if (read(client, &request, sizeof(request)) < 0) { goto error_io; } if ((sock = sock_by_fd(server->socks_by_fd, request.socket)) == NULL) { response.ret = -1; response.eno = EBADF; goto error_sock_by_fd; } /* * If there is already a local address associated with this socket, then * that's a problem. */ if (sock->local.callsign[0] != '\0') { response.ret = -1; response.eno = EINVAL; goto error_bound; } if (patty_dict_get(server->socks_bound, &sock->local, sizeof(sock->local))) { response.ret = -1; response.eno = EADDRINUSE; goto error_inuse; } memcpy(&sock->local, &request.peer, sizeof(request.peer)); if (patty_dict_set(server->socks_bound, &sock->local, sizeof(sock->local), sock) == NULL) { goto error_dict_set; } if (write(client, &response, sizeof(response)) < 0) { goto error_io; } response.ret = 0; response.eno = 0; error_inuse: error_bound: error_sock_by_fd: return 0; error_dict_set: error_io: return -1; } static int server_listen(patty_ax25_server *server, int client) { patty_ax25_call_listen_request request; patty_ax25_call_listen_response response; patty_ax25_sock *sock; if (read(client, &request, sizeof(request)) < 0) { goto error_io; } if ((sock = sock_by_fd(server->socks_by_fd, request.socket)) == NULL) { response.ret = -1; response.eno = EBADF; goto error_sock_by_fd; } if (sock->local.callsign[0] == '\0') { response.eno = EINVAL; goto error_invalid_fd; } sock->status = PATTY_AX25_SOCK_LISTENING; response.ret = 0; response.eno = 0; error_invalid_fd: error_sock_by_fd: if (write(client, &response, sizeof(response)) < 0) { goto error_io; } return 0; error_io: return -1; } static int server_accept_pty(patty_ax25_server *server, patty_ax25_call_accept_response *response, patty_ax25_sock *sock) { if ((sock->fd = open("/dev/ptmx", O_RDWR)) < 0) { goto error_open; } if (grantpt(sock->fd) < 0) { goto error_grantpt; } if (unlockpt(sock->fd) < 0) { goto error_unlockpt; } if (ptsname_r(sock->fd, response->path, PATTY_AX25_SOCK_PATH_SIZE) < 0) { goto error_ptsname_r; } response->ret = sock->fd; response->eno = 0; return 0; error_ptsname_r: error_unlockpt: error_grantpt: close(sock->fd); error_open: return -1; } static int server_accept_unix(patty_ax25_server *server, patty_ax25_call_accept_response *response, patty_ax25_sock *sock) { struct sockaddr_un addr; if ((sock->fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) { goto error_socket; } if (snprintf(response->path, PATTY_AX25_SOCK_PATH_SIZE, "/var/run/patty/%d.sock", sock->fd) < 0) { goto error_snprintf; } memset(&addr, '\0', sizeof(addr)); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, response->path, sizeof(addr.sun_path)); if (bind(sock->fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { goto error_bind; } response->ret = sock->fd; response->eno = 0; return 0; error_bind: error_snprintf: close(sock->fd); error_socket: return -1; } static int server_accept(patty_ax25_server *server, int client) { patty_ax25_call_accept_request request; patty_ax25_call_accept_response response; patty_ax25_sock *local, *remote; if (read(client, &request, sizeof(request)) < 0) { goto error_io; } if ((local = sock_by_fd(server->socks_by_fd, request.socket)) == NULL) { response.ret = -1; response.eno = EBADF; goto error_sock_by_fd; } if ((remote = malloc(sizeof(*remote))) == NULL) { goto error_malloc_remote; } sock_init(remote, local->type); if (local->type & PATTY_AX25_SOCK_PTY) { if (server_accept_pty(server, &response, remote) < 0) { goto error_server_accept_pty; } } else { if (server_accept_unix(server, &response, remote) < 0) { goto error_server_accept_unix; } } error_sock_by_fd: if (write(client, &response, sizeof(response)) < 0) { goto error_io; } return 0; error_server_accept_unix: error_server_accept_pty: free(remote); error_malloc_remote: error_io: return -1; }