// // Copyright 2020 Staysail Systems, Inc. // Copyright 2018 Capitar IT Group BV // Copyright 2019 Devolutions // // This software is supplied under the terms of the MIT License, a // copy of which should be located in the distribution where this // file was obtained (LICENSE.txt). A copy of the license may also be // found online at https://opensource.org/licenses/MIT. // #include "core/nng_impl.h" #include "win_ipc.h" #include typedef struct { nng_stream_listener sl; char * path; bool started; bool closed; HANDLE f; SECURITY_ATTRIBUTES sec_attr; nni_list aios; nni_mtx mtx; nni_cv cv; nni_win_io io; nni_sockaddr sa; int rv; } ipc_listener; static void ipc_accept_done(ipc_listener *l, int rv) { nni_aio * aio; HANDLE f; nng_stream *c; aio = nni_list_first(&l->aios); nni_list_remove(&l->aios, aio); nni_cv_wake(&l->cv); if (l->closed) { // Closed, so bail. DisconnectNamedPipe(l->f); nni_aio_finish_error(aio, NNG_ECLOSED); return; } // Create a replacement pipe. f = CreateNamedPipeA(l->path, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS, PIPE_UNLIMITED_INSTANCES, 4096, 4096, 0, &l->sec_attr); if (f == INVALID_HANDLE_VALUE) { // We couldn't create a replacement pipe, so we have to // abort the client from our side, so that we can keep // our server pipe available. rv = nni_win_error(GetLastError()); DisconnectNamedPipe(l->f); nni_aio_finish_error(aio, rv); return; } if (((rv = nni_win_io_register(f)) != 0) || ((rv = nni_win_ipc_init(&c, l->f, &l->sa, false)) != 0)) { DisconnectNamedPipe(l->f); DisconnectNamedPipe(f); CloseHandle(f); nni_aio_finish_error(aio, rv); return; } // Install the replacement pipe. l->f = f; nni_aio_set_output(aio, 0, c); nni_aio_finish(aio, 0, 0); } static void ipc_accept_start(ipc_listener *l) { nni_aio *aio; if (l->closed) { while ((aio = nni_list_first(&l->aios)) != NULL) { nni_list_remove(&l->aios, aio); nni_aio_finish_error(aio, NNG_ECLOSED); } nni_cv_wake(&l->cv); } while ((aio = nni_list_first(&l->aios)) != NULL) { int rv; if ((ConnectNamedPipe(l->f, &l->io.olpd)) || ((rv = GetLastError()) == ERROR_IO_PENDING)) { // Success, or pending, handled via completion pkt. return; } if (rv == ERROR_PIPE_CONNECTED) { // Kind of like success, but as this is technically // an "error", we have to complete it ourself. // Fake a completion. ipc_accept_done(l, 0); } else { // Fast-fail (synchronous). nni_aio_finish_error(aio, nni_win_error(rv)); } } } static void ipc_accept_cb(nni_win_io *io, int rv, size_t cnt) { ipc_listener *l = io->ptr; NNI_ARG_UNUSED(cnt); nni_mtx_lock(&l->mtx); if (nni_list_empty(&l->aios)) { // We canceled this somehow. We no longer care. DisconnectNamedPipe(l->f); nni_mtx_unlock(&l->mtx); return; } if (l->rv != 0) { rv = l->rv; l->rv = 0; } ipc_accept_done(l, rv); ipc_accept_start(l); nni_mtx_unlock(&l->mtx); } static int ipc_listener_set_sec_desc(void *arg, const void *buf, size_t sz, nni_type t) { ipc_listener *l = arg; void * desc; int rv; if ((rv = nni_copyin_ptr(&desc, buf, sz, t)) != 0) { return (rv); } if (!IsValidSecurityDescriptor((SECURITY_DESCRIPTOR *) desc)) { return (NNG_EINVAL); } nni_mtx_lock(&l->mtx); if (l->started) { nni_mtx_unlock(&l->mtx); return (NNG_EBUSY); } l->sec_attr.lpSecurityDescriptor = desc; nni_mtx_unlock(&l->mtx); return (0); } static int ipc_listener_get_addr(void *arg, void *buf, size_t *szp, nni_type t) { ipc_listener *l = arg; return ((nni_copyout_sockaddr(&l->sa, buf, szp, t))); } static const nni_option ipc_listener_options[] = { { .o_name = NNG_OPT_IPC_SECURITY_DESCRIPTOR, .o_set = ipc_listener_set_sec_desc, }, { .o_name = NNG_OPT_LOCADDR, .o_get = ipc_listener_get_addr, }, { .o_name = NULL, }, }; static int ipc_listener_set( void *arg, const char *name, const void *buf, size_t sz, nni_type t) { ipc_listener *l = arg; return (nni_setopt(ipc_listener_options, name, l, buf, sz, t)); } static int ipc_listener_get( void *arg, const char *name, void *buf, size_t *szp, nni_type t) { ipc_listener *l = arg; return (nni_getopt(ipc_listener_options, name, l, buf, szp, t)); } static int ipc_listener_listen(void *arg) { ipc_listener *l = arg; int rv; HANDLE f; char * path; nni_mtx_lock(&l->mtx); if (l->started) { nni_mtx_unlock(&l->mtx); return (NNG_EBUSY); } if (l->closed) { nni_mtx_unlock(&l->mtx); return (NNG_ECLOSED); } rv = nni_asprintf(&path, IPC_PIPE_PREFIX "%s", l->sa.s_ipc.sa_path); if (rv != 0) { nni_mtx_unlock(&l->mtx); return (rv); } f = CreateNamedPipeA(path, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS, PIPE_UNLIMITED_INSTANCES, 4096, 4096, 0, &l->sec_attr); if (f == INVALID_HANDLE_VALUE) { if ((rv = GetLastError()) == ERROR_ACCESS_DENIED) { rv = NNG_EADDRINUSE; } else { rv = nni_win_error(rv); } nni_mtx_unlock(&l->mtx); nni_strfree(path); return (rv); } if ((rv = nni_win_io_register(f)) != 0) { CloseHandle(f); nni_mtx_unlock(&l->mtx); nni_strfree(path); return (rv); } l->f = f; l->path = path; l->started = true; nni_mtx_unlock(&l->mtx); return (0); } static void ipc_accept_cancel(nni_aio *aio, void *arg, int rv) { ipc_listener *l = arg; nni_mtx_unlock(&l->mtx); if (aio == nni_list_first(&l->aios)) { l->rv = rv; CancelIoEx(l->f, &l->io.olpd); } else if (nni_aio_list_active(aio)) { nni_list_remove(&l->aios, aio); nni_cv_wake(&l->cv); nni_aio_finish_error(aio, rv); } nni_mtx_unlock(&l->mtx); } static void ipc_listener_accept(void *arg, nni_aio *aio) { ipc_listener *l = arg; if (nni_aio_begin(aio) != 0) { return; } nni_mtx_lock(&l->mtx); if (!l->started) { nni_mtx_unlock(&l->mtx); nni_aio_finish_error(aio, NNG_ESTATE); return; } if (l->closed) { nni_mtx_unlock(&l->mtx); nni_aio_finish_error(aio, NNG_ECLOSED); return; } nni_list_append(&l->aios, aio); if (nni_list_first(&l->aios) == aio) { ipc_accept_start(l); } nni_mtx_unlock(&l->mtx); } static void ipc_listener_close(void *arg) { ipc_listener *l = arg; nni_mtx_lock(&l->mtx); if (!l->closed) { l->closed = true; if (!nni_list_empty(&l->aios)) { CancelIoEx(l->f, &l->io.olpd); } DisconnectNamedPipe(l->f); CloseHandle(l->f); } nni_mtx_unlock(&l->mtx); } static void ipc_listener_free(void *arg) { ipc_listener *l = arg; nni_mtx_lock(&l->mtx); while (!nni_list_empty(&l->aios)) { nni_cv_wait(&l->cv); } nni_mtx_unlock(&l->mtx); nni_win_io_fini(&l->io); nni_strfree(l->path); nni_cv_fini(&l->cv); nni_mtx_fini(&l->mtx); NNI_FREE_STRUCT(l); } int nni_ipc_listener_alloc(nng_stream_listener **lp, const nng_url *url) { ipc_listener *l; int rv; if ((strcmp(url->u_scheme, "ipc") != 0) || (url->u_path == NULL) || (strlen(url->u_path) == 0) || (strlen(url->u_path) >= NNG_MAXADDRLEN)) { return (NNG_EADDRINVAL); } if ((l = NNI_ALLOC_STRUCT(l)) == NULL) { return (NNG_ENOMEM); } if ((rv = nni_win_io_init(&l->io, ipc_accept_cb, l)) != 0) { NNI_FREE_STRUCT(l); return (rv); } l->started = false; l->closed = false; l->sec_attr.nLength = sizeof(l->sec_attr); l->sec_attr.lpSecurityDescriptor = NULL; l->sec_attr.bInheritHandle = FALSE; l->sa.s_ipc.sa_family = NNG_AF_IPC; l->sl.sl_free = ipc_listener_free; l->sl.sl_close = ipc_listener_close; l->sl.sl_listen = ipc_listener_listen; l->sl.sl_accept = ipc_listener_accept; l->sl.sl_get = ipc_listener_get; l->sl.sl_set = ipc_listener_set; snprintf(l->sa.s_ipc.sa_path, NNG_MAXADDRLEN, "%s", url->u_path); nni_aio_list_init(&l->aios); nni_mtx_init(&l->mtx); nni_cv_init(&l->cv, &l->mtx); *lp = (void *) l; return (0); }