#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include typedef int (*patty_ax25_server_call)(patty_ax25_server *, int); struct _patty_ax25_server { char path[PATTY_AX25_SOCK_PATH_SIZE]; int fd, /* fd of UNIX domain socket */ fd_max; struct timeval timeout; fd_set fds_watch, /* fds to monitor with select() */ fds_r; /* fds select()ed for reading */ patty_list *ifaces; patty_ax25_route_table *routes; patty_dict *socks_by_fd, *socks_pending_accept, *socks_pending_connect, *socks_established; patty_dict *clients, *clients_by_sock; }; patty_ax25_server *patty_ax25_server_new(const char *path) { patty_ax25_server *server; if ((server = malloc(sizeof(*server))) == NULL) { goto error_malloc_server; } memset(server, '\0', sizeof(*server)); strncpy(server->path, path, PATTY_AX25_SOCK_PATH_SIZE); if ((server->ifaces = patty_list_new()) == NULL) { goto error_list_new_ifaces; } if ((server->routes = patty_ax25_route_table_new()) == NULL) { goto error_route_table_new; } if ((server->socks_by_fd = patty_dict_new()) == NULL) { goto error_dict_new_socks_by_fd; } 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; } if ((server->clients = patty_dict_new()) == NULL) { goto error_dict_new_clients; } if ((server->clients_by_sock = patty_dict_new()) == NULL) { goto error_dict_new_clients_by_sock; } return server; error_dict_new_clients_by_sock: patty_dict_destroy(server->clients); error_dict_new_clients: patty_dict_destroy(server->socks_established); 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_by_fd); error_dict_new_socks_by_fd: patty_ax25_route_table_destroy(server->routes); error_route_table_new: patty_list_destroy(server->ifaces); error_list_new_ifaces: free(server); error_malloc_server: return NULL; } static int destroy_if(patty_ax25_if *iface, void *ctx) { patty_ax25_if_destroy(iface); return 0; } void patty_ax25_server_destroy(patty_ax25_server *server) { patty_dict_destroy(server->clients_by_sock); patty_dict_destroy(server->clients); 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_by_fd); patty_ax25_server_each_if(server, destroy_if, NULL); patty_ax25_route_table_destroy(server->routes); patty_list_destroy(server->ifaces); free(server); } static inline void fd_watch(patty_ax25_server *server, int fd) { FD_SET(fd, &server->fds_watch); if (server->fd_max <= fd) { server->fd_max = fd + 1; } } static inline void fd_clear(patty_ax25_server *server, int fd) { int i; FD_CLR(fd, &server->fds_watch); for (i=0; i<8*sizeof(&server->fds_watch); i++) { if (FD_ISSET(i, &server->fds_watch)) { server->fd_max = i; } } server->fd_max++; } static patty_ax25_sock *sock_by_fd(patty_dict *dict, int fd) { return patty_dict_get(dict, (uint32_t)fd); } static patty_ax25_sock *sock_by_addr(patty_dict *dict, patty_ax25_addr *addr) { uint32_t hash; patty_hash_init(&hash); patty_ax25_addr_hash(&hash, addr); patty_hash_end(&hash); return patty_dict_get(dict, hash); } static patty_ax25_sock *sock_by_addrpair(patty_dict *dict, patty_ax25_addr *local, patty_ax25_addr *remote) { uint32_t hash; patty_hash_init(&hash); patty_ax25_addr_hash(&hash, local); patty_ax25_addr_hash(&hash, remote); patty_hash_end(&hash); return patty_dict_get(dict, hash); } static int sock_save_by_fd(patty_dict *dict, patty_ax25_sock *sock) { if (patty_dict_set(dict, (uint32_t)sock->fd, sock) == NULL) { goto error_dict_set; } return sock->fd; error_dict_set: return -1; } static inline int client_by_sock(patty_ax25_server *server, patty_ax25_sock *sock) { void *value; if ((value = patty_dict_get(server->clients_by_sock, (uint32_t)sock->fd)) == NULL) { goto error_dict_get; } return (int)((int64_t)value); error_dict_get: return -1; } static inline int client_save_by_sock(patty_ax25_server *server, int client, patty_ax25_sock *sock) { if (patty_dict_set(server->clients_by_sock, (uint32_t)sock->fd, NULL + client) == NULL) { goto error_dict_set; } return 0; error_dict_set: return -1; } static inline int client_delete_by_sock(patty_ax25_server *server, patty_ax25_sock *sock) { return patty_dict_delete(server->clients_by_sock, (uint32_t)sock->fd); } static inline uint32_t hash_addr(patty_ax25_addr *addr) { uint32_t hash; patty_hash_init(&hash); patty_ax25_addr_hash(&hash, addr); patty_hash_end(&hash); return hash; } static inline uint32_t hash_addrpair(patty_ax25_addr *local, patty_ax25_addr *remote) { uint32_t hash; patty_hash_init(&hash); patty_ax25_addr_hash(&hash, local); patty_ax25_addr_hash(&hash, remote); patty_hash_end(&hash); return hash; } static int sock_save_by_addr(patty_dict *dict, patty_ax25_sock *sock, patty_ax25_addr *addr) { uint32_t hash = hash_addr(addr); if (patty_dict_set(dict, hash, sock) == NULL) { goto error_dict_set; } return 0; error_dict_set: 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(dict, hash, sock) == NULL) { goto error_dict_set; } return 0; error_dict_set: return -1; } static int sock_delete_by_addr(patty_dict *dict, patty_ax25_addr *addr) { uint32_t hash = hash_addr(addr); return patty_dict_delete(dict, hash); } static int sock_delete_by_addrpair(patty_dict *dict, patty_ax25_addr *local, patty_ax25_addr *remote) { uint32_t hash = hash_addrpair(local, remote); return patty_dict_delete(dict, hash); } static int sock_close(patty_ax25_server *server, patty_ax25_sock *sock) { if (sock->status == PATTY_AX25_SOCK_ESTABLISHED) { if (patty_ax25_sock_send_disc(sock, PATTY_AX25_FRAME_POLL) < 0) { goto error_sock_send_disc; } if (sock_delete_by_addrpair(server->socks_established, &sock->local, &sock->remote) < 0) { goto error_sock_delete_by_addrpair_established; } } if (sock->status == PATTY_AX25_SOCK_PENDING_CONNECT) { if (sock_delete_by_addrpair(server->socks_pending_connect, &sock->local, &sock->remote) < 0) { goto error_sock_delete_by_addrpair_pending_connect; } } if (sock->status == PATTY_AX25_SOCK_PENDING_ACCEPT) { if (sock_delete_by_addr(server->socks_pending_accept, &sock->local) < 0) { goto error_sock_delete_by_addr_pending_accept; } } if (patty_dict_delete(server->socks_by_fd, (uint32_t)sock->fd) < 0) { goto error_dict_delete_by_fd_socks; } (void)client_delete_by_sock(server, sock); fd_clear(server, sock->fd); patty_ax25_sock_destroy(sock); return 0; error_dict_delete_by_fd_socks: error_sock_delete_by_addr_pending_accept: error_sock_delete_by_addrpair_pending_connect: error_sock_delete_by_addrpair_established: error_sock_send_disc: 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_watch(server, fd); } if (patty_list_append(server->ifaces, iface) == NULL) { goto error_list_append; } return 0; error_list_append: 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_watch(server, fd); if (patty_dict_delete(server->socks_by_fd, (uint32_t)fd) < 0) { 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_item *item = server->ifaces->first; while (item) { patty_ax25_if *iface = item->value; if (strncmp(iface->name, name, sizeof(iface->name)) == 0) { return iface; } item = item->next; } return NULL; } int patty_ax25_server_each_if(patty_ax25_server *server, int (*callback)(patty_ax25_if *, void *), void *ctx) { patty_list_item *item = server->ifaces->first; while (item) { patty_ax25_if *iface = item->value; if (callback(iface, ctx) < 0) { goto error_callback; } item = item->next; } return 0; error_callback: return -1; } int patty_ax25_server_add_route(patty_ax25_server *server, patty_ax25_route *route) { return patty_ax25_route_table_add(server->routes, route); } 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 (read(client, &request, sizeof(request)) < 0) { goto error_read; } if ((sock = patty_ax25_sock_new(request.proto, request.type)) == NULL) { goto error_sock_new; } if (sock_save_by_fd(server->socks_by_fd, sock) < 0) { response.ret = -1; response.eno = errno; goto error_sock_save_by_fd; } if (client_save_by_sock(server, client, sock) < 0) { goto error_client_save_by_sock; } response.ret = sock->fd; response.eno = 0; memcpy(response.path, sock->path, sizeof(response.path)); return write(client, &response, sizeof(response)); error_client_save_by_sock: error_sock_save_by_fd: patty_ax25_sock_destroy(sock); error_sock_new: error_read: return -1; } static int server_setsockopt(patty_ax25_server *server, int client) { patty_ax25_call_setsockopt_request request; patty_ax25_call_setsockopt_response response; patty_ax25_sock *sock; if (read(client, &request, sizeof(request)) < 0) { goto error_read; } if ((sock = sock_by_fd(server->socks_by_fd, request.fd)) == NULL) { response.ret = -1; response.eno = EBADF; goto error_sock_by_fd; } switch (request.opt) { case PATTY_AX25_SOCK_IF: { patty_ax25_call_setsockopt_if data; patty_ax25_if *iface; if (sock->type != PATTY_AX25_SOCK_RAW) { response.ret = -1; response.eno = EINVAL; goto error_invalid_type; } if (read(client, &data, request.len) < 0) { goto error_read; } if ((iface = patty_ax25_server_get_if(server, data.name)) == NULL) { response.ret = -1; response.eno = ENODEV; goto error_get_if; } patty_ax25_sock_bind_if(sock, iface); if (sock_save_by_fd(server->socks_established, sock) < 0) { goto error_sock_save_by_fd; } fd_watch(server, sock->fd); break; } default: response.ret = -1; response.eno = EINVAL; goto error_invalid_opt; } response.ret = 0; response.eno = 0; error_sock_by_fd: error_get_if: error_invalid_type: error_invalid_opt: return write(client, &response, sizeof(response)); error_sock_save_by_fd: error_read: 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.fd)) == NULL) { response.ret = -1; response.eno = EBADF; goto error_sock_by_fd; } switch (sock->status) { case PATTY_AX25_SOCK_LISTENING: case PATTY_AX25_SOCK_ESTABLISHED: response.ret = -1; response.eno = EINVAL; goto error_invalid_status; default: break; } memcpy(&sock->local, &request.peer, sizeof(request.peer)); response.ret = 0; response.eno = 0; error_invalid_status: error_sock_by_fd: return write(client, &response, sizeof(response)); 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.fd)) == 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: return write(client, &response, sizeof(response)); error_io: 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 *sock; if (read(client, &request, sizeof(request)) < 0) { goto error_io; } if ((sock = sock_by_fd(server->socks_by_fd, request.fd)) == NULL) { response.ret = -1; response.eno = EBADF; goto error_sock_by_fd; } if (sock_save_by_addr(server->socks_pending_accept, sock, &sock->local) < 0) { goto error_save_by_addr; } return 0; error_sock_by_fd: return write(client, &response, sizeof(response)); error_save_by_addr: error_io: return -1; } static int server_connect(patty_ax25_server *server, int client) { patty_ax25_call_connect_request request; patty_ax25_call_connect_response response; patty_ax25_sock *sock; patty_ax25_route *route; patty_ax25_if *iface; if (read(client, &request, sizeof(request)) < 0) { goto error_io; } if ((sock = sock_by_fd(server->socks_by_fd, request.fd)) == NULL) { response.ret = -1; response.eno = EBADF; goto error_sock_by_fd; } switch (sock->status) { case PATTY_AX25_SOCK_LISTENING: response.ret = -1; response.eno = EINVAL; goto error_invalid_status; break; case PATTY_AX25_SOCK_ESTABLISHED: response.ret = -1; response.eno = EISCONN; goto error_invalid_status; break; default: break; } if (sock->local.callsign[0]) { /* * If there is an address already bound to this socket, locate the * appropriate route. */ route = patty_ax25_route_table_find(server->routes, &sock->local); } else { /* * Otherwise, locate the default route. */ route = patty_ax25_route_table_default(server->routes); } /* * If no route could be found, then assume the network is down or not * configured. */ if (route == NULL) { response.ret = -1; response.eno = ENETDOWN; goto error_network_down; } patty_ax25_sock_bind_if(sock, iface = route->iface); /* * If there is no local address bound to this sock, then bind the * address of the default route interface. */ if (sock->local.callsign[0] == '\0') { memcpy(&sock->local, &iface->addr, sizeof(sock->local)); } /* * Bind the requested remote address to the socket. */ memcpy(&sock->remote, &request.peer, sizeof(request.peer)); sock->status = PATTY_AX25_SOCK_PENDING_CONNECT; if (sock_save_by_addrpair(server->socks_pending_connect, sock, &sock->local, &sock->remote) < 0) { goto error_sock_save_by_addrpair; } if (client_save_by_sock(server, client, sock) < 0) { goto error_client_save_by_sock; } /* * Send an XID frame, to attempt to negotiate AX.25 v2.2 and its default * parameters. */ if (patty_ax25_sock_send_xid(sock) < 0) { response.ret = -1; response.eno = errno; goto error_sock_send_xid; } /* * At this point, we will wait for a DM, FRMR or XID response, which * will help us determine what version of AX.25 to apply for this socket, * or whether the peer is not accepting connections. */ return 0; error_sock_send_xid: error_network_down: error_invalid_status: error_sock_by_fd: return write(client, &response, sizeof(response)); error_client_save_by_sock: error_sock_save_by_addrpair: error_io: return -1; } static int server_close(patty_ax25_server *server, int client) { patty_ax25_call_close_request request; patty_ax25_call_close_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.fd)) == NULL) { response.ret = -1; response.eno = EBADF; goto error_sock_by_fd; } if (sock_close(server, sock) < 0) { response.ret = -1; response.eno = EBADF; goto error_sock_delete; } response.ret = 0; response.eno = 0; error_sock_delete: error_sock_by_fd: return write(client, &response, sizeof(response)); error_io: return -1; } static patty_ax25_server_call server_calls[PATTY_AX25_CALL_COUNT] = { NULL, server_socket, server_setsockopt, server_bind, server_listen, server_accept, server_connect, server_close, NULL, NULL }; static int listen_unix(patty_ax25_server *server, const char *path) { struct sockaddr_un addr; struct stat st; if (server->fd) { errno = EBUSY; goto error_listening; } if (stat(path, &st) >= 0) { unlink(path); } if ((server->fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) { goto error_socket; } memset(&addr, '\0', sizeof(addr)); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, path, sizeof(addr.sun_path)); if (bind(server->fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { goto error_bind; } if (listen(server->fd, 0) < 0) { goto error_listen; } fd_watch(server, server->fd); return 0; error_listen: error_bind: close(server->fd); error_socket: error_listening: return -1; } static int accept_client(patty_ax25_server *server) { int fd; struct sockaddr addr; socklen_t addrlen = sizeof(addr); memset(&addr, '\0', addrlen); if (!FD_ISSET(server->fd, &server->fds_r)) { goto done; } if ((fd = accept(server->fd, &addr, &addrlen)) < 0) { goto error_accept; } if (patty_dict_set(server->clients, (uint32_t)fd, NULL + fd) == NULL) { goto error_dict_set; } fd_watch(server, fd); done: return 0; error_dict_set: close(fd); error_accept: return -1; } static int handle_client(uint32_t key, void *value, void *ctx) { patty_ax25_server *server = ctx; int client = (int)key; ssize_t readlen; enum patty_ax25_call call; if (!FD_ISSET(client, &server->fds_r)) { goto done; } if ((readlen = read(client, &call, sizeof(call))) < 0) { goto error_io; } else if (readlen == 0) { fd_clear(server, client); if (patty_dict_delete(server->clients, key) < 0) { goto error_dict_delete; } if (close(client) < 0) { goto error_io; } goto done; } if (call <= PATTY_AX25_CALL_NONE || call >= PATTY_AX25_CALL_COUNT) { goto error_io; } if (server_calls[call] == NULL) { errno = ENOSYS; goto error_not_implemented; } return server_calls[call](server, client); done: return 0; error_not_implemented: error_dict_delete: error_io: return -1; } static int handle_clients(patty_ax25_server *server) { return patty_dict_each(server->clients, handle_client, server); } static void save_reply_addr(patty_ax25_sock *sock, patty_ax25_frame *frame) { unsigned int i, hops = frame->hops > PATTY_AX25_MAX_HOPS? PATTY_AX25_MAX_HOPS: sock->hops; memcpy(&sock->remote, &frame->src, sizeof(patty_ax25_addr)); memcpy(&sock->local, &frame->dest, sizeof(patty_ax25_addr)); for (i=0; irepeaters[i], &frame->repeaters[hops-1-i], sizeof(patty_ax25_addr)); } sock->hops = hops; } static int reply_to(patty_ax25_if *iface, patty_ax25_frame *frame, patty_ax25_frame *reply) { ssize_t len; if ((len = patty_ax25_frame_encode_reply_to(frame, reply, PATTY_AX25_FRAME_NORMAL, iface->tx_buf, iface->mtu)) < 0) { goto error_toobig; } return patty_ax25_if_send(iface, iface->tx_buf, len); error_toobig: return -1; } static int reply_dm(patty_ax25_if *iface, patty_ax25_frame *frame, int flag) { patty_ax25_frame reply = { .control = PATTY_AX25_FRAME_DM | (flag << 4), .proto = PATTY_AX25_PROTO_NONE, .info = NULL, .infolen = 0 }; return reply_to(iface, frame, &reply); } static int reply_ua(patty_ax25_if *iface, patty_ax25_frame *frame, int flag) { patty_ax25_frame reply = { .control = PATTY_AX25_FRAME_UA | (flag << 4), .proto = PATTY_AX25_PROTO_NONE, .info = NULL, .infolen = 0 }; char callsign[7]; uint8_t ssid; patty_ax25_ntop(&frame->dest, callsign, &ssid, sizeof(callsign)); return reply_to(iface, frame, &reply); } static int handle_frmr(patty_ax25_server *server, patty_ax25_if *iface, patty_ax25_frame *frame) { patty_ax25_sock *sock; if ((sock = sock_by_addrpair(server->socks_pending_connect, &frame->dest, &frame->src)) == NULL) { return 0; } return patty_ax25_sock_send_sabm(sock, PATTY_AX25_FRAME_POLL); } static int handle_sabm(patty_ax25_server *server, patty_ax25_if *iface, patty_ax25_frame *frame) { int client; patty_ax25_sock *local, *remote; patty_ax25_call_accept_response response; enum patty_ax25_sock_mode mode; if ((local = sock_by_addr(server->socks_pending_accept, &frame->dest)) == NULL) { return reply_dm(iface, frame, PATTY_AX25_FRAME_FINAL); } if ((client = client_by_sock(server, local)) < 0) { goto error_client_by_sock; } if ((remote = patty_ax25_sock_new(local->proto, local->type)) == NULL) { goto error_sock_new; } mode = (frame->type == PATTY_AX25_FRAME_SABM)? PATTY_AX25_SOCK_SABM: PATTY_AX25_SOCK_SABME; remote->status = PATTY_AX25_SOCK_ESTABLISHED; remote->iface = iface; if (patty_ax25_sock_mode_set(remote, mode) < 0) { goto error_sock_set_mode; } save_reply_addr(remote, frame); if (sock_save_by_fd(server->socks_by_fd, remote) < 0) { goto error_sock_save_by_fd; } if (sock_save_by_addrpair(server->socks_established, remote, &remote->local, &remote->remote) < 0) { goto error_sock_save_by_addrpair; } if (sock_delete_by_addr(server->socks_pending_accept, &local->local) < 0) { goto error_sock_delete_by_addr; } if (frame->pf) { if (reply_ua(iface, frame, PATTY_AX25_FRAME_FINAL) < 0) { goto error_reply_ua; } } memset(&response, '\0', sizeof(response)); response.ret = 0; response.eno = 0; memcpy(&response.peer, &frame->src, sizeof(patty_ax25_addr)); memcpy(&response.path, &remote->path, sizeof(response.path)); if (write(client, &response, sizeof(response)) < 0) { goto error_write; } fd_watch(server, remote->fd); return 0; error_write: error_reply_ua: error_sock_delete_by_addr: error_sock_save_by_addrpair: error_sock_save_by_fd: error_sock_set_mode: patty_ax25_sock_destroy(remote); error_sock_new: error_client_by_sock: return -1; } static int handle_ua(patty_ax25_server *server, patty_ax25_if *iface, patty_ax25_frame *frame) { int client; patty_ax25_sock *sock; patty_ax25_call_connect_response response; if ((sock = sock_by_addrpair(server->socks_pending_connect, &frame->dest, &frame->src)) == NULL) { return reply_dm(iface, frame, PATTY_AX25_FRAME_FINAL); } if ((client = client_by_sock(server, sock)) < 0) { goto error_client_by_sock; } sock->status = PATTY_AX25_SOCK_ESTABLISHED; sock->mode = PATTY_AX25_SOCK_SABM; if (sock_save_by_addrpair(server->socks_established, sock, &sock->local, &sock->remote) < 0) { goto error_save_by_addrpair; } if (sock_delete_by_addrpair(server->socks_pending_connect, &sock->local, &sock->remote) < 0) { goto error_delete_by_addrpair; } response.ret = 0; response.eno = 0; if (write(client, &response, sizeof(response)) < 0) { goto error_write; } fd_watch(server, sock->fd); return 0; error_write: patty_ax25_sock_destroy(sock); error_delete_by_addrpair: error_save_by_addrpair: error_client_by_sock: return -1; } static int handle_dm(patty_ax25_server *server, patty_ax25_if *iface, patty_ax25_frame *frame) { int client; patty_ax25_sock *sock; patty_ax25_call_connect_response response; if ((sock = sock_by_addrpair(server->socks_established, &frame->dest, &frame->src)) != NULL) { return sock_close(server, sock); } if ((sock = sock_by_addrpair(server->socks_pending_connect, &frame->dest, &frame->src)) == NULL) { return 0; } if ((client = client_by_sock(server, sock)) < 0) { goto error_client_by_sock; } if (sock_delete_by_addrpair(server->socks_pending_connect, &frame->dest, &frame->src) < 0) { goto error_delete_by_addrpair; } response.ret = -1; response.eno = ECONNREFUSED; patty_ax25_sock_reset(sock); if (write(client, &response, sizeof(response)) < 0) { goto error_write; } return 0; error_write: error_delete_by_addrpair: error_client_by_sock: return -1; } static int handle_i(patty_ax25_server *server, patty_ax25_if *iface, patty_ax25_sock *sock, patty_ax25_frame *frame) { if (sock == NULL || sock->status != PATTY_AX25_SOCK_ESTABLISHED) { return frame->pf? reply_dm(iface, frame, PATTY_AX25_FRAME_FINAL): 0; } if (sock->seq_recv == frame->ns) { patty_ax25_sock_seq_recv_incr(sock); } else { return frame->pf? patty_ax25_sock_send_rej(sock, PATTY_AX25_FRAME_RESPONSE): 0; } if (write(sock->fd, frame->info, frame->infolen) < 0) { goto error_write; } return frame->pf? patty_ax25_sock_send_rr(sock, PATTY_AX25_FRAME_RESPONSE): 0; error_write: return -1; } static int handle_disc(patty_ax25_server *server, patty_ax25_if *iface, patty_ax25_frame *frame) { patty_ax25_sock *sock; if ((sock = sock_by_addrpair(server->socks_established, &frame->dest, &frame->src)) == NULL) { goto done; } if (sock_close(server, sock) < 0) { goto error_sock_delete; } done: if (frame->pf) { return reply_dm(iface, frame, PATTY_AX25_FRAME_FINAL); } return 0; error_sock_delete: return -1; } static int handle_rej(patty_ax25_server *server, patty_ax25_sock *sock, patty_ax25_frame *frame) { unsigned int i; for (i=frame->nr; iseq_send; i++) { if (patty_ax25_sock_resend(sock, i) < 0) { goto error_sock_resend; } } return 0; error_sock_resend: return -1; } static int handle_srej(patty_ax25_server *server, patty_ax25_sock *sock, patty_ax25_frame *frame) { /* * TODO: Read the fine print of section 4.3.2.4 */ if (patty_ax25_sock_resend(sock, frame->nr) < 0) { goto error_sock_resend; } return 0; error_sock_resend: return -1; } static int handle_xid(patty_ax25_server *server, patty_ax25_if *iface, patty_ax25_frame *frame, void *buf, size_t offset, size_t len) { patty_ax25_params params; patty_ax25_sock *local, *remote; if (patty_ax25_frame_decode_xid(¶ms, buf, offset, len) < 0) { goto error_io; } /* * First, check if this XID packet is for a pending outbound connection. */ if ((remote = sock_by_addrpair(server->socks_pending_connect, &frame->dest, &frame->src)) != NULL) { /* * Since we've received an XID packet, we can assume that the remote * station is capable of speaking AX.25 v2.2. Therefore, we should * upgrade the socket defaults accordingly, and negotiate downwards * as necessary. */ patty_ax25_sock_upgrade(remote); if (patty_ax25_sock_params_set(remote, ¶ms) < 0) { goto error_sock_params_set; } /* * Since this XID frame is for a socket that is awaiting outbound * connection, we can send an SABM or SABME packet, as necessary. */ if (params.hdlc & PATTY_AX25_PARAM_HDLC_MODULO_128) { remote->mode = PATTY_AX25_SOCK_SABME; return patty_ax25_sock_send_sabme(remote, PATTY_AX25_FRAME_POLL); } else { return patty_ax25_sock_send_sabm(remote, PATTY_AX25_FRAME_POLL); } } /* * Second, check if this XID packet is for a socket pending accept. */ if ((local = sock_by_addr(server->socks_pending_accept, &frame->dest)) != NULL) { if ((remote = patty_ax25_sock_new(local->proto, local->type)) == NULL) { goto error_sock_new; } remote->status = PATTY_AX25_SOCK_PENDING_ACCEPT; if (params.hdlc & PATTY_AX25_PARAM_HDLC_MODULO_128) { remote->mode = PATTY_AX25_SOCK_SABME; } patty_ax25_sock_bind_if(remote, iface); patty_ax25_sock_upgrade(remote); if (patty_ax25_sock_params_set(remote, ¶ms) < 0) { goto error_sock_params_set; } save_reply_addr(remote, frame); if (sock_save_by_fd(server->socks_by_fd, remote) < 0) { goto error_sock_save_by_fd; } return patty_ax25_sock_send_xid(remote); } return reply_dm(iface, frame, PATTY_AX25_FRAME_FINAL); error_sock_save_by_fd: error_sock_new: error_sock_params_set: error_io: return -1; } static int handle_frame(patty_ax25_server *server, patty_ax25_if *iface, void *buf, size_t len) { patty_ax25_frame frame; enum patty_ax25_frame_format format = PATTY_AX25_FRAME_NORMAL; ssize_t decoded, offset = 0; patty_ax25_sock *sock; if ((decoded = patty_ax25_frame_decode_address(&frame, buf, len)) < 0) { goto error_io; } else { offset += decoded; } if ((sock = sock_by_addrpair(server->socks_established, &frame.dest, &frame.src)) != NULL) { if (sock->mode == PATTY_AX25_SOCK_SABME) { format = PATTY_AX25_FRAME_EXTENDED; } } if ((decoded = patty_ax25_frame_decode_control(&frame, format, buf, offset, len)) < 0) { goto error_io; } else { offset += decoded; } switch (frame.type) { case PATTY_AX25_FRAME_UA: return handle_ua(server, iface, &frame); case PATTY_AX25_FRAME_DM: return handle_dm(server, iface, &frame); case PATTY_AX25_FRAME_SABM: case PATTY_AX25_FRAME_SABME: return handle_sabm(server, iface, &frame); case PATTY_AX25_FRAME_DISC: return handle_disc(server, iface, &frame); case PATTY_AX25_FRAME_I: return handle_i(server, iface, sock, &frame); case PATTY_AX25_FRAME_REJ: return handle_rej(server, sock, &frame); case PATTY_AX25_FRAME_SREJ: return handle_srej(server, sock, &frame); case PATTY_AX25_FRAME_XID: return handle_xid(server, iface, &frame, buf, offset, len); case PATTY_AX25_FRAME_FRMR: return handle_frmr(server, iface, &frame); default: break; } return 0; error_io: iface->stats.dropped++; return -1; } static int handle_iface(patty_ax25_server *server, patty_ax25_if *iface) { int fd = patty_kiss_tnc_fd(iface->tnc); void *buf; ssize_t readlen; if (!FD_ISSET(fd, &server->fds_r)) { goto done; } do { if ((readlen = patty_ax25_if_recv(iface, &buf)) < 0) { goto error_io; } else if (readlen == 0) { fd_clear(server, fd); goto done; } if (handle_frame(server, iface, buf, readlen) < 0) { goto error_io; } } while (patty_ax25_if_pending(iface)); done: return 0; error_io: return -1; } static int handle_ifaces(patty_ax25_server *server) { patty_list_item *item = server->ifaces->first; while (item) { patty_ax25_if *iface = item->value; if (handle_iface(server, iface) < 0) { goto error_io; } item = item->next; } return 0; error_io: return -1; } static int handle_sock(uint32_t key, void *value, void *ctx) { patty_ax25_server *server = ctx; patty_ax25_sock *sock = value; ssize_t len; if (!FD_ISSET(sock->fd, &server->fds_r)) { goto done; } if ((len = read(sock->fd, sock->buf, sock->n_maxlen_rx)) < 0) { if (errno == EIO) { (void)sock_close(server, sock); goto done; } goto error_io; } else if (len == 0) { sock_close(server, sock); goto done; } if (patty_ax25_sock_write(sock, sock->buf, len) < 0) { goto error_sock_write; } done: return 0; error_sock_write: error_io: return -1; } static int handle_socks(patty_ax25_server *server) { return patty_dict_each(server->socks_established, handle_sock, server); } int patty_ax25_server_run(patty_ax25_server *server) { if (listen_unix(server, server->path) < 0) { goto error_listen_unix; } while (1) { int nready; memcpy(&server->fds_r, &server->fds_watch, sizeof(server->fds_r)); if ((nready = select( server->fd_max, &server->fds_r, NULL, NULL, NULL)) < 0) { goto error_io; } if (handle_clients(server) < 0) { goto error_io; } if (handle_ifaces(server) < 0) { goto error_io; } if (handle_socks(server) < 0) { goto error_io; } if (accept_client(server) < 0) { goto error_io; } } close(server->fd); return 0; error_io: close(server->fd); error_listen_unix: return -1; }