summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--CMakeLists.txt41
-rw-r--r--LICENSE.txt202
-rw-r--r--README.md52
-rw-r--r--cert23
-rw-r--r--cynagoauth.service15
-rw-r--r--key39
-rw-r--r--memo.txt16
-rwxr-xr-xmkbuild.sh17
-rw-r--r--src/CMakeLists.txt87
-rw-r--r--src/README.md13
-rw-r--r--src/base64.c263
-rw-r--r--src/base64.h117
-rw-r--r--src/cred.c907
-rw-r--r--src/cred.h61
-rw-r--r--src/crypt.c295
-rw-r--r--src/crypt.h28
-rw-r--r--src/curl-wrap.c216
-rw-r--r--src/curl-wrap.h45
-rw-r--r--src/cynagoauth-launch.c272
-rw-r--r--src/cynagoauth-server.c830
-rw-r--r--src/escape.c449
-rw-r--r--src/escape.h24
-rw-r--r--src/uid.c543
-rw-r--r--src/uid.h68
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
diff --git a/cert b/cert
new file mode 100644
index 0000000..066ab2b
--- /dev/null
+++ b/cert
@@ -0,0 +1,23 @@
+-----BEGIN CERTIFICATE-----
+MIID5TCCAk2gAwIBAgIUBJtxecHlDMYCJfnDLqo8iWmNREkwDQYJKoZIhvcNAQEL
+BQAwADAgFw0xODEyMDQxNDA1MDZaGA8yMjY1MDUwMzE0MDUxN1owADCCAaIwDQYJ
+KoZIhvcNAQEBBQADggGPADCCAYoCggGBAPC8qdhly0wFUL+EG5t0WcjHPr7y1BKo
+0KPQZdBskU2EkmoN3L92v37VDY6SFralEUiqFPU1DSUFREe8gEZiNUbjQMati4CZ
+yUZM1i4HYjTd15WLF4HPepoVcaNiZZSK3Agjt0iBOvuF17ZXB4GJXIzGojR3z+j3
+FGIRHj0vWW782hEXPcCtufsMOVpcOJsMmqMbYS640t0PZOfDdLE4ODvM7QsN1F1a
+o1KEf9E1KAJLyoUYv0kTZMrPkUfw5h3kZfhIk5izrZkPzd4+o+ncfCGJXoq4/erz
+uRSU/rSPSWQZ4Rn8p9g9XxPbUO8UZ2WS+z+7HQts1mq8taIgm33paYZ0xcFLOrXA
+pXvlt5QPSCoEFAZtkcxckYTinp2qJLxL8nez+2Ad6no+l5S/OxltNZiuYBGbwMap
+Mb7TkgAxX78b5/Qb4hni+Z1bYJ6oIWd6wPZFwbI9nIfh6si8gcwSmgS74XTImGq0
+4QswJ/ZGA60Z7IVPxMvQOpqEKl3c42n1mwIDAQABo1UwUzAMBgNVHRMBAf8EAjAA
+MBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdDwEB/wQFAwMHsAAwHQYDVR0OBBYE
+FClRUl1jtO+cT4H0k+lzDavGYJQQMA0GCSqGSIb3DQEBCwUAA4IBgQA6ZfZ42qEC
+8+NYDLaebdGKfB6rHD2CSgvaA5gOPEp/aORPUGGiREN8dXRPjbec/K73y9ufFdaS
+dGMeCziRhyBox1HK6r+1E1I0ax4T2I4cHc3Afvc6rGs5cvw/FIpCt51BAXC8d6Xa
+PxMM2DB4F33T3YFzBaZGFnkvIX7F2aB/sClf1+DcarXLa05M3iDIvIvDhUUloAeR
+XkR89I/eAEUFpe5qgZPo0i0qh2UvcvdJWV8iuQEp3zpOLVmR7babUX99kBtw2Q2e
+NE4yBJSooZ4uh/XA7abkj7C0fATK+oG1xgcxT/lPez6SUFwa+VBQXiCRgDAJsb1N
+e+369zrRODs8MXy0SSc+EM4nlRgehu/OlylZOeLfVQQOMxaZV/xkjnuEaAfWT7iD
+qCee083jVOEShDlSZib+UfEVVx67mc7W88T9mimTCeACck1Tt95nUiYn6QHz57Os
+C9iUNJf/fdZ1WBLV8V6YUmgmU1j3VPe5fnMumXyMwcDnJZtdMvbAb04=
+-----END CERTIFICATE-----
diff --git a/cynagoauth.service b/cynagoauth.service
new file mode 100644
index 0000000..cd84afb
--- /dev/null
+++ b/cynagoauth.service
@@ -0,0 +1,15 @@
+[Unit]
+Description=Authorization server OAuth2
+BindsTo=cynagora.service
+
+[Service]
+#User=daemon
+#Group=nobody
+#SupplementaryGroups=cynagora
+ExecStart=/usr/bin/cynagoauth-server
+Restart=on-failure
+RestartSec=5
+
+[Install]
+WantedBy=multi-user.target
+
diff --git a/key b/key
new file mode 100644
index 0000000..5ade75f
--- /dev/null
+++ b/key
@@ -0,0 +1,39 @@
+-----BEGIN RSA PRIVATE KEY-----
+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
+-----END RSA PRIVATE KEY-----
diff --git a/memo.txt b/memo.txt
new file mode 100644
index 0000000..cadbb09
--- /dev/null
+++ b/memo.txt
@@ -0,0 +1,16 @@
+cd build
+
+afb-daemon --no-ldpaths --binding=./src/binding-oauth-oidc.so --traceevt=all --tracereq=all -vvv -t '' -p 4444
+
+afb-client-demo -H localuser:4444/api?token=4
+
+oauth-oidc auth {"authorization_endpoint":"http://localhost:7777/auth","token_endpoint":"http://localhost:7777/tok","redirect_uri":"http://localhost:0/hell-entry","client_id":"me","client_secret":"jojo","scope":["a", "b", "c", "c", "b", "aa"],"data":"data-1"}
+
+oauth-oidc auth {"authorization_endpoint":"https://localhost:7777/auth","token_endpoint":"https://localhost:7777/tok","redirect_uri":"http://localhost:0/hell-entry","client_id":"me","client_secret":"jojo","scope":["a", "b", "c", "c", "b", "aa"],"data":"data-1"}
+
+oauth-oidc auth {"authorization_endpoint":"https://github.com/login/oauth/authorize","client":"03e364709b579e82db5c"}
+
+src/test-ep 7777
+
+src/test-ep s 7777
+
diff --git a/mkbuild.sh b/mkbuild.sh
new file mode 100755
index 0000000..9f70321
--- /dev/null
+++ b/mkbuild.sh
@@ -0,0 +1,17 @@
+#/bin/sh
+
+h="$(dirname $0)"
+
+mkdir -p "$h/build" || exit
+cd "$h/build" || exit
+
+[ "$1" = "-f" ] && { rm -r * 2>/dev/null; shift; }
+[ "$1" = "--force" ] && { rm -r * 2>/dev/null; shift; }
+
+cmake \
+ -DCMAKE_BUILD_TYPE=Debug \
+ -DCMAKE_INSTALL_PREFIX=~/.local \
+ ..
+
+make -j "$@"
+
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644
index 0000000..b955d63
--- /dev/null
+++ b/src/CMakeLists.txt
@@ -0,0 +1,87 @@
+###########################################################################
+# Copyright (C) 2015-2019 "IoT.bzh"
+#
+# author: José Bollo <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 &lt;<b>%s</b>&gt;</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 &lt;<b>%s</b>&gt;</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);
+