diff --git a/.gitignore b/.gitignore index 7e03b51..8cfcf63 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ aclocal.m4 autom4te.cache compile +confdefs.h +conftest.* config.guess config.h config.h.in @@ -25,3 +27,6 @@ missing *.o *.so stamp-h1 +*.swp +*.swo +test_xoauth2_token_conv diff --git a/Makefile.am b/Makefile.am index c6f21f4..82b4a4a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,10 +1,21 @@ +SUBDIRS = . tests ACLOCAL_AMFLAGS = -I m4 CYRYS_SASL_PREFIX = @CYRUS_SASL_PREFIX@ CYRYS_SASL_CPPFLAGS = @CYRUS_SASL_CPPFLAGS@ CYRUS_SASL_LDFLAGS = @CYRUS_SASL_LDFLAGS@ +XOAUTH2_TOKEN_CONV_EXTRA_LIBS = @XOAUTH2_TOKEN_CONV_EXTRA_LIBS@ + +noinst_LTLIBRARIES = libxoauth2_token_conv.la +libxoauth2_token_conv_la_SOURCES = xoauth2_socket_common.c xoauth2_token_conv.c +if XOAUTH2_WIN32 +libxoauth2_token_conv_la_SOURCES += xoauth2_socket_win32.c +else +libxoauth2_token_conv_la_SOURCES += xoauth2_socket_unix.c +endif pkglibdir = ${CYRUS_SASL_PREFIX}/lib/sasl2 pkglib_LTLIBRARIES = libxoauth2.la libxoauth2_la_CPPFLAGS = ${CYRUS_SASL_CPPFLAGS} libxoauth2_la_LDFLAGS = ${CYRUS_SASL_LDFLAGS} -module libxoauth2_la_SOURCES = xoauth2_str.c xoauth2_init.c xoauth2_server.c xoauth2_client.c +libxoauth2_la_LIBADD = libxoauth2_token_conv.la ${XOAUTH2_TOKEN_CONV_EXTRA_LIBS} diff --git a/configure.ac b/configure.ac index 59c38c1..b0326bf 100644 --- a/configure.ac +++ b/configure.ac @@ -24,19 +24,28 @@ AC_CONFIG_MACRO_DIR([m4]) LT_INIT AC_PROG_CC AC_CONFIG_HEADERS([config.h]) -AC_CONFIG_FILES([Makefile]) +AC_CONFIG_FILES([Makefile tests/Makefile]) CYRUS_SASL_CPPFLAGS= CYRUS_SASL_PREFIX=/usr CYRUS_SASL_LDFLAGS= +XOAUTH2_WIN32= +XOAUTH2_TOKEN_CONV_EXTRA_LIBS= # Cygwin stuff case "$host" in *-*-cygwin*) CYRUS_SASL_LDFLAGS=-no-undefined ;; + *-*-mingw32*) + CPPFLAGS="${CPPFLAGS} -DXOAUTH2_WIN32 -D_WIN32 -D_WIN32_WINNT=_WIN32_WINNT_VISTA" + XOAUTH2_TOKEN_CONV_EXTRA_LIBS="-lws2_32" + XOAUTH2_WIN32=1 + ;; esac +AM_CONDITIONAL([XOAUTH2_WIN32], [test x$XOAUTH2_WIN32 != x]) + AC_ARG_WITH( [cyrus-sasl], AS_HELP_STRING([--with-cyrus-sasl=[[PREFIX]]], [Installation prefix of Cyrus-SASL (defaults to /usr)]), @@ -61,7 +70,14 @@ if test -z "$CYRUS_SASL_PREFIX"; then AC_MSG_ERROR([Cyrus-SASL not found under $CYRUS_SASL_PREFIX]) fi +AC_CHECK_HEADERS([winsock2.h ws2tcpip.h], [], [], [/**/]) +AC_CHECK_HEADERS([alloca.h unistd.h poll.h sys/ioctl.h sys/socket.h sys/types.h sys/uio.h sys/un.h netinet/in.h netinet/in6.h netdb.h]) + +AC_TYPE_UINT32_T + AC_SUBST([CYRUS_SASL_CPPFLAGS], [$CYRUS_SASL_CPPFLAGS]) AC_SUBST([CYRUS_SASL_LDFLAGS], [$CYRUS_SASL_LDFLAGS]) AC_SUBST([CYRUS_SASL_PREFIX], [$CYRUS_SASL_PREFIX]) +AC_SUBST([XOAUTH2_WIN32], [$XOAUTH2_WIN32]) +AC_SUBST([XOAUTH2_TOKEN_CONV_EXTRA_LIBS], [$XOAUTH2_TOKEN_CONV_EXTRA_LIBS]) AC_OUTPUT diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 0000000..a0ab786 --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1 @@ +test_xoauth2_token_conv* diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 0000000..fa330d8 --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1,9 @@ +CYRYS_SASL_PREFIX = @CYRUS_SASL_PREFIX@ +CYRYS_SASL_CPPFLAGS = @CYRUS_SASL_CPPFLAGS@ +CYRUS_SASL_LDFLAGS = @CYRUS_SASL_LDFLAGS@ +XOAUTH2_TOKEN_CONV_EXTRA_LIBS = @XOAUTH2_TOKEN_CONV_EXTRA_LIBS@ + +noinst_PROGRAMS = test_xoauth2_token_conv +test_xoauth2_token_conv_SOURCES = test_xoauth2_token_conv.c test_xoauth2_token_conv.h ../xoauth2_str.c +test_xoauth2_token_conv_CPPFLAGS = ${CYRUS_SASL_CPPFLAGS} +test_xoauth2_token_conv_LDADD = ../libxoauth2_token_conv.la ${CYRUS_SASL_LDFLAGS} ${XOAUTH2_TOKEN_CONV_EXTRA_LIBS} diff --git a/tests/test_xoauth2_token_conv.c b/tests/test_xoauth2_token_conv.c new file mode 100644 index 0000000..1ea9d28 --- /dev/null +++ b/tests/test_xoauth2_token_conv.c @@ -0,0 +1,76 @@ +#include +#include +#include +#include + +#include "xoauth2_token_conv.h" +#include "xoauth2_socket.h" + +static void _log(UNUSED(sasl_conn_t *conn), int level, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + fputs("\n", stderr); + va_end(ap); +} + +static const xoauth2_plugin_token_conv_settings_t settings = { 2000, 2000, 2000 }; + +static int do_converse(const sasl_utils_t *utils, const char *addr) +{ + int err; + xoauth2_plugin_token_conv_t token_conv; + xoauth2_plugin_str_t str; + + err = xoauth2_plugin_str_init(utils, &str); + if (SASL_OK != err) { + xoauth2_plugin_token_conv_free(utils, &token_conv); + return err; + } + + { + int i; + for (i = 0; i < 3; i++) { + int j; + err = xoauth2_plugin_token_conv_init(utils, &token_conv, &settings, addr); + if (SASL_OK != err) { + xoauth2_plugin_str_free(utils, &str); + return err; + } + for (j = 0; j < 3; j++) { + str.len = 0; + err = xoauth2_plugin_token_conv_retrieve_access_token(utils, &token_conv, &str, "AUTHID!", sizeof("AUTHID!") - 1); + if (SASL_OK != err) { + xoauth2_plugin_str_free(utils, &str); + xoauth2_plugin_token_conv_free(utils, &token_conv); + return err; + } + fprintf(stderr, "(%d) %s\n", str.len, str.buf); + } + xoauth2_plugin_token_conv_free(utils, &token_conv); + } + } + + xoauth2_plugin_str_free(utils, &str); + + return SASL_OK; +} + +int main() +{ + sasl_utils_t utils; + + setlocale(LC_ALL, ""); + + utils.log = _log; + utils.malloc = malloc; + utils.calloc = calloc; + utils.realloc = realloc; + utils.free = free; + + xoauth2_plugin_socket_setup(); + do_converse(&utils, "tcp:127.0.0.1:65321"); + do_converse(&utils, "unix:/tmp/test.sock"); + xoauth2_plugin_socket_teardown(); +} diff --git a/tests/token_conv_server.py b/tests/token_conv_server.py new file mode 100644 index 0000000..0fef7de --- /dev/null +++ b/tests/token_conv_server.py @@ -0,0 +1,51 @@ +import io +import asyncio +import socket +import struct + + +async def handler(reader, writer): + r = await reader.read(8) + print(r) + writer.write(b"\x81\x9d\x74\x13") + await asyncio.sleep(1) + writer.write(b"\x00\x00\x00\x01") + await asyncio.sleep(1) + while True: + r = await reader.read(4) + if len(r) == 0: + break + l, = struct.unpack(">I", r) + print(l) + in_ = await reader.read(l) + print(in_) + payload = b"hello world" + writer.write(struct.pack(">I", len(payload)) + payload) + + +async def main(): + server1 = await asyncio.start_server( + handler, + family=socket.AF_INET, + host="127.0.0.1", + port=65321, + ) + server2 = await asyncio.start_server( + handler, + family=socket.AF_INET6, + host="::", + port=65321, + ) + server3 = await asyncio.start_unix_server( + handler, + path="/tmp/test.sock", + ) + await server1.start_serving() + await server2.start_serving() + await server3.start_serving() + await server1.wait_closed() + await server2.wait_closed() + await server3.wait_closed() + + +asyncio.run(main()) diff --git a/xoauth2_client.c b/xoauth2_client.c index 37c8786..5c065ca 100644 --- a/xoauth2_client.c +++ b/xoauth2_client.c @@ -23,12 +23,51 @@ #endif #include +#include #include #include + #include "xoauth2_plugin.h" +#include "xoauth2_token_conv.h" + +typedef struct _xoauth2_plugin_client_global_context_t { + int token_conv_enabled; + xoauth2_plugin_token_conv_t token_conv; +} xoauth2_plugin_client_global_context_t; + +static const xoauth2_plugin_token_conv_settings_t settings = { + 2000, /* connect */ + 2000, /* write */ + 2000 /* read */ +}; + +static int xoauth2_plugin_client_global_context_init( + const sasl_utils_t *utils, + xoauth2_plugin_client_global_context_t *glob_context) +{ + glob_context->token_conv_enabled = 0; + { + const char *token_conv_conn_spec = getenv("SASL_XOAUTH2_CLIENT_TOKEN_CONV"); + + if (token_conv_conn_spec && strlen(token_conv_conn_spec) != 0) { + glob_context->token_conv_enabled = 1; + return xoauth2_plugin_token_conv_init(utils, &glob_context->token_conv, &settings, token_conv_conn_spec); + } + } + return SASL_OK; +} + +static void xoauth2_plugin_client_global_context_free( + const sasl_utils_t *utils, + xoauth2_plugin_client_global_context_t *glob_context) +{ + if (glob_context->token_conv_enabled) { + xoauth2_plugin_token_conv_free(utils, &glob_context->token_conv); + } +} static int xoauth2_plugin_client_mech_new( - UNUSED(void *glob_context), + void *glob_context, sasl_client_params_t *params, void **pcontext) { @@ -44,10 +83,17 @@ static int xoauth2_plugin_client_mech_new( context->state = 0; context->resp.buf = NULL; + context->glob_context = glob_context; err = xoauth2_plugin_str_init(utils, &context->outbuf); if (err != SASL_OK) { SASL_free(context); - return err; + return err; + } + err = xoauth2_plugin_str_init(utils, &context->token); + if (err != SASL_OK) { + xoauth2_plugin_str_free(utils, &context->outbuf); + SASL_free(context); + return err; } *pcontext = context; return SASL_OK; @@ -137,7 +183,7 @@ static int get_cb_value(const sasl_utils_t *utils, unsigned id, const char **res if (secret->len >= UINT_MAX) { return SASL_BADPROT; } - *result = secret->data; + *result = (const char *)secret->data; *result_len = secret->len; } break; @@ -226,6 +272,27 @@ static int xoauth2_plugin_client_mech_step1( } } + if (context->glob_context->token_conv_enabled && + !authid_wanted && + ( + password_wanted + || resp.token_len == 0 + || (resp.token_len == 1 && strncmp(resp.token, "-", 1) == 0) + )) { + err = xoauth2_plugin_token_conv_retrieve_access_token( + utils, + &context->glob_context->token_conv, + &context->token, + resp.authid, + resp.authid_len); + if (SASL_OK != err) { + goto out; + } + resp.token = context->token.buf; + resp.token_len = context->token.len; + password_wanted = 0; + } + if (!authid_wanted && !password_wanted) { err = params->canon_user( utils->conn, resp.authid, resp.authid_len, @@ -250,7 +317,7 @@ static int xoauth2_plugin_client_mech_step1( if (!prompt_returned) { SASL_log((utils->conn, SASL_LOG_ERR, "failed to allocate buffer")); err = SASL_NOMEM; - return err; + goto out; } memset(prompt_returned, 0, sizeof(sasl_interact_t) * prompts); p = prompt_returned; @@ -369,10 +436,16 @@ static void xoauth2_plugin_client_mech_dispose( return; } + xoauth2_plugin_str_free(utils, &context->token); xoauth2_plugin_str_free(utils, &context->outbuf); SASL_free(context); } +static void xoauth2_plugin_client_mech_free(void *glob_context, const sasl_utils_t *utils) +{ + xoauth2_plugin_client_global_context_free(utils, (xoauth2_plugin_client_global_context_t *)glob_context); +} + static sasl_client_plug_t xoauth2_client_plugins[] = { { @@ -387,15 +460,17 @@ static sasl_client_plug_t xoauth2_client_plugins[] = &xoauth2_plugin_client_mech_new, /* mech_new */ &xoauth2_plugin_client_mech_step, /* mech_step */ &xoauth2_plugin_client_mech_dispose,/* mech_dispose */ - NULL, /* mech_free */ + &xoauth2_plugin_client_mech_free, /* mech_free */ NULL, /* idle */ NULL, /* spare */ NULL /* spare */ } }; +static xoauth2_plugin_client_global_context_t xoauth2_client_global_context; + int xoauth2_client_plug_init( - sasl_utils_t *utils, + const sasl_utils_t *utils, int maxversion, int *out_version, sasl_client_plug_t **pluglist, @@ -406,10 +481,18 @@ int xoauth2_client_plug_init( return SASL_BADVERS; } + { + int err = xoauth2_plugin_client_global_context_init(utils, &xoauth2_client_global_context); + if (SASL_OK != err) { + SASL_seterror((utils->conn, 0, "xoauth2: global context initialization failed (%d)", err)); + return SASL_FAIL; + } + xoauth2_client_plugins[0].glob_context = &xoauth2_client_global_context; + } + *out_version = SASL_CLIENT_PLUG_VERSION; *pluglist = xoauth2_client_plugins; *plugcount = sizeof(xoauth2_client_plugins) / sizeof(*xoauth2_client_plugins); return SASL_OK; } - diff --git a/xoauth2_init.c b/xoauth2_init.c index 57bd128..9f48cb1 100644 --- a/xoauth2_init.c +++ b/xoauth2_init.c @@ -24,8 +24,10 @@ #include #include "xoauth2_plugin.h" +#include "xoauth2_socket.h" + +#if XOAUTH2_WIN32 -#ifdef WIN32 BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { switch (ul_reason_for_call) @@ -34,6 +36,7 @@ BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserve case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: + xoauth2_plugin_socket_teardown(); break; } return TRUE; @@ -42,10 +45,20 @@ BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserve SASLPLUGINAPI int sasl_client_plug_init(const sasl_utils_t *utils, int maxversion, int *out_version, sasl_client_plug_t **pluglist, int *plugcount) { - xoauth2_client_plug_init(utils, maxversion, out_version, pluglist, plugcount); + int err; + err = xoauth2_plugin_socket_setup(); + if (SASL_OK != err) { + return err; + } + return xoauth2_client_plug_init(utils, maxversion, out_version, pluglist, plugcount); } SASLPLUGINAPI int sasl_server_plug_init(const sasl_utils_t *utils, int maxversion, int *out_version, sasl_server_plug_t **pluglist, int *plugcount) { - xoauth2_server_plug_init(utils, maxversion, out_version, pluglist, plugcount); + int err; + err = xoauth2_plugin_socket_setup(); + if (SASL_OK != err) { + return err; + } + return xoauth2_server_plug_init(utils, maxversion, out_version, pluglist, plugcount); } diff --git a/xoauth2_plugin.h b/xoauth2_plugin.h index 22d97ea..0b7c3a8 100644 --- a/xoauth2_plugin.h +++ b/xoauth2_plugin.h @@ -55,10 +55,14 @@ typedef struct { xoauth2_plugin_str_t outbuf; } xoauth2_plugin_server_context_t; +typedef struct _xoauth2_plugin_client_global_context_t xoauth2_plugin_client_global_context_t; + typedef struct { + xoauth2_plugin_client_global_context_t *glob_context; int state; xoauth2_plugin_auth_response_t resp; xoauth2_plugin_str_t outbuf; + xoauth2_plugin_str_t token; } xoauth2_plugin_client_context_t; int xoauth2_plugin_str_init(const sasl_utils_t *utils, xoauth2_plugin_str_t *s); @@ -67,14 +71,14 @@ int xoauth2_plugin_str_append(const sasl_utils_t *utils, xoauth2_plugin_str_t *s void xoauth2_plugin_str_free(const sasl_utils_t *utils, xoauth2_plugin_str_t *s); int xoauth2_server_plug_init( - sasl_utils_t *utils, + const sasl_utils_t *utils, int maxversion, int *out_version, sasl_server_plug_t **pluglist, int *plugcount); int xoauth2_client_plug_init( - sasl_utils_t *utils, + const sasl_utils_t *utils, int maxversion, int *out_version, sasl_client_plug_t **pluglist, diff --git a/xoauth2_server.c b/xoauth2_server.c index d1abb8c..b667378 100644 --- a/xoauth2_server.c +++ b/xoauth2_server.c @@ -435,7 +435,7 @@ static void xoauth2_plugin_server_mech_dispose(void *_context, const sasl_utils_ SASL_free(context); } -static int xoauth2_server_plug_get_options(sasl_utils_t *utils, xoauth2_plugin_server_settings_t *settings) +static int xoauth2_server_plug_get_options(const sasl_utils_t *utils, xoauth2_plugin_server_settings_t *settings) { int err; err = utils->getopt( @@ -476,7 +476,7 @@ static sasl_server_plug_t xoauth2_server_plugins[] = }; int xoauth2_server_plug_init( - sasl_utils_t *utils, + const sasl_utils_t *utils, int maxversion, int *out_version, sasl_server_plug_t **pluglist, diff --git a/xoauth2_socket.h b/xoauth2_socket.h new file mode 100644 index 0000000..f52f894 --- /dev/null +++ b/xoauth2_socket.h @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2020 Moriyoshi Koizumi + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#ifndef XOAUTH2_SOCKET_H +#define XOAUTH2_SOCKET_H + +#include +#include + +typedef struct { + char *host; + char *port_or_service; +} xoauth2_plugin_host_port_pair_t; + +typedef struct _xoauth2_plugin_socket_t xoauth2_plugin_socket_t; + +int xoauth2_plugin_host_port_pair_new( + const sasl_utils_t *utils, + xoauth2_plugin_host_port_pair_t **retval, + const char *host_port_pair, + const char *default_service); +int xoauth2_plugin_host_port_pair_new_no_port( + const sasl_utils_t *utils, + xoauth2_plugin_host_port_pair_t **retval, + const char *host); +void xoauth2_plugin_host_port_pair_free( + const sasl_utils_t *utils, + xoauth2_plugin_host_port_pair_t *hp); + +typedef struct _xoauth2_plugin_socket_iovec_t { + void *iov_base; + size_t iov_len; +} xoauth2_plugin_socket_iovec_t; + +typedef void (*xoauth2_plugin_socket_cleanup_fn_t)( + const sasl_utils_t *utils, + xoauth2_plugin_socket_t *s); +typedef void (*xoauth2_plugin_socket_close_fn_t)( + const sasl_utils_t *utils, + xoauth2_plugin_socket_t *s); +typedef int (*xoauth2_plugin_socket_read_fn_t)( + const sasl_utils_t *utils, + xoauth2_plugin_socket_t *s, + xoauth2_plugin_socket_iovec_t *iv, + size_t nivs, + unsigned *nread, + unsigned minread, + int timeout_ms); +typedef int (*xoauth2_plugin_socket_write_fn_t)( + const sasl_utils_t *utils, + xoauth2_plugin_socket_t *s, + xoauth2_plugin_socket_iovec_t *iv, + size_t nivs, + unsigned *nwritten, + int timeout_ms); + +typedef struct { + xoauth2_plugin_socket_cleanup_fn_t cleanup; + xoauth2_plugin_socket_cleanup_fn_t close; + xoauth2_plugin_socket_read_fn_t read; + xoauth2_plugin_socket_write_fn_t write; +} xoauth2_plugin_socket_vtbl_t; + +typedef struct _xoauth2_plugin_socket_t { + xoauth2_plugin_socket_vtbl_t *vtbl; +} xoauth2_plugin_socket_t; + + +#define xoauth2_plugin_socket_cleanup(utils, s) \ + ((xoauth2_plugin_socket_t *)(s))->vtbl->cleanup((utils), (xoauth2_plugin_socket_t *)(s)) +#define xoauth2_plugin_socket_close(utils, s) \ + ((xoauth2_plugin_socket_t *)(s))->vtbl->close((utils), (xoauth2_plugin_socket_t *)(s)) +#define xoauth2_plugin_socket_read(utils, s, iv, nivs, nread, minread, timeout_ms) \ + ((xoauth2_plugin_socket_t *)(s))->vtbl->read((utils), (xoauth2_plugin_socket_t *)(s), (iv), (nivs), (nread), (minread), (timeout_ms)) +#define xoauth2_plugin_socket_write(utils, s, iv, nivs, nwrite, timeout_ms) \ + ((xoauth2_plugin_socket_t *)(s))->vtbl->write((utils), (xoauth2_plugin_socket_t *)(s), (iv), (nivs), (nwrite), (timeout_ms)) + +enum xoauth2_plugin_af { + XOAUTH2_PLUGIN_UNKNOWN_AF, + XOAUTH2_PLUGIN_UNIX_AF_UNSPEC, + XOAUTH2_PLUGIN_UNIX_AF_UNIX, + XOAUTH2_PLUGIN_UNIX_AF_INET, + XOAUTH2_PLUGIN_UNIX_AF_INET6, + XOAUTH2_PLUGIN_WIN32_AF_UNSPEC, + XOAUTH2_PLUGIN_WIN32_AF_UNIX, + XOAUTH2_PLUGIN_WIN32_AF_INET, + XOAUTH2_PLUGIN_WIN32_AF_INET6 +}; + +int xoauth2_plugin_socket_connect(const sasl_utils_t *utils, xoauth2_plugin_socket_t **retval, const char *family, const char *addr, int timeout_ms); + +int xoauth2_plugin_socket_setup(); +void xoauth2_plugin_socket_teardown(); + +#endif /* XOAUTH2_SOCKET_H */ diff --git a/xoauth2_socket_common.c b/xoauth2_socket_common.c new file mode 100644 index 0000000..5756bdc --- /dev/null +++ b/xoauth2_socket_common.c @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2020 Moriyoshi Koizumi + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include +#endif + +#ifdef HAVE_SYS_UN_H +#include +#endif + +#include + +#include "xoauth2_plugin.h" +#include "xoauth2_socket.h" +#include "xoauth2_socket_win32.h" +#include "xoauth2_socket_unix.h" + +int xoauth2_plugin_host_port_pair_new(const sasl_utils_t *utils, xoauth2_plugin_host_port_pair_t **retval, const char *host_port_pair, const char *default_service) +{ + xoauth2_plugin_host_port_pair_t *hp; + const char *e_host_port_pair = host_port_pair + strlen(host_port_pair), + *host_part = NULL, *e_host_part = NULL, *col = NULL; + + if (*host_port_pair == '[' && (e_host_part = strchr(host_port_pair + 1, ']'))) { + host_part = host_port_pair + 1; + col = strchr(e_host_part + 1, ':'); + } else { + host_part = host_port_pair; + col = strchr(host_port_pair, ':'); + e_host_part = col ? col : e_host_port_pair; + } + + if (col) { + char *s; + hp = SASL_malloc(sizeof(xoauth2_plugin_host_port_pair_t) + (e_host_port_pair - host_port_pair + 1)); + if (!hp) { + return SASL_NOMEM; + } + s = (char *)(hp + 1); + memcpy(s, host_part, e_host_part - host_part); + s[e_host_part - host_part] = '\0'; + hp->host = s; + memcpy(s + (e_host_part - host_part) + 1, col + 1, (e_host_port_pair - col)); + hp->port_or_service = s + (e_host_part - host_part) + 1; + } else { + char *s; + size_t nn = strlen(default_service) + 1; + hp = SASL_malloc(sizeof(xoauth2_plugin_host_port_pair_t) + (e_host_port_pair - host_port_pair + 1) + nn); + if (!hp) { + return SASL_NOMEM; + } + s = (char *)(hp + 1); + memcpy(s, host_part, e_host_part - host_part); + s[e_host_part - host_part] = '\0'; + hp->host = s; + memcpy(s + (e_host_part - host_part) + 1, default_service, nn); + hp->host = s; + hp->port_or_service = s + (e_host_part - host_part) + 1; + } + *retval = hp; + return SASL_OK; +} + +int xoauth2_plugin_host_port_pair_new_no_port(const sasl_utils_t *utils, xoauth2_plugin_host_port_pair_t **retval, const char *host) +{ + xoauth2_plugin_host_port_pair_t *hp; + size_t n = strlen(host) + 1; + char *s; + hp = SASL_malloc(sizeof(xoauth2_plugin_host_port_pair_t) + n); + if (!hp) { + return SASL_NOMEM; + } + s = (char *)(hp + 1); + memcpy(s, host, n); + s[n - 1] = '\0'; + hp->host = s; + hp->port_or_service = NULL; + *retval = hp; + return SASL_OK; +} + +void xoauth2_plugin_host_port_pair_free(const sasl_utils_t *utils, xoauth2_plugin_host_port_pair_t *hp) +{ + SASL_free(hp); +} + +static enum xoauth2_plugin_af xoauth2_family_str_to_i(const char *family) +{ + if (strcasecmp(family, "unix") == 0) { +#if defined(XOAUTH2_WIN32) && !defined(__CYGWIN__) + return XOAUTH2_PLUGIN_WIN32_AF_UNIX; +#else + return XOAUTH2_PLUGIN_UNIX_AF_UNIX; +#endif + } else if (strcasecmp(family, "tcp") == 0) { +#if defined(XOAUTH2_WIN32) && !defined(__CYGWIN__) + return XOAUTH2_PLUGIN_WIN32_AF_UNSPEC; +#else + return XOAUTH2_PLUGIN_UNIX_AF_UNSPEC; +#endif + } else if (strcasecmp(family, "tcp4") == 0) { +#if defined(XOAUTH2_WIN32) && !defined(__CYGWIN__) + return XOAUTH2_PLUGIN_WIN32_AF_INET; +#else + return XOAUTH2_PLUGIN_UNIX_AF_INET; +#endif + } else if (strcasecmp(family, "tcp6") == 0) { +#if defined(XOAUTH2_WIN32) && !defined(__CYGWIN__) + return XOAUTH2_PLUGIN_WIN32_AF_INET6; +#else + return XOAUTH2_PLUGIN_UNIX_AF_INET6; +#endif + } + return XOAUTH2_PLUGIN_UNKNOWN_AF; +} + +int xoauth2_plugin_socket_connect(const sasl_utils_t *utils, xoauth2_plugin_socket_t **retval, const char *family, const char *addr, int timeout_ms) +{ + int err; + enum xoauth2_plugin_af af = xoauth2_family_str_to_i(family); + if (XOAUTH2_PLUGIN_UNKNOWN_AF == af) { + SASL_log((utils->conn, SASL_LOG_NOTE, "xoauth2: unknown address family: %s", family)); + return SASL_FAIL; + } +#ifdef XOAUTH2_WIN32 + err = xoauth2_plugin_win32_socket_connect(utils, retval, af, addr, timeout_ms); +#else + err = xoauth2_plugin_unix_socket_connect(utils, retval, af, addr, timeout_ms); +#endif + if (SASL_OK == err) { + SASL_log((utils->conn, SASL_LOG_NOTE, "xoauth2: connected to %s:%s", family, addr)); + } + return err; +} + +int xoauth2_plugin_socket_setup() +{ + int err; +#ifdef XOAUTH2_WIN32 + err = xoauth2_plugin_win32_socket_setup(); + if (SASL_OK != err) { + return err; + } +#endif + err = xoauth2_plugin_unix_socket_setup(); + if (SASL_OK != err) { +#ifdef XOAUTH2_WIN32 + xoauth2_plugin_win32_socket_teardown(); +#endif + return err; + } + return SASL_OK; +} + +void xoauth2_plugin_socket_teardown() +{ +#ifdef XOAUTH2_WIN32 + xoauth2_plugin_win32_socket_teardown(); +#endif + xoauth2_plugin_unix_socket_teardown(); +} diff --git a/xoauth2_socket_unix.c b/xoauth2_socket_unix.c new file mode 100644 index 0000000..0ce6268 --- /dev/null +++ b/xoauth2_socket_unix.c @@ -0,0 +1,479 @@ +/* + * Copyright (c) 2020 Moriyoshi Koizumi + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include +#endif + +#ifdef __CYGWIN__ +#undef WIN32 +#undef _WIN32 +#endif + +#ifdef HAVE_POLL_H +#include +#endif + +#ifdef HAVE_UNISTD_H +#include +#endif + +#ifdef HAVE_SYS_IOCTL_H +#include +#endif + +#ifdef HAVE_SYS_SOCKET_H +#include +#endif + +#ifdef HAVE_SYS_TYPES_H +#include +#endif + +#ifdef HAVE_SYS_UIO_H +#include +#endif + +#ifdef HAVE_SYS_UN_H +#include +#endif + +#ifdef HAVE_NETINET_IN_H +#include +#endif + +#ifdef HAVE_NETINET_IN6_H +#include +#endif + +#ifdef HAVE_NETDB_H +#include +#endif + +#ifdef HAVE_STDINT_H +#include +#endif + +#include +#include +#include + +#include "xoauth2_token_conv.h" +#include "xoauth2_socket.h" + +typedef struct { + int af; /* address family */ + union { + struct addrinfo *addr; + struct sockaddr_un sun; + } addr; +} xoauth2_plugin_unix_addr_info_t; + +typedef struct _xoauth2_plugin_unix_socket_t { + const xoauth2_plugin_socket_vtbl_t *vtbl; + int s; +} xoauth2_plugin_unix_socket_t; + +static int addrinfo_error_to_sasl_code(int eai) +{ + switch (eai) { + case 0: + return SASL_OK; + case EAI_AGAIN: + return SASL_TRYAGAIN; + case EAI_MEMORY: + return SASL_NOMEM; + } + return SASL_FAIL; +} + +static int xoauth2_plugin_unix_addr_info_lookup(const sasl_utils_t *utils, xoauth2_plugin_unix_addr_info_t *info, int af, const xoauth2_plugin_host_port_pair_t *host_port_pair) +{ + info->af = af; + + if (info->af == AF_UNIX) { + size_t n = strlen(host_port_pair->host); + if (sizeof(info->addr.sun.sun_path) < n + 1) { + return SASL_BADPARAM; + } + info->addr.sun.sun_family = AF_UNIX; + memcpy(info->addr.sun.sun_path, host_port_pair->host, n + 1); + return SASL_OK; + } + + { + struct addrinfo hint; + struct addrinfo *results; + int ais; + + hint.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG; + hint.ai_family = af; + hint.ai_socktype = SOCK_STREAM; + hint.ai_protocol = IPPROTO_TCP; + hint.ai_addrlen = 0; + hint.ai_addr = NULL; + hint.ai_canonname = NULL; + hint.ai_next = NULL; + + SASL_log((utils->conn, SASL_LOG_DEBUG, "xoauth2: lookup %s:%s with getaddrinfo()", host_port_pair->host, host_port_pair->port_or_service)); + ais = getaddrinfo( + host_port_pair->host, + host_port_pair->port_or_service, + &hint, + &results); + if (ais) { + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: getaddrinfo() returned error %d (%s)", ais, gai_strerror(ais))); + } + info->addr.addr = results; + return addrinfo_error_to_sasl_code(ais); + } +} + +static void xoauth2_plugin_unix_addr_info_free(const sasl_utils_t *utils, xoauth2_plugin_unix_addr_info_t *info) +{ + if (info->af != AF_UNIX) { + freeaddrinfo(info->addr.addr); + } +} + +static int xoauth2_plugin_unix_socket_read(const sasl_utils_t *utils, xoauth2_plugin_unix_socket_t *s, xoauth2_plugin_socket_iovec_t *iv, size_t nivs, unsigned *nread, unsigned minread, int timeout_ms) +{ + unsigned total_sz; + const xoauth2_plugin_socket_iovec_t *e; + + /* Roll to (INT_MAX + 1) / 16 - 1 */ + if (nivs > (((uint32_t)-1) >> 4)) { + nivs = (((uint32_t)-1) >> 4); + } + + { + const xoauth2_plugin_socket_iovec_t *p; + total_sz = 0; + for (p = iv, e = iv + nivs; p < e; p++) { + if (total_sz + p->iov_len < total_sz) { + break; + } + if (total_sz > (((uint32_t)-1) >> 1)) { + break; + } + total_sz += p->iov_len; + } + e = p; + } + + if (!minread) { + minread = total_sz; + } + + *nread = 0; + { + struct pollfd fds[] = { s->s, POLLIN | POLLHUP, 0 }; + char buf[1024] = "(unknown error)"; + int first = 1; + while (*nread < minread) { + int n; + /* optimization; no polling on the first try */ + if (!first) { + int pn = poll(fds, sizeof(fds) / sizeof(*fds), timeout_ms < 0 ? -1 : timeout_ms); + switch (pn) { + case -1: + strerror_r(errno, buf, sizeof(buf)); + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: poll() returned error %d (%s)", errno, buf)); + return SASL_FAIL; + case 0: + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: timed out")); + return SASL_FAIL; + } + } + n = readv(s->s, (struct iovec *)iv, e - iv); + first = 0; + if (-1 == n) { + if (EWOULDBLOCK == errno) { + continue; + } + strerror_r(errno, buf, sizeof(buf)); + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: readv() returned error %d (%s)", errno, buf)); + return SASL_FAIL; + } + if (n == 0) { + /* EOF */ + break; + } + *nread += n; + if (iv >= e) { + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: unexpected condition")); + return SASL_FAIL; + } + while (n > iv->iov_len) { + n -= iv->iov_len; + ++iv; + } + *((unsigned char **)&iv->iov_base) += n; + iv->iov_len -= n; + } + return SASL_OK; + } +} + +static int xoauth2_plugin_unix_socket_write(const sasl_utils_t *utils, xoauth2_plugin_unix_socket_t *s, xoauth2_plugin_socket_iovec_t *iv, size_t nivs, unsigned *nwritten, int timeout_ms) +{ + size_t total_sz; + const xoauth2_plugin_socket_iovec_t *e; + + /* Roll to (INT_MAX + 1) / 16 - 1 */ + if (nivs > (((uint32_t)-1) >> 4)) { + nivs = (((uint32_t)-1) >> 4); + } + + { + const xoauth2_plugin_socket_iovec_t *p; + total_sz = 0; + for (p = iv, e = iv + nivs; p < e; p++) { + if (total_sz + p->iov_len < total_sz) { + break; + } + if (total_sz > (((uint32_t)-1) >> 1)) { + break; + } + total_sz += p->iov_len; + } + e = p; + } + + *nwritten = 0; + { + struct pollfd fds[] = { s->s, POLLOUT | POLLHUP, 0 }; + char buf[1024] = "(unknown error)"; + while (*nwritten < total_sz) { + int n; + { + int pn = poll(fds, sizeof(fds) / sizeof(*fds), timeout_ms < 0 ? -1 : timeout_ms); + switch (pn) { + case -1: + strerror_r(errno, buf, sizeof(buf)); + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: poll() returned error %d (%s)", errno, buf)); + return SASL_FAIL; + case 0: + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: timed out")); + return SASL_FAIL; + } + } + n = writev(s->s, (struct iovec *)iv, e - iv); + if (-1 == n) { + if (EWOULDBLOCK == errno) { + continue; + } + strerror_r(errno, buf, sizeof(buf)); + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: writev() returned error %d (%s)", errno, buf)); + return SASL_FAIL; + } + *nwritten += n; + if (iv >= e) { + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: unexpected condition")); + return SASL_FAIL; + } + while (n > iv->iov_len) { + n -= iv->iov_len; + ++iv; + } + *((unsigned char **)&iv->iov_base) += n; + iv->iov_len -= n; + } + return SASL_OK; + } +} + +void xoauth2_plugin_unix_socket_close(UNUSED(const sasl_utils_t *utils), xoauth2_plugin_unix_socket_t *s) +{ + if (-1 == s->s) { + return; + } + SASL_log((utils->conn, SASL_LOG_NOTE, "xoauth2: socket closed")); + close(s->s); + s->s = -1; +} + +static void xoauth2_plugin_unix_socket_cleanup(const sasl_utils_t *utils, xoauth2_plugin_unix_socket_t *s) +{ + xoauth2_plugin_unix_socket_close(utils, s); + SASL_free(s); +} + +static const xoauth2_plugin_socket_vtbl_t xoauth2_plugin_unix_socket_vtbl = { + (xoauth2_plugin_socket_cleanup_fn_t)xoauth2_plugin_unix_socket_cleanup, + (xoauth2_plugin_socket_close_fn_t)xoauth2_plugin_unix_socket_close, + (xoauth2_plugin_socket_read_fn_t)xoauth2_plugin_unix_socket_read, + (xoauth2_plugin_socket_write_fn_t)xoauth2_plugin_unix_socket_write +}; + +static int xoauth2_plugin_unix_socket_conv_af(enum xoauth2_plugin_af af) +{ + switch (af) { + case XOAUTH2_PLUGIN_UNIX_AF_UNSPEC: + return AF_UNSPEC; + case XOAUTH2_PLUGIN_UNIX_AF_UNIX: + return AF_UNIX; + case XOAUTH2_PLUGIN_UNIX_AF_INET: + return AF_INET; + case XOAUTH2_PLUGIN_UNIX_AF_INET6: + return AF_INET6; + default: + return AF_UNSPEC; + } +} + +int xoauth2_plugin_unix_socket_connect(const sasl_utils_t *utils, xoauth2_plugin_socket_t **retval, enum xoauth2_plugin_af _af, const char *addr, int timeout_ms) +{ + int err; + int af = xoauth2_plugin_unix_socket_conv_af(_af); + xoauth2_plugin_unix_addr_info_t info = { -1 }; + xoauth2_plugin_host_port_pair_t *pair = NULL; + xoauth2_plugin_unix_socket_t *s; + + s = SASL_malloc(sizeof(xoauth2_plugin_unix_socket_t)); + if (!s) { + return SASL_NOMEM; + } + s->vtbl = &xoauth2_plugin_unix_socket_vtbl; + s->s = -1; + + if (af == AF_UNIX) { + err = xoauth2_plugin_host_port_pair_new_no_port(utils, &pair, addr); + } else { + err = xoauth2_plugin_host_port_pair_new(utils, &pair, addr, "65321"); + } + if (SASL_OK != err) { + goto out; + } + err = xoauth2_plugin_unix_addr_info_lookup(utils, &info, af, pair); + if (SASL_OK != err) { + goto out; + } + + { + struct addrinfo tmp; + struct addrinfo *addr; + + if (AF_UNIX == info.af) { + tmp.ai_family = info.af; + tmp.ai_protocol = 0; + tmp.ai_addr = (struct sockaddr *)&info.addr.sun; + tmp.ai_addrlen = sizeof(info.addr.sun); + tmp.ai_next = NULL; + addr = &tmp; + } else { + addr = info.addr.addr; + } + for (; addr; addr = addr->ai_next) { + char buf[1024] = "(unknown error)"; + int _errno; + static const int nonzero = 1; + s->s = socket(addr->ai_family, SOCK_STREAM, addr->ai_protocol); + if (-1 == s->s) { + strerror_r(errno, buf, sizeof(buf)); + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: socket() returned error %d (%s)", errno, buf)); + err = SASL_FAIL; + goto out; + } + if (0 != ioctl(s->s, FIONBIO, &nonzero)) { + strerror_r(errno, buf, sizeof(buf)); + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: ioctl() returned error %d (%s)", errno, buf)); + err = SASL_FAIL; + goto out; + } + if (connect(s->s, addr->ai_addr, addr->ai_addrlen)) { + _errno = errno; + if (_errno != EINPROGRESS) { + close(s->s); + strerror_r(_errno, buf, sizeof(buf)); + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: connect() returned error %d (%s)", _errno, buf)); + err = SASL_FAIL; + continue; + } + } + { + struct pollfd fds[] = { s->s, POLLIN | POLLOUT | POLLHUP, 0 }; + int n; + n = poll(fds, sizeof(fds) / sizeof(*fds), timeout_ms < 0 ? -1 : timeout_ms); + switch (n) { + case -1: + _errno = errno; + close(s->s); + strerror_r(_errno, buf, sizeof(buf)); + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: poll() returned error %d (%s)", _errno, buf)); + err = SASL_FAIL; + goto out; + case 0: + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: poll() timed out")); + err = SASL_FAIL; + continue; + } + if (fds[0].revents & (POLLHUP | POLLERR)) { + int _errno; + socklen_t _errno_len = sizeof(_errno); + if (-1 == getsockopt(s->s, SOL_SOCKET, SO_ERROR, &_errno, &_errno_len)) { + _errno = errno; + close(s->s); + strerror_r(_errno, buf, sizeof(buf)); + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: getsockopt() returned error %d (%s)", _errno, buf)); + err = SASL_FAIL; + goto out; + } + if (_errno == ECONNREFUSED) { + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: connection refused")); + err = SASL_FAIL; + continue; + } + close(s->s); + strerror_r(_errno, buf, sizeof(buf)); + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: connect failed: %d (%s)", _errno, buf)); + err = SASL_FAIL; + goto out; + } + } + break; + } + if (!addr) { + err = SASL_FAIL; + goto out; + } + } + *retval = (xoauth2_plugin_socket_t *)s; +out: + if (pair) { + xoauth2_plugin_host_port_pair_free(utils, pair); + } + if (info.af >= 0) { + xoauth2_plugin_unix_addr_info_free(utils, &info); + } + if (SASL_OK != err) { + xoauth2_plugin_unix_socket_cleanup(utils, s); + } + return err; +} + +int xoauth2_plugin_unix_socket_setup() +{ + return SASL_OK; +} + +void xoauth2_plugin_unix_socket_teardown() +{ +} diff --git a/xoauth2_socket_unix.h b/xoauth2_socket_unix.h new file mode 100644 index 0000000..4243fa8 --- /dev/null +++ b/xoauth2_socket_unix.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2020 Moriyoshi Koizumi + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#ifndef XOAUTH2_SOCKET_UNIX_H +#define XOAUTH2_SOCKET_UNIX_H + +#include "xoauth2_socket.h" + +int xoauth2_plugin_unix_socket_connect(const sasl_utils_t *utils, xoauth2_plugin_socket_t **retval, enum xoauth2_plugin_af af, const char *addr, int timeout_ms); +int xoauth2_plugin_unix_socket_setup(); +void xoauth2_plugin_unix_socket_teardown(); + +#endif /* XOAUTH2_SOCKET_UNIX_H */ diff --git a/xoauth2_socket_win32.c b/xoauth2_socket_win32.c new file mode 100644 index 0000000..2442d08 --- /dev/null +++ b/xoauth2_socket_win32.c @@ -0,0 +1,612 @@ +/* + * Copyright (c) 2020 Moriyoshi Koizumi + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include +#endif + +#ifdef HAVE_WINSOCK2_H +#include +#endif + +#ifdef HAVE_WS2TCPIP_H +#include +#endif + +#ifdef HAVE_WS2DEF_H +#include +#endif + +#ifdef HAVE_STDINT_H +#include +#endif + +#include + +#include +#include +#include +#include + +#include "xoauth2_token_conv.h" +#include "xoauth2_socket.h" + +typedef struct { + int af; /* address family */ + union { +#ifdef WIN32_UDS + struct sockaddr_un sun; +#endif + PADDRINFOEXA addr; + } addr; +} xoauth2_plugin_win32_addr_info_t; + +typedef struct { + const xoauth2_plugin_socket_vtbl_t *vtbl; + SOCKET s; +} xoauth2_plugin_win32_socket_t; + +static int ws2_strerror(int _errno, char *buf, size_t buf_size) +{ + DWORD n; + WCHAR _buf[4096]; + n = FormatMessageW( + FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + (DWORD)_errno, + 0, + _buf, + (DWORD)sizeof(_buf), + NULL + ); + if (0 == n) { + return 1; + } + { + mbstate_t mbs; + const WCHAR *p = _buf; + memset(&mbs, 0, sizeof(mbs)); + wcsrtombs(buf, &p, buf_size, &mbs); + } + { + char *p = buf + strlen(buf); + while (--p > buf && (*p == '\n' || *p == '\r')); + *(p + 1) = '\0'; + } + return 0; +} + +static int addrinfo_error_to_sasl_code(int eai) +{ + switch (eai) { + case 0: + return SASL_OK; + case EAI_AGAIN: + return SASL_TRYAGAIN; + case EAI_MEMORY: + return SASL_NOMEM; + } + return SASL_FAIL; +} + +static int xoauth2_plugin_win32_addr_info_lookup(const sasl_utils_t *utils, xoauth2_plugin_win32_addr_info_t *info, int af, const xoauth2_plugin_host_port_pair_t *host_port_pair) +{ + info->af = af; + +#ifdef WIN32_UDS + if (AF_UNIX == info->af) { + size_t n = strlen(host_port_pair->host); + if (sizeof(info->addr.sun.sun_path) < n + 1) { + return SASL_BADPARAM; + } + info->addr.sun.sun_family = AF_UNIX; + memcpy(info->addr.sun.sun_path, host_port_pair->host, n + 1); + return SASL_OK; + } +#endif + + { + ADDRINFOEXA hint; + PADDRINFOEXA results; + int _errno; + + hint.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG; + hint.ai_family = af; + hint.ai_socktype = SOCK_STREAM; + hint.ai_protocol = IPPROTO_TCP; + hint.ai_addrlen = 0; + hint.ai_addr = NULL; + hint.ai_canonname = NULL; + hint.ai_next = NULL; + + SASL_log((utils->conn, SASL_LOG_DEBUG, "xoauth2: lookup %s:%s with GetAddrInfoExA()", host_port_pair->host, host_port_pair->port_or_service)); + _errno = GetAddrInfoExA( + host_port_pair->host, + host_port_pair->port_or_service, + NS_ALL, + NULL, + &hint, + &results, + NULL, + NULL, + NULL, + NULL); + info->addr.addr = results; + if (0 != _errno) { + char buf[1024] = "(unknown error)"; + ws2_strerror(_errno, buf, sizeof(buf)); + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: GetAddrInfoExA() returned error %d (%s)", _errno, buf)); + } + return addrinfo_error_to_sasl_code(_errno); + } +} + +static void xoauth2_plugin_win32_addr_info_free(const sasl_utils_t *utils, xoauth2_plugin_win32_addr_info_t *info) +{ + if (info->af != AF_UNIX) { + FreeAddrInfoEx(info->addr.addr); + } + return; +} + +static int xoauth2_plugin_win32_socket_read(const sasl_utils_t *utils, xoauth2_plugin_win32_socket_t *s, xoauth2_plugin_socket_iovec_t *iv, size_t nivs, unsigned *nread, unsigned minread, int timeout_ms) +{ + int err; + unsigned total_sz; + WSABUF *wsiv, *e_wsiv; + + /* Roll to (INT_MAX + 1) / 16 - 1 */ + if (nivs > (((uint32_t)-1) >> 4)) { + nivs = (((uint32_t)-1) >> 4); + } + + if (sizeof(WSABUF) * nivs < nivs) { + return SASL_NOMEM; + } + wsiv = SASL_malloc(sizeof(WSABUF) * nivs); + if (!wsiv) { + return SASL_NOMEM; + } + + { + WSABUF *p_wsiv = wsiv; + const xoauth2_plugin_socket_iovec_t *p, *e = iv + nivs; + total_sz = 0; + for (p = iv, e = iv + nivs; p < e; p++) { + if (total_sz + p->iov_len < total_sz) { + break; + } + if (total_sz > (((uint32_t)-1) >> 1)) { + break; + } + p_wsiv->len = p->iov_len; + p_wsiv->buf = p->iov_base; + total_sz += p->iov_len; + ++p_wsiv; + } + e_wsiv = p_wsiv; + } + + if (!minread) { + minread = total_sz; + } + + *nread = 0; + { + WSABUF *p_wsiv = wsiv; + WSAEVENT pol = WSACreateEvent(); + char buf[1024] = "(unknown error)"; + int first = 1; + if (WSA_INVALID_EVENT == pol) { + int _errno = WSAGetLastError(); + ws2_strerror(_errno, buf, sizeof(buf)); + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: WSACreateEvent() returned error %d (%s)", _errno, buf)); + err = SASL_FAIL; + goto out; + } + while (*nread < minread) { + int wsc; + int _errno; + DWORD n; + DWORD flags = 0; + /* optimization; no polling on the first try */ + if (!first) { + DWORD sig; + if (SOCKET_ERROR == WSAEventSelect(s->s, pol, FD_READ)) { + _errno = WSAGetLastError(); + WSACloseEvent(pol); + ws2_strerror(_errno, buf, sizeof(buf)); + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: WSAEventSelect() returned error %d (%s)", _errno, buf)); + err = SASL_FAIL; + goto out; + } + sig = WaitForSingleObject(pol, timeout_ms < 0 ? INFINITE : timeout_ms); + _errno = WSAGetLastError(); + switch (sig) { + case WAIT_TIMEOUT: + WSACloseEvent(pol); + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: timed out")); + err = SASL_FAIL; + goto out; + case WAIT_OBJECT_0: + WSAResetEvent(pol); + break; + default: + _errno = WSAGetLastError(); + WSACloseEvent(pol); + ws2_strerror(_errno, buf, sizeof(buf)); + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: WaitForSingleObject() returned error %d (%s)", _errno, buf)); + err = SASL_FAIL; + goto out; + } + } + wsc = WSARecv(s->s, p_wsiv, (DWORD)(e_wsiv - p_wsiv), &n, &flags, NULL, NULL); + first = 0; + if (SOCKET_ERROR == wsc) { + int _errno = WSAGetLastError(); + if (WSAEWOULDBLOCK == _errno) { + continue; + } + WSACloseEvent(pol); + ws2_strerror(_errno, buf, sizeof(buf)); + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: WSARecv() returned error %d (%s)", _errno, buf)); + err = SASL_FAIL; + goto out; + } + if (n == 0) { + /* EOF */ + break; + } + *nread += n; + if (p_wsiv >= e_wsiv) { + WSACloseEvent(pol); + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: unexpected condition")); + err = SASL_FAIL; + goto out; + } + while (n > p_wsiv->len) { + n -= p_wsiv->len; + ++p_wsiv; + } + *((unsigned char **)&p_wsiv->buf) += n; + p_wsiv->len -= n; + } + WSACloseEvent(pol); + err = SASL_OK; + } +out: + SASL_free(wsiv); + return err; +} + +static int xoauth2_plugin_win32_socket_write(const sasl_utils_t *utils, xoauth2_plugin_win32_socket_t *s, xoauth2_plugin_socket_iovec_t *iv, size_t nivs, unsigned *nwritten, int timeout_ms) +{ + int err; + size_t total_sz; + WSABUF *wsiv, *e_wsiv; + + /* Roll to (INT_MAX + 1) / 16 - 1 */ + if (nivs > (((uint32_t)-1) >> 4)) { + nivs = (((uint32_t)-1) >> 4); + } + + if (sizeof(WSABUF) * nivs < nivs) { + return SASL_NOMEM; + } + wsiv = SASL_malloc(sizeof(WSABUF) * nivs); + if (!wsiv) { + return SASL_NOMEM; + } + + { + WSABUF *p_wsiv = wsiv; + const xoauth2_plugin_socket_iovec_t *p, *e = iv + nivs; + total_sz = 0; + for (p = iv, e = iv + nivs; p < e; p++) { + if (total_sz + p->iov_len < total_sz) { + break; + } + if (total_sz > (((uint32_t)-1) >> 1)) { + break; + } + p_wsiv->len = p->iov_len; + p_wsiv->buf = p->iov_base; + total_sz += p->iov_len; + ++p_wsiv; + } + e_wsiv = p_wsiv; + } + + *nwritten = 0; + { + WSABUF *p_wsiv = wsiv; + WSAEVENT pol = WSACreateEvent(); + char buf[1024] = "(unknown error)"; + if (WSA_INVALID_EVENT == pol) { + int _errno = WSAGetLastError(); + ws2_strerror(_errno, buf, sizeof(buf)); + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: WSACreateEvent() returned error %d (%s)", _errno, buf)); + err = SASL_FAIL; + goto out; + } + while (*nwritten < total_sz) { + DWORD n; + int _errno; + { + DWORD sig; + if (SOCKET_ERROR == WSAEventSelect(s->s, pol, FD_WRITE)) { + _errno = WSAGetLastError(); + WSACloseEvent(pol); + ws2_strerror(_errno, buf, sizeof(buf)); + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: WSAEventSelect() returned error %d (%s)", _errno, buf)); + err = SASL_FAIL; + goto out; + } + sig = WaitForSingleObject(pol, timeout_ms < 0 ? INFINITE : timeout_ms); + _errno = WSAGetLastError(); + switch (sig) { + case WAIT_TIMEOUT: + WSACloseEvent(pol); + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: timed out")); + err = SASL_FAIL; + goto out; + case WAIT_OBJECT_0: + WSAResetEvent(pol); + break; + default: + _errno = WSAGetLastError(); + WSACloseEvent(pol); + ws2_strerror(_errno, buf, sizeof(buf)); + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: WaitForSingleObject() returned error %d (%s)", _errno, buf)); + err = SASL_FAIL; + goto out; + } + } + if (SOCKET_ERROR == WSASend(s->s, p_wsiv, (DWORD)(e_wsiv - p_wsiv), &n, 0, NULL, NULL)) { + int _errno = WSAGetLastError(); + if (WSAEWOULDBLOCK == _errno) { + continue; + } + WSACloseEvent(pol); + ws2_strerror(_errno, buf, sizeof(buf)); + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: WSASend() returned error %d (%s)", _errno, buf)); + err = SASL_FAIL; + goto out; + } + *nwritten += n; + if (p_wsiv >= e_wsiv) { + WSACloseEvent(pol); + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: unexpected condition")); + err = SASL_FAIL; + goto out; + } + while (n > p_wsiv->len) { + n -= p_wsiv->len; + ++p_wsiv; + --nivs; + } + *((unsigned char **)&p_wsiv->buf) += n; + p_wsiv->len -= n; + } + WSACloseEvent(pol); + err = SASL_OK; + } +out: + SASL_free(wsiv); + return err; +} + +void xoauth2_plugin_win32_socket_close(UNUSED(const sasl_utils_t *utils), xoauth2_plugin_win32_socket_t *s) +{ + if (INVALID_SOCKET == s->s) { + return; + } + SASL_log((utils->conn, SASL_LOG_NOTE, "xoauth2: socket closed")); + closesocket(s->s); + s->s = INVALID_SOCKET; +} + +static void xoauth2_plugin_win32_socket_cleanup(const sasl_utils_t *utils, xoauth2_plugin_win32_socket_t *s) +{ + xoauth2_plugin_win32_socket_close(utils, s); + SASL_free(s); +} + +static const xoauth2_plugin_socket_vtbl_t xoauth2_plugin_win32_socket_vtbl = { + (xoauth2_plugin_socket_cleanup_fn_t)xoauth2_plugin_win32_socket_cleanup, + (xoauth2_plugin_socket_close_fn_t)xoauth2_plugin_win32_socket_close, + (xoauth2_plugin_socket_read_fn_t)xoauth2_plugin_win32_socket_read, + (xoauth2_plugin_socket_write_fn_t)xoauth2_plugin_win32_socket_write +}; + +static int xoauth2_plugin_win32_socket_conv_af(enum xoauth2_plugin_af af) +{ + switch (af) { + case XOAUTH2_PLUGIN_WIN32_AF_UNSPEC: + return AF_UNSPEC; + case XOAUTH2_PLUGIN_WIN32_AF_UNIX: + return AF_UNIX; + case XOAUTH2_PLUGIN_WIN32_AF_INET: + return AF_INET; + case XOAUTH2_PLUGIN_WIN32_AF_INET6: + return AF_INET6; + } + return AF_UNSPEC; +} + +int xoauth2_plugin_win32_socket_connect(const sasl_utils_t *utils, xoauth2_plugin_socket_t **retval, enum xoauth2_plugin_af _af, const char *addr, int timeout_ms) +{ + int err; + int af = xoauth2_plugin_win32_socket_conv_af(_af); + xoauth2_plugin_win32_addr_info_t info = { -1 }; + xoauth2_plugin_host_port_pair_t *pair = NULL; + xoauth2_plugin_win32_socket_t *s; + + s = SASL_malloc(sizeof(xoauth2_plugin_win32_socket_t)); + if (!s) { + return SASL_NOMEM; + } + s->vtbl = &xoauth2_plugin_win32_socket_vtbl; + s->s = -1; + + if (af == AF_UNIX) { + err = xoauth2_plugin_host_port_pair_new_no_port(utils, &pair, addr); + } else { + err = xoauth2_plugin_host_port_pair_new(utils, &pair, addr, "65321"); + } + if (SASL_OK != err) { + goto out; + } + err = xoauth2_plugin_win32_addr_info_lookup(utils, &info, af, pair); + if (SASL_OK != err) { + goto out; + } + + { + ADDRINFOEXA tmp; + PADDRINFOEXA addr; + + if (AF_UNIX == info.af) { +#ifdef WIN32_UDS + tmp.ai_family = info.af; + tmp.ai_protocol = AF_INET; + tmp.ai_addr = (struct sockaddr *)&info.addr.sun; + tmp.ai_addrlen = sizeof(info.addr.sun); + tmp.ai_next = NULL; + addr = &tmp; +#else + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: unsupported address family")); + err = SASL_FAIL; + goto out; +#endif + } else { + addr = info.addr.addr; + } + for (; addr; addr = addr->ai_next) { + char buf[1024] = "(unknown error)"; + int _errno; + const ULONG nonzero = 1; + WSAEVENT pol; + DWORD sig; + s->s = WSASocket(addr->ai_family, SOCK_STREAM, addr->ai_protocol, NULL, 0, WSA_FLAG_OVERLAPPED); + if (INVALID_SOCKET == s->s) { + _errno = WSAGetLastError(); + ws2_strerror(_errno, buf, sizeof(buf)); + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: WSASocket() returned error %d (%s)", _errno, buf)); + err = SASL_FAIL; + goto out; + } + /*{ + DWORD dummy; + if (SOCKET_ERROR == WSAIoctl(s->s, FIONBIO, (LPVOID)&nonzero, sizeof(nonzero), NULL, 0, &dummy, NULL, NULL)) { + _errno = WSAGetLastError(); + closesocket(s->s); + ws2_strerror(_errno, buf, sizeof(buf)); + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: WSAIoctl() returned error %d (%s)", _errno, buf)); + err = SASL_FAIL; + goto out; + } + }*/ + pol = WSACreateEvent(); + if (WSA_INVALID_EVENT == pol) { + _errno = WSAGetLastError(); + closesocket(s->s); + ws2_strerror(_errno, buf, sizeof(buf)); + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: WSACreateEvent() returned error %d (%s)", _errno, buf)); + err = SASL_FAIL; + goto out; + } + if (SOCKET_ERROR == WSAEventSelect(s->s, pol, FD_CONNECT)) { + _errno = WSAGetLastError(); + WSACloseEvent(pol); + closesocket(s->s); + ws2_strerror(_errno, buf, sizeof(buf)); + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: WSAEventSelect() returned error %d (%s)", _errno, buf)); + err = SASL_FAIL; + goto out; + } + if (SOCKET_ERROR == WSAConnect(s->s, addr->ai_addr, addr->ai_addrlen, NULL, NULL, NULL, NULL)) { + _errno = WSAGetLastError(); + if (WSAEWOULDBLOCK != _errno && WSAEINPROGRESS != _errno) { + WSACloseEvent(pol); + closesocket(s->s); + ws2_strerror(_errno, buf, sizeof(buf)); + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: WSAConnect() returned error %d (%s)", _errno, buf)); + continue; + } + } + sig = WaitForSingleObject(pol, timeout_ms < 0 ? INFINITE : timeout_ms); + _errno = WSAGetLastError(); + WSACloseEvent(pol); + switch (sig) { + case WAIT_TIMEOUT: + closesocket(s->s); + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: WSAConnect() timed out")); + err = SASL_FAIL; + continue; + case WAIT_OBJECT_0: + err = SASL_OK; + break; + default: + WSACloseEvent(pol); + closesocket(s->s); + ws2_strerror(_errno, buf, sizeof(buf)); + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: WaitForSingleObject() returned error %d (%s)", _errno, buf)); + err = SASL_FAIL; + goto out; + } + break; + } + if (!addr) { + err = SASL_FAIL; + goto out; + } + } + *retval = (xoauth2_plugin_socket_t *)s; +out: + if (pair) { + xoauth2_plugin_host_port_pair_free(utils, pair); + } + if (info.af >= 0) { + xoauth2_plugin_win32_addr_info_free(utils, &info); + } + if (SASL_OK != err) { + xoauth2_plugin_win32_socket_cleanup(utils, s); + } + return err; +} + +int xoauth2_plugin_win32_socket_setup() +{ + WSADATA dummy; + int _errno = WSAStartup(WINSOCK_VERSION, &dummy); + if (0 != _errno) { + char buf[1024]; + ws2_strerror(_errno, buf, sizeof(buf)); + fprintf(stderr, "xoauth2: WSAStartup() failed (%s)\n", buf); + return SASL_FAIL; + } + return SASL_OK; +} + +void xoauth2_plugin_win32_socket_teardown() +{ + WSACleanup(); +} diff --git a/xoauth2_socket_win32.h b/xoauth2_socket_win32.h new file mode 100644 index 0000000..9798b50 --- /dev/null +++ b/xoauth2_socket_win32.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2020 Moriyoshi Koizumi + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#ifndef XOAUTH2_SOCKET_WIN32_H +#define XOAUTH2_SOCKET_WIN32_H + +#include "xoauth2_socket.h" + +int xoauth2_plugin_win32_socket_connect(const sasl_utils_t *utils, xoauth2_plugin_socket_t **retval, enum xoauth2_plugin_af af, const char *addr, int timeout_ms); +int xoauth2_plugin_win32_socket_setup(); +void xoauth2_plugin_win32_socket_teardown(); + +#endif /* XOAUTH2_SOCKET_WIN32_H */ diff --git a/xoauth2_str.c b/xoauth2_str.c index d0c2bb6..2c8231f 100644 --- a/xoauth2_str.c +++ b/xoauth2_str.c @@ -87,4 +87,3 @@ void xoauth2_plugin_str_free(const sasl_utils_t *utils, xoauth2_plugin_str_t *s) s->len = s->size = 0; } } - diff --git a/xoauth2_token_conv.c b/xoauth2_token_conv.c new file mode 100644 index 0000000..2758ee4 --- /dev/null +++ b/xoauth2_token_conv.c @@ -0,0 +1,319 @@ +/* + * Copyright (c) 2020 Moriyoshi Koizumi + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include +#endif + +#ifdef HAVE_STDINT_H +#include +#endif + +#include +#include +#include + +#include "xoauth2_token_conv.h" +#include "xoauth2_socket.h" + +static const char signature[] = { 0x81, 0x9d, 0x74, 0x13 }; +static const int32_t version = 1; + +static int xoauth2_plugin_token_conv_initiate(const sasl_utils_t *utils, xoauth2_plugin_token_conv_t *token_conv) +{ + int err; + xoauth2_plugin_socket_t *s; + + if (token_conv->s) { + return SASL_FAIL; + } + + err = xoauth2_plugin_socket_connect(utils, &s, token_conv->family.buf, token_conv->addr.buf, token_conv->settings->connect_timeout); + if (SASL_OK != err) { + return err; + } + + /* send signature and version */ + { + const unsigned char ver_buf[4] = { + (version >> 24) & 0xff, + (version >> 16) & 0xff, + (version >> 8) & 0xff, + version & 0xff, + }; + xoauth2_plugin_socket_iovec_t iv[] = { + { (char *)signature, 4 }, + { (char *)ver_buf, 4 } + }; + unsigned n; + err = xoauth2_plugin_socket_write(utils, s, iv, sizeof(iv) / sizeof(*iv), &n, token_conv->settings->write_timeout); + if (SASL_OK == err && n != 8) { + err = SASL_FAIL; + } + if (SASL_OK != err) { + goto out; + } + } + + /* receive signature and server version */ + { + unsigned char buf[8]; + xoauth2_plugin_socket_iovec_t iv[] = { { buf, sizeof(buf) } }; + unsigned n; + int32_t ver; + err = xoauth2_plugin_socket_read(utils, s, iv, sizeof(iv) / sizeof(*iv), &n, 0, token_conv->settings->read_timeout); + if (SASL_OK == err && n != 8) { + err = SASL_FAIL; + } + if (SASL_OK != err) { + goto out; + } + /* verify signature */ + SASL_log((utils->conn, SASL_LOG_DEBUG, "signature: %02x %02x %02x %02x %02x %02x %02x %02x", buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7])); + if (memcmp(&buf[0], signature, sizeof(signature)) != 0) { + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: signature mismatch")); + err = SASL_FAIL; + goto out; + } + /* verify version */ + ver = (((unsigned int)buf[4]) << 24) + | (((unsigned int)buf[5]) << 16) + | (((unsigned int)buf[6]) << 8) + | ((unsigned int)buf[7]); + SASL_log((utils->conn, SASL_LOG_DEBUG, "version: client=%d, server=%d", version, ver)); + if (version < ver) { + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: server version too old")); + err = SASL_FAIL; + goto out; + } + } +out: + if (SASL_OK != err) { + xoauth2_plugin_socket_cleanup(utils, s); + } else { + token_conv->s = s; + } + return err; +} + +static int xoauth2_plugin_token_conv_send_packet(const sasl_utils_t *utils, xoauth2_plugin_token_conv_t *token_conv, const xoauth2_plugin_socket_iovec_t *ivs, size_t nivs) +{ + int err; + size_t total_len = 0; + + if (nivs > (((uint32_t)-1) >> 5)) { + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: too long packet")); + return SASL_FAIL; + } + + { + const xoauth2_plugin_socket_iovec_t *p, *e = ivs + nivs; + for (p = ivs; p < e; p++) { + size_t new_total_len = total_len + p->iov_len; + if (new_total_len < total_len) { + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: too long packet")); + return SASL_FAIL; + } + total_len = new_total_len; + } + if (total_len >= (((uint32_t)-1) >> 1)) { + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: too long packet")); + return SASL_FAIL; + } + } + + { + unsigned char buf[4] = { + (unsigned char)(total_len >> 24), + (unsigned char)(total_len >> 16), + (unsigned char)(total_len >> 8), + (unsigned char)total_len + }; + xoauth2_plugin_socket_iovec_t *_ivs; + unsigned nwritten; + + _ivs = SASL_malloc(sizeof(xoauth2_plugin_socket_iovec_t) * (nivs + 1)); + if (!_ivs) { + return SASL_NOMEM; + } + + /* insert packet length */ + _ivs[0].iov_base = buf; + _ivs[0].iov_len = sizeof(buf); + memcpy(_ivs + 1, ivs, sizeof(xoauth2_plugin_socket_iovec_t) * nivs); + + err = xoauth2_plugin_socket_write(utils, token_conv->s, _ivs, nivs + 1, &nwritten, token_conv->settings->write_timeout); + SASL_free(_ivs); + if (SASL_OK != err) { + return err; + } + } + return SASL_OK; +} + +static int xoauth2_plugin_token_conv_read_packet(const sasl_utils_t *utils, xoauth2_plugin_token_conv_t *token_conv, xoauth2_plugin_str_t *retval, unsigned *sz) +{ + int err; + unsigned _sz, off, nread; + err = xoauth2_plugin_str_alloc(utils, retval, retval->len + 1024); + if (SASL_OK != err) { + return err; + } + + off = 0; + *sz = 0; + { + unsigned char buf[4]; + xoauth2_plugin_socket_iovec_t iv[] = { { buf, sizeof(buf) }, { retval->buf + retval->len, retval->size - retval->len } }; + err = xoauth2_plugin_socket_read(utils, token_conv->s, iv, sizeof(iv) / sizeof(*iv), &nread, sizeof(buf), token_conv->settings->read_timeout); + if (SASL_OK != err) { + return err; + } + if (nread < 4) { + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: unexpected EOF (at least 4 bytes wanted, got %u bytes)", nread)); + return SASL_BADPROT; + } + _sz = (((unsigned)buf[0]) << 24) + | (((unsigned)buf[1]) << 16) + | (((unsigned)buf[2]) << 8) + | (unsigned)buf[3]; + SASL_log((utils->conn, SASL_LOG_DEBUG, "xoauth2: packet_size=%u", _sz)); + if (_sz > (((unsigned int)-1) >> 1)) { + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: too large packet (the peer tolds us %u bytes follow)", _sz)); + return SASL_BADPROT; + } + if (_sz + retval->len < _sz) { + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: too large packet")); + return SASL_NOMEM; + } + *sz = _sz; + off += nread - 4; + if (off == _sz) { + retval->len += off; + return SASL_OK; + } + } + err = xoauth2_plugin_str_alloc(utils, retval, retval->len + (_sz - off)); + if (SASL_OK != err) { + return err; + } + { + xoauth2_plugin_socket_iovec_t iv[] = { { retval->buf + retval->len + off, retval->size - retval->len - off } }; + err = xoauth2_plugin_socket_read(utils, token_conv->s, iv, sizeof(iv) / sizeof(*iv), &nread, 0, token_conv->settings->read_timeout); + if (SASL_OK != err) { + return err; + } + } + retval->len += *sz; + return SASL_OK; +} + +static void xoauth2_plugin_token_conv_close_socket(const sasl_utils_t *utils, xoauth2_plugin_token_conv_t *token_conv) +{ + if (token_conv->s) { + xoauth2_plugin_socket_t *s = token_conv->s; + token_conv->s = 0; + xoauth2_plugin_socket_cleanup(utils, s); + } +} + +int xoauth2_plugin_token_conv_retrieve_access_token(const sasl_utils_t *utils, xoauth2_plugin_token_conv_t *token_conv, xoauth2_plugin_str_t *token, const char *authid, unsigned authid_len) +{ + int err; + unsigned token_len; + + if (!token_conv->s) { + err = xoauth2_plugin_token_conv_initiate(utils, token_conv); + if (SASL_OK != err) { + return err; + } + } + + { + const xoauth2_plugin_socket_iovec_t ivs[] = { + { "authid", sizeof("authid") }, + { (void *)authid, authid_len } + }; + + err = xoauth2_plugin_token_conv_send_packet(utils, token_conv, ivs, sizeof(ivs) / sizeof(*ivs)); + if (SASL_OK != err) { + xoauth2_plugin_token_conv_close_socket(utils, token_conv); + return err; + } + } + + token->len = 0; + err = xoauth2_plugin_token_conv_read_packet(utils, token_conv, token, &token_len); + if (SASL_OK != err) { + xoauth2_plugin_token_conv_close_socket(utils, token_conv); + return err; + } + + return SASL_OK; +} + +void xoauth2_plugin_token_conv_free(const sasl_utils_t *utils, xoauth2_plugin_token_conv_t *token_conv) +{ + if (token_conv->s) { + xoauth2_plugin_socket_cleanup(utils, token_conv->s); + } + xoauth2_plugin_str_free(utils, &token_conv->addr); + xoauth2_plugin_str_free(utils, &token_conv->family); +} + +int xoauth2_plugin_token_conv_init(const sasl_utils_t *utils, xoauth2_plugin_token_conv_t *token_conv, const xoauth2_plugin_token_conv_settings_t *settings, const char *family_and_addr) +{ + int err; + const char *p; + p = strchr(family_and_addr, ':'); + if (!p) { + SASL_log((utils->conn, SASL_LOG_ERR, "xoauth2: invalid connection string: %s", family_and_addr)); + return SASL_FAIL; + } + + err = xoauth2_plugin_str_init(utils, &token_conv->family); + if (SASL_OK != err) { + return err; + } + + err = xoauth2_plugin_str_append(utils, &token_conv->family, family_and_addr, p - family_and_addr); + if (SASL_OK != err) { + xoauth2_plugin_str_free(utils, &token_conv->family); + return err; + } + + err = xoauth2_plugin_str_init(utils, &token_conv->addr); + if (SASL_OK != err) { + xoauth2_plugin_str_free(utils, &token_conv->family); + return err; + } + + err = xoauth2_plugin_str_append(utils, &token_conv->addr, p + 1, strlen(family_and_addr) - (p - family_and_addr - 1)); + if (SASL_OK != err) { + xoauth2_plugin_str_free(utils, &token_conv->addr); + xoauth2_plugin_str_free(utils, &token_conv->family); + return err; + } + + token_conv->settings = settings; + token_conv->s = NULL; + + return SASL_OK; +} diff --git a/xoauth2_token_conv.h b/xoauth2_token_conv.h new file mode 100644 index 0000000..0cbd32a --- /dev/null +++ b/xoauth2_token_conv.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2020 Moriyoshi Koizumi + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#ifndef XOAUTH2_TOKEN_CONV_H +#define XOAUTH2_TOKEN_CONV_H + +#include "xoauth2_plugin.h" + +typedef struct { + int connect_timeout; + int write_timeout; + int read_timeout; +} xoauth2_plugin_token_conv_settings_t; + +typedef struct _xoauth2_plugin_socket_t xoauth2_plugin_socket_t; + +typedef struct { + const xoauth2_plugin_token_conv_settings_t *settings; + xoauth2_plugin_str_t family; + xoauth2_plugin_str_t addr; + xoauth2_plugin_socket_t *s; +} xoauth2_plugin_token_conv_t; + +typedef struct _xoauth2_plugin_socket_iovec_t xoauth2_plugin_socket_iovec_t; + +int xoauth2_plugin_token_conv_init(const sasl_utils_t *utils, xoauth2_plugin_token_conv_t *token_conv, const xoauth2_plugin_token_conv_settings_t *settings, const char *family_and_addr); +void xoauth2_plugin_token_conv_free(const sasl_utils_t *utils, xoauth2_plugin_token_conv_t *token_conv); +int xoauth2_plugin_token_conv_retrieve_access_token(const sasl_utils_t *utils, xoauth2_plugin_token_conv_t *token_conv, xoauth2_plugin_str_t *retval, const char *authid, unsigned authid_len); + +#endif /* XOAUTH2_TOKEN_CONV_H */