From f1bcc5cd93e050b9147d56c53c8e7fbe36298c55 Mon Sep 17 00:00:00 2001 From: Jose Bollo Date: Tue, 10 Dec 2019 09:32:00 +0100 Subject: Initial commit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Provide a simple authorization server that delivers token using "client credential" flow, based on the Smack label. Bug-AGL: SPEC-2968 Change-Id: I941c96b06a6de3b2b38875c12ada42621593791c Signed-off-by: José Bollo --- .gitignore | 3 + CMakeLists.txt | 41 +++ LICENSE.txt | 202 +++++++++++ README.md | 52 +++ cert | 23 ++ cynagoauth.service | 15 + key | 39 +++ memo.txt | 16 + mkbuild.sh | 17 + src/CMakeLists.txt | 87 +++++ src/README.md | 13 + src/base64.c | 263 ++++++++++++++ src/base64.h | 117 +++++++ src/cred.c | 907 ++++++++++++++++++++++++++++++++++++++++++++++++ src/cred.h | 61 ++++ src/crypt.c | 295 ++++++++++++++++ src/crypt.h | 28 ++ src/curl-wrap.c | 216 ++++++++++++ src/curl-wrap.h | 45 +++ src/cynagoauth-launch.c | 272 +++++++++++++++ src/cynagoauth-server.c | 830 ++++++++++++++++++++++++++++++++++++++++++++ src/escape.c | 449 ++++++++++++++++++++++++ src/escape.h | 24 ++ src/uid.c | 543 +++++++++++++++++++++++++++++ src/uid.h | 68 ++++ 25 files changed, 4626 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 cert create mode 100644 cynagoauth.service create mode 100644 key create mode 100644 memo.txt create mode 100755 mkbuild.sh create mode 100644 src/CMakeLists.txt create mode 100644 src/README.md create mode 100644 src/base64.c create mode 100644 src/base64.h create mode 100644 src/cred.c create mode 100644 src/cred.h create mode 100644 src/crypt.c create mode 100644 src/crypt.h create mode 100644 src/curl-wrap.c create mode 100644 src/curl-wrap.h create mode 100644 src/cynagoauth-launch.c create mode 100644 src/cynagoauth-server.c create mode 100644 src/escape.c create mode 100644 src/escape.h create mode 100644 src/uid.c create mode 100644 src/uid.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..afe3a1c --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +build/ +autobuild/ +nbproject/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..059cc3e --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,41 @@ +########################################################################### +# Copyright (C) 2015-2019 "IoT.bzh" +# +# author: José Bollo +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +########################################################################### + +cmake_minimum_required(VERSION 3.3) + +project(cynagoauth C) + +set(PROJECT_DESCRIPTION "Simple authorization server using cynagora as backend for token check") +set(PROJECT_VERSION 0.1 CACHE STRING "Version of the project") + +include(FindPkgConfig) +include(CheckIncludeFiles) +include(CheckLibraryExists) +include(GNUInstallDirs) +include(CTest) + +add_subdirectory(src) + +set(UNITDIR_SYSTEM "${CMAKE_INSTALL_FULL_LIBDIR}/systemd/system" + CACHE PATH "Path to systemd system unit files") +install( + FILES + ${CMAKE_CURRENT_SOURCE_DIR}/cynagoauth.service + DESTINATION + ${UNITDIR_SYSTEM} +) diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..448aed1 --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +SUMMARY +------- + +This contains a basic OAuth2 authorization and token server: +cynagoauth-server. + +It currently only implments the client credential flow, checking +the client identity using the Smack label. A tiny launcher, +cynagoauth-launch, is provided to negociate the token and run +the final client, setting CYNAGOAUTH_TOKEN environment +variable and substi + + + +LICENSE +------- + +This is released under the terms of APLv2 as explained +in file LICENSE.txt + + +DEPENDENCIES +------------ + +It depends of: + + - json-c + - libmicrohttpd + - openssl + - libcurl + + +COMPILING +--------- + +To compile and install it: + +``` +mkdir build +cd build +cmake .. +make +``` + +RFCs +---- + +OAuth 2.0 Authorization Server Metadata: https://tools.ietf.org/html/rfc8414 + +OAuth 2.0 Dynamic Client Registration Protocol: https://tools.ietf.org/html/rfc7591 + +OpenID Connect Discovery 1.0: https://openid.net/specs/openid-connect-discovery-1_0.html diff --git a/cert b/cert new file mode 100644 index 0000000..066ab2b --- /dev/null +++ b/cert @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIID5TCCAk2gAwIBAgIUBJtxecHlDMYCJfnDLqo8iWmNREkwDQYJKoZIhvcNAQEL +BQAwADAgFw0xODEyMDQxNDA1MDZaGA8yMjY1MDUwMzE0MDUxN1owADCCAaIwDQYJ +KoZIhvcNAQEBBQADggGPADCCAYoCggGBAPC8qdhly0wFUL+EG5t0WcjHPr7y1BKo +0KPQZdBskU2EkmoN3L92v37VDY6SFralEUiqFPU1DSUFREe8gEZiNUbjQMati4CZ +yUZM1i4HYjTd15WLF4HPepoVcaNiZZSK3Agjt0iBOvuF17ZXB4GJXIzGojR3z+j3 +FGIRHj0vWW782hEXPcCtufsMOVpcOJsMmqMbYS640t0PZOfDdLE4ODvM7QsN1F1a +o1KEf9E1KAJLyoUYv0kTZMrPkUfw5h3kZfhIk5izrZkPzd4+o+ncfCGJXoq4/erz +uRSU/rSPSWQZ4Rn8p9g9XxPbUO8UZ2WS+z+7HQts1mq8taIgm33paYZ0xcFLOrXA +pXvlt5QPSCoEFAZtkcxckYTinp2qJLxL8nez+2Ad6no+l5S/OxltNZiuYBGbwMap +Mb7TkgAxX78b5/Qb4hni+Z1bYJ6oIWd6wPZFwbI9nIfh6si8gcwSmgS74XTImGq0 +4QswJ/ZGA60Z7IVPxMvQOpqEKl3c42n1mwIDAQABo1UwUzAMBgNVHRMBAf8EAjAA +MBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdDwEB/wQFAwMHsAAwHQYDVR0OBBYE +FClRUl1jtO+cT4H0k+lzDavGYJQQMA0GCSqGSIb3DQEBCwUAA4IBgQA6ZfZ42qEC +8+NYDLaebdGKfB6rHD2CSgvaA5gOPEp/aORPUGGiREN8dXRPjbec/K73y9ufFdaS +dGMeCziRhyBox1HK6r+1E1I0ax4T2I4cHc3Afvc6rGs5cvw/FIpCt51BAXC8d6Xa +PxMM2DB4F33T3YFzBaZGFnkvIX7F2aB/sClf1+DcarXLa05M3iDIvIvDhUUloAeR +XkR89I/eAEUFpe5qgZPo0i0qh2UvcvdJWV8iuQEp3zpOLVmR7babUX99kBtw2Q2e +NE4yBJSooZ4uh/XA7abkj7C0fATK+oG1xgcxT/lPez6SUFwa+VBQXiCRgDAJsb1N +e+369zrRODs8MXy0SSc+EM4nlRgehu/OlylZOeLfVQQOMxaZV/xkjnuEaAfWT7iD +qCee083jVOEShDlSZib+UfEVVx67mc7W88T9mimTCeACck1Tt95nUiYn6QHz57Os +C9iUNJf/fdZ1WBLV8V6YUmgmU1j3VPe5fnMumXyMwcDnJZtdMvbAb04= +-----END CERTIFICATE----- diff --git a/cynagoauth.service b/cynagoauth.service new file mode 100644 index 0000000..cd84afb --- /dev/null +++ b/cynagoauth.service @@ -0,0 +1,15 @@ +[Unit] +Description=Authorization server OAuth2 +BindsTo=cynagora.service + +[Service] +#User=daemon +#Group=nobody +#SupplementaryGroups=cynagora +ExecStart=/usr/bin/cynagoauth-server +Restart=on-failure +RestartSec=5 + +[Install] +WantedBy=multi-user.target + diff --git a/key b/key new file mode 100644 index 0000000..5ade75f --- /dev/null +++ b/key @@ -0,0 +1,39 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/memo.txt b/memo.txt new file mode 100644 index 0000000..cadbb09 --- /dev/null +++ b/memo.txt @@ -0,0 +1,16 @@ +cd build + +afb-daemon --no-ldpaths --binding=./src/binding-oauth-oidc.so --traceevt=all --tracereq=all -vvv -t '' -p 4444 + +afb-client-demo -H localuser:4444/api?token=4 + +oauth-oidc auth {"authorization_endpoint":"http://localhost:7777/auth","token_endpoint":"http://localhost:7777/tok","redirect_uri":"http://localhost:0/hell-entry","client_id":"me","client_secret":"jojo","scope":["a", "b", "c", "c", "b", "aa"],"data":"data-1"} + +oauth-oidc auth {"authorization_endpoint":"https://localhost:7777/auth","token_endpoint":"https://localhost:7777/tok","redirect_uri":"http://localhost:0/hell-entry","client_id":"me","client_secret":"jojo","scope":["a", "b", "c", "c", "b", "aa"],"data":"data-1"} + +oauth-oidc auth {"authorization_endpoint":"https://github.com/login/oauth/authorize","client":"03e364709b579e82db5c"} + +src/test-ep 7777 + +src/test-ep s 7777 + diff --git a/mkbuild.sh b/mkbuild.sh new file mode 100755 index 0000000..9f70321 --- /dev/null +++ b/mkbuild.sh @@ -0,0 +1,17 @@ +#/bin/sh + +h="$(dirname $0)" + +mkdir -p "$h/build" || exit +cd "$h/build" || exit + +[ "$1" = "-f" ] && { rm -r * 2>/dev/null; shift; } +[ "$1" = "--force" ] && { rm -r * 2>/dev/null; shift; } + +cmake \ + -DCMAKE_BUILD_TYPE=Debug \ + -DCMAKE_INSTALL_PREFIX=~/.local \ + .. + +make -j "$@" + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..b955d63 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,87 @@ +########################################################################### +# Copyright (C) 2015-2019 "IoT.bzh" +# +# author: José Bollo +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +########################################################################### + +pkg_check_modules(json REQUIRED json-c) +pkg_check_modules(ssl REQUIRED openssl) +pkg_check_modules(mhd REQUIRED libmicrohttpd>=0.9.60) +pkg_check_modules(curl REQUIRED libcurl) +pkg_check_modules(cyna REQUIRED cynagora) + +# for the server +if(DEFINED DEFAULTPORT) + add_definitions(-DDEFAULTPORT="${DEFAULTPORT}") +endif() +if(DEFINED DEFAULTHOSTS) + add_definitions(-DDEFAULTHOSTS="${DEFAULTHOSTS}") +endif() +if(DEFINED ENVHOSTS) + add_definitions(-DENVHOSTS="${ENVHOSTS}") +endif() + +# for the launcher +if(DEFINED DEFAULTNAME) + add_definitions(-DDEFAULTNAME="${DEFAULTNAME}") +endif() +if(DEFINED VARURL) + add_definitions(-DVARURL="${VARURL}") +endif() +if(DEFINED DEFAULTURL) + add_definitions(-DDEFAULTURL="${DEFAULTURL}") +endif() + +add_executable( + cynagoauth-server + cynagoauth-server.c + uid.c + base64.c + escape.c + cred.c + crypt.c +) +target_link_libraries( + cynagoauth-server + ${json_LDFLAGS} + ${ssl_LDFLAGS} + ${mhd_LDFLAGS} + ${cyna_LDFLAGS} +) +install( + TARGETS + cynagoauth-server + DESTINATION + ${CMAKE_INSTALL_BINDIR} +) + +add_executable( + cynagoauth-launch + cynagoauth-launch.c + curl-wrap.c + escape.c +) +target_link_libraries( + cynagoauth-launch + ${curl_LDFLAGS} + ${json_LDFLAGS} +) + +install( + TARGETS + cynagoauth-launch + DESTINATION + ${CMAKE_INSTALL_BINDIR} +) diff --git a/src/README.md b/src/README.md new file mode 100644 index 0000000..a7a66a8 --- /dev/null +++ b/src/README.md @@ -0,0 +1,13 @@ +This code implement various aspects of OAuth2/OIDC items. + +It provides implementation: + - a resource client + - a resource server + - an authorization server + - a token server + - a link to cynara services + - a AGL resource client service + - a AGL resource server service + +rfc7591 + 2. forbid client_secret_post \ No newline at end of file diff --git a/src/base64.c b/src/base64.c new file mode 100644 index 0000000..82dfb48 --- /dev/null +++ b/src/base64.c @@ -0,0 +1,263 @@ +/* + * Copyright (C) 2017 "IoT.bzh" + * Author: José Bollo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include "base64.h" + +static const char variant_chars[4] = { + '+', '/', /* normal variant */ + '-', '_' /* url variant */ +}; + +/** encode 6bits value to its base64 character, following the variant */ +static char eb64(int x, const char *variant) +{ + if (x < 52) + return (char)(x + (x < 26 ? 'A' : ('a' - 26))); + else + return x < 62 ? (char)(x - (52 - '0')) : variant[x - 62]; +} + +/** decode base64 character to its 6bits value, following the variant */ +static int db64(char c) +{ + if (c >= 'A' && c <= 'Z') + return (int)(c - 'A'); + if (c >= 'a' && c <= 'z') + return 26 + (int)(c - 'a'); + if (c >= '0' && c <= '9') + return 52 + (int)(c - '0'); + if (c == '-' || c == '+') + return 62; + if (c == '_' || c == '/') + return 63; + return -1; +} + +/** return the encoded length for a given buffer length and a variant */ +size_t base64_decode_length(size_t length) +{ + size_t d = length >> 2; + size_t r = length & 3; + return d * 3 + (r <= 1 ? 0 : r - 1); +} + +/** return the encoded length for a given buffer length and a variant */ +void base64_decode_put(const char *buffer, size_t size, char **pout, int *psta) +{ + const char *i, *e; + char *out, c; + int state, x; + + /* encode */ + out = *pout; + state = *psta & 255; + i = buffer; + e = &i[size]; + while (i < e) { + c = *i++; + if (c == '=') + continue; + x = db64(c); + if (x < 0) + continue; + switch(state & 3) { + case 0: + state = (x << 2) | 1; + break; + case 1: + *out++ = (char)((state & 0374) | ((x >> 4) & 0003)); + state = (x << 4) | 2; + break; + case 2: + *out++ = (char)((state & 0360) | ((x >> 2) & 0017)); + state = (x << 6) | 3; + break; + case 3: + *out++ = (char)((state & 0300) | (x & 0077)); + state = 0; + break; + } + } + *pout = out; + *psta = state; +} + +/** return the encoded length for a given buffer length and a variant */ +size_t base64_encode_length(size_t length, enum base64_variant variant) +{ + size_t d = length / 3; + size_t r = length % 3; + return (d << 2) + (r == 0 ? 0 + : (variant & Base64_Variant_Trunc) ? 1 + r + : 4); +} + +void base64_encode_put(const char *buffer, size_t size, char **pout, int *psta, + enum base64_variant variant) +{ + const char *v; + const unsigned char *i, *e; + char *out; + int state, c; + + /* assumes that Base64_Variant_URL==2 */ + v = &variant_chars[variant & Base64_Variant_URL]; + + /* encode */ + out = *pout; + state = *psta & 255; + i = (const unsigned char*)buffer; + e = &i[size]; + while (i < e) { + if (!(state & 1)) { + /* encode groups of 3 bytes to 4 chars */ + while (e - i >= 3) { + c = (int)*i++; + *out++ = eb64(c >> 2, v); + c &= 3; + c <<= 8; + c |= (int)*i++; + *out++ = eb64(c >> 4, v); + c &= 15; + c <<= 8; + c |= (int)*i++; + *out++ = eb64(c >> 6, v); + *out++ = eb64(c & 63, v); + } + /* encode remaining if needed */ + if (i < e) { + c = (int)*i++; + *out++ = eb64(c >> 2, v); + state = ((c & 3) << 6) | 1; + } + } else if (!(state & 2)) { + c = (int)*i++; + *out++ = eb64((state >> 2) | ((c >> 4) & 15), v); + state = ((c & 15) << 4) | 3; + } else { + c = (int)*i++; + *out++ = eb64((state >> 2) | ((c >> 6) & 3), v); + *out++ = eb64(c & 63, v); + state = 0; + } + } + *pout = out; + *psta = state; +} + +void base64_encode_flush(char **pout, int *psta, enum base64_variant variant) +{ + const char *v; + char *out; + int state; + + /* assumes that Base64_Variant_URL==2 */ + v = &variant_chars[variant & Base64_Variant_URL]; + + /* encode */ + out = *pout; + state = *psta & 255; + switch (*psta & 3) { + case 0: + break; + case 1: + *out++ = eb64(state >> 2, v); + if (!(variant & Base64_Variant_Trunc)) { + *out++ = '='; + *out++ = '='; + } + break; + default: + *out++ = eb64(state >> 2, v); + if (!(variant & Base64_Variant_Trunc)) + *out++ = '='; + break; + } + *pout = out; + *psta = 0; +} + +char *base64_encode_array_variant(const char * const *args, size_t count, + enum base64_variant variant) +{ + char *buffer, *iter; + size_t n, sz, *asz; + int state; + + /* compute size and allocate */ + asz = alloca(count * sizeof *asz); + for (sz = n = 0 ; n < count ; n++) + sz += asz[n] = strlen(args[n]); + sz = base64_encode_length(sz, variant); + buffer = malloc(1 + sz); + if (!buffer) + return NULL; + + /* encode */ + iter = buffer; + state = 0; + for (n = 0 ; n < count ; n++) + base64_encode_put(args[n], asz[n], &iter, &state, variant); + base64_encode_flush(&iter, &state, variant); + *iter = 0; + return buffer; +} + +char *base64_encode_multi_variant(const char * const *args, + enum base64_variant variant) +{ + size_t count; + + for (count = 0 ; args[count] ; count++); + return base64_encode_array_variant(args, count, variant); +} + +char *base64_encode_variant_variant(const char *arg, + enum base64_variant variant) +{ + return base64_encode_array_variant(&arg, !!arg, variant); +} + +char *base64_encode_buffer_variant(const void *buffer, size_t size, + enum base64_variant variant) +{ + int state; + char *outbuf, *iter; + size_t sz; + + sz = base64_encode_length(size, variant); + outbuf = malloc(1 + sz); + if (outbuf) { + iter = outbuf; + state = 0; + base64_encode_put(buffer, size, &iter, &state, variant); + base64_encode_flush(&iter, &state, variant); + *iter = 0; + } + return outbuf; +} + +char base64_encode_value_variant(int value, enum base64_variant variant) +{ + return eb64(value & 63, &variant_chars[variant & Base64_Variant_URL]); +} + +/* vim: set colorcolumn=80: */ diff --git a/src/base64.h b/src/base64.h new file mode 100644 index 0000000..7e929e0 --- /dev/null +++ b/src/base64.h @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2017 "IoT.bzh" + * Author: José Bollo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +enum base64_variant +{ + Base64_Variant_Standard = 0, + Base64_Variant_Trunc = 1, + Base64_Variant_URL = 2, + Base64_Variant_URL_Trunc = 3 +}; + +extern size_t base64_encode_length( + size_t length, + enum base64_variant variant); +extern void base64_encode_put( + const char *buffer, + size_t size, + char **pout, + int *psta, + enum base64_variant variant); +extern void base64_encode_flush( + char **pout, + int *psta, + enum base64_variant variant); + +extern size_t base64_decode_length(size_t length); +extern void base64_decode_put(const char *buffer, size_t size, char **pout, int *psta); + + +extern char *base64_encode_array_variant( + const char * const *args, + size_t count, + enum base64_variant variant); +extern char *base64_encode_multi_variant( + const char * const *args, + enum base64_variant variant); +extern char *base64_encode_variant( + const char *arg, + enum base64_variant variant); +extern char *base64_encode_buffer_variant( + const void *buffer, + size_t size, + enum base64_variant variant); +extern char base64_encode_value_variant( + int value, + enum base64_variant variant); + +#define base64_encode_array(args,count) \ + base64_encode_array_variant(args,count,Base64_Variant_Standard) +#define base64_encode_multi(args) \ + base64_encode_multi_variant(args,Base64_Variant_Standard) +#define base64_encode(arg) \ + base64_encode_variant(arg,Base64_Variant_Standard) +#define base64_encode_buffer(arg,sz) \ + base64_encode_buffer_variant(arg,sz,Base64_Variant_Standard) +#define base64_encode_value(val) \ + base64_encode_value_variant(val,Base64_Variant_Standard) + +#define base64_encode_array_standard(args,count) \ + base64_encode_array_variant(args,count,Base64_Variant_Standard) +#define base64_encode_multi_standard(args) \ + base64_encode_multi_variant(args,Base64_Variant_Standard) +#define base64_encode_standard(arg) \ + base64_encode_variant(arg,Base64_Variant_Standard) +#define base64_encode_buffer_standard(arg,sz) \ + base64_encode_buffer_variant(arg,sz,Base64_Variant_Standard) +#define base64_encode_value_standard(val) \ + base64_encode_value_variant(val,Base64_Variant_Standard) + +#define base64_encode_array_url(args,count) \ + base64_encode_array_variant(args,count,Base64_Variant_URL) +#define base64_encode_multi_url(args) \ + base64_encode_multi_variant(args,Base64_Variant_URL) +#define base64_encode_url(arg) \ + base64_encode_variant(arg,Base64_Variant_URL) +#define base64_encode_buffer_url(arg,sz) \ + base64_encode_buffer_variant(arg,sz,Base64_Variant_URL) +#define base64_encode_value_url(val) \ + base64_encode_value_variant(val,Base64_Variant_URL) + +#define base64_encode_array_trunc(args,count) \ + base64_encode_array_variant(args,count,Base64_Variant_Trunc) +#define base64_encode_multi_trunc(args) \ + base64_encode_multi_variant(args,Base64_Variant_Trunc) +#define base64_encode_trunc(arg) \ + base64_encode_variant(arg,Base64_Variant_Trunc) +#define base64_encode_buffer_trunc(arg,sz) \ + base64_encode_buffer_variant(arg,sz,Base64_Variant_Trunc) + +#define base64_encode_array_url_trunc(args,count) \ + base64_encode_array_variant(args,count,Base64_Variant_URL_Trunc) +#define base64_encode_multi_url_trunc(args) \ + base64_encode_multi_variant(args,Base64_Variant_URL_Trunc) +#define base64_encode_url_trunc(arg) \ + base64_encode_variant(arg,Base64_Variant_URL_Trunc) +#define base64_encode_buffer_url_trunc(arg,sz) \ + base64_encode_buffer_variant(arg,sz,Base64_Variant_URL_Trunc) + + + +/* vim: set colorcolumn=80: */ diff --git a/src/cred.c b/src/cred.c new file mode 100644 index 0000000..6eb5d04 --- /dev/null +++ b/src/cred.c @@ -0,0 +1,907 @@ +/* + * Copyright (C) 2019 "IoT.bzh" + * Author: José Bollo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "uid.h" +#include "cred.h" +#include "base64.h" +#include "crypt.h" + + +/* + * token-court: crypt(salt, id-token-long, expiration) + */ + +#define MAGIC_V1 0x70832 + +#define TOKLEN 64 +#define IDLEN 4 +#define CDCHAR 'A' +#define MAXSTRLEN 2047 +#define MAXTIMEBIT 48 + +struct scope; + +struct scope { + unsigned refcount; + char name[1]; +}; + +struct scopearray { + unsigned count; + unsigned capacity; + struct scope **scopes; +}; + +struct cred { + struct credset *set; + struct uid *token; + void *data; + uint32_t id; + unsigned refcount; + time_t expiration; + int locked; + struct scopearray scopes; +}; + +union bearer { + uint64_t u64[3]; + uint32_t u32[6]; + uint16_t u16[12]; + uint8_t u8[24]; + unsigned char uc[24]; + char c[24]; +}; + +struct credset { + struct uidset *tokens; + struct scopearray scopes; + unsigned refcount; + struct crypt *crypt; +}; + +/******************************************************************************/ + +static inline void getrand(void *buffer, size_t length) +{ + ssize_t rgr __attribute__ ((unused)); + rgr = getrandom(buffer, length, 0); +} + +void bearer_encode(union bearer *bearer, struct credset *credset, uint32_t id, time_t time) +{ + /* 0 / 0..1 / 0..3 / 0..7 */ +#if MAXTIMEBIT <= 48 + bearer->u32[0] = (uint32_t)time; + bearer->u16[2] = (uint16_t)(time >> 32); + getrand(&bearer->u16[3], sizeof bearer->u16[3]); + +#else + bearer->u64[0] = (uint64_t)time; +#endif + /* 1 / 2..3 / 4..7 / 8..15 */ + bearer->u32[2] = id; + bearer->u16[6] = 0; + getrand(&bearer->u16[7], sizeof bearer->u16[7]); + + /* 2 / 4..5 / 8..11 / 16..23 */ + getrand(&bearer->u64[2], sizeof bearer->u64[2]); + + crypt_encrypt(credset->crypt, bearer, sizeof *bearer, 1); +} + +int bearer_decode(union bearer *bearer, struct credset *credset, uint32_t *id, time_t *time) +{ + crypt_decrypt(credset->crypt, bearer, sizeof *bearer, 1); + + if (bearer->u16[6]) + return -EINVAL; + + *id = bearer->u32[2]; +#if MAXTIMEBIT <= 48 + *time = (time_t)((uint64_t)bearer->u32[0] | + (((uint64_t)bearer->u16[2]) << 32)); +#else + *time = (time_t)bearer->u64[0]; +#endif + return 0; +} + + +/** + * Search the scope of 'name' in the array 'scopes' + * Returns in *index (if not NULL) the index of the scope or its insertion + * place + * @param scopes array of scopes + * @param name name of the scope to search + * @param index where to store the index or insertion index + * @return the found scope or NULL if not found + */ +static struct scope *scopes_search( + const struct scopearray *scopes, + const char *name, + unsigned *index +) { + int c; + unsigned low, up, mid; + struct scope *s; + + low = 0; + up = scopes->count; + while (low < up) { + mid = (low + up) >> 1; + s = scopes->scopes[mid]; + c = strcasecmp(s->name, name); + if (c < 0) + low = mid + 1; + else if (c > 0) + up = mid; + else { + if (index) + *index = mid; + return s; + } + } + if (index) + *index = low; + return NULL; +} + +/** + * Drop the element of 'scopes' of 'index' + * @param scopes array of scopes + * @param index to drop + */ +static void scopes_drop(struct scopearray *scopes, unsigned index) +{ + scopes->count--; + while (index < scopes->count) { + scopes->scopes[index] = scopes->scopes[index + 1]; + index++; + } +} + +/** + * Insert the element 'scope' in 'scopes' at 'index' + * @param scopes array of scopes + * @aram scope the scope to insert + * @param index of insertion + * @return 0 in case of success or -ENOMEM on error + */ +static int scopes_insert(struct scopearray *scopes, struct scope *scope, unsigned index) +{ + unsigned capacity; + struct scope *other, **array; + + capacity = scopes->capacity; + if (scopes->count == capacity) { + capacity = (capacity << 1) ?: 4; + array = realloc(scopes->scopes, capacity * sizeof *array); + if (!array) + return -ENOMEM; + scopes->scopes = array; + scopes->capacity = capacity; + } + while (index < scopes->count) { + other = scopes->scopes[index]; + scopes->scopes[index] = scope; + scope = other; + index++; + } + scopes->scopes[scopes->count++] = scope; + return 0; +} + +/** + * Allocates a fresh credset + * @return the allocated credset or NULL if error + */ +static struct credset *allocate() +{ + struct credset *credset = calloc(1, sizeof *credset); + if (credset) { + credset->refcount = 1; + if (uidset_create(&credset->tokens, TOKLEN) < 0) { + free(credset); + credset = NULL; + } + } + return credset; +} + +/** + * Deallocate a credset + * @param result + * @return + */ +static void deallocate(struct credset *credset) +{ + if (credset) { + crypt_destroy(credset->crypt); + uidset_unref(credset->tokens); + free(credset); + } +} + +static struct cred *search_token(struct credset *credset, const char *token) +{ + struct uid *uid = uidset_search(credset->tokens, token); + return uid ? uid_data(uid) : NULL; +} + +struct searchid { + uint32_t id; + struct cred *cred; +}; + +static void stokid(void *closure, struct uid *uid, void *data) +{ + struct searchid *sid = closure; + struct cred *cred = data; + if (cred->id == sid->id) + sid->cred = cred; +} + +static struct cred *search_id(struct credset *credset, uint32_t id) +{ + struct searchid sid = { .id = id, .cred = NULL }; + uidset_for_all(credset->tokens, stokid, &sid); + return sid.cred; +} + +static int make_cred( + struct credset *credset, + struct cred **result, + const char *token, + uint32_t id +) { + int rc; + struct cred *cred; + + if (!id) { + getrand(&id, sizeof id); + while (search_id(credset, id) || !id) + id++; + } + + cred = malloc(sizeof *cred); + if (!cred) + rc = -ENOMEM; + else { + cred->set = credset; + cred->id = id; + cred->refcount = 1; + cred->expiration = 0; + cred->locked = 0; + cred->scopes.count = 0; + cred->scopes.capacity = 0; + cred->scopes.scopes = NULL; + cred->data = NULL; + rc = uid_create_for_text(&cred->token, credset->tokens, token, cred); + if (!rc) + credset_addref(credset); + else { + rc = -errno; + free(cred); + cred = NULL; + } + } + *result = cred; + return rc; +} + +/******************************************************************************/ + +static int store_unsigned(FILE *file, unsigned value, int nl) +{ + return fprintf(file, "%x%c", value, nl ? '\n' : ' ') > 0 ? 0 : -errno; +} + +static int store_raw(FILE *file, const void *buffer, size_t length) +{ + return fwrite(buffer, length, 1, file) == 1 ? 0 : -errno; +} + +static int store_bin(FILE *file, const void *buffer, size_t length) +{ + const unsigned char *data = buffer; + char x[2]; + size_t i; + int rc = 0; + + for (i = 0 ; rc == 0 && i < length ; i++) { + x[0] = (char)(CDCHAR + ((data[i] >> 4) & 15)); + x[1] = (char)(CDCHAR + (data[i] & 15)); + rc = store_raw(file, x, 2); + } + return rc; +} + +static int store_item(FILE *file, const void *buffer, size_t length, int string) +{ + int rc; + + if (length > UINT_MAX) { + rc = -EINVAL; + goto end; + } + + rc = store_unsigned(file, (unsigned)length, 0); + if (rc) + goto end; + + rc = (string ? store_raw : store_bin)(file, buffer, length); + if (rc) + goto end; + + rc = store_raw(file, "\n", 1); +end: + return rc; +} + +static int store_string(FILE *file, const char *string) +{ + return store_item(file, string, strlen(string), 1); +} + +static int load_unsigned(FILE *file, unsigned *value) +{ + return fscanf(file, "%x ", value) > 0 ? 0 : -errno; +} + +static int load_raw(FILE *file, void *buffer, size_t length) +{ + return fread(buffer, length, 1, file) == 1 ? 0 : -errno; +} + +static int load_bin(FILE *file, void *buffer, size_t length) +{ + char *data = buffer; + char x[2]; + size_t i; + int rc = 0; + + for (i = 0 ; rc == 0 && i < length ; i++) { + rc = load_raw(file, x, 2); + if (!rc) + data[i] = (char)(((x[0] - CDCHAR) << 4) | (x[1] - CDCHAR)); + } + return rc; +} + +static int load_item(FILE *file, void *buffer, size_t length, int string, unsigned *rlen) +{ + unsigned len; + int rc; + + rc = load_unsigned(file, &len); + if (rc || len > length) { + rc = -EINVAL; + goto end; + } + + if (rlen) + *rlen = len; + + rc = (string ? load_raw : load_bin)(file, buffer, len); + if (rc) + goto end; + + fscanf(file, " "); +end: + return rc; +} + +static int load_string(FILE *file, char *string, size_t length, unsigned *rlen) +{ + unsigned len; + int rc = load_item(file, string, length - 1, 1, &len); + if (!rc) { + if (rlen) + *rlen = len; + string[len] = 0; + } + return rc; +} + +struct credfile { + int rc; + FILE *file; +}; + +static void store_cred(void *closure, struct uid *uid, void *data) +{ + unsigned i; + struct credfile *cf = closure; + struct cred *cred = data; + FILE *file = cf->file; + int rc = cf->rc; + + if (rc) + return; + + rc = store_string(file, uid_text(uid)); + if (rc) + goto end; + + rc = store_unsigned(file, (unsigned)cred->id, 1); + if (rc) + goto end; + + rc = store_item(file, &cred->expiration, sizeof cred->expiration, 0); + if (rc) + goto end; + + rc = store_unsigned(file, cred->scopes.count, 1); + for (i = 0 ; !rc && i < cred->scopes.count ; i++) + rc = store_string(file, cred->scopes.scopes[i]->name); + +end: + cf->rc = rc; +} + +static int load_cred(FILE *file, struct credset *credset) +{ + char buffer[MAXSTRLEN + 1]; + unsigned i, n, len; + int rc; + struct cred *cred; + time_t expire; + uint32_t id; + + rc = load_string(file, buffer, sizeof buffer, &len); + if (rc) + goto end; + + rc = load_unsigned(file, &i); + if (rc) + goto end; + + id = (uint32_t)i; + rc = load_item(file, &expire, sizeof expire, 0, NULL); + if (rc) + goto end; + + cred = search_token(credset, buffer) ?: search_id(credset, id); + if (cred) { + rc = -EINVAL; + goto end; + } + + rc = make_cred(credset, &cred, buffer, id); + if (rc) + goto end; + + rc = load_unsigned(file, &n); + for (i = 0 ; !rc && i < n ; i++) { + rc = load_string(file, buffer, sizeof buffer, &len); + if (!rc) + rc = cred_add_scope(cred, buffer); + } + +end: + return rc; +} + +static int storefile(FILE *file, struct credset *credset) +{ + int rc; + struct credfile cf; + + rc = store_unsigned(file, MAGIC_V1, 1); + if (!rc) + rc = store_unsigned(file, uidset_count(credset->tokens), 1); + if (!rc) { + cf.rc = 0; + cf.file = file; + uidset_for_all(credset->tokens, store_cred, &cf); + rc = cf.rc; + } + return rc; +} + +static int loadfile(FILE *file, struct credset *credset) +{ + unsigned i, n; + int rc; + + rc = load_unsigned(file, &n); + if (!rc && n != MAGIC_V1) + rc = -EINVAL; + if (!rc) + rc = load_unsigned(file, &n); + for (i = 0 ; !rc && i < n ; i++) + rc = load_cred(file, credset); + return rc; +} + +static int store(struct credset *credset, const char *filename) +{ + FILE *file; + int rc; + mode_t m; + + m = umask(0077); + file = fopen(filename, "w"); + umask(m); + if (!file) + rc = -errno; + else { + rc = storefile(file, credset); + fclose(file); + } + return rc; +} + +static int load(struct credset *credset, const char *filename) +{ + FILE *file; + int rc; + + file = fopen(filename, "r"); + if (!file) + rc = -errno; + else { + rc = loadfile(file, credset); + fclose(file); + } + return rc; +} + +/******************************************************************************/ + +/** + * Create a new fresh credset + * @param result where to put the created credset + * @return 0 in case of success or a negative value + */ +int credset_create(struct credset **result) +{ + int rc; + struct credset *credset; + + *result = credset = allocate(); + if (!credset) + rc = -ENOMEM; + else { + rc = crypt_create(&credset->crypt, sizeof(union bearer)); + if (rc) { + *result = NULL; + deallocate(credset); + } + } + return rc; +} + +/** + * Create the credset from the content of 'filename' + * @param result the loaded credset + * @param filename the file to load + * @return 0 in case of success or a negative value + */ +int credset_load(struct credset **result, const char *filename) +{ + int rc; + struct credset *credset; + + *result = credset = allocate(); + if (!credset) + rc = -ENOMEM; + else { + rc = load(credset, filename); + if (rc < 0) { + *result = NULL; + deallocate(credset); + } + } + return rc; +} + +/** + * Store the credset to the 'filename' + * @param credset the credset to store + * @param filename the file to create + * @return 0 in case of success or a negative value + */ +int credset_store(struct credset *credset, const char *filename) +{ + return store(credset, filename); +} + +struct credset *credset_addref(struct credset *credset) +{ + if (credset) + __atomic_add_fetch(&credset->refcount, 1, __ATOMIC_RELAXED); + return credset; +} + +void credset_unref(struct credset *credset) +{ + if (credset && !__atomic_sub_fetch(&credset->refcount, 1, __ATOMIC_RELAXED)) + deallocate(credset); +} + +struct itercred { + void (*callback)(void *closure, struct cred *cred); + void *closure; +}; + +static void for_all_cred(void *closure, struct uid *uid, void *data) +{ + struct itercred *it = closure; + struct cred *cred = data; + it->callback(it->closure, cred); +} + +void credset_for_all( + struct credset *credset, + void (*callback)(void *closure, struct cred *cred), + void *closure +) { + struct itercred it = { .callback = callback, .closure = closure }; + uidset_for_all(credset->tokens, for_all_cred, &it); +} + +static void purge_cred(void *closure, struct uid *uid, void *data) +{ + time_t *time = closure; + struct cred *cred = data; + if (cred_is_expired(cred, *time)) + cred_revoke(cred); +} + +void credset_purge(struct credset *credset, time_t time) +{ + uidset_for_all(credset->tokens, purge_cred, &time); +} + +int credset_new_cred(struct credset *credset, struct cred **result) +{ + return make_cred(credset, result, NULL, 0); +} + +int credset_search_token(struct credset *credset, const char *token, struct cred **result, time_t *expire) +{ + int rc; + struct uid *uid; + struct cred *cred; + + uid = uidset_search(credset->tokens, token); + if (!uid) { + cred = NULL; + rc = -ENOENT; + } else { + cred = uid_data(uid); + rc = 0; + } + if (result) + *result = cred; + if (expire) + *expire = cred ? cred->expiration : 0; + return rc; +} + +int credset_search_bearer(struct credset *credset, const char *bearer, struct cred **result, time_t *expire) +{ + char *pout; + int sta, rc; + union bearer b; + size_t blen; + struct cred *cred; + uint32_t id; + time_t time; + + if (result) + *result = NULL; + if (expire) + *expire = 0; + + blen = strlen(bearer); + if (base64_encode_length(sizeof b.c, Base64_Variant_URL_Trunc) != blen) + return -EINVAL; + + pout = b.c; + sta = 0; + base64_decode_put(bearer, blen, &pout, &sta); + if (pout - b.c != sizeof b.c) + return -EINVAL; + + rc = bearer_decode(&b, credset, &id, &time); + if (rc) + return rc; + + cred = search_id(credset, id); + if (result) + *result = cred; + if (expire) + *expire = time; + return 0; + +} + +int credset_search(struct credset *credset, const char *code, struct cred **result, time_t *expire) +{ + int rc; + + rc = credset_search_bearer(credset, code, result, expire); + if (rc) + rc = credset_search_token(credset, code, result, expire); + return rc; +} + +struct cred *cred_addref(struct cred *cred) +{ + if (cred) + __atomic_add_fetch(&cred->refcount, 1, __ATOMIC_RELAXED); + return cred; +} + +void cred_unref(struct cred *cred) +{ + if (cred) + __atomic_sub_fetch(&cred->refcount, 1, __ATOMIC_RELAXED); +} + +int cred_add_scope(struct cred *cred, const char *scopename) +{ + int rc; + unsigned index, sindex; + struct scope *s; + size_t slen; + + if (cred->locked) + return -EPERM; + + s = scopes_search(&cred->scopes, scopename, &index); + if (s) + return 0; + + s = scopes_search(&cred->set->scopes, scopename, &sindex); + if (!s) { + slen = strlen(scopename); + if (slen > MAXSTRLEN) + return -EINVAL; + s = malloc(slen + sizeof *s); + if (!s) + return -ENOMEM; + s->refcount = 0; + memcpy(s->name, scopename, slen + 1); + rc = scopes_insert(&cred->set->scopes, s, sindex); + if (rc) { + free(s); + return rc; + } + } + + __atomic_add_fetch(&s->refcount, 1, __ATOMIC_RELAXED); + rc = scopes_insert(&cred->scopes, s, index); + if (rc && !__atomic_sub_fetch(&s->refcount, 1, __ATOMIC_RELAXED)) { + scopes_drop(&cred->set->scopes, sindex); + free(s); + } + return rc; +} + +int cred_sub_scope(struct cred *cred, const char *scopename) +{ + unsigned index; + struct scopearray *scopes; + struct scope *s; + + if (cred->locked) + return -EPERM; + + scopes = &cred->scopes; + s = scopes_search(scopes, scopename, &index); + if (!s) + return -ENOENT; + + scopes_drop(scopes, index); + if (!__atomic_sub_fetch(&s->refcount, 1, __ATOMIC_RELAXED)) { + scopes = &cred->set->scopes; + if (s == scopes_search(scopes, scopename, &index)) + scopes_drop(scopes, index); + free(s); + } + return 0; +} + +int cred_has_scope(struct cred *cred, const char *scopename) +{ + return !!scopes_search(&cred->scopes, scopename, NULL); +} + +void cred_revoke(struct cred *cred) +{ + struct uid *token = cred->token; + + cred->token = NULL; + if (token) { + uid_unref(token); + cred_unref(cred); + } +} + +void cred_set_expire(struct cred *cred, time_t expire) +{ + cred->expiration = expire; +} + +time_t cred_get_expire(struct cred *cred) +{ + return cred->expiration; +} + +int cred_is_expired(struct cred *cred, time_t time) +{ + return cred->expiration && cred->expiration < time; +} + +int cred_get_token(struct cred *cred, char **result) +{ + char *token = strdup(uid_text(cred->token)); + *result = token; + return token ? 0 : -ENOMEM; +} + +int cred_get_bearer(struct cred *cred, time_t expire, char **result) +{ + char *bearer; + int sta; + union bearer b; + + if (expire >> MAXTIMEBIT) + return -EINVAL; + + bearer = malloc(1 + base64_encode_length(sizeof b.c, Base64_Variant_URL_Trunc)); + *result = bearer; + if (!bearer) + return -ENOMEM; + + bearer_encode(&b, cred->set, cred->id, expire); + + sta = 0; + base64_encode_put(b.c, sizeof b.c, &bearer, &sta, Base64_Variant_URL_Trunc); + base64_encode_flush(&bearer, &sta, Base64_Variant_URL_Trunc); + *bearer = 0; + + cred->locked = 1; + return 0; +} + +void *cred_data(struct cred *cred) +{ + return cred->data; +} + +void cred_set_data(struct cred *cred, void *data) +{ + cred->data = data; +} diff --git a/src/cred.h b/src/cred.h new file mode 100644 index 0000000..4f42f6e --- /dev/null +++ b/src/cred.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2018 "IoT.bzh" + * Author: José Bollo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +struct cred; +struct credset; + +extern int credset_create(struct credset **result); +extern int credset_load(struct credset **result, const char *file); +extern int credset_store(struct credset *credset, const char *file); + +extern struct credset *credset_addref(struct credset *credset); +extern void credset_unref(struct credset *credset); + +extern void credset_purge(struct credset *credset, time_t time); +extern void credset_for_all( + struct credset *credset, + void (*callback)(void *closure, struct cred *cred), + void *closure +); + +extern int credset_new_cred(struct credset *credset, struct cred **result); +extern int credset_search_token(struct credset *credset, const char *token, struct cred **result, time_t *expire); +extern int credset_search_bearer(struct credset *credset, const char *bearer, struct cred **result, time_t *expire); +extern int credset_search(struct credset *credset, const char *code, struct cred **result, time_t *expire); + +extern struct cred *cred_addref(struct cred *cred); +extern void cred_unref(struct cred *cred); + +extern void cred_revoke(struct cred *cred); + +extern void cred_set_expire(struct cred *cred, time_t expire); +extern int cred_is_expired(struct cred *cred, time_t time); +extern time_t cred_get_expire(struct cred *cred); + +extern int cred_add_scope(struct cred *cred, const char *scope); +extern int cred_sub_scope(struct cred *cred, const char *scope); +extern int cred_has_scope(struct cred *cred, const char *scope); + +extern int cred_get_token(struct cred *cred, char **result); +extern int cred_get_bearer(struct cred *cred, time_t expire, char **result); + +extern void *cred_data(struct cred *cred); +extern void cred_set_data(struct cred *cred, void *data); diff --git a/src/crypt.c b/src/crypt.c new file mode 100644 index 0000000..3ea334e --- /dev/null +++ b/src/crypt.c @@ -0,0 +1,295 @@ +/* + * Copyright (C) 2019 "IoT.bzh" + * Author: José Bollo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "crypt.h" + +#define KEYLEN 56 + +struct crypt { + BF_KEY key; + uint16_t blocksize; +}; + +/******************************************************************************/ + +static inline uint8_t *scramble_array(struct crypt *crypt) +{ + return (uint8_t*)&((&crypt->blocksize)[1]); +} + +static inline uint16_t *shuffle_array(struct crypt *crypt) +{ + return (uint16_t*)&(scramble_array(crypt)[crypt->blocksize]); +} + +static inline size_t compute_size(size_t blocksize) +{ + return sizeof(struct crypt) + blocksize * sizeof(uint16_t) + blocksize; +} + +/******************************************************************************/ + +static inline void getrand(void *buffer, size_t length) +{ + ssize_t rgr __attribute__ ((unused)); + rgr = getrandom(buffer, length, 0); +} + +static void xor(uint8_t *data, const uint8_t *ref, uint16_t n) +{ + uint16_t i; + for(i = 0 ; i < n ; i++) + data[i] ^= ref[i]; +} + +static void scramble(uint8_t *data, struct crypt *crypt) +{ + xor(data, scramble_array(crypt), crypt->blocksize); +} + +static void shuffle(uint8_t *data, struct crypt *crypt, int enc) +{ + uint16_t *s = shuffle_array(crypt); + int i, d, k, a, n = crypt->blocksize; + uint8_t p, t; + + d = n; + a = enc ? 1 : n - 1; + i = 0; + k = s[i]; + p = data[k]; + do { + i += a; + if (i >= n) + i -= n; + k = s[i]; + t = data[k]; + data[k] = p; + p = t; + } while (--d) ; +} + +static void docrypt(uint8_t *data, struct crypt *crypt, int enc) +{ + int i, n = crypt->blocksize; + enc = enc ? BF_ENCRYPT : BF_DECRYPT; + for(i = 0 ; i < n ; i += 8) + BF_ecb_encrypt(&data[i], &data[i], &crypt->key, enc); +} + +#ifdef GCD_SHUFFLE +static int gcd(int x, int y) +{ + int z; + for (;;) { + z = y % x; + if (!z) + return x; + y = x; + x = z; + } + return x; +} +#endif + +static void initcrypt(struct crypt *crypt) +{ + unsigned char key[KEYLEN]; + uint16_t *sh = shuffle_array(crypt); + uint8_t *sc = scramble_array(crypt); + uint16_t k; + int i, j, n = crypt->blocksize; + + /* create the BF key */ + getrand(key, sizeof key); + BF_set_key(&crypt->key, sizeof key, key); + + /* create the shuffling */ +#ifdef GCD_SHUFFLE + j = (n >> 1) - 3; + while (j > 1 && gcd(j, n) > 1) + j--; + k = 0; + for (i = 0 ; i < n ; i++) { + k = (uint16_t)((k + j) % n); + sh[i] = k; + } +#else + for (i = 0 ; i < n ; i++) + sh[i] = (uint16_t)i; + getrand(sc, n - 1); + for (i = 0 ; i < n - 1 ; i++) { + j = i + (sc[i] % (n - i)); + k = sh[i]; + sh[i] = sh[j]; + sh[j] = k; + } +#endif + + /* create the scrambling */ + getrand(sc, n); +} + +/******************************************************************************/ + +int crypt_create(struct crypt **result, size_t blocksize) +{ + struct crypt *crypt; + + /* clear the result */ + *result = NULL; + + /* not a too big blocksize */ + if (blocksize > UINT16_MAX) + return -EINVAL; + + /* blocksize must be multiple of 8 */ + if ((blocksize & 7) != 0) + return -EINVAL; + + /* not a too small blocksize */ + if (blocksize == 0) + return -EINVAL; + + /* allocate */ + crypt = malloc(compute_size(blocksize)); + if (!crypt) + return -ENOMEM; + + /* init */ + crypt->blocksize = (uint16_t)blocksize; + initcrypt(crypt); + *result = crypt; + return 0; +} + +int crypt_load(struct crypt **result, int fd) +{ + struct crypt *crypt; + size_t size; + ssize_t rc; + + /* clear the result */ + *result = NULL; + + /* read the size */ + rc = read(fd, &size, sizeof size); + if (rc != sizeof size) + return -errno; + + /* allocate the data */ + crypt = malloc(compute_size(size)); + if (!crypt) + return -ENOMEM; + + /* read the data */ + rc = read(fd, crypt, size); + if (rc != size) + return -errno; + + /* check the data */ + if (size != compute_size(crypt->blocksize)) { + free(crypt); + return -EINVAL; + } + + /* return the result */ + *result = crypt; + return 0; +} + +int crypt_save(struct crypt *crypt, int fd) +{ + size_t size; + ssize_t rc; + + /* write the size */ + size = compute_size(crypt->blocksize); + rc = write(fd, &size, sizeof size); + if (rc != sizeof size) + return -errno; + + /* write the data */ + rc = write(fd, crypt, size); + if (rc != size) + return -errno; + + return 0; +} + +void crypt_destroy(struct crypt *crypt) +{ + free(crypt); +} + +size_t crypt_encrypt(struct crypt *crypt, void *data, size_t size, int meld) +{ + uint8_t *d = data; + size_t count = 0; + uint16_t n = crypt->blocksize; + + for (count = 0 ; count + n <= size ; count += n) { + if (meld && count) + xor(&d[count], &d[count - n], n); + shuffle(&d[count], crypt, 1); + } + + for (count = 0 ; count + n <= size ; count += n) + scramble(&d[count], crypt); + + for (count = 0 ; count + n <= size ; count += n) + docrypt(&d[count], crypt, 1); + + return count; +} + +size_t crypt_decrypt(struct crypt *crypt, void *data, size_t size, int meld) +{ + uint8_t *d = data; + size_t count = 0; + uint16_t n = crypt->blocksize; + + for (count = 0 ; count + n <= size ; count += n) + docrypt(&d[count], crypt, 0); + + for (count = 0 ; count + n <= size ; count += n) + scramble(&d[count], crypt); + + for (count = 0 ; count + n <= size ; count += n) { + shuffle(&d[count], crypt, 0); + if (meld && count) + xor(&d[count], &d[count - n], n); + } + + return count; +} diff --git a/src/crypt.h b/src/crypt.h new file mode 100644 index 0000000..02ed7ec --- /dev/null +++ b/src/crypt.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2019 "IoT.bzh" + * Author: José Bollo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +struct crypt; + +extern int crypt_create(struct crypt **result, size_t blocksize); +extern int crypt_load(struct crypt **result, int fd); +extern int crypt_save(struct crypt *crypt, int fd); +extern void crypt_destroy(struct crypt *crypt); + +extern size_t crypt_encrypt(struct crypt *crypt, void *data, size_t size, int meld); +extern size_t crypt_decrypt(struct crypt *crypt, void *data, size_t size, int meld); diff --git a/src/curl-wrap.c b/src/curl-wrap.c new file mode 100644 index 0000000..4eec8c8 --- /dev/null +++ b/src/curl-wrap.c @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2015, 2016 "IoT.bzh" + * Author: José Bollo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#define _GNU_SOURCE + +#include +#include +#include +#include + +#include + +#include "curl-wrap.h" +#include "escape.h" + + +/* internal representation of buffers */ +struct buffer { + size_t size; + char *data; +}; + +/* write callback for filling buffers with the response */ +static size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata) +{ + struct buffer *buffer = userdata; + size_t sz = size * nmemb; + size_t old_size = buffer->size; + size_t new_size = old_size + sz; + char *data = realloc(buffer->data, new_size + 1); + if (!data) + return 0; + memcpy(&data[old_size], ptr, sz); + data[new_size] = 0; + buffer->size = new_size; + buffer->data = data; + return sz; +} + +/* + * Perform the CURL operation for 'curl' and put the result in + * memory. If 'result' isn't NULL it receives the returned content + * that then must be freed. If 'size' isn't NULL, it receives the + * size of the returned content. Note that if not NULL, the real + * content is one byte greater than the read size and the last byte + * zero. This facility allows to handle the returned content as a + * null terminated C-string. + */ +int curl_wrap_perform(CURL *curl, char **result, size_t *size) +{ + int rc; + struct buffer buffer; + CURLcode code; + + /* init the buffer */ + buffer.size = 0; + buffer.data = strdup(""); + + /* Perform the request, res will get the return code */ + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer); + + /* Perform the request, res will get the return code */ + code = curl_easy_perform(curl); + rc = code == CURLE_OK; + + /* Check for no errors */ + if (rc) { + /* no error */ + if (size) + *size = buffer.size; + if (result) + *result = buffer.data; + else + free(buffer.data); + } else { + /* had error */ + if (size) + *size = 0; + if (result) + *result = NULL; + free(buffer.data); + } + + return rc; +} + +void curl_wrap_do(CURL *curl, void (*callback)(void *closure, int status, CURL *curl, const char *result, size_t size), void *closure) +{ + int rc; + char *result; + size_t size; + char errbuf[CURL_ERROR_SIZE]; + + curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf); + rc = curl_wrap_perform(curl, &result, &size); + if (rc) + callback(closure, rc, curl, result, size); + else + callback(closure, rc, curl, errbuf, 0); + free(result); + curl_easy_cleanup(curl); +} + +int curl_wrap_content_type_is(CURL *curl, const char *value) +{ + char *actual; + CURLcode code; + + code = curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &actual); + if (code != CURLE_OK || !actual) + return 0; + + return !strncasecmp(actual, value, strcspn(actual, "; ")); +} + +CURL *curl_wrap_prepare_get_url(const char *url) +{ + CURL *curl; + CURLcode code; + + curl = curl_easy_init(); + if(curl) { + code = curl_easy_setopt(curl, CURLOPT_URL, url); + if (code == CURLE_OK) + return curl; + curl_easy_cleanup(curl); + } + return NULL; +} + +CURL *curl_wrap_prepare_get(const char *base, const char *path, const char * const *args) +{ + CURL *res; + char *url; + + url = escape_url(base, path, args, NULL); + res = url ? curl_wrap_prepare_get_url(url) : NULL; + free(url); + return res; +} + +int curl_wrap_add_header(CURL *curl, const char *header) +{ + int rc; + struct curl_slist *list; + + list = curl_slist_append(NULL, header); + rc = list ? curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list) == CURLE_OK : 0; +/* + curl_slist_free_all(list); +*/ + return rc; +} + +int curl_wrap_add_header_value(CURL *curl, const char *name, const char *value) +{ + char *h; + int rc; + size_t sname, svalue; + + sname = strlen(name); + svalue = strlen(value); + h = alloca(sname + svalue + 3); + memcpy(h, name, sname); + h[sname] = ':'; + h[sname + 1] = ' '; + memcpy(h + sname + 2, value, svalue + 1); + rc = curl_wrap_add_header(curl, h); + return rc; +} + + +CURL *curl_wrap_prepare_post_url_data(const char *url, const char *datatype, const char *data, size_t szdata) +{ + CURL *curl; + + curl = curl_easy_init(); + if (curl + && CURLE_OK == curl_easy_setopt(curl, CURLOPT_URL, url) + && (!szdata || CURLE_OK == curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, szdata)) + && CURLE_OK == curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data) + && (!datatype || curl_wrap_add_header_value(curl, "content-type", datatype))) + return curl; + curl_easy_cleanup(curl); + return NULL; +} + +CURL *curl_wrap_prepare_post(const char *base, const char *path, const char * const *args) +{ + CURL *res; + char *url; + char *data; + size_t szdata; + + url = escape_url(base, path, NULL, NULL); + data = escape_args(args, &szdata); + res = url ? curl_wrap_prepare_post_url_data(url, NULL, data, szdata) : NULL; + free(url); + return res; +} + +/* vim: set colorcolumn=80: */ diff --git a/src/curl-wrap.h b/src/curl-wrap.h new file mode 100644 index 0000000..2e44f47 --- /dev/null +++ b/src/curl-wrap.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2015, 2016 "IoT.bzh" + * Author: José Bollo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include + +extern char *curl_wrap_url (const char *base, const char *path, + const char *const *query, size_t * size); + +extern int curl_wrap_perform (CURL * curl, char **result, size_t * size); + +extern void curl_wrap_do(CURL *curl, void (*callback)(void *closure, int status, CURL *curl, const char *result, size_t size), void *closure); + +extern int curl_wrap_content_type_is (CURL * curl, const char *value); + +extern CURL *curl_wrap_prepare_get_url(const char *url); + +extern CURL *curl_wrap_prepare_get(const char *base, const char *path, const char * const *args); + +extern CURL *curl_wrap_prepare_post_url_data(const char *url, const char *datatype, const char *data, size_t szdata); + +extern CURL *curl_wrap_prepare_post(const char *base, const char *path, const char * const *args); + +extern int curl_wrap_add_header(CURL *curl, const char *header); + +extern int curl_wrap_add_header_value(CURL *curl, const char *name, const char *value); + +/* vim: set colorcolumn=80: */ + diff --git a/src/cynagoauth-launch.c b/src/cynagoauth-launch.c new file mode 100644 index 0000000..a762877 --- /dev/null +++ b/src/cynagoauth-launch.c @@ -0,0 +1,272 @@ +/* + * Copyright (C) 2018 "IoT.bzh" + * Author: José Bollo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include + +#include + +#include "curl-wrap.h" + +#if !defined(DEFAULTNAME) +# define DEFAULTNAME "CYNAGOAUTH_TOKEN" +#endif +#if !defined(VARURL) +# define VARURL "CYNAGOAUTH_URL" +#endif +#if !defined(DEFAULTURL) +# define DEFAULTURL "http://localhost:7777/tok" +#endif + +const char shortopts[] = "+n:r:t:u:"; + +const struct option longopts[] = { + { "name", 1, 0, 'n' }, + { "replace", 1, 0, 'r' }, + { "token", 1, 0, 't' }, + { "url", 1, 0, 'u' }, + { 0, 0, 0, 0 } +}; + +char *optname = NULL; +char *optreplace = NULL; +char *opttoken = NULL; +char *opturl = NULL; + +int runac; +char **runav; + +char *rewrite_search(char *string, const char *defs[], int *idxpat) +{ + char *sfound, *sit; + const char *spat; + int ifound, iit; + + ifound = -1; + sfound = NULL; + for (iit = 0 ; (spat = defs[iit]) ; iit += 2) { + if (*spat) { + sit = strstr(string, spat); + if (sit && (sfound == NULL || sfound > sit)) { + ifound = iit; + sfound = sit; + } + } + } + *idxpat = ifound; + return sfound; +} + + +char *rewrite_replace(char *string, const char *defs[]) +{ + char *resu, *it, *wr; + const char *spat, *srep; + size_t add, sub, lpat, lrep; + int idx; + + /* get the changes */ + add = sub = 0; + it = rewrite_search(string, defs, &idx); + while (it) { + spat = defs[idx]; + srep = defs[idx + 1]; + lpat = strlen(spat); + lrep = strlen(srep); + sub += lpat; + add += lrep; + it = rewrite_search(&it[lpat], defs, &idx); + } + + /* return arg when no change */ + if (!sub) + return string; + + /* allocates the result */ + resu = malloc(strlen(string) + add - sub + 1); + if (!resu) { + fprintf(stderr, "out of memory"); + exit(1); + } + + /* instanciate the arguments */ + wr = resu; + for (;;) { + it = rewrite_search(string, defs, &idx); + if (it == NULL) { + strcpy(wr, string); + return resu; + } + if (it != string) + wr = mempcpy(wr, string, it - string); + spat = defs[idx]; + srep = defs[idx + 1]; + lpat = strlen(spat); + lrep = strlen(srep); + wr = mempcpy(wr, srep, lrep); + string = &it[lpat]; + } +} + +char *rewrite(char *arg, const char *token) +{ + const char *defs[5]; + + if (optreplace) { + defs[0] = optreplace; + defs[1] = token; + defs[4] = NULL; + } else { + defs[0] = "@t"; + defs[1] = token; + defs[2] = "@@"; + defs[3] = "@"; + defs[4] = NULL; + } + return rewrite_replace(arg, defs); +} + +void onreply(void *closure, int status, CURL *curl, const char *result, size_t size) +{ + struct json_object **object = closure; + + if (size == 0) { + fprintf(stderr, "HTTP error %s\n", result?:"?"); + exit(1); + } + + *object = json_tokener_parse(result); +} + +struct json_object *querytoken(const char *url) +{ + static const char *req[] = { "grant_type", "client_credentials", NULL }; + + struct json_object *object; + CURL *curl; + + curl = curl_wrap_prepare_post(url, NULL, req); + if (curl == NULL) { + fprintf(stderr, "out of memory\n"); + exit(1); + } + curl_wrap_do(curl, onreply, &object); + return object; +} + +void process_arguments(int ac, char **av) +{ + int optid; + + if (ac < 2) { + fprintf(stderr, "wrong count of arguments\n"); + exit(1); + } + for(;;) { + optid = getopt_long(ac, av, shortopts, longopts, NULL); + if (optid < 0) { + /* end of options */ + runac = ac - optind; + runav = &av[optind]; + return; + } + switch (optid) { + case 'n': + optname = optarg; + break; + + case 'r': + optreplace = optarg; + break; + + case 't': + opttoken = optarg; + break; + + case 'u': + opturl = optarg; + break; + + default: + fprintf(stderr, "Bad option detected"); + exit(1); + } + } +} + +int main(int ac, char **av) +{ + struct json_object *object, *type, *tok; + const char *token; + int rc, i; + + /* process arguments */ + process_arguments(ac, av); + + if (opttoken) + token = opttoken; + else { + if (!opturl) { + opturl = getenv(VARURL); + if (!opturl) + opturl = DEFAULTURL; + } + object = querytoken(opturl); + if (!json_object_object_get_ex(object, "access_token", &tok)) { + fprintf(stderr, "no token returned\n"); + exit(1); + } + if (!json_object_object_get_ex(object, "token_type", &type)) { + fprintf(stderr, "no token type\n"); + exit(1); + } + if (!json_object_is_type(type, json_type_string) + || !json_object_is_type(tok, json_type_string) + || strcmp(json_object_get_string(type), "bearer")) { + fprintf(stderr, "unknown token type\n"); + exit(1); + } + + token = json_object_get_string(tok); + } + + /* set the environment variable */ + if (!optname) + optname = DEFAULTNAME; + if (*optname) { + rc = setenv(optname, token, 1); + if (rc < 0) { + fprintf(stderr, "out of memory\n"); + exit(1); + } + } + + for (i = 1 ; i < runac ; i++) + runav[i] = rewrite(runav[i], token); + + execv(runav[0], runav); + fprintf(stderr, "can't execute %s: %s\n", runav[0], strerror(errno)); + exit(1); + return 0; +} + diff --git a/src/cynagoauth-server.c b/src/cynagoauth-server.c new file mode 100644 index 0000000..f826c88 --- /dev/null +++ b/src/cynagoauth-server.c @@ -0,0 +1,830 @@ +/* + * Copyright (C) 2018 "IoT.bzh" + * Author: José Bollo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "cred.h" +#include "uid.h" +#include "escape.h" +#include "crypt.h" +#include "base64.h" + +#define DUMP 0 +#define UNKNOWN 1 +#define AUTH_CODE 0 +#define AUTH_IMPL 0 +#define TOK_CODE 0 +#define TOK_CLIENT 1 + +#if !defined(DEFAULTPORT) +# define DEFAULTPORT "7777" +#endif +#if !defined(DEFAULTHOSTS) +# define DEFAULTHOSTS "*:"DEFAULTPORT +#endif +#if !defined(ENVHOSTS) +# define ENVHOSTS "CYNAGOAUTH_HOSTS_SPEC" +#endif + +static char cert[] = +"-----BEGIN CERTIFICATE-----\n" +"MIID5TCCAk2gAwIBAgIUBJtxecHlDMYCJfnDLqo8iWmNREkwDQYJKoZIhvcNAQEL\n" +"BQAwADAgFw0xODEyMDQxNDA1MDZaGA8yMjY1MDUwMzE0MDUxN1owADCCAaIwDQYJ\n" +"KoZIhvcNAQEBBQADggGPADCCAYoCggGBAPC8qdhly0wFUL+EG5t0WcjHPr7y1BKo\n" +"0KPQZdBskU2EkmoN3L92v37VDY6SFralEUiqFPU1DSUFREe8gEZiNUbjQMati4CZ\n" +"yUZM1i4HYjTd15WLF4HPepoVcaNiZZSK3Agjt0iBOvuF17ZXB4GJXIzGojR3z+j3\n" +"FGIRHj0vWW782hEXPcCtufsMOVpcOJsMmqMbYS640t0PZOfDdLE4ODvM7QsN1F1a\n" +"o1KEf9E1KAJLyoUYv0kTZMrPkUfw5h3kZfhIk5izrZkPzd4+o+ncfCGJXoq4/erz\n" +"uRSU/rSPSWQZ4Rn8p9g9XxPbUO8UZ2WS+z+7HQts1mq8taIgm33paYZ0xcFLOrXA\n" +"pXvlt5QPSCoEFAZtkcxckYTinp2qJLxL8nez+2Ad6no+l5S/OxltNZiuYBGbwMap\n" +"Mb7TkgAxX78b5/Qb4hni+Z1bYJ6oIWd6wPZFwbI9nIfh6si8gcwSmgS74XTImGq0\n" +"4QswJ/ZGA60Z7IVPxMvQOpqEKl3c42n1mwIDAQABo1UwUzAMBgNVHRMBAf8EAjAA\n" +"MBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdDwEB/wQFAwMHsAAwHQYDVR0OBBYE\n" +"FClRUl1jtO+cT4H0k+lzDavGYJQQMA0GCSqGSIb3DQEBCwUAA4IBgQA6ZfZ42qEC\n" +"8+NYDLaebdGKfB6rHD2CSgvaA5gOPEp/aORPUGGiREN8dXRPjbec/K73y9ufFdaS\n" +"dGMeCziRhyBox1HK6r+1E1I0ax4T2I4cHc3Afvc6rGs5cvw/FIpCt51BAXC8d6Xa\n" +"PxMM2DB4F33T3YFzBaZGFnkvIX7F2aB/sClf1+DcarXLa05M3iDIvIvDhUUloAeR\n" +"XkR89I/eAEUFpe5qgZPo0i0qh2UvcvdJWV8iuQEp3zpOLVmR7babUX99kBtw2Q2e\n" +"NE4yBJSooZ4uh/XA7abkj7C0fATK+oG1xgcxT/lPez6SUFwa+VBQXiCRgDAJsb1N\n" +"e+369zrRODs8MXy0SSc+EM4nlRgehu/OlylZOeLfVQQOMxaZV/xkjnuEaAfWT7iD\n" +"qCee083jVOEShDlSZib+UfEVVx67mc7W88T9mimTCeACck1Tt95nUiYn6QHz57Os\n" +"C9iUNJf/fdZ1WBLV8V6YUmgmU1j3VPe5fnMumXyMwcDnJZtdMvbAb04=\n" +"-----END CERTIFICATE-----\n" +; + +static char key[] = +"-----BEGIN RSA PRIVATE KEY-----\n" +"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\n" +"-----END RSA PRIVATE KEY-----\n" +; + +struct credset *creds; +struct uidset *uids; + + + + +#define ARG(con,name) MHD_lookup_connection_value(con, MHD_GET_ARGUMENT_KIND | MHD_POSTDATA_KIND, name) + +int it(void *cls, enum MHD_ValueKind kind, const char *key, const char *value) +{ + printf(" %d-%s: %s\n", (int)kind, key, value); + return MHD_YES; +} + +void dump(struct MHD_Connection *connection, const char *str, int code) +{ + printf("\nFROM-QUERY\n"); + MHD_get_connection_values(connection, MHD_HEADER_KIND|MHD_GET_ARGUMENT_KIND|MHD_POSTDATA_KIND, it, NULL); + printf("REPLY [%d]\n", (int)code); + printf("%s\n\n", str); +} + +/** + * Send an HTML reply + */ +void html_reply( + struct MHD_Connection *connection, + int code, + const char *format, + ... +) { + char *str; + int rc; + va_list ap; + struct MHD_Response *resp; + + va_start(ap, format); + rc = vasprintf(&str, format, ap); + va_end(ap); +#if DUMP + dump(connection, str, code); +#endif + resp = MHD_create_response_from_buffer(rc, str, MHD_RESPMEM_MUST_FREE); + MHD_add_response_header(resp, MHD_HTTP_HEADER_CONTENT_TYPE, "text/html"); + MHD_queue_response(connection, code ?: MHD_HTTP_OK, resp); + MHD_destroy_response(resp); +} + +/** + * Send a JSON reply + */ +void json_reply( + struct MHD_Connection *connection, + int code, + struct json_object *obj +) { + char *str; + size_t sz; + struct MHD_Response *resp; + + str = strdup(json_object_to_json_string(obj)); + sz = strlen(str); +#if DUMP + dump(connection, str, code); +#endif + resp = MHD_create_response_from_buffer (sz, str, MHD_RESPMEM_MUST_FREE); + MHD_add_response_header(resp, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); + MHD_queue_response(connection, code ?: MHD_HTTP_OK, resp); + MHD_destroy_response(resp); + json_object_put(obj); +} + +/****************************************************************************/ +/*** HANDLE SMACK LABELS ***/ +/****************************************************************************/ + +/** + * returns the smack label of the connection + */ +char *smack_of_connection(struct MHD_Connection *connection) +{ + int fd, rc; + char *result; + const union MHD_ConnectionInfo *info; + socklen_t length; + char label[1025]; + + info = MHD_get_connection_info (connection, MHD_CONNECTION_INFO_CONNECTION_FD); + result = NULL; + if (info != NULL) { + fd = info->connect_fd; + + /* get the security label */ + length = (socklen_t)(sizeof label - 1); + rc = getsockopt(fd, SOL_SOCKET, SO_PEERSEC, label, &length); +#if UNKNOWN + if (rc < 0 && errno == ENOPROTOOPT) { + strcpy(label, "User::App::_UNKNOW_"); + length = (socklen_t)strlen(label); + rc = 0; + } +#endif + if (rc == 0) { + if (length < (socklen_t)(sizeof label)) { + label[length] = 0; + result = strdup(label); + } + } + } + return result; +} + +/** + * Check that the smack label is correct + */ +int smack_label_is_acceptable(const char *label) +{ + static const char prefix[] = "User::App::"; + return !strncmp(label, prefix, sizeof prefix - 1); +} + +/****************************************************************************/ +/*** MANAGE CYNAGORA DATABASE ***/ +/****************************************************************************/ + +cynagora_t *cynagora = NULL; + +int cynenter() +{ + int rc; + + rc = cynagora ? 0 : cynagora_create(&cynagora, cynagora_Admin, 0, NULL); + if (rc >= 0) + rc = cynagora_enter(cynagora); + return rc; +} + +void cynleave(int commit) +{ + if (cynagora) + cynagora_leave(cynagora, commit); +} + +int cynset(const char *client, const char *session, const char *user, const char *permission, const char *value, time_t expire) +{ + int rc; + cynagora_key_t key; + cynagora_value_t val; + + key.client = client; + key.session = session; + key.user = user; + key.permission = permission; + val.value = value; + val.expire = expire; + rc = cynagora_set(cynagora, &key, &val); + return rc; +} + +char *escaped(const char *str) +{ + size_t i, j; + char *result, c; + + j = i = 0; + while((c = str[i++])) + j += 1 + (c == '%' || c == ';'); + + result = malloc(j + 1); + if (result) { + j = i = 0; + while((c = str[i++])) { + if (c == '%' || c == ';') + result[j++] = '%'; + result[j++] = c; + } + result[j] = 0; + } + + return result; +} + +int add_token_as_client_rules(const char *token, const char *client, time_t expire) +{ + int rc; + char *redir, *esclient; + + esclient = escaped(client); + if (!esclient) + rc = -ENOMEM; + else { + redir = NULL; + rc = asprintf(&redir, "@:%s;%%s;%%u;%%p", esclient); + if (rc > 0) { + rc = cynenter(); + if (rc >= 0) { + rc = cynset("*", token, "*", "*", redir, expire); + if (rc >= 0) + rc = cynset("*", token, "*", "urn:AGL:token:valid", "yes", expire); + } + cynleave(rc >= 0); + free(redir); + } + free(esclient); + } + return rc; +} + +/****************************************************************************/ +/*** AUTHORIZATION ENDPOINT ***/ +/****************************************************************************/ + +#if AUTH_CODE +void auth_code_flow(struct MHD_Connection *connection) +{ + const char *state, *clientid, *redirect, *scope; + char *bearer; + struct cred *cred; + time_t now, t; + + now = time(NULL); + state = ARG(connection, "state"); + clientid = ARG(connection, "client_id"); + redirect = ARG(connection, "redirect_uri"); + scope = ARG(connection, "scope"); + + credset_purge(creds, now); + credset_new_cred(creds, &cred); + t = now + 3600; + cred_set_expire(cred, t); + cred_get_bearer(cred, now + 180, &bearer); + + html_reply(connection, 200, + "

authorisation code required by <%s>

" + "

scope %s" + "

accept and authorize" + "

refuse" + , clientid, scope?:"" + , redirect, bearer, state ? "&state=" : "", state ?: "" + , redirect, state ? "&state=" : "", state ?: ""); + + free(bearer); +} +#endif + +#if AUTH_IMPL +void auth_implicit_flow(struct MHD_Connection *connection) +{ + const char *state, *clientid, *redirect, *scope; + char *bearer; + struct cred *cred; + time_t now, t; + + now = time(NULL); + state = ARG(connection, "state"); + clientid = ARG(connection, "client_id"); + redirect = ARG(connection, "redirect_uri"); + scope = ARG(connection, "scope"); + + credset_purge(creds, now); + credset_new_cred(creds, &cred); + t = now + 3600; + cred_set_expire(cred, t); + cred_get_bearer(cred, now + 180, &bearer); + + html_reply(connection, 200, + "

authorisation implicit required by <%s>

" + "

scope %s" + "

accept and authorize" + "

refuse" + , clientid, scope?:"" + , redirect, bearer, state ? "&state=" : "", state ?: "" + , redirect, state ? "&state=" : "", state ?: ""); + + free(bearer); +} +#endif + +void auth(struct MHD_Connection *connection) +{ +#if AUTH_CODE || AUTH_IMPL + const char *type; + + type = ARG(connection, "response_type") ?: ""; +#endif + +#if AUTH_CODE + /* authorization code flow */ + if (!strcmp(type, "code")) { + auth_code_flow(connection); + return; + } +#endif + +#if AUTH_IMPL + /* implicit flow */ + if (!strcmp(type, "token")) { + auth_implicit_flow(connection); + return; + } +#endif + + /* unknown flow */ + html_reply(connection, 404, "Bad request"); +} + +/****************************************************************************/ +/*** TOKEN ENDPOINT ***/ +/****************************************************************************/ + +#if TOK_CODE +void token_authorization_code_flow(struct MHD_Connection *connection) +{ + int rc, rcode; + const char *code, *clientid, *redirect, *scope; + char *bearer, *token; + struct json_object *obj; + struct cred *cred; + time_t now, t; + + now = time(NULL); + code = ARG(connection, "code"); + clientid = ARG(connection, "client_id"); + redirect = ARG(connection, "redirect_uri"); + scope = ARG(connection, "scope"); + + credset_purge(creds, now); + obj = json_object_new_object(); + + /* continuation of authorization code flow */ + rc = credset_search_bearer(creds, code, &cred, &t); + if (rc || t < now) { + json_object_object_add(obj, "error", json_object_new_string("unauthorized_client")); + rcode = MHD_HTTP_UNAUTHORIZED; + } else { + cred_get_bearer(cred, now + 3600, &bearer); + cred_get_token(cred, &token); + json_object_object_add(obj, "token_type", json_object_new_string("bearer")); + json_object_object_add(obj, "access_token", json_object_new_string(bearer)); + json_object_object_add(obj, "refresh_token", json_object_new_string(token)); + json_object_object_add(obj, "expires_in", json_object_new_int(3600)); + free(bearer); + free(token); + rcode = MHD_HTTP_OK; + } + return json_reply(connection, rcode, obj); +} +#endif + +#if TOK_CLIENT +void token_client_credentials_flow(struct MHD_Connection *connection) +{ + int rc, code; + char *bearer; + struct json_object *obj; + struct cred *cred; + time_t now, dur, exp; + char *smack_label = NULL; + struct uid *uid; + + /* create the object */ + obj = json_object_new_object(); + + /* get times */ + now = time(NULL); + + /* purge internals */ + credset_purge(creds, now); + + /* check if smack is allowed */ + smack_label = smack_of_connection(connection); + if (!smack_label || !smack_label_is_acceptable(smack_label)) + goto forbidden; + + /* retrieves the cred associated to the client */ + uid = uidset_search(uids, smack_label); + if (uid != NULL) { + cred = uid_data(uid); + bearer = cred_data(cred); + exp = cred_get_expire(cred); + dur = 5 * 24 * 60 * 60; /* 5 days */ + if (now + dur >= exp) { + /* remove the current token for creating a new */ + free(bearer); + cred_unref(cred); + uid_unref(uid); + uid = NULL; + } + } + if (uid == NULL) { + /* create a cred for the client */ + dur = 2 * 365 * 24 * 60 * 60; /* 2 years */ + exp = now + dur; + rc = credset_new_cred(creds, &cred); + if (rc < 0) + goto internal; + cred_set_expire(cred, exp); + + /* create the client token */ + rc = cred_get_bearer(cred, exp, &bearer); + if (rc < 0) { + cred_unref(cred); + goto internal; + } + cred_set_data(cred, bearer); + + /* record the client */ + rc = uid_create_for_text(&uid, uids, smack_label, cred); + if (rc < 0) { + free(bearer); + cred_unref(cred); + goto internal; + } + + /* add rules to cynagora */ + rc = add_token_as_client_rules(bearer, smack_label, exp); + if (rc < 0) { + free(bearer); + cred_unref(cred); + uid_unref(uid); + goto internal; + } + } + + json_object_object_add(obj, "token_type", json_object_new_string("bearer")); + json_object_object_add(obj, "access_token", json_object_new_string(bearer)); + json_object_object_add(obj, "expires_in", json_object_new_int((int32_t)(exp - now))); + code = MHD_HTTP_OK; + goto end; + +internal: + json_object_object_add(obj, "error", json_object_new_string("internal_error")); + code = MHD_HTTP_INTERNAL_SERVER_ERROR; + goto end; + +forbidden: + json_object_object_add(obj, "error", json_object_new_string("unauthorized_client")); + code = MHD_HTTP_UNAUTHORIZED; + goto end; + +end: + free(smack_label); + return json_reply(connection, code, obj); +} +#endif + +void token(struct MHD_Connection *connection) +{ + struct json_object *obj; + +#if TOK_CODE || TOK_CLIENT + const char *type; + + type = ARG(connection, "grant_type") ?: ""; +#endif + +#if TOK_CODE + /* continuation of authorization code flow */ + if (!strcmp(type, "authorization_code")) { + token_authorization_code_flow(connection); + return; + } +#endif + +#if TOK_CLIENT + /* special client credentials */ + if (!strcmp(type, "client_credentials")) { + token_client_credentials_flow(connection); + return; + } +#endif + + /* unknown flow */ + obj = json_object_new_object(); + json_object_object_add(obj, "error", json_object_new_string("invalid_request")); + json_reply(connection, 200, obj); +} + +/****************************************************************************/ +/*** SERVE AND ROUTE ***/ +/****************************************************************************/ + +void request_completed ( + void *cls, + struct MHD_Connection *connection, + void **con_cls, + enum MHD_RequestTerminationCode toe +) { + free(*con_cls); +} + +int access_handler( + void *cls, + struct MHD_Connection *connection, + const char *url, + const char *method, + const char *version, + const char *upload_data, + size_t *upload_data_size, + void **con_cls +) { + if (!strcasecmp(method, MHD_HTTP_METHOD_POST) && !*con_cls) { + if (*upload_data_size) { + const char **args; int i; + *con_cls = args = unescape_args_size(upload_data, *upload_data_size); + *upload_data_size = 0; + for (i = 0 ; args[i] ; i+=2) { + MHD_set_connection_value (connection, MHD_GET_ARGUMENT_KIND, args[i], args[i + 1]); + } + } + return MHD_YES; + } + + if (!strcasecmp(url, "/auth")) + auth(connection); + else if (!strcasecmp(url, "/tok")) + token(connection); + else + html_reply(connection, 404, "%s not found", url); + return MHD_YES; +} + +int openhost(const char *hostname) +{ + int rc, fd; + char *host, *service; + struct addrinfo hint, *rai, *iai; + + /* scan the uri */ + host = strdup(hostname); + service = strchr(host, ':'); + if (service == NULL) + service = DEFAULTPORT; + else + *service++ = 0; + + /* get addr */ + memset(&hint, 0, sizeof hint); + hint.ai_family = AF_INET; + hint.ai_socktype = SOCK_STREAM; + hint.ai_flags = AI_PASSIVE; + if (host[0] == 0 || (host[0] == '*' && host[1] == 0)) + host = NULL; + rc = getaddrinfo(host, service, &hint, &rai); + if (rc != 0) { + fprintf(stderr, "Can't get info about %s", hostname); + exit(10); + } + + /* get the socket */ + iai = rai; + while (iai != NULL) { + fd = socket(iai->ai_family, iai->ai_socktype, iai->ai_protocol); + if (fd >= 0) { + rc = 1; + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &rc, sizeof rc); + rc = bind(fd, iai->ai_addr, iai->ai_addrlen); + if (rc == 0) { + rc = listen(fd, 3); + if (rc== 0) { + freeaddrinfo(rai); + return fd; + } + } + close(fd); + } + iai = iai->ai_next; + } + freeaddrinfo(rai); + fprintf(stderr, "Not able to open server %s", hostname); + exit(11); + return -1; +} + +int openhosts(const char *hostnames, struct pollfd *fds, int count) +{ + int r; + char *hosts; + size_t s; + static const char seps[] = " \n\t;"; + + hostnames += strspn(hostnames, seps); + hosts = strdup(hostnames); + r = 0; + for(;;) { + s = strcspn(hosts, seps); + if (!s) + return r; + if (r == count) { + fprintf(stderr, "too many listening adresses"); + exit(9); + } + if (hosts[s]) + hosts[s++] = 0; + fds[r].fd = openhost(hosts); + fds[r++].events = POLLIN; + hosts += s; + hosts += strspn(hosts, seps); + } +} + +void makeconnection(struct MHD_Daemon *mhd, int fd) +{ + int con, sts; + struct sockaddr addr; + socklen_t lenaddr; + + lenaddr = (socklen_t)sizeof addr; + con = accept(fd, &addr, &lenaddr); + if (con < 0) { + fprintf(stderr, "can't accept incoming connection"); + exit(6); + } + sts = MHD_add_connection(mhd, con, &addr, lenaddr); + if (sts != MHD_YES) { + fprintf(stderr, "can't add incoming connection"); + exit(7); + } +} + +void main(int ac, char **av) +{ + struct MHD_Daemon *mhd; + const union MHD_DaemonInfo *info; + MHD_UNSIGNED_LONG_LONG to; + int i, r, n, s; + unsigned int flags = 0; + struct pollfd pfds[20]; + + credset_create(&creds); + uidset_create(&uids, 50); + + i = 1; + if (av[i] && !strcmp(av[i], "-s")) { + flags |= MHD_USE_TLS; + i++; + } + + mhd = MHD_start_daemon( + MHD_USE_EPOLL + | MHD_USE_DEBUG + | MHD_USE_NO_LISTEN_SOCKET + | flags, + -1, + NULL, NULL, + access_handler, NULL, + MHD_OPTION_NOTIFY_COMPLETED, &request_completed, NULL, + flags ? MHD_OPTION_HTTPS_MEM_KEY : MHD_OPTION_END, key, + MHD_OPTION_HTTPS_MEM_CERT, cert, + MHD_OPTION_END); + + if (NULL == mhd) { + fprintf(stderr, "cant't start http server\n"); + exit(1); + } + + info = MHD_get_daemon_info(mhd, MHD_DAEMON_INFO_EPOLL_FD); + if (info == NULL) { + fprintf(stderr, "cant't get http server\n"); + MHD_stop_daemon(mhd); + exit(2); + } + + s = (int)(sizeof pfds / sizeof *pfds); + pfds[0].fd = info->epoll_fd; + pfds[0].events = POLLIN; + n = 1; + + /* open hosts */ + if (!av[i]) + n += openhosts(getenv(ENVHOSTS)?:DEFAULTHOSTS, &pfds[n], s - n); + else + while (av[i]) + n += openhosts(av[i++], &pfds[n], s - n); + + /* main loop */ + for(;;) { + if (MHD_get_timeout(mhd, &to) == MHD_NO) + i = -1; + else + i = (int)to; + r = poll(pfds, n, i); + if (r < 0) { + fprintf(stderr, "poll error\n"); + MHD_stop_daemon (mhd); + exit(3); + } + if (r == 0 || (pfds[0].revents & POLLIN)) + MHD_run(mhd); + if (pfds[0].revents & POLLHUP) { + fprintf(stderr, "epoll hung\n"); + MHD_stop_daemon (mhd); + exit(4); + } + + for (i = 1 ; i < n ; i++) { + if (pfds[i].revents & POLLIN) + makeconnection(mhd, pfds[i].fd); + if (pfds[i].revents & POLLHUP) { + fprintf(stderr, "listen hung\n"); + MHD_stop_daemon (mhd); + exit(5); + } + } + } +} diff --git a/src/escape.c b/src/escape.c new file mode 100644 index 0000000..17569ad --- /dev/null +++ b/src/escape.c @@ -0,0 +1,449 @@ +/* + * Copyright (C) 2015, 2016 "IoT.bzh" + * Author: José Bollo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#define _GNU_SOURCE + +#include +#include +#include + +/* + * Test if 'c' is to be escaped or not. + * Any character that is not in [-.0-9A-Z_a-z~] + * must be escaped. + * Note that space versus + is not managed here. + */ +static inline int should_escape(char c) +{ +/* ASCII CODES OF UNESCAPED CHARS +car hx/oct idx +'-' 2d/055 1 +'.' 2e/056 2 +'0' 30/060 3 +... .. +'9' 39/071 12 +'A' 41/101 13 +... .. +'Z' 5a/132 38 +'_' 5f/137 39 +'a' 61/141 40 +... .. +'z' 7a/172 65 +'~' 7e/176 66 +*/ + /* [-.0-9A-Z_a-z~] */ + if (c <= 'Z') { + /* [-.0-9A-Z] */ + if (c < '0') { + /* [-.] */ + return c != '-' && c != '.'; + } else { + /* [0-9A-Z] */ + return c < 'A' && c > '9'; + } + } else { + /* [_a-z~] */ + if (c <= 'z') { + /* [_a-z] */ + return c < 'a' && c != '_'; + } else { + /* [~] */ + return c != '~'; + } + } +} + +/* + * returns the ASCII char for the hexadecimal + * digit of the binary value 'f'. + * returns 0 if f isn't in [0 ... 15]. + */ +static inline char bin2hex(int f) +{ + if ((f & 15) != f) + f = 0; + else if (f < 10) + f += '0'; + else + f += 'A' - 10; + return (char)f; +} + +/* + * returns the binary value for the hexadecimal + * digit whose char is 'c'. + * returns -1 if c isn't i n[0-9A-Fa-f] + */ +static inline int hex2bin(char c) +{ + /* [0-9A-Fa-f] */ + if (c <= 'F') { + /* [0-9A-F] */ + if (c <= '9') { + /* [0-9] */ + if (c >= '0') { + return (int)(c - '0'); + } + } else if (c >= 'A') { + /* [A-F] */ + return (int)(c - ('A' - 10)); + } + } else { + /* [a-f] */ + if (c >= 'a' && c <= 'f') { + return (int)(c - ('a' - 10)); + } + } + return -1; +} + +/* + * returns the length that will have the text 'itext' of length + * 'ilen' when escaped. When 'ilen' == 0, strlen is used to + * compute the size of 'itext'. + */ +static size_t escaped_length(const char *itext, size_t ilen) +{ + char c; + size_t i, r; + + if (!ilen) + ilen = strlen(itext); + c = itext[i = r = 0]; + while (i < ilen) { + r += c != ' ' && should_escape(c) ? 3 : 1; + c = itext[++i]; + } + return r; +} + +/* + * Escapes the text 'itext' of length 'ilen'. + * When 'ilen' == 0, strlen is used to compute the size of 'itext'. + * The escaped text is put in 'otext' of length 'olen'. + * Returns the length of the escaped text (it can be greater than 'olen'). + * When 'olen' is greater than the needed length, an extra null terminator + * is appened to the escaped string. + */ +static size_t escape_to(const char *itext, size_t ilen, char *otext, size_t olen) +{ + char c; + size_t i, r; + + if (!ilen) + ilen = strlen(itext); + c = itext[i = r = 0]; + while (i < ilen) { + if (c == ' ') + c = '+'; + else if (should_escape(c)) { + if (r < olen) + otext[r] = '%'; + r++; + if (r < olen) + otext[r] = bin2hex((c >> 4) & 15); + r++; + c = bin2hex(c & 15); + } + if (r < olen) + otext[r] = c; + r++; + c = itext[++i]; + } + if (r < olen) + otext[r] = 0; + return r; +} + +/* + * returns the length of 'itext' of length 'ilen' that can be unescaped. + * compute the size of 'itext' when 'ilen' == 0. + */ +static size_t unescapable_length(const char *itext, size_t ilen) +{ + char c; + size_t i; + + c = itext[i = 0]; + while (i < ilen) { + if (c != '%') + i++; + else { + if (i + 3 > ilen + || hex2bin(itext[i + 1]) < 0 + || hex2bin(itext[i + 2]) < 0) + break; + i += 3; + } + c = itext[i]; + } + return i; +} + +/* + * returns the length that will have the text 'itext' of length + * 'ilen' when escaped. When 'ilen' == 0, strlen is used to + * compute the size of 'itext'. + */ +static size_t unescaped_length(const char *itext, size_t ilen) +{ + char c; + size_t i, r; + + c = itext[i = r = 0]; + while (i < ilen) { + i += (size_t)(1 + ((c == '%') << 1)); + r++; + c = itext[i]; + } + return r; +} + +static size_t unescape_to(const char *itext, size_t ilen, char *otext, size_t olen) +{ + char c; + size_t i, r; + int h, l; + + ilen = unescapable_length(itext, ilen); + c = itext[i = r = 0]; + while (i < ilen) { + if (c != '%') { + if (c == '+') + c = ' '; + i++; + } else { + if (i + 2 >= ilen) + break; + h = hex2bin(itext[i + 1]); + l = hex2bin(itext[i + 2]); + c = (char)((h << 4) | l); + i += 3; + } + if (r < olen) + otext[r] = c; + r++; + c = itext[i]; + } + if (r < olen) + otext[r] = 0; + return r; +} + +/* create an url */ +char *escape_url(const char *base, const char *path, const char * const *args, size_t *length) +{ + int i; + size_t lb, lp, lq, l, L; + const char *null; + char *result; + + /* ensure args */ + if (!args) { + null = NULL; + args = &null; + } + + /* compute lengths */ + lb = base ? strlen(base) : 0; + lp = path ? strlen(path) : 0; + lq = 0; + i = 0; + while (args[i]) { + lq += 1 + escaped_length(args[i], strlen(args[i])); + i++; + if (args[i]) + lq += 1 + escaped_length(args[i], strlen(args[i])); + i++; + } + + /* allocation */ + L = lb + lp + lq + 1; + result = malloc(L + 1); + if (result) { + /* make the resulting url */ + l = lb; + if (lb) { + memcpy(result, base, lb); + if (result[l - 1] != '/' && path && path[0] != '/') + result[l++] = '/'; + } + if (lp) { + memcpy(result + l, path, lp); + l += lp; + } + i = 0; + while (args[i]) { + if (i) { + result[l++] = '&'; + } else if (base || path) { + result[l] = memchr(result, '?', l) ? '&' : '?'; + l++; + } + l += escape_to(args[i], strlen(args[i]), result + l, L - l); + i++; + if (args[i]) { + result[l++] = '='; + l += escape_to(args[i], strlen(args[i]), result + l, L - l); + } + i++; + } + result[l] = 0; + if (length) + *length = l; + } + return result; +} + +char *escape_args(const char * const *args, size_t *length) +{ + return escape_url(NULL, NULL, args, length); +} + +const char **unescape_args_size(const char *args, size_t size) +{ + const char **r; + char c, s, *p; + int h, l; + size_t i, na, nr; + + /* estimate the size to allocate (bigger if empty values exists -&&&&) */ + na = !!size; + for (nr = i = 0 ; i < size ; i++) { + c = args[i]; + if (c == 0) + size = i; + else if (c == '&') + na++; + else if (c == '%') { + if (i + 2 >= size + || hex2bin(args[i + 1]) < 0 + || hex2bin(args[i + 2]) < 0) + size = i; + else + nr += 2; + } + } + + /* allocates the storage */ + r = malloc(1 + size - nr + (2 * na + 1) * sizeof(char *)); + if (!r) + return r; + + /* unescape and fill the storage */ + p = (void*)&r[2 * na + 1]; + na = 0; + s = 0; + i = 0; + do { + c = i >= size ? '&' : args[i++]; + switch (c) { + case '+': + c = ' '; + break; + case '%': + h = hex2bin(args[i++]); + l = hex2bin(args[i++]); + c = (char)((h << 4) | l); + break; + case '&': + if (s) { + if (s == 1) + r[na++] = NULL; + s = 0; + *p++ = 0; + } + c = 0; + break; + case '=': + if (!s) + r[na++] = p; + *p++ = 0; + r[na++] = p; + s = 2; + c = 0; + break; + } + if (c) { + if (!s) { + r[na++] = p; + s = 1; + } + *p++ = c; + } + } while (i < size); + *p = 0; + r[na] = NULL; + return r; +} + +const char **unescape_args(const char *args) +{ + return unescape_args_size(args, SIZE_MAX); +} + +char *escape(const char *text, size_t textlen, size_t *reslength) +{ + size_t len; + char *result; + + len = 1 + escaped_length(text, textlen); + result = malloc(len); + if (result) + escape_to(text, textlen, result, len); + if (reslength) + *reslength = len - 1; + return result; +} + +char *unescape(const char *text, size_t textlen, size_t *reslength) +{ + size_t len; + char *result; + + len = 1 + unescaped_length(text, textlen); + result = malloc(len); + if (result) + unescape_to(text, textlen, result, len); + if (reslength) + *reslength = len - 1; + return result; +} + +#if TEST_ESCAPE +#include +int main(int ac, char **av) +{ + int i; + char *x = escape_args((void*)++av, NULL); + char *y = escape(x, strlen(x), NULL); + char *z = unescape(y, strlen(y), NULL); + const char **v = unescape_args(x); + + printf("%s\n%s\n%s\n", x, y, z); + free(x); + free(y); + free(z); + i = 0; + while(v[i]) { + printf("%s=%s / %s=%s\n", av[i], av[i+1], v[i], v[i+1]); + i += 2; + } + free(v); + return 0; +} +#endif +/* vim: set colorcolumn=80: */ diff --git a/src/escape.h b/src/escape.h new file mode 100644 index 0000000..ba2df8a --- /dev/null +++ b/src/escape.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2017 "IoT.bzh" + * Author: José Bollo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +extern char *escape_url(const char *base, const char *path, const char * const *args, size_t *length); +extern char *escape_args(const char * const *args, size_t *length); +extern const char **unescape_args(const char *args); +extern const char **unescape_args_size(const char *args, size_t size); + +/* vim: set colorcolumn=80: */ diff --git a/src/uid.c b/src/uid.c new file mode 100644 index 0000000..e73c988 --- /dev/null +++ b/src/uid.c @@ -0,0 +1,543 @@ +/* + * Copyright (C) 2018 "IoT.bzh" + * Author: José Bollo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include + +#include "uid.h" +#include "base64.h" + +/* + * uid are object having a text associated that is unic within an + * uidset. + */ + +/* + * MUST be multiple of 4. + * 4 ensures a minimum of 2^24~16*10^6 differents places cause 24=8*(3/4)*4 + */ +#define MINLEN 4 + +/* + * MUST be multiple of 4. + * 4 ensures 2^24 times more uids in the set + */ +#define INCLEN 4 + +/* + * With MINLEN = 4, the minimal count of items is 2^24 ~ 16.10^6. + * + * We target a maximum uid count of 10^6. + * + * It means that the probability p to pick randomly an already used + * uid will be better than 1/16 ~ 10^6 / 2^24 + * + * So in the worst case for this conditions after N trials, the + * probability to pick only used uids is p^N + * + * For having p^N less than 10^-6 we can use (1/16)^N < 2^-20 + * to compute N. It leads to N >= 5 + */ +#define DEFTRIAL 5 + +struct uidset; + +/** + * Structure for a unic id + */ +struct uid +{ + /** link to a next */ + struct uid *next; + + /** container */ + struct uidset *set; + + /** some user data */ + void *data; + + /** refence count */ + unsigned refcount; + + /** text length (without tailing zero), MUST multiple of 4 */ + int txtlen; + + /** identifier */ + char text[1]; +}; + +/** + * structure that records a set of unic ids. + * The ids are unic only within a set. + */ +struct uidset +{ + /** head of items */ + struct uid *head; + + /** mutual exclusion on the list */ + pthread_mutex_t mutex; + + /** refence count */ + unsigned refcount; + + /** count of items */ + unsigned count; + + /** length of the uid key */ + int minlen; +}; + +/** + * Compute length: the lowest multiple of 4 greater or equal to max(minlen, MINLEN) + * @param minlen the minimal length of the uid to create + * @return the length of the uid to create + */ +static int complen(int minlen) +{ + return minlen <= MINLEN ? MINLEN : minlen + (3 & (-minlen)); +} + +/** + * Generate an uid text (zero terminated) of length 'txtlen' in 'buffer' + * + * @param buffer the buffer where to store the generate uid text + * @param txtlen the length of the text to generate without the terminating null + * must be a multiple of 4 + */ +static void genid( + char *buffer, + int txtlen +) { + ssize_t rgr __attribute__ ((unused)); + int ftl; /* ftl: forth of textlen */ + int x; /* state of encoding */ + int blen; /* length of binary random data */ + char *bbuf; /* head of binary random data */ + + /* + * The generation of a uid is based on base64 encoding of some + * random value. + * Base64 encoding of 3.N bytes is 4.N bytes length. + * This explains that the text length L must be a multiple of 4. + * Then having L = 4.N the computation tells N = L/4 and 3.N = L-N. + * As an optimisation of memory usage, the same 'buffer' is used + * to handle the random value and the generated uid text. The trick + * is that for avoiding memory crush, the random value is generated + * at the end of the buffer. + * +-------+-------+-------+-------+0 + * ^ ^ + * `uid `bbuf + */ + ftl = txtlen >> 2; + blen = txtlen - ftl; + bbuf = &buffer[1 + ftl]; + rgr = getrandom(bbuf, blen, 0); + x = 0; + base64_encode_put(bbuf, blen, &buffer, &x, Base64_Variant_URL_Trunc); + base64_encode_flush(&buffer, &x, Base64_Variant_URL_Trunc); + *buffer = 0; +} + +/** + * Search within 'uidset' the uid matching the given 'text' + * @param uidset the set to search + * @param text the uid text to search + * @return the uid structure found or NULL if none matched + */ +static struct uid *search( + struct uidset *uidset, + const char *text +) { + struct uid *uid = uidset->head; + while(uid && strcmp(uid->text, text)) + uid = uid->next; + return uid; +} + +/** + * Creates a new text value for the uid + * @param uidset the set within wich the uid must be unic + * @param uid the uid to set + * @return 0 in case of success or -1 with errno = EGAIN when none generated + */ +static int newid( + struct uidset *uidset, + struct uid *uid +) { + char *temp; + int trialcount; + + /* default trial count */ + trialcount = DEFTRIAL; + + /* use local buffer to allow renew of uid (see uid_change) */ + temp = alloca(uid->txtlen + 1); + do { + genid(temp, uid->txtlen); + if (!search(uidset, temp)) { + strcpy(uid->text, temp); + return 0; + } + } while(--trialcount); + + /* max trial reached */ + errno = EAGAIN; + return -1; +} + +/** + * Creates a uidset in 'result' for uid of default 'minlen' + * @param result where to put the handler to the created uidset + * @param minlen the default minlen of created uids + * @return 0 in case of success or -1 with errno = ENOMEM + */ +int uidset_create( + struct uidset **result, + int minlen +) { + struct uidset *uidset; + + uidset = *result = malloc(sizeof *uidset); + if (!uidset) + return -1; + + uidset->head = NULL; + uidset->count = 0; + pthread_mutex_init(&uidset->mutex, NULL); + uidset->minlen = minlen; + uidset->refcount = 1; + + return 0; +} + +/** + * Increment the use count of the 'uidset' + * @param uidset the uidset whose use count is to be incremented + * @return the uidset + */ +struct uidset *uidset_addref( + struct uidset *uidset +) { + if (uidset) + __atomic_add_fetch(&uidset->refcount, 1, __ATOMIC_RELAXED); + return uidset; +} + +/** + * Decrement the use count of the 'uidset' and possibly destroy it if no + * more used. + * @param uidset the uidset whose use count is to be decremented + */ +void uidset_unref( + struct uidset *uidset +) { + if (uidset && !__atomic_sub_fetch(&uidset->refcount, 1, __ATOMIC_RELAXED)) { + pthread_mutex_destroy(&uidset->mutex); + free(uidset); + } +} + +/** + * Search the uid of 'text' within the 'uidset' + * @param uidset the uidset to inspect + * @param text the text of the uid to search + * @return the found uid or NULL if not found + */ +struct uid *uidset_search( + struct uidset *uidset, + const char *text +) { + struct uid *uid; + + pthread_mutex_lock(&uidset->mutex); + uid = search(uidset, text); + pthread_mutex_unlock(&uidset->mutex); + if (!uid) + errno = ENOENT; + return uid; +} + +/** + * Get the count of uid of the 'uidset' + * @param uidset the set whose item count is queried + * @return the count of uid of the set + */ +unsigned uidset_count(struct uidset *uidset) +{ + return uidset->count; +} + +/** + * Call 'callback' for all uid of 'uidset' + * It is safe to unref the recived 'uid' within the callback + * @param uidset the uidset to process + * @param callback the callback to call receives: (closure, uid, data) + * @param closure the closure to pass to the callback + */ +void uidset_for_all( + struct uidset *uidset, + void (*callback)(void *closure, struct uid *uid, void *data), + void *closure +) { + unsigned n, i; + struct uid *uid, **arruid; + + pthread_mutex_lock(&uidset->mutex); + n = uidset->count; + if (n == 0) + /* nothing to do */ + pthread_mutex_unlock(&uidset->mutex); + else { + /* copy uids (with addref) in local array */ + arruid = alloca(n * sizeof *arruid); + uid = uidset->head; + i = 0; + while (uid) { + arruid[i++] = uid_addref(uid); + uid = uid->next; + } + assert(i == n); + pthread_mutex_unlock(&uidset->mutex); + + /* process the saved uids */ + while(i) { + uid = arruid[--i]; + callback(closure, uid, uid->data); + uid_unref(uid); + } + } +} + +/** + * Creates a new random base64url uid of 'minlen' and 'data' in the 'uidset' + * @param result where to put created uid + * @param uidset the set of uid + * @param data the data to set to the uid + * @param minlen the minimal length of the random uid + * @return 0 in case of success or -1 with errno = ENOMEM + */ +int uid_create_length( + struct uid **result, + struct uidset *uidset, + void *data, + int minlen +) { + struct uid *uid; + int len; + int rc; + + len = complen(minlen); + for (;;) { + *result = uid = malloc(len + sizeof *uid); + if (!uid) + return -1; + + uid->set = uidset_addref(uidset); + uid->refcount = 1; + uid->data = data; + uid->txtlen = len; + + /* */ + pthread_mutex_lock(&uidset->mutex); + rc = newid(uidset, uid); + if (rc == 0) { + /* done so add it */ + uid->next = uidset->head; + uidset->head = uid; + uidset->count++; + pthread_mutex_unlock(&uidset->mutex); + return 0; + } + + pthread_mutex_unlock(&uidset->mutex); + free(uid); + len = complen(len + INCLEN); + } +} + +/** + * Creates a new random base64url uid of the default minlen and 'data' in the 'uidset' + * @param result where to put created uid + * @param uidset the set of uid + * @param data the data to set to the uid + * @return 0 in case of success or -1 with errno = ENOMEM + */ +int uid_create( + struct uid **result, + struct uidset *uidset, + void *data +) { + return uid_create_length(result, uidset, data, uidset->minlen); +} + +/** + * Creates a new uid of the given 'text' and 'data' in the 'uidset' + * @param result where to put created uid + * @param uidset the set of uid + * @param text the text to set for the uid + * @param data the data to set to the uid + * @return 0 in case of success or -1 with errno = ENOMEM or EEXIST + */ +int uid_create_for_text( + struct uid **result, + struct uidset *uidset, + const char *text, + void *data +) { + struct uid *uid; + int len, rc = -1; + + if (!text || !*text) + return uid_create(result, uidset, data); + + pthread_mutex_lock(&uidset->mutex); + uid = search(uidset, text); + if (uid) { + uid = NULL; + errno = EEXIST; + } else { + len = complen((int)strlen(text)); + uid = malloc(len + sizeof *uid); + if (uid) { + uid->set = uidset_addref(uidset); + uid->refcount = 1; + uid->data = data; + uid->txtlen = len; + strcpy(uid->text, text); + uid->next = uidset->head; + uidset->head = uid; + uidset->count++; + rc = 0; + } + } + pthread_mutex_unlock(&uidset->mutex); + *result = uid; + return rc; +} + +/** + * Increment the use count of 'uid' + * @param uid the uid whose use count is to increment + * @return the uid + */ +struct uid *uid_addref( + struct uid *uid +) { + if (uid) + __atomic_add_fetch(&uid->refcount, 1, __ATOMIC_RELAXED); + return uid; +} + +/** + * Decrement the use count of 'uid' and possibly destroy it + * @param uid the uid whose use count is to decrement + */ +void uid_unref( + struct uid *uid +) { + struct uidset *set; + struct uid **puid; + + if (uid && !__atomic_sub_fetch(&uid->refcount, 1, __ATOMIC_RELAXED)) { + set = uid->set; + pthread_mutex_lock(&set->mutex); + puid = &set->head; + while(*puid) { + if (*puid != uid) + puid = &(*puid)->next; + else { + *puid = uid->next; + set->count--; + break; + } + } + pthread_mutex_unlock(&set->mutex); + free(uid); + uidset_unref(set); + } +} + +/** + * Change the text of the 'uid' + * @param uid uid whose text is to change + * @return in case of success or -1 in case of error + */ +int uid_change( + struct uid *uid +) { + int rc; + + struct uidset *uidset = uid->set; + pthread_mutex_lock(&uidset->mutex); + rc = newid(uidset, uid); + pthread_mutex_unlock(&uidset->mutex); + return rc; +} + +/** + * Get the text of the 'uid' + * @param uid the uid whose text is queried + * @return the text of the uid + */ +const char *uid_text( + struct uid *uid +) { + return uid->text; +} + +/** + * Get the uidset of the 'uid' + * @param uid the uid whose uidset is queried + * @return the uidset of the uid + */ +struct uidset *uid_set( + struct uid *uid +) { + return uid->set; +} + +/** + * Get the data of the 'uid' + * @param uid the uid whose data is queried + * @return the data of the uid + */ +void *uid_data( + struct uid *uid +) { + return uid->data; +} + +/** + * Set the data of the 'uid' + * @param uid the uid whose data is to be set + * @param data the data to be set + */ +void uid_data_set( + struct uid *uid, + void *data +) { + uid->data = data; +} + diff --git a/src/uid.h b/src/uid.h new file mode 100644 index 0000000..f0fc3ed --- /dev/null +++ b/src/uid.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2018 "IoT.bzh" + * Author: José Bollo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +struct uidset; +struct uid; + +extern int uidset_create(struct uidset **result, int minlen); + +extern struct uidset *uidset_addref(struct uidset *uidset); + +extern void uidset_unref(struct uidset *uidset); + +extern struct uid *uidset_search(struct uidset *uidset, const char *text); + +extern unsigned uidset_count(struct uidset *uidset); + +extern void uidset_for_all( + struct uidset *uidset, + void (*callback)(void *closure, struct uid *uid, void *data), + void *closure +); + +extern int uid_create(struct uid **result, struct uidset *uidset, void *data); + +extern int uid_create_length( + struct uid **result, + struct uidset *uidset, + void *data, + int minlen +); + +extern int uid_create_for_text( + struct uid **result, + struct uidset *uidset, + const char *text, + void *data +); + +extern struct uid *uid_addref(struct uid *uid); + +extern void uid_unref(struct uid *uid); + +extern int uid_change(struct uid *uid); + +extern const char *uid_text(struct uid *uid); + +extern void *uid_data(struct uid *uid); + +extern void uid_data_set(struct uid *uid, void *data); + +extern struct uidset *uid_set(struct uid *uid); + -- cgit 1.2.3-korg