diff options
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | CMakeLists.txt | 41 | ||||
-rw-r--r-- | LICENSE.txt | 202 | ||||
-rw-r--r-- | README.md | 52 | ||||
-rw-r--r-- | cert | 23 | ||||
-rw-r--r-- | cynagoauth.service | 15 | ||||
-rw-r--r-- | key | 39 | ||||
-rw-r--r-- | memo.txt | 16 | ||||
-rwxr-xr-x | mkbuild.sh | 17 | ||||
-rw-r--r-- | src/CMakeLists.txt | 87 | ||||
-rw-r--r-- | src/README.md | 13 | ||||
-rw-r--r-- | src/base64.c | 263 | ||||
-rw-r--r-- | src/base64.h | 117 | ||||
-rw-r--r-- | src/cred.c | 907 | ||||
-rw-r--r-- | src/cred.h | 61 | ||||
-rw-r--r-- | src/crypt.c | 295 | ||||
-rw-r--r-- | src/crypt.h | 28 | ||||
-rw-r--r-- | src/curl-wrap.c | 216 | ||||
-rw-r--r-- | src/curl-wrap.h | 45 | ||||
-rw-r--r-- | src/cynagoauth-launch.c | 272 | ||||
-rw-r--r-- | src/cynagoauth-server.c | 830 | ||||
-rw-r--r-- | src/escape.c | 449 | ||||
-rw-r--r-- | src/escape.h | 24 | ||||
-rw-r--r-- | src/uid.c | 543 | ||||
-rw-r--r-- | src/uid.h | 68 |
25 files changed, 4626 insertions, 0 deletions
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 <jose.bollo@iot.bzh> +# +# 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 @@ -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 + @@ -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 <jose.bollo@iot.bzh> +# +# 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 <jose.bollo@iot.bzh> + * + * 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 <stdlib.h> +#include <stdint.h> +#include <string.h> + +#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 <jose.bollo@iot.bzh> + * + * 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 <jose.bollo@iot.bzh> + * + * 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 <stdint.h> +#include <stdlib.h> +#include <limits.h> + +#include <stdio.h> +#include <string.h> + +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/random.h> + +#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 <jose.bollo@iot.bzh> + * + * 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 <time.h> + +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 <jose.bollo@iot.bzh> + * + * 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 <stdint.h> +#include <stdlib.h> +#include <limits.h> + +#include <stdio.h> +#include <string.h> + +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/random.h> + +#include <openssl/blowfish.h> + +#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 <jose.bollo@iot.bzh> + * + * 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 <jose.bollo@iot.bzh> + * + * 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 <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#include <curl/curl.h> + +#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 <jose.bollo@iot.bzh> + * + * 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 <curl/curl.h> + +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 <jose.bollo@iot.bzh> + * + * 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 <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <getopt.h> + +#include <json-c/json.h> + +#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 <jose.bollo@iot.bzh> + * + * 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 <stdlib.h> +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include <time.h> +#include <errno.h> +#include <poll.h> +#include <netdb.h> +#include <sys/socket.h> + +#include <microhttpd.h> +#include <json-c/json.h> +#include <cynagora.h> + +#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, + "<html><h1>authorisation code required by <<b>%s</b>></h1>" + "<p>scope %s" + "<p><a href=\"%s?code=%s%s%s\">accept and authorize</a>" + "<p><a href=\"%s?error=refused%s%s\">refuse</a>" + , 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, + "<html><h1>authorisation implicit required by <<b>%s</b>></h1>" + "<p>scope %s" + "<p><a href=\"%s?token=%s%s%s\">accept and authorize</a>" + "<p><a href=\"%s?error=refused%s%s\">refuse</a>" + , 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, "<html>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, "<html>%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 <jose.bollo@iot.bzh> + * + * 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 <stdint.h> +#include <stdlib.h> +#include <string.h> + +/* + * 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 <stdio.h> +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 <jose.bollo@iot.bzh> + * + * 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 <jose.bollo@iot.bzh> + * + * 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 <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <assert.h> +#include <pthread.h> +#include <sys/random.h> + +#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 <jose.bollo@iot.bzh> + * + * 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); + |