mirror of
https://github.com/obgm/libcoap.git
synced 2025-05-09 15:21:35 +08:00

Add in limited caching for multiple clients requesting an Observe subscription for the same upstream session resource so that only a single observation request is sent upstream and any unsolicited response is sent back to all the requesting clients. This requires a change in the recently added handler registered to coap_register_proxy_response_handler(), which no longer should no longer call coap_proxy_forward_response(), but just return a PDU for forwarding on.
2820 lines
93 KiB
C
2820 lines
93 KiB
C
/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
|
|
/* coap -- simple implementation of the Constrained Application Protocol (CoAP)
|
|
* as defined in RFC 7252
|
|
*
|
|
* Copyright (C) 2010--2025 Olaf Bergmann <bergmann@tzi.org> and others
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*
|
|
* This file is part of the CoAP library libcoap. Please see README for terms
|
|
* of use.
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <ctype.h>
|
|
#include <inttypes.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <errno.h>
|
|
#include <signal.h>
|
|
#ifdef _WIN32
|
|
#define strcasecmp _stricmp
|
|
#define strncasecmp _strnicmp
|
|
#include "getopt.c"
|
|
#if !defined(S_ISDIR)
|
|
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
|
|
#endif
|
|
#ifndef R_OK
|
|
#define R_OK 4
|
|
#endif
|
|
char *strndup(const char *s1, size_t n);
|
|
char *
|
|
strndup(const char *s1, size_t n) {
|
|
char *copy = (char *)malloc(n + 1);
|
|
if (copy) {
|
|
memcpy(copy, s1, n);
|
|
copy[n] = 0;
|
|
}
|
|
return copy;
|
|
}
|
|
#include <io.h>
|
|
#define access _access
|
|
#define fileno _fileno
|
|
#else
|
|
#include <unistd.h>
|
|
#include <sys/select.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
#include <netdb.h>
|
|
#include <dirent.h>
|
|
#include <syslog.h>
|
|
#endif
|
|
|
|
/* Need to refresh time once per sec */
|
|
#define COAP_RESOURCE_CHECK_TIME 1
|
|
|
|
#include <coap3/coap.h>
|
|
#include <coap3/coap_defines.h>
|
|
|
|
#if COAP_THREAD_SAFE
|
|
/* Define the number of coap_io_process() threads required */
|
|
#ifndef NUM_SERVER_THREADS
|
|
#define NUM_SERVER_THREADS 3
|
|
#endif /* NUM_SERVER_THREADS */
|
|
#endif /* COAP_THREAD_SAFE */
|
|
|
|
#ifndef min
|
|
#define min(a,b) ((a) < (b) ? (a) : (b))
|
|
#endif
|
|
|
|
static coap_oscore_conf_t *oscore_conf;
|
|
static int doing_oscore = 0;
|
|
static int doing_tls_engine = 0;
|
|
static char *tls_engine_conf = NULL;
|
|
static int ec_jpake = 0;
|
|
|
|
/* set to 1 to request clean server shutdown */
|
|
static volatile int quit = 0;
|
|
|
|
/* set to 1 if persist information is to be kept on server shutdown */
|
|
static int keep_persist = 0;
|
|
|
|
/* changeable clock base (see handle_put_time()) */
|
|
static time_t clock_offset;
|
|
static time_t my_clock_base = 0;
|
|
|
|
coap_resource_t *time_resource = NULL;
|
|
|
|
static int resource_flags = COAP_RESOURCE_FLAGS_NOTIFY_CON;
|
|
static int track_observes = 0;
|
|
|
|
/*
|
|
* For PKI, if one or more of cert_file, key_file and ca_file is in PKCS11 URI
|
|
* format, then the remainder of cert_file, key_file and ca_file are treated
|
|
* as being in DER format to provide consistency across the underlying (D)TLS
|
|
* libraries.
|
|
*/
|
|
static char *cert_file = NULL; /* certificate and optional private key in PEM,
|
|
or PKCS11 URI*/
|
|
static char *key_file = NULL; /* private key in PEM, DER or PKCS11 URI */
|
|
static char *pkcs11_pin = NULL; /* PKCS11 pin to unlock access to token */
|
|
static char *ca_file = NULL; /* CA for cert_file - for cert checking in PEM,
|
|
DER or PKCS11 URI */
|
|
static char *root_ca_file = NULL; /* List of trusted Root CAs in PEM */
|
|
static int use_pem_buf = 0; /* Map these cert/key files into memory to test
|
|
PEM_BUF logic if set */
|
|
static int is_rpk_not_cert = 0; /* Cert is RPK if set */
|
|
/* Used to hold initial PEM_BUF setup */
|
|
static uint8_t *cert_mem_base = NULL; /* certificate and private key in PEM_BUF */
|
|
static uint8_t *key_mem_base = NULL; /* private key in PEM_BUF */
|
|
static uint8_t *ca_mem_base = NULL; /* CA for cert checking in PEM_BUF */
|
|
/* Used for verify_pki_sni_callback PEM_BUF temporary holding */
|
|
static uint8_t *cert_mem = NULL; /* certificate and private key in PEM_BUF */
|
|
static uint8_t *key_mem = NULL; /* private key in PEM_BUF */
|
|
static uint8_t *ca_mem = NULL; /* CA for cert checking in PEM_BUF */
|
|
static size_t cert_mem_len = 0;
|
|
static size_t key_mem_len = 0;
|
|
static size_t ca_mem_len = 0;
|
|
static int verify_peer_cert = 1; /* PKI granularity - by default set */
|
|
static int no_trust_store = 0; /* Trust store not to be installed. */
|
|
#define MAX_KEY 64 /* Maximum length of a pre-shared key in bytes. */
|
|
static uint8_t *key = NULL;
|
|
static ssize_t key_length = 0;
|
|
int key_defined = 0;
|
|
static const char *hint = "CoAP";
|
|
static int support_dynamic = 0;
|
|
static uint32_t block_mode = COAP_BLOCK_USE_LIBCOAP;
|
|
static int echo_back = 0;
|
|
static uint32_t csm_max_message_size = 0;
|
|
static size_t extended_token_size = COAP_TOKEN_DEFAULT_MAX;
|
|
static coap_proto_t use_unix_proto = COAP_PROTO_NONE;
|
|
static int enable_ws = 0;
|
|
static int ws_port = 80;
|
|
static int wss_port = 443;
|
|
static uint32_t reconnect_secs = 0;
|
|
|
|
static coap_dtls_pki_t *setup_pki(coap_context_t *ctx, coap_dtls_role_t role, char *sni);
|
|
|
|
typedef struct psk_sni_def_t {
|
|
char *sni_match;
|
|
coap_bin_const_t *new_key;
|
|
coap_bin_const_t *new_hint;
|
|
} psk_sni_def_t;
|
|
|
|
typedef struct valid_psk_snis_t {
|
|
size_t count;
|
|
psk_sni_def_t *psk_sni_list;
|
|
} valid_psk_snis_t;
|
|
|
|
static valid_psk_snis_t valid_psk_snis = {0, NULL};
|
|
|
|
typedef struct id_def_t {
|
|
char *hint_match;
|
|
coap_bin_const_t *identity_match;
|
|
coap_bin_const_t *new_key;
|
|
} id_def_t;
|
|
|
|
typedef struct valid_ids_t {
|
|
size_t count;
|
|
id_def_t *id_list;
|
|
} valid_ids_t;
|
|
|
|
static valid_ids_t valid_ids = {0, NULL};
|
|
typedef struct pki_sni_def_t {
|
|
char *sni_match;
|
|
char *new_cert;
|
|
char *new_ca;
|
|
} pki_sni_def_t;
|
|
|
|
typedef struct valid_pki_snis_t {
|
|
size_t count;
|
|
pki_sni_def_t *pki_sni_list;
|
|
} valid_pki_snis_t;
|
|
|
|
static valid_pki_snis_t valid_pki_snis = {0, NULL};
|
|
|
|
typedef struct transient_value_t {
|
|
coap_binary_t *value;
|
|
size_t ref_cnt;
|
|
} transient_value_t;
|
|
|
|
/* temporary storage for dynamic resource representations */
|
|
static transient_value_t *example_data_value = NULL;
|
|
static int example_data_media_type = COAP_MEDIATYPE_TEXT_PLAIN;
|
|
|
|
/* SIGINT handler: set quit to 1 for graceful termination */
|
|
static void
|
|
handle_sigint(int signum COAP_UNUSED) {
|
|
quit = 1;
|
|
coap_send_recv_terminate();
|
|
#if NUM_SERVER_THREADS
|
|
coap_io_process_terminate_loop();
|
|
#endif /* NUM_SERVER_THREADS */
|
|
}
|
|
|
|
#ifndef _WIN32
|
|
/*
|
|
* SIGUSR2 handler: set quit to 1 for graceful termination
|
|
* Disable sending out 4.04 for any active observations.
|
|
* Note: coap_*() functions should not be called at sig interrupt.
|
|
*/
|
|
static void
|
|
handle_sigusr2(int signum COAP_UNUSED) {
|
|
quit = 1;
|
|
keep_persist = 1;
|
|
#if NUM_SERVER_THREADS
|
|
coap_io_process_terminate_loop();
|
|
#endif /* NUM_SERVER_THREADS */
|
|
}
|
|
#endif /* ! _WIN32 */
|
|
|
|
/*
|
|
* This will return a correctly formed transient_value_t *, or NULL.
|
|
* If an error, the passed in coap_binary_t * will get deleted.
|
|
* Note: transient_value->value will never be returned as NULL.
|
|
*/
|
|
static transient_value_t *
|
|
alloc_resource_data(coap_binary_t *value) {
|
|
transient_value_t *transient_value;
|
|
if (!value)
|
|
return NULL;
|
|
transient_value = coap_malloc(sizeof(transient_value_t));
|
|
if (!transient_value) {
|
|
coap_delete_binary(value);
|
|
return NULL;
|
|
}
|
|
transient_value->ref_cnt = 1;
|
|
transient_value->value = value;
|
|
return transient_value;
|
|
}
|
|
|
|
/*
|
|
* Need to handle race conditions of data being updated (by PUT) and
|
|
* being read by a blocked response to GET.
|
|
*/
|
|
static void
|
|
release_resource_data(coap_session_t *session COAP_UNUSED,
|
|
void *app_ptr) {
|
|
transient_value_t *transient_value = (transient_value_t *)app_ptr;
|
|
|
|
if (!transient_value)
|
|
return;
|
|
|
|
if (--transient_value->ref_cnt > 0)
|
|
return;
|
|
coap_delete_binary(transient_value->value);
|
|
coap_free(transient_value);
|
|
}
|
|
|
|
/*
|
|
* Bump the reference count and return reference to data
|
|
*/
|
|
static coap_binary_t
|
|
reference_resource_data(transient_value_t *entry) {
|
|
coap_binary_t body;
|
|
if (entry) {
|
|
/* Bump reference so not removed elsewhere */
|
|
entry->ref_cnt++;
|
|
assert(entry->value);
|
|
body.length = entry->value->length;
|
|
body.s = entry->value->s;
|
|
} else {
|
|
body.length = 0;
|
|
body.s = NULL;
|
|
}
|
|
return body;
|
|
}
|
|
|
|
#define INDEX "This is a test server made with libcoap (see https://libcoap.net)\n" \
|
|
"Copyright (C) 2010--2025 Olaf Bergmann <bergmann@tzi.org> and others\n\n"
|
|
|
|
static void
|
|
hnd_get_index(coap_resource_t *resource,
|
|
coap_session_t *session,
|
|
const coap_pdu_t *request,
|
|
const coap_string_t *query COAP_UNUSED,
|
|
coap_pdu_t *response) {
|
|
|
|
coap_pdu_set_code(response, COAP_RESPONSE_CODE_CONTENT);
|
|
coap_add_data_large_response(resource, session, request, response,
|
|
query, COAP_MEDIATYPE_TEXT_PLAIN,
|
|
0x2ffff, 0, strlen(INDEX),
|
|
(const uint8_t *)INDEX, NULL, NULL);
|
|
}
|
|
|
|
static void
|
|
hnd_get_fetch_time(coap_resource_t *resource,
|
|
coap_session_t *session,
|
|
const coap_pdu_t *request,
|
|
const coap_string_t *query,
|
|
coap_pdu_t *response) {
|
|
unsigned char buf[40];
|
|
size_t len;
|
|
time_t now;
|
|
coap_tick_t t;
|
|
(void)request;
|
|
coap_pdu_code_t code = coap_pdu_get_code(request);
|
|
size_t size;
|
|
const uint8_t *data;
|
|
coap_str_const_t *ticks = coap_make_str_const("ticks");
|
|
|
|
if (my_clock_base) {
|
|
|
|
/* calculate current time */
|
|
coap_ticks(&t);
|
|
now = my_clock_base + (t / COAP_TICKS_PER_SECOND);
|
|
|
|
/* coap_get_data() sets size to 0 on error */
|
|
(void)coap_get_data(request, &size, &data);
|
|
|
|
if (code == COAP_REQUEST_CODE_GET && query != NULL &&
|
|
coap_string_equal(query, ticks)) {
|
|
/* parameter is in query, output ticks */
|
|
len = snprintf((char *)buf, sizeof(buf), "%" PRIi64, (int64_t)now);
|
|
} else if (code == COAP_REQUEST_CODE_FETCH && size == ticks->length &&
|
|
memcmp(data, ticks->s, ticks->length) == 0) {
|
|
/* parameter is in data, output ticks */
|
|
len = snprintf((char *)buf, sizeof(buf), "%" PRIi64, (int64_t)now);
|
|
} else { /* output human-readable time */
|
|
struct tm *tmp;
|
|
tmp = gmtime(&now);
|
|
if (!tmp) {
|
|
/* If 'now' is not valid */
|
|
coap_pdu_set_code(response, COAP_RESPONSE_CODE_NOT_FOUND);
|
|
return;
|
|
} else {
|
|
len = strftime((char *)buf, sizeof(buf), "%b %d %H:%M:%S", tmp);
|
|
}
|
|
}
|
|
coap_pdu_set_code(response, COAP_RESPONSE_CODE_CONTENT);
|
|
coap_add_data_large_response(resource, session, request, response,
|
|
query, COAP_MEDIATYPE_TEXT_PLAIN, 1, 0,
|
|
len,
|
|
buf, NULL, NULL);
|
|
} else {
|
|
/* if my_clock_base was deleted, we pretend to have no such resource */
|
|
coap_pdu_set_code(response, COAP_RESPONSE_CODE_NOT_FOUND);
|
|
}
|
|
}
|
|
|
|
static void
|
|
hnd_put_time(coap_resource_t *resource,
|
|
coap_session_t *session COAP_UNUSED,
|
|
const coap_pdu_t *request,
|
|
const coap_string_t *query COAP_UNUSED,
|
|
coap_pdu_t *response) {
|
|
coap_tick_t t;
|
|
size_t size;
|
|
const uint8_t *data;
|
|
|
|
/* FIXME: re-set my_clock_base to clock_offset if my_clock_base == 0
|
|
* and request is empty. When not empty, set to value in request payload
|
|
* (insist on query ?ticks). Return Created or Ok.
|
|
*/
|
|
|
|
/* if my_clock_base was deleted, we pretend to have no such resource */
|
|
coap_pdu_set_code(response, my_clock_base ? COAP_RESPONSE_CODE_CHANGED :
|
|
COAP_RESPONSE_CODE_CREATED);
|
|
|
|
coap_resource_notify_observers(resource, NULL);
|
|
|
|
/* coap_get_data() sets size to 0 on error */
|
|
(void)coap_get_data(request, &size, &data);
|
|
|
|
if (size == 0) { /* re-init */
|
|
my_clock_base = clock_offset;
|
|
} else {
|
|
my_clock_base = 0;
|
|
coap_ticks(&t);
|
|
while (size--)
|
|
my_clock_base = my_clock_base * 10 + *data++;
|
|
my_clock_base -= t / COAP_TICKS_PER_SECOND;
|
|
|
|
/* Sanity check input value */
|
|
if (!gmtime(&my_clock_base)) {
|
|
unsigned char buf[3];
|
|
coap_pdu_set_code(response, COAP_RESPONSE_CODE_BAD_REQUEST);
|
|
coap_add_option(response,
|
|
COAP_OPTION_CONTENT_FORMAT,
|
|
coap_encode_var_safe(buf, sizeof(buf),
|
|
COAP_MEDIATYPE_TEXT_PLAIN), buf);
|
|
coap_add_data(response, 22, (const uint8_t *)"Invalid set time value");
|
|
/* re-init as value is bad */
|
|
my_clock_base = clock_offset;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
hnd_delete_time(coap_resource_t *resource COAP_UNUSED,
|
|
coap_session_t *session COAP_UNUSED,
|
|
const coap_pdu_t *request COAP_UNUSED,
|
|
const coap_string_t *query COAP_UNUSED,
|
|
coap_pdu_t *response COAP_UNUSED) {
|
|
my_clock_base = 0; /* mark clock as "deleted" */
|
|
|
|
/* type = request->hdr->type == COAP_MESSAGE_CON */
|
|
/* ? COAP_MESSAGE_ACK : COAP_MESSAGE_NON; */
|
|
}
|
|
|
|
/*
|
|
* This logic is used to test out that the client correctly handles a
|
|
* "separate" response (empty ACK followed by data response at a later stage).
|
|
*/
|
|
static void
|
|
hnd_get_async(coap_resource_t *resource,
|
|
coap_session_t *session,
|
|
const coap_pdu_t *request,
|
|
const coap_string_t *query,
|
|
coap_pdu_t *response) {
|
|
unsigned long delay = 4; /* Less than COAP_DEFAULT_LEISURE */
|
|
size_t size;
|
|
coap_async_t *async;
|
|
coap_bin_const_t token = coap_pdu_get_token(request);
|
|
|
|
/*
|
|
* See if this is the initial, or delayed request
|
|
*/
|
|
|
|
async = coap_find_async(session, token);
|
|
if (!async) {
|
|
/* Set up an async request to trigger delay in the future */
|
|
if (query) {
|
|
/* Expect the query to just be the number of seconds to delay */
|
|
const uint8_t *p = query->s;
|
|
|
|
if (isdigit(*p)) {
|
|
delay = 0;
|
|
for (size = query->length; size; --size, ++p) {
|
|
if (!isdigit(*p))
|
|
break;
|
|
delay = delay * 10 + (*p - '0');
|
|
}
|
|
} else {
|
|
coap_log_debug("async: query is just a number of seconds to alter delay\n");
|
|
}
|
|
if (delay == 0) {
|
|
coap_log_info("async: delay of 0 not supported\n");
|
|
coap_pdu_set_code(response, COAP_RESPONSE_CODE_BAD_REQUEST);
|
|
return;
|
|
}
|
|
}
|
|
async = coap_register_async(session,
|
|
request,
|
|
COAP_TICKS_PER_SECOND * delay);
|
|
if (async == NULL) {
|
|
coap_pdu_set_code(response, COAP_RESPONSE_CODE_SERVICE_UNAVAILABLE);
|
|
return;
|
|
}
|
|
/*
|
|
* Not setting response code will cause empty ACK to be sent
|
|
* if Confirmable
|
|
*/
|
|
return;
|
|
}
|
|
/* no request (observe) or async set up, so this is the delayed request */
|
|
|
|
/* Send back the appropriate data */
|
|
coap_add_data_large_response(resource, session, request, response,
|
|
query, COAP_MEDIATYPE_TEXT_PLAIN, -1, 0, 4,
|
|
(const uint8_t *)"done", NULL, NULL);
|
|
coap_pdu_set_code(response, COAP_RESPONSE_CODE_CONTENT);
|
|
|
|
/* async is automatically removed by libcoap on return from this handler */
|
|
}
|
|
|
|
/*
|
|
* Large Data GET handler
|
|
*/
|
|
|
|
#ifndef INITIAL_EXAMPLE_SIZE
|
|
#define INITIAL_EXAMPLE_SIZE 1500
|
|
#endif
|
|
static void
|
|
hnd_get_example_data(coap_resource_t *resource,
|
|
coap_session_t *session,
|
|
const coap_pdu_t *request,
|
|
const coap_string_t *query,
|
|
coap_pdu_t *response) {
|
|
coap_binary_t body;
|
|
if (!example_data_value) {
|
|
/* Initialise for the first time */
|
|
int i;
|
|
coap_binary_t *value = coap_new_binary(INITIAL_EXAMPLE_SIZE);
|
|
if (value) {
|
|
for (i = 0; i < INITIAL_EXAMPLE_SIZE; i++) {
|
|
if ((i % 10) == 0) {
|
|
value->s[i] = 'a' + (i/10) % 26;
|
|
} else {
|
|
value->s[i] = '0' + i%10;
|
|
}
|
|
}
|
|
}
|
|
example_data_value = alloc_resource_data(value);
|
|
}
|
|
coap_pdu_set_code(response, COAP_RESPONSE_CODE_CONTENT);
|
|
body = reference_resource_data(example_data_value);
|
|
coap_add_data_large_response(resource, session, request, response,
|
|
query, example_data_media_type, -1, 0,
|
|
body.length,
|
|
body.s,
|
|
release_resource_data, example_data_value);
|
|
}
|
|
|
|
static void
|
|
cache_free_app_data(void *data) {
|
|
coap_binary_t *bdata = (coap_binary_t *)data;
|
|
coap_delete_binary(bdata);
|
|
}
|
|
|
|
/*
|
|
* Large Data PUT handler
|
|
*/
|
|
|
|
static void
|
|
hnd_put_example_data(coap_resource_t *resource,
|
|
coap_session_t *session,
|
|
const coap_pdu_t *request,
|
|
const coap_string_t *query COAP_UNUSED,
|
|
coap_pdu_t *response) {
|
|
size_t size;
|
|
const uint8_t *data;
|
|
coap_opt_iterator_t opt_iter;
|
|
coap_opt_t *option;
|
|
size_t offset;
|
|
size_t total;
|
|
coap_binary_t *data_so_far;
|
|
|
|
if (coap_get_data_large(request, &size, &data, &offset, &total) &&
|
|
size != total) {
|
|
coap_binary_t *old_data_in_cache;
|
|
/*
|
|
* A part of the data has been received (COAP_BLOCK_SINGLE_BODY not set).
|
|
* However, total unfortunately is only an indication, so it is not safe to
|
|
* allocate a block based on total. As per
|
|
* https://rfc-editor.org/rfc/rfc7959#section-4
|
|
* o In a request carrying a Block1 Option, to indicate the current
|
|
* estimate the client has of the total size of the resource
|
|
* representation, measured in bytes ("size indication").
|
|
*
|
|
* coap_cache_ignore_options() must have previously been called with at
|
|
* least COAP_OPTION_BLOCK1 set as the option value will change per block.
|
|
*/
|
|
coap_cache_entry_t *cache_entry = coap_cache_get_by_pdu(session,
|
|
request,
|
|
COAP_CACHE_IS_SESSION_BASED);
|
|
|
|
if (offset == 0) {
|
|
if (!cache_entry) {
|
|
/*
|
|
* Set idle_timeout parameter to COAP_MAX_TRANSMIT_WAIT if you want
|
|
* early removal on transmission failure. 0 means only delete when
|
|
* the session is deleted as session_based is set here.
|
|
*/
|
|
cache_entry = coap_new_cache_entry(session, request,
|
|
COAP_CACHE_NOT_RECORD_PDU,
|
|
COAP_CACHE_IS_SESSION_BASED, 0);
|
|
} else {
|
|
old_data_in_cache = coap_cache_set_app_data2(cache_entry, NULL, NULL);
|
|
coap_delete_binary(old_data_in_cache);
|
|
}
|
|
}
|
|
if (!cache_entry) {
|
|
if (offset == 0) {
|
|
coap_log_warn("Unable to create a new cache entry\n");
|
|
} else {
|
|
coap_log_warn("No cache entry available for the non-first BLOCK\n");
|
|
}
|
|
coap_pdu_set_code(response, COAP_RESPONSE_CODE_INTERNAL_ERROR);
|
|
return;
|
|
}
|
|
|
|
if (size) {
|
|
/* Add in the new data to cache entry */
|
|
data_so_far = coap_cache_get_app_data(cache_entry);
|
|
data_so_far = coap_block_build_body(data_so_far, size, data,
|
|
offset, total);
|
|
/* Yes, data_so_far can be NULL if error */
|
|
coap_cache_set_app_data2(cache_entry, data_so_far, cache_free_app_data);
|
|
}
|
|
if (offset + size == total) {
|
|
/* All the data is now in */
|
|
data_so_far = coap_cache_set_app_data2(cache_entry, NULL, NULL);
|
|
} else {
|
|
/* Give us the next block response */
|
|
coap_pdu_set_code(response, COAP_RESPONSE_CODE_CONTINUE);
|
|
return;
|
|
}
|
|
} else {
|
|
/* single body of data received */
|
|
data_so_far = coap_new_binary(size);
|
|
if (data_so_far) {
|
|
memcpy(data_so_far->s, data, size);
|
|
}
|
|
}
|
|
|
|
if (example_data_value) {
|
|
/* pre-existed response */
|
|
coap_pdu_set_code(response, COAP_RESPONSE_CODE_CHANGED);
|
|
/* Need to de-reference as value may be in use elsewhere */
|
|
release_resource_data(session, example_data_value);
|
|
} else
|
|
/* just generated response */
|
|
coap_pdu_set_code(response, COAP_RESPONSE_CODE_CREATED);
|
|
|
|
example_data_value = alloc_resource_data(data_so_far);
|
|
if (!example_data_value) {
|
|
coap_pdu_set_code(response, COAP_RESPONSE_CODE_INTERNAL_ERROR);
|
|
return;
|
|
}
|
|
if ((option = coap_check_option(request, COAP_OPTION_CONTENT_FORMAT,
|
|
&opt_iter)) != NULL) {
|
|
example_data_media_type =
|
|
coap_decode_var_bytes(coap_opt_value(option),
|
|
coap_opt_length(option));
|
|
} else {
|
|
example_data_media_type = COAP_MEDIATYPE_TEXT_PLAIN;
|
|
}
|
|
|
|
coap_resource_notify_observers(resource, NULL);
|
|
if (echo_back) {
|
|
coap_binary_t body;
|
|
|
|
body = reference_resource_data(example_data_value);
|
|
coap_add_data_large_response(resource, session, request, response,
|
|
query, example_data_media_type, -1, 0,
|
|
body.length,
|
|
body.s,
|
|
release_resource_data, example_data_value);
|
|
}
|
|
}
|
|
|
|
#if COAP_PROXY_SUPPORT
|
|
|
|
#define MAX_USER 128 /* Maximum length of a user name (i.e., PSK
|
|
* identity) in bytes. */
|
|
static unsigned char *user = NULL;
|
|
static ssize_t user_length = -1;
|
|
|
|
static size_t proxy_host_name_count = 0;
|
|
static const char **proxy_host_name_list = NULL;
|
|
static coap_proxy_server_list_t forward_proxy = { NULL, 0, 0, COAP_PROXY_FORWARD_STATIC, 0, 300};
|
|
static coap_proxy_server_list_t reverse_proxy = { NULL, 0, 0, COAP_PROXY_REVERSE_STRIP, 0, 10};
|
|
|
|
static coap_dtls_cpsk_t *
|
|
setup_cpsk(char *client_sni) {
|
|
static coap_dtls_cpsk_t dtls_cpsk;
|
|
|
|
memset(&dtls_cpsk, 0, sizeof(dtls_cpsk));
|
|
dtls_cpsk.version = COAP_DTLS_CPSK_SETUP_VERSION;
|
|
dtls_cpsk.client_sni = client_sni;
|
|
dtls_cpsk.psk_info.identity.s = user;
|
|
dtls_cpsk.psk_info.identity.length = user_length;
|
|
dtls_cpsk.psk_info.key.s = key;
|
|
dtls_cpsk.psk_info.key.length = key_length;
|
|
return &dtls_cpsk;
|
|
}
|
|
|
|
static void
|
|
hnd_forward_proxy_uri(coap_resource_t *resource,
|
|
coap_session_t *req_session,
|
|
const coap_pdu_t *request,
|
|
const coap_string_t *query COAP_UNUSED,
|
|
coap_pdu_t *response) {
|
|
|
|
if (!coap_proxy_forward_request(req_session, request, response, resource,
|
|
NULL, &forward_proxy)) {
|
|
coap_log_debug("hnd_forward_proxy_uri: Failed to forward PDU\n");
|
|
/* Non ACK response code set on error detection */
|
|
}
|
|
|
|
/* Leave response code as is */
|
|
}
|
|
|
|
static void
|
|
hnd_reverse_proxy_uri(coap_resource_t *resource,
|
|
coap_session_t *rsp_session,
|
|
const coap_pdu_t *request,
|
|
const coap_string_t *query COAP_UNUSED,
|
|
coap_pdu_t *response) {
|
|
|
|
if (!coap_proxy_forward_request(rsp_session, request, response, resource,
|
|
NULL, &reverse_proxy)) {
|
|
coap_log_debug("hnd_reverse_proxy_uri: Failed to forward PDU\n");
|
|
/* Non ACK response code set on error detection */
|
|
}
|
|
|
|
/* Leave response code as is */
|
|
}
|
|
|
|
#endif /* COAP_PROXY_SUPPORT */
|
|
|
|
typedef struct dynamic_resource_t {
|
|
coap_string_t *uri_path;
|
|
transient_value_t *value;
|
|
coap_resource_t *resource;
|
|
int created;
|
|
uint16_t media_type;
|
|
} dynamic_resource_t;
|
|
|
|
static int dynamic_count = 0;
|
|
static dynamic_resource_t *dynamic_entry = NULL;
|
|
|
|
/*
|
|
* Regular DELETE handler - used by resources created by the
|
|
* Unknown Resource PUT handler
|
|
*/
|
|
|
|
static void
|
|
hnd_delete(coap_resource_t *resource,
|
|
coap_session_t *session COAP_UNUSED,
|
|
const coap_pdu_t *request,
|
|
const coap_string_t *query COAP_UNUSED,
|
|
coap_pdu_t *response) {
|
|
int i;
|
|
coap_string_t *uri_path;
|
|
|
|
/* get the uri_path */
|
|
uri_path = coap_get_uri_path(request);
|
|
if (!uri_path) {
|
|
coap_pdu_set_code(response, COAP_RESPONSE_CODE_NOT_FOUND);
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < dynamic_count; i++) {
|
|
if (coap_string_equal(uri_path, dynamic_entry[i].uri_path)) {
|
|
/* Dynamic entry no longer required - delete it */
|
|
release_resource_data(session, dynamic_entry[i].value);
|
|
coap_delete_string(dynamic_entry[i].uri_path);
|
|
if (dynamic_count-i > 1) {
|
|
memmove(&dynamic_entry[i],
|
|
&dynamic_entry[i+1],
|
|
(dynamic_count-i-1) * sizeof(dynamic_entry[0]));
|
|
}
|
|
dynamic_count--;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Dynamic resource no longer required - delete it */
|
|
coap_delete_resource(NULL, resource);
|
|
coap_delete_string(uri_path);
|
|
coap_pdu_set_code(response, COAP_RESPONSE_CODE_DELETED);
|
|
}
|
|
|
|
/*
|
|
* Regular GET handler - used by resources created by the
|
|
* Unknown Resource PUT handler
|
|
*/
|
|
|
|
static void
|
|
hnd_get(coap_resource_t *resource,
|
|
coap_session_t *session,
|
|
const coap_pdu_t *request,
|
|
const coap_string_t *query,
|
|
coap_pdu_t *response) {
|
|
coap_str_const_t *uri_path;
|
|
int i;
|
|
dynamic_resource_t *resource_entry = NULL;
|
|
coap_binary_t body;
|
|
/*
|
|
* request will be NULL if an Observe triggered request, so the uri_path,
|
|
* if needed, must be abstracted from the resource.
|
|
* The uri_path string is a const pointer
|
|
*/
|
|
|
|
uri_path = coap_resource_get_uri_path(resource);
|
|
if (!uri_path) {
|
|
coap_pdu_set_code(response, COAP_RESPONSE_CODE_NOT_FOUND);
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < dynamic_count; i++) {
|
|
if (coap_string_equal(uri_path, dynamic_entry[i].uri_path)) {
|
|
break;
|
|
}
|
|
}
|
|
if (i == dynamic_count) {
|
|
coap_pdu_set_code(response, COAP_RESPONSE_CODE_NOT_FOUND);
|
|
return;
|
|
}
|
|
|
|
resource_entry = &dynamic_entry[i];
|
|
|
|
coap_pdu_set_code(response, COAP_RESPONSE_CODE_CONTENT);
|
|
body = reference_resource_data(resource_entry->value);
|
|
coap_add_data_large_response(resource, session, request, response,
|
|
query, resource_entry->media_type, -1, 0,
|
|
body.length,
|
|
body.s,
|
|
release_resource_data, resource_entry->value);
|
|
}
|
|
|
|
/*
|
|
* Regular PUT or POST handler - used by resources created by the
|
|
* Unknown Resource PUT/POST handler
|
|
*/
|
|
|
|
static void
|
|
hnd_put_post(coap_resource_t *resource,
|
|
coap_session_t *session,
|
|
const coap_pdu_t *request,
|
|
const coap_string_t *query COAP_UNUSED,
|
|
coap_pdu_t *response) {
|
|
coap_string_t *uri_path;
|
|
int i;
|
|
size_t size;
|
|
const uint8_t *data;
|
|
size_t offset;
|
|
size_t total;
|
|
dynamic_resource_t *resource_entry = NULL;
|
|
unsigned char buf[6]; /* space to hold encoded/decoded uints */
|
|
coap_opt_iterator_t opt_iter;
|
|
coap_opt_t *option;
|
|
coap_binary_t *data_so_far;
|
|
transient_value_t *transient_value;
|
|
|
|
/* get the uri_path */
|
|
uri_path = coap_get_uri_path(request);
|
|
if (!uri_path) {
|
|
coap_pdu_set_code(response, COAP_RESPONSE_CODE_NOT_FOUND);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Locate the correct dynamic block for this request
|
|
*/
|
|
for (i = 0; i < dynamic_count; i++) {
|
|
if (coap_string_equal(uri_path, dynamic_entry[i].uri_path)) {
|
|
break;
|
|
}
|
|
}
|
|
if (i == dynamic_count) {
|
|
if (dynamic_count >= support_dynamic) {
|
|
/* Should have been caught hnd_put_post_unknown() */
|
|
coap_pdu_set_code(response, COAP_RESPONSE_CODE_NOT_ACCEPTABLE);
|
|
coap_delete_string(uri_path);
|
|
return;
|
|
}
|
|
dynamic_count++;
|
|
dynamic_entry = realloc(dynamic_entry,
|
|
dynamic_count * sizeof(dynamic_entry[0]));
|
|
if (dynamic_entry) {
|
|
dynamic_entry[i].uri_path = uri_path;
|
|
dynamic_entry[i].value = NULL;
|
|
dynamic_entry[i].resource = resource;
|
|
dynamic_entry[i].created = 1;
|
|
if ((option = coap_check_option(request, COAP_OPTION_CONTENT_FORMAT,
|
|
&opt_iter)) != NULL) {
|
|
dynamic_entry[i].media_type =
|
|
coap_decode_var_bytes(coap_opt_value(option),
|
|
coap_opt_length(option));
|
|
} else {
|
|
dynamic_entry[i].media_type = COAP_MEDIATYPE_TEXT_PLAIN;
|
|
}
|
|
/* Store media type of new resource in ct. We can use buf here
|
|
* as coap_add_attr() will copy the passed string. */
|
|
memset(buf, 0, sizeof(buf));
|
|
snprintf((char *)buf, sizeof(buf), "%d", dynamic_entry[i].media_type);
|
|
/* ensure that buf is always zero-terminated */
|
|
assert(buf[sizeof(buf) - 1] == '\0');
|
|
buf[sizeof(buf) - 1] = '\0';
|
|
coap_add_attr(resource,
|
|
coap_make_str_const("ct"),
|
|
coap_make_str_const((char *)buf),
|
|
0);
|
|
} else {
|
|
dynamic_count--;
|
|
coap_pdu_set_code(response, COAP_RESPONSE_CODE_INTERNAL_ERROR);
|
|
coap_delete_string(uri_path);
|
|
return;
|
|
}
|
|
} else {
|
|
/* Need to do this as coap_get_uri_path() created it */
|
|
coap_delete_string(uri_path);
|
|
}
|
|
|
|
resource_entry = &dynamic_entry[i];
|
|
|
|
if (coap_get_data_large(request, &size, &data, &offset, &total) &&
|
|
size != total) {
|
|
coap_binary_t *old_data_in_cache;
|
|
/*
|
|
* A part of the data has been received (COAP_BLOCK_SINGLE_BODY not set).
|
|
* However, total unfortunately is only an indication, so it is not safe to
|
|
* allocate a block based on total. As per
|
|
* https://rfc-editor.org/rfc/rfc7959#section-4
|
|
* o In a request carrying a Block1 Option, to indicate the current
|
|
* estimate the client has of the total size of the resource
|
|
* representation, measured in bytes ("size indication").
|
|
*
|
|
* coap_cache_ignore_options() must have previously been called with at
|
|
* least COAP_OPTION_BLOCK1 set as the option value will change per block.
|
|
*/
|
|
coap_cache_entry_t *cache_entry = coap_cache_get_by_pdu(session,
|
|
request,
|
|
COAP_CACHE_IS_SESSION_BASED);
|
|
|
|
if (offset == 0) {
|
|
if (!cache_entry) {
|
|
cache_entry = coap_new_cache_entry(session, request,
|
|
COAP_CACHE_NOT_RECORD_PDU,
|
|
COAP_CACHE_IS_SESSION_BASED, 0);
|
|
} else {
|
|
old_data_in_cache = coap_cache_set_app_data2(cache_entry, NULL, NULL);
|
|
coap_delete_binary(old_data_in_cache);
|
|
}
|
|
}
|
|
if (!cache_entry) {
|
|
if (offset == 0) {
|
|
coap_log_warn("Unable to create a new cache entry\n");
|
|
} else {
|
|
coap_log_warn("No cache entry available for the non-first BLOCK\n");
|
|
}
|
|
coap_pdu_set_code(response, COAP_RESPONSE_CODE_INTERNAL_ERROR);
|
|
return;
|
|
}
|
|
|
|
if (size) {
|
|
/* Add in the new data to cache entry */
|
|
data_so_far = coap_cache_get_app_data(cache_entry);
|
|
if (!data_so_far) {
|
|
data_so_far = coap_new_binary(size);
|
|
if (data_so_far)
|
|
memcpy(data_so_far->s, data, size);
|
|
} else {
|
|
/* Add in new block to end of current data */
|
|
coap_binary_t *new = coap_resize_binary(data_so_far, offset + size);
|
|
|
|
if (new) {
|
|
data_so_far = new;
|
|
memcpy(&data_so_far->s[offset], data, size);
|
|
} else {
|
|
/* Insufficient space to extend data_so_far */
|
|
coap_delete_binary(data_so_far);
|
|
data_so_far = NULL;
|
|
}
|
|
}
|
|
/* Yes, data_so_far can be NULL */
|
|
coap_cache_set_app_data2(cache_entry, data_so_far, cache_free_app_data);
|
|
}
|
|
if (offset + size == total) {
|
|
/* All the data is now in */
|
|
data_so_far = coap_cache_set_app_data2(cache_entry, NULL, NULL);
|
|
} else {
|
|
coap_pdu_set_code(response, COAP_RESPONSE_CODE_CONTINUE);
|
|
return;
|
|
}
|
|
} else {
|
|
/* single body of data received */
|
|
data_so_far = coap_new_binary(size);
|
|
if (data_so_far && size) {
|
|
memcpy(data_so_far->s, data, size);
|
|
}
|
|
}
|
|
/* Need to de-reference as value may be in use elsewhere */
|
|
release_resource_data(session, resource_entry->value);
|
|
resource_entry->value = NULL;
|
|
transient_value = alloc_resource_data(data_so_far);
|
|
if (!transient_value) {
|
|
coap_pdu_set_code(response, COAP_RESPONSE_CODE_INTERNAL_ERROR);
|
|
return;
|
|
}
|
|
resource_entry->value = transient_value;
|
|
|
|
if (resource_entry->created) {
|
|
coap_pdu_code_t code = coap_pdu_get_code(request);
|
|
|
|
resource_entry->created = 0;
|
|
coap_pdu_set_code(response, COAP_RESPONSE_CODE_CREATED);
|
|
if (code == COAP_REQUEST_CODE_POST) {
|
|
/* Add in Location-Path / Location-Query Options */
|
|
coap_option_iterator_init(request, &opt_iter, COAP_OPT_ALL);
|
|
while ((option = coap_option_next(&opt_iter))) {
|
|
switch (opt_iter.number) {
|
|
case COAP_OPTION_URI_PATH:
|
|
if (!coap_add_option(response, COAP_OPTION_LOCATION_PATH,
|
|
coap_opt_length(option),
|
|
coap_opt_value(option)))
|
|
goto fail;
|
|
break;
|
|
case COAP_OPTION_URI_QUERY:
|
|
if (!coap_add_option(response, COAP_OPTION_LOCATION_QUERY,
|
|
coap_opt_length(option),
|
|
coap_opt_value(option)))
|
|
goto fail;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
coap_pdu_set_code(response, COAP_RESPONSE_CODE_CHANGED);
|
|
coap_resource_notify_observers(resource_entry->resource, NULL);
|
|
}
|
|
|
|
if (echo_back) {
|
|
coap_binary_t body;
|
|
|
|
body = reference_resource_data(resource_entry->value);
|
|
coap_add_data_large_response(resource, session, request, response,
|
|
query, resource_entry->media_type, -1, 0,
|
|
body.length,
|
|
body.s,
|
|
release_resource_data, resource_entry->value);
|
|
}
|
|
return;
|
|
|
|
fail:
|
|
coap_pdu_set_code(response, COAP_RESPONSE_CODE_INTERNAL_ERROR);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Unknown Resource PUT handler
|
|
*/
|
|
|
|
static void
|
|
hnd_put_post_unknown(coap_resource_t *resource COAP_UNUSED,
|
|
coap_session_t *session,
|
|
const coap_pdu_t *request,
|
|
const coap_string_t *query,
|
|
coap_pdu_t *response) {
|
|
coap_resource_t *r;
|
|
coap_string_t *uri_path;
|
|
|
|
/* check if creating a new resource is allowed */
|
|
if (dynamic_count >= support_dynamic) {
|
|
coap_pdu_set_code(response, COAP_RESPONSE_CODE_NOT_ACCEPTABLE);
|
|
return;
|
|
}
|
|
|
|
/* get the uri_path - will get used by coap_resource_init() */
|
|
uri_path = coap_get_uri_path(request);
|
|
if (!uri_path) {
|
|
coap_pdu_set_code(response, COAP_RESPONSE_CODE_NOT_FOUND);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Create a resource to handle the new URI
|
|
* uri_path will get deleted when the resource is removed
|
|
*/
|
|
r = coap_resource_init((coap_str_const_t *)uri_path,
|
|
COAP_RESOURCE_FLAGS_RELEASE_URI | resource_flags);
|
|
coap_add_attr(r, coap_make_str_const("title"), coap_make_str_const("\"Dynamic\""), 0);
|
|
coap_register_request_handler(r, COAP_REQUEST_PUT, hnd_put_post);
|
|
coap_register_request_handler(r, COAP_REQUEST_POST, hnd_put_post);
|
|
coap_register_request_handler(r, COAP_REQUEST_DELETE, hnd_delete);
|
|
coap_register_request_handler(r, COAP_REQUEST_FETCH, hnd_get);
|
|
/* We possibly want to Observe the GETs */
|
|
coap_resource_set_get_observable(r, 1);
|
|
coap_register_request_handler(r, COAP_REQUEST_GET, hnd_get);
|
|
coap_add_resource(coap_session_get_context(session), r);
|
|
|
|
/* Do the PUT/POST for this first call */
|
|
hnd_put_post(r, session, request, query, response);
|
|
}
|
|
|
|
#if COAP_PROXY_SUPPORT
|
|
static int
|
|
proxy_event_handler(coap_session_t *session COAP_UNUSED,
|
|
coap_event_t event) {
|
|
|
|
switch (event) {
|
|
case COAP_EVENT_DTLS_CLOSED:
|
|
case COAP_EVENT_TCP_CLOSED:
|
|
case COAP_EVENT_SESSION_CLOSED:
|
|
case COAP_EVENT_OSCORE_DECRYPTION_FAILURE:
|
|
case COAP_EVENT_OSCORE_NOT_ENABLED:
|
|
case COAP_EVENT_OSCORE_NO_PROTECTED_PAYLOAD:
|
|
case COAP_EVENT_OSCORE_NO_SECURITY:
|
|
case COAP_EVENT_OSCORE_INTERNAL_ERROR:
|
|
case COAP_EVENT_OSCORE_DECODE_ERROR:
|
|
case COAP_EVENT_WS_PACKET_SIZE:
|
|
case COAP_EVENT_WS_CLOSED:
|
|
case COAP_EVENT_DTLS_CONNECTED:
|
|
case COAP_EVENT_DTLS_RENEGOTIATE:
|
|
case COAP_EVENT_DTLS_ERROR:
|
|
case COAP_EVENT_TCP_CONNECTED:
|
|
case COAP_EVENT_TCP_FAILED:
|
|
case COAP_EVENT_SESSION_CONNECTED:
|
|
case COAP_EVENT_SESSION_FAILED:
|
|
case COAP_EVENT_PARTIAL_BLOCK:
|
|
case COAP_EVENT_XMIT_BLOCK_FAIL:
|
|
case COAP_EVENT_SERVER_SESSION_NEW:
|
|
case COAP_EVENT_SERVER_SESSION_DEL:
|
|
case COAP_EVENT_BAD_PACKET:
|
|
case COAP_EVENT_MSG_RETRANSMITTED:
|
|
case COAP_EVENT_WS_CONNECTED:
|
|
case COAP_EVENT_KEEPALIVE_FAILURE:
|
|
default:
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static coap_pdu_t *
|
|
proxy_response_handler(coap_session_t *rsp_session COAP_UNUSED,
|
|
const coap_pdu_t *sent COAP_UNUSED,
|
|
coap_pdu_t *received,
|
|
coap_cache_key_t *cache_key COAP_UNUSED) {
|
|
return received;
|
|
}
|
|
|
|
static void
|
|
proxy_nack_handler(coap_session_t *session COAP_UNUSED,
|
|
const coap_pdu_t *sent COAP_UNUSED,
|
|
const coap_nack_reason_t reason,
|
|
const coap_mid_t mid COAP_UNUSED) {
|
|
|
|
switch (reason) {
|
|
case COAP_NACK_TOO_MANY_RETRIES:
|
|
case COAP_NACK_NOT_DELIVERABLE:
|
|
case COAP_NACK_RST:
|
|
case COAP_NACK_TLS_FAILED:
|
|
case COAP_NACK_WS_FAILED:
|
|
case COAP_NACK_TLS_LAYER_FAILED:
|
|
case COAP_NACK_WS_LAYER_FAILED:
|
|
case COAP_NACK_ICMP_ISSUE:
|
|
case COAP_NACK_BAD_RESPONSE:
|
|
default:
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
#endif /* COAP_PROXY_SUPPORT */
|
|
|
|
static void
|
|
init_resources(coap_context_t *ctx) {
|
|
coap_resource_t *r;
|
|
|
|
#if COAP_PROXY_SUPPORT
|
|
if (reverse_proxy.entry_count) {
|
|
/* Create a reverse proxy resource to handle PUTs */
|
|
r = coap_resource_reverse_proxy_init(hnd_reverse_proxy_uri, 0);
|
|
coap_add_resource(ctx, r);
|
|
coap_register_event_handler(ctx, proxy_event_handler);
|
|
coap_register_proxy_response_handler(ctx, proxy_response_handler);
|
|
coap_register_nack_handler(ctx, proxy_nack_handler);
|
|
} else {
|
|
#endif /* COAP_PROXY_SUPPORT */
|
|
r = coap_resource_init(NULL, COAP_RESOURCE_FLAGS_HAS_MCAST_SUPPORT);
|
|
coap_register_request_handler(r, COAP_REQUEST_GET, hnd_get_index);
|
|
|
|
coap_add_attr(r, coap_make_str_const("ct"), coap_make_str_const("0"), 0);
|
|
coap_add_attr(r, coap_make_str_const("title"), coap_make_str_const("\"General Info\""), 0);
|
|
coap_add_resource(ctx, r);
|
|
|
|
/* store clock base to use in /time */
|
|
my_clock_base = clock_offset;
|
|
|
|
r = coap_resource_init(coap_make_str_const("time"), resource_flags);
|
|
coap_register_request_handler(r, COAP_REQUEST_GET, hnd_get_fetch_time);
|
|
coap_register_request_handler(r, COAP_REQUEST_FETCH, hnd_get_fetch_time);
|
|
coap_register_request_handler(r, COAP_REQUEST_PUT, hnd_put_time);
|
|
coap_register_request_handler(r, COAP_REQUEST_DELETE, hnd_delete_time);
|
|
coap_resource_set_get_observable(r, 1);
|
|
|
|
coap_add_attr(r, coap_make_str_const("ct"), coap_make_str_const("0"), 0);
|
|
coap_add_attr(r, coap_make_str_const("title"), coap_make_str_const("\"Internal Clock\""), 0);
|
|
coap_add_attr(r, coap_make_str_const("rt"), coap_make_str_const("\"ticks\""), 0);
|
|
coap_add_attr(r, coap_make_str_const("if"), coap_make_str_const("\"clock\""), 0);
|
|
|
|
coap_add_resource(ctx, r);
|
|
time_resource = r;
|
|
|
|
if (support_dynamic > 0) {
|
|
/* Create a resource to handle PUTs to unknown URIs */
|
|
r = coap_resource_unknown_init2(hnd_put_post_unknown, 0);
|
|
/* Add in handling POST as well */
|
|
coap_register_handler(r, COAP_REQUEST_POST, hnd_put_post_unknown);
|
|
coap_add_resource(ctx, r);
|
|
}
|
|
|
|
if (coap_async_is_supported()) {
|
|
r = coap_resource_init(coap_make_str_const("async"),
|
|
resource_flags |
|
|
COAP_RESOURCE_FLAGS_HAS_MCAST_SUPPORT |
|
|
COAP_RESOURCE_FLAGS_LIB_DIS_MCAST_DELAYS);
|
|
coap_register_request_handler(r, COAP_REQUEST_GET, hnd_get_async);
|
|
|
|
coap_add_attr(r, coap_make_str_const("ct"), coap_make_str_const("0"), 0);
|
|
coap_add_resource(ctx, r);
|
|
}
|
|
|
|
r = coap_resource_init(coap_make_str_const("example_data"), resource_flags);
|
|
coap_register_request_handler(r, COAP_REQUEST_GET, hnd_get_example_data);
|
|
coap_register_request_handler(r, COAP_REQUEST_PUT, hnd_put_example_data);
|
|
coap_register_request_handler(r, COAP_REQUEST_FETCH, hnd_get_example_data);
|
|
coap_resource_set_get_observable(r, 1);
|
|
|
|
coap_add_attr(r, coap_make_str_const("ct"), coap_make_str_const("0"), 0);
|
|
coap_add_attr(r, coap_make_str_const("title"), coap_make_str_const("\"Example Data\""), 0);
|
|
coap_add_resource(ctx, r);
|
|
|
|
#if COAP_PROXY_SUPPORT
|
|
}
|
|
if (proxy_host_name_count) {
|
|
r = coap_resource_proxy_uri_init2(hnd_forward_proxy_uri, proxy_host_name_count,
|
|
proxy_host_name_list, 0);
|
|
coap_add_resource(ctx, r);
|
|
coap_register_event_handler(ctx, proxy_event_handler);
|
|
coap_register_proxy_response_handler(ctx, proxy_response_handler);
|
|
coap_register_nack_handler(ctx, proxy_nack_handler);
|
|
}
|
|
#endif /* COAP_PROXY_SUPPORT */
|
|
}
|
|
|
|
static int
|
|
verify_cn_callback(const char *cn,
|
|
const uint8_t *asn1_public_cert COAP_UNUSED,
|
|
size_t asn1_length COAP_UNUSED,
|
|
coap_session_t *session COAP_UNUSED,
|
|
unsigned depth,
|
|
int validated COAP_UNUSED,
|
|
void *arg) {
|
|
union {
|
|
coap_dtls_role_t r;
|
|
void *v;
|
|
} role = { .v = arg };
|
|
|
|
coap_log_info("CN '%s' presented by %s (%s)\n",
|
|
cn, role.r == COAP_DTLS_ROLE_SERVER ? "client" : "server",
|
|
depth ? "CA" : "Certificate");
|
|
return 1;
|
|
}
|
|
|
|
static uint8_t *
|
|
read_file_mem(const char *file, size_t *length) {
|
|
FILE *f;
|
|
uint8_t *buf;
|
|
struct stat statbuf;
|
|
|
|
*length = 0;
|
|
if (!file || !(f = fopen(file, "r")))
|
|
return NULL;
|
|
|
|
if (fstat(fileno(f), &statbuf) == -1) {
|
|
fclose(f);
|
|
return NULL;
|
|
}
|
|
|
|
buf = coap_malloc(statbuf.st_size+1);
|
|
if (!buf) {
|
|
fclose(f);
|
|
return NULL;
|
|
}
|
|
|
|
if (fread(buf, 1, statbuf.st_size, f) != (size_t)statbuf.st_size) {
|
|
fclose(f);
|
|
coap_free(buf);
|
|
return NULL;
|
|
}
|
|
buf[statbuf.st_size] = '\000';
|
|
*length = (size_t)(statbuf.st_size + 1);
|
|
fclose(f);
|
|
return buf;
|
|
}
|
|
|
|
static void
|
|
update_pki_key(coap_dtls_key_t *dtls_key, const char *key_name,
|
|
const char *cert_name, const char *ca_name) {
|
|
memset(dtls_key, 0, sizeof(*dtls_key));
|
|
if (doing_tls_engine) {
|
|
dtls_key->key_type = COAP_PKI_KEY_DEFINE;
|
|
dtls_key->key.define.public_cert.s_byte = cert_file;
|
|
dtls_key->key.define.private_key.s_byte = key_file ? key_file : cert_file;
|
|
dtls_key->key.define.ca.s_byte = ca_file;
|
|
dtls_key->key.define.public_cert_def = COAP_PKI_KEY_DEF_ENGINE;
|
|
dtls_key->key.define.private_key_def = COAP_PKI_KEY_DEF_ENGINE;
|
|
dtls_key->key.define.ca_def = COAP_PKI_KEY_DEF_ENGINE;
|
|
dtls_key->key.define.user_pin = pkcs11_pin;
|
|
} else if ((key_name && strncasecmp(key_name, "pkcs11:", 7) == 0) ||
|
|
(cert_name && strncasecmp(cert_name, "pkcs11:", 7) == 0) ||
|
|
(ca_name && strncasecmp(ca_name, "pkcs11:", 7) == 0)) {
|
|
dtls_key->key_type = COAP_PKI_KEY_PKCS11;
|
|
dtls_key->key.pkcs11.public_cert = cert_name;
|
|
dtls_key->key.pkcs11.private_key = key_name ? key_name : cert_name;
|
|
dtls_key->key.pkcs11.ca = ca_name;
|
|
dtls_key->key.pkcs11.user_pin = pkcs11_pin;
|
|
} else if (!use_pem_buf && !is_rpk_not_cert) {
|
|
dtls_key->key_type = COAP_PKI_KEY_PEM;
|
|
dtls_key->key.pem.public_cert = cert_name;
|
|
dtls_key->key.pem.private_key = key_name ? key_name : cert_name;
|
|
dtls_key->key.pem.ca_file = ca_name;
|
|
} else {
|
|
/* Map file into memory */
|
|
coap_free(ca_mem);
|
|
coap_free(cert_mem);
|
|
coap_free(key_mem);
|
|
ca_mem = read_file_mem(ca_name, &ca_mem_len);
|
|
cert_mem = read_file_mem(cert_name, &cert_mem_len);
|
|
key_mem = read_file_mem(key_name, &key_mem_len);
|
|
|
|
dtls_key->key_type = COAP_PKI_KEY_PEM_BUF;
|
|
dtls_key->key.pem_buf.ca_cert = ca_mem;
|
|
dtls_key->key.pem_buf.public_cert = cert_mem;
|
|
dtls_key->key.pem_buf.private_key = key_mem ? key_mem : cert_mem;
|
|
dtls_key->key.pem_buf.ca_cert_len = ca_mem_len;
|
|
dtls_key->key.pem_buf.public_cert_len = cert_mem_len;
|
|
dtls_key->key.pem_buf.private_key_len = key_mem ?
|
|
key_mem_len : cert_mem_len;
|
|
}
|
|
}
|
|
|
|
static coap_dtls_key_t *
|
|
verify_pki_sni_callback(const char *sni,
|
|
void *arg COAP_UNUSED) {
|
|
static coap_dtls_key_t dtls_key;
|
|
|
|
update_pki_key(&dtls_key, key_file, cert_file, ca_file);
|
|
|
|
if (sni[0]) {
|
|
size_t i;
|
|
coap_log_info("SNI '%s' requested\n", sni);
|
|
for (i = 0; i < valid_pki_snis.count; i++) {
|
|
/* Test for SNI to change cert + ca */
|
|
if (strcasecmp(sni, valid_pki_snis.pki_sni_list[i].sni_match) == 0) {
|
|
coap_log_info("Switching to using cert '%s' + ca '%s'\n",
|
|
valid_pki_snis.pki_sni_list[i].new_cert,
|
|
valid_pki_snis.pki_sni_list[i].new_ca);
|
|
update_pki_key(&dtls_key, valid_pki_snis.pki_sni_list[i].new_cert,
|
|
valid_pki_snis.pki_sni_list[i].new_cert,
|
|
valid_pki_snis.pki_sni_list[i].new_ca);
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
coap_log_debug("SNI not requested\n");
|
|
}
|
|
return &dtls_key;
|
|
}
|
|
|
|
static const coap_dtls_spsk_info_t *
|
|
verify_psk_sni_callback(const char *sni,
|
|
coap_session_t *c_session COAP_UNUSED,
|
|
void *arg COAP_UNUSED) {
|
|
static coap_dtls_spsk_info_t psk_info;
|
|
|
|
/* Preset with the defined keys */
|
|
memset(&psk_info, 0, sizeof(psk_info));
|
|
psk_info.hint.s = (const uint8_t *)hint;
|
|
psk_info.hint.length = hint ? strlen(hint) : 0;
|
|
psk_info.key.s = key;
|
|
psk_info.key.length = key_length;
|
|
if (sni) {
|
|
size_t i;
|
|
coap_log_info("SNI '%s' requested\n", sni);
|
|
for (i = 0; i < valid_psk_snis.count; i++) {
|
|
/* Test for identity match to change key */
|
|
if (strcasecmp(sni,
|
|
valid_psk_snis.psk_sni_list[i].sni_match) == 0) {
|
|
coap_log_info("Switching to using '%.*s' hint + '%.*s' key\n",
|
|
(int)valid_psk_snis.psk_sni_list[i].new_hint->length,
|
|
valid_psk_snis.psk_sni_list[i].new_hint->s,
|
|
(int)valid_psk_snis.psk_sni_list[i].new_key->length,
|
|
valid_psk_snis.psk_sni_list[i].new_key->s);
|
|
psk_info.hint = *valid_psk_snis.psk_sni_list[i].new_hint;
|
|
psk_info.key = *valid_psk_snis.psk_sni_list[i].new_key;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
coap_log_debug("SNI not requested\n");
|
|
}
|
|
return &psk_info;
|
|
}
|
|
|
|
static const coap_bin_const_t *
|
|
verify_id_callback(coap_bin_const_t *identity,
|
|
coap_session_t *c_session,
|
|
void *arg COAP_UNUSED) {
|
|
static coap_bin_const_t psk_key;
|
|
const coap_bin_const_t *s_psk_hint = coap_session_get_psk_hint(c_session);
|
|
const coap_bin_const_t *s_psk_key;
|
|
size_t i;
|
|
|
|
coap_log_info("Identity '%.*s' requested, current hint '%.*s'\n", (int)identity->length,
|
|
identity->s,
|
|
s_psk_hint ? (int)s_psk_hint->length : 0,
|
|
s_psk_hint ? (const char *)s_psk_hint->s : "");
|
|
|
|
for (i = 0; i < valid_ids.count; i++) {
|
|
/* Check for hint match */
|
|
if (s_psk_hint &&
|
|
strcmp((const char *)s_psk_hint->s,
|
|
valid_ids.id_list[i].hint_match)) {
|
|
continue;
|
|
}
|
|
/* Test for identity match to change key */
|
|
if (coap_binary_equal(identity, valid_ids.id_list[i].identity_match)) {
|
|
coap_log_info("Switching to using '%.*s' key\n",
|
|
(int)valid_ids.id_list[i].new_key->length,
|
|
valid_ids.id_list[i].new_key->s);
|
|
return valid_ids.id_list[i].new_key;
|
|
}
|
|
}
|
|
|
|
s_psk_key = coap_session_get_psk_key(c_session);
|
|
if (s_psk_key) {
|
|
/* Been updated by SNI callback */
|
|
psk_key = *s_psk_key;
|
|
return &psk_key;
|
|
}
|
|
|
|
/* Just use the defined key for now */
|
|
psk_key.s = key;
|
|
psk_key.length = key_length;
|
|
return &psk_key;
|
|
}
|
|
|
|
static coap_dtls_pki_t *
|
|
setup_pki(coap_context_t *ctx, coap_dtls_role_t role, char *client_sni) {
|
|
static coap_dtls_pki_t dtls_pki;
|
|
|
|
/* If trust store CAs are to be defined */
|
|
if (verify_peer_cert && !no_trust_store && !ca_file) {
|
|
coap_context_load_pki_trust_store(ctx);
|
|
}
|
|
|
|
/* If general root CAs are defined */
|
|
if (role == COAP_DTLS_ROLE_SERVER && root_ca_file) {
|
|
struct stat stbuf;
|
|
if ((stat(root_ca_file, &stbuf) == 0) && S_ISDIR(stbuf.st_mode)) {
|
|
coap_context_set_pki_root_cas(ctx, NULL, root_ca_file);
|
|
} else {
|
|
coap_context_set_pki_root_cas(ctx, root_ca_file, NULL);
|
|
}
|
|
}
|
|
|
|
memset(&dtls_pki, 0, sizeof(dtls_pki));
|
|
dtls_pki.version = COAP_DTLS_PKI_SETUP_VERSION;
|
|
/*
|
|
* Add in additional certificate checking.
|
|
* This list of enabled can be tuned for the specific
|
|
* requirements - see 'man coap_encryption'.
|
|
*
|
|
* Note: root_ca_file is setup separately using
|
|
* coap_context_set_pki_root_cas(), but this is used to define what
|
|
* checking actually takes place.
|
|
*/
|
|
dtls_pki.verify_peer_cert = verify_peer_cert;
|
|
dtls_pki.check_common_ca = !root_ca_file;
|
|
dtls_pki.allow_self_signed = 1;
|
|
dtls_pki.allow_expired_certs = 1;
|
|
dtls_pki.cert_chain_validation = 1;
|
|
dtls_pki.cert_chain_verify_depth = 2;
|
|
dtls_pki.check_cert_revocation = 1;
|
|
dtls_pki.allow_no_crl = 1;
|
|
dtls_pki.allow_expired_crl = 1;
|
|
dtls_pki.is_rpk_not_cert = is_rpk_not_cert;
|
|
dtls_pki.validate_cn_call_back = verify_cn_callback;
|
|
dtls_pki.cn_call_back_arg = (void *)role;
|
|
dtls_pki.validate_sni_call_back = role == COAP_DTLS_ROLE_SERVER ?
|
|
verify_pki_sni_callback : NULL;
|
|
dtls_pki.sni_call_back_arg = NULL;
|
|
|
|
if (role == COAP_DTLS_ROLE_CLIENT) {
|
|
dtls_pki.client_sni = client_sni;
|
|
}
|
|
|
|
update_pki_key(&dtls_pki.pki_key, key_file, cert_file, ca_file);
|
|
/* Need to keep base initialization copies of any COAP_PKI_KEY_PEM_BUF */
|
|
ca_mem_base = ca_mem;
|
|
cert_mem_base = cert_mem;
|
|
key_mem_base = key_mem;
|
|
ca_mem = NULL;
|
|
cert_mem = NULL;
|
|
key_mem = NULL;
|
|
return &dtls_pki;
|
|
}
|
|
|
|
static coap_dtls_spsk_t *
|
|
setup_spsk(void) {
|
|
static coap_dtls_spsk_t dtls_spsk;
|
|
|
|
memset(&dtls_spsk, 0, sizeof(dtls_spsk));
|
|
dtls_spsk.version = COAP_DTLS_SPSK_SETUP_VERSION;
|
|
dtls_spsk.ec_jpake = ec_jpake;
|
|
dtls_spsk.validate_id_call_back = valid_ids.count ?
|
|
verify_id_callback : NULL;
|
|
dtls_spsk.validate_sni_call_back = valid_psk_snis.count ?
|
|
verify_psk_sni_callback : NULL;
|
|
dtls_spsk.psk_info.hint.s = (const uint8_t *)hint;
|
|
dtls_spsk.psk_info.hint.length = hint ? strlen(hint) : 0;
|
|
dtls_spsk.psk_info.key.s = key;
|
|
dtls_spsk.psk_info.key.length = key_length;
|
|
return &dtls_spsk;
|
|
}
|
|
|
|
static void
|
|
fill_keystore(coap_context_t *ctx) {
|
|
|
|
if (cert_file == NULL && key_defined == 0) {
|
|
if (coap_dtls_is_supported() || coap_tls_is_supported()) {
|
|
coap_log_debug("(D)TLS not enabled as none of -k, -c or -M options specified\n");
|
|
}
|
|
return;
|
|
}
|
|
if (cert_file) {
|
|
coap_dtls_pki_t *dtls_pki = setup_pki(ctx,
|
|
COAP_DTLS_ROLE_SERVER, NULL);
|
|
if (!coap_context_set_pki(ctx, dtls_pki)) {
|
|
coap_log_info("Unable to set up %s keys\n",
|
|
is_rpk_not_cert ? "RPK" : "PKI");
|
|
/* So we do not set up DTLS */
|
|
cert_file = NULL;
|
|
}
|
|
}
|
|
if (key_defined) {
|
|
coap_dtls_spsk_t *dtls_spsk = setup_spsk();
|
|
|
|
if (!coap_context_set_psk2(ctx, dtls_spsk)) {
|
|
coap_log_info("Unable to set up PSK\n");
|
|
/* So we do not set up DTLS */
|
|
key_defined = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
#if COAP_PROXY_SUPPORT
|
|
static void
|
|
proxy_dtls_setup(coap_context_t *ctx, coap_proxy_server_list_t *proxy_info) {
|
|
size_t i;
|
|
static char client_sni[256];
|
|
|
|
for (i = 0; i < proxy_info->entry_count; i++) {
|
|
coap_proxy_server_t *proxy_server = &proxy_info->entry[i];
|
|
|
|
if (proxy_info->type == COAP_PROXY_FORWARD_DYNAMIC ||
|
|
proxy_info->type == COAP_PROXY_FORWARD_DYNAMIC_STRIP) {
|
|
/* This will get filled in by the libcoap proxy logic */
|
|
memset(client_sni, 0, sizeof(client_sni));
|
|
} else {
|
|
snprintf(client_sni, sizeof(client_sni), "%*.*s", (int)proxy_server->uri.host.length,
|
|
(int)proxy_server->uri.host.length, proxy_server->uri.host.s);
|
|
}
|
|
if (!key_defined) {
|
|
/* Use our defined PKI certs (or NULL) */
|
|
proxy_server->dtls_pki = setup_pki(ctx, COAP_DTLS_ROLE_CLIENT,
|
|
client_sni);
|
|
proxy_server->dtls_cpsk = NULL;
|
|
} else {
|
|
/* Use our defined PSK */
|
|
proxy_server->dtls_cpsk = setup_cpsk(client_sni);
|
|
proxy_server->dtls_pki = NULL;
|
|
}
|
|
/*
|
|
* Set this to a client specific oscore_conf if needed.
|
|
* proxy_server->oscore_conf = oscore_conf;
|
|
*/
|
|
}
|
|
}
|
|
#endif /* COAP_PROXY_SUPPORT */
|
|
|
|
|
|
static void
|
|
usage(const char *program, const char *version) {
|
|
const char *p;
|
|
char buffer[120];
|
|
const char *lib_build = coap_package_build();
|
|
|
|
p = strrchr(program, '/');
|
|
if (p)
|
|
program = ++p;
|
|
|
|
fprintf(stderr, "%s v%s -- a small CoAP implementation\n"
|
|
"(c) 2010,2011,2015-2025 Olaf Bergmann <bergmann@tzi.org> and others\n\n"
|
|
"Build: %s\n"
|
|
"%s\n"
|
|
, program, version, lib_build,
|
|
coap_string_tls_version(buffer, sizeof(buffer)));
|
|
fprintf(stderr, "%s\n", coap_string_tls_support(buffer, sizeof(buffer)));
|
|
fprintf(stderr, "\n"
|
|
"Usage: %s [-a priority] [-b max_block_size] [-d max] [-e]\n"
|
|
"\t\t[-f scheme://address[:port] [-g group] -l loss] [-o] [-p port]\n"
|
|
"\t\t[-q tls_engine_conf_file] [-r] [-v num] [-w [port][,secure_port]]\n"
|
|
"\t\t[-x] [-y rec_secs] [-A address] [-E oscore_conf_file[,seq_file]]\n"
|
|
"\t\t[-G group_if]\n"
|
|
"\t\t[-L value] [-N] [-P scheme://address[:port],[name1[,name2..]]]\n"
|
|
"\t\t[-T max_token_size] [-U type] [-V num] [-X size]\n"
|
|
"\t\t[[-h hint] [-i match_identity_file] [-k key]\n"
|
|
"\t\t[-s match_psk_sni_file] [-u user] [-2]]\n"
|
|
"\t\t[[-c certfile] [-j keyfile] [-m] [-n] [-C cafile]\n"
|
|
"\t\t[-J pkcs11_pin] [-M rpk_file] [-R trust_casfile]\n"
|
|
"\t\t[-S match_pki_sni_file] [-Y]]\n"
|
|
"General Options\n"
|
|
"\t-a priority\tSend logging output to syslog at priority (0-7) level\n"
|
|
"\t-b max_block_size\n"
|
|
"\t \t\tMaximum block size server supports (16, 32, 64,\n"
|
|
"\t \t\t128, 256, 512 or 1024) in bytes\n"
|
|
"\t-d max \t\tAllow dynamic creation of up to a total of max\n"
|
|
"\t \t\tresources. If max is reached, a 4.06 code is returned\n"
|
|
"\t \t\tuntil one of the dynamic resources has been deleted\n"
|
|
"\t-e \t\tEcho back the data sent with a PUT\n"
|
|
"\t-f scheme://address[:port]\n"
|
|
"\t \t\tAct as a reverse proxy where scheme, address and optional\n"
|
|
"\t \t\tport define how to connect to the internal server.\n"
|
|
"\t \t\tScheme is one of coap, coaps, coap+tcp, coaps+tcp,\n"
|
|
"\t \t\tcoap+ws, and coaps+ws. http(s) is not currently supported.\n"
|
|
"\t \t\tThis option can be repeated to provide multiple internal\n"
|
|
"\t \t\tservers (each has to be different) that are round-robin\n"
|
|
"\t \t\tload balanced\n"
|
|
"\t-g group\tJoin the given multicast group\n"
|
|
"\t \t\tNote: DTLS over multicast is not currently supported\n"
|
|
"\t-l list\t\tFail to send some datagrams specified by a comma\n"
|
|
"\t \t\tseparated list of numbers or number ranges\n"
|
|
"\t \t\t(for debugging only)\n"
|
|
"\t-l loss%%\tRandomly fail to send datagrams with the specified\n"
|
|
"\t \t\tprobability - 100%% all datagrams, 0%% no datagrams\n"
|
|
"\t \t\t(for debugging only)\n"
|
|
"\t-o \t\tDisable sending observe failures on shutdown\n"
|
|
"\t-p port\t\tListen on specified port for UDP and TCP. If (D)TLS is\n"
|
|
"\t \t\tenabled, then the coap-server will also listen on\n"
|
|
"\t \t\t'port'+1 for DTLS and TLS. The default port is 5683\n"
|
|
"\t-q tls_engine_conf_file\n"
|
|
"\t \t\ttls_engine_conf_file contains TLS ENGINE configuration.\n"
|
|
"\t \t\tSee coap-tls-engine-conf(5) for definitions.\n"
|
|
"\t-r \t\tEnable multicast per resource support. If enabled,\n"
|
|
"\t \t\tonly '/', '/async' and '/.well-known/core' are enabled\n"
|
|
"\t \t\tfor multicast requests support, otherwise all\n"
|
|
"\t \t\tresources are enabled\n"
|
|
"\t-t \t\tTrack resource's observe values so observe\n"
|
|
"\t \t\tsubscriptions can be maintained over a server restart.\n"
|
|
"\t \t\tNote: Use 'kill SIGUSR2 <pid>' for controlled shutdown\n"
|
|
"\t-v num \t\tVerbosity level (default 4, maximum is 8) for general\n"
|
|
"\t \t\tCoAP logging\n"
|
|
"\t-w [port][,secure_port]\n"
|
|
"\t \t\tEnable WebSockets support on port (WS) and/or secure_port\n"
|
|
"\t \t\t(WSS), comma separated\n"
|
|
"\t-x \t\tDisable output of PDU data when displaying PDUs\n"
|
|
"\t-y rec_secs\tAttempt to reconnect a failed proxy session every\n"
|
|
"\t \t\trec_secs\n"
|
|
"\t-A address\tInterface address to bind to\n"
|
|
"\t-E oscore_conf_file[,seq_file]\n"
|
|
"\t \t\toscore_conf_file contains OSCORE configuration. See\n"
|
|
"\t \t\tcoap-oscore-conf(5) for definitions.\n"
|
|
"\t \t\tOptional seq_file is used to save the current transmit\n"
|
|
"\t \t\tsequence number, so on restart sequence numbers continue\n"
|
|
"\t-G group_if\tUse this interface for listening for the multicast\n"
|
|
"\t \t\tgroup. This can be different from the implied interface\n"
|
|
"\t \t\tif the -A option is used\n"
|
|
"\t-L value\tSum of one or more COAP_BLOCK_* flag valuess for block\n"
|
|
"\t \t\thandling methods. Default is 1 (COAP_BLOCK_USE_LIBCOAP)\n"
|
|
"\t \t\t(Sum of one or more of 1,2,4 64, 128 and 256)\n"
|
|
"\t-N \t\tMake \"observe\" responses NON-confirmable. Even if set\n"
|
|
"\t \t\tevery fifth response will still be sent as a confirmable\n"
|
|
"\t \t\tresponse (RFC 7641 requirement)\n"
|
|
, program);
|
|
fprintf(stderr,
|
|
"\t-P scheme://address[:port],[name1[,name2[,name3..]]]\n"
|
|
"\t \t\tScheme, address, optional port of how to connect to the\n"
|
|
"\t \t\tnext proxy server and zero or more names (comma\n"
|
|
"\t \t\tseparated) that this proxy server is known by. The\n"
|
|
"\t \t\t, (comma) is required. If there is no name1 or if the\n"
|
|
"\t \t\thostname of the incoming proxy request matches one of\n"
|
|
"\t \t\tthese names, then this server is considered to be the\n"
|
|
"\t \t\tfinal endpoint. If scheme://address[:port] is not\n"
|
|
"\t \t\tdefined before the leading , (comma) of the first name,\n"
|
|
"\t \t\tthen the ongoing connection will be a direct connection.\n"
|
|
"\t \t\tScheme is one of coap, coaps, coap+tcp, coaps+tcp,\n"
|
|
"\t \t\tcoap+ws, and coaps+ws. http(s) is not currently supported.\n"
|
|
"\t \t\tThis option can be repeated to provide multiple upstream\n"
|
|
"\t \t\tservers that are round-robin load balanced\n"
|
|
"\t-T max_token_length\tSet the maximum token length (8-65804)\n"
|
|
"\t-U type\t\tTreat address defined by -A as a Unix socket address.\n"
|
|
"\t \t\ttype is 'coap', 'coaps', 'coap+tcp' or 'coaps+tcp'\n"
|
|
"\t-V num \t\tVerbosity level (default 3, maximum is 7) for (D)TLS\n"
|
|
"\t \t\tlibrary logging\n"
|
|
"\t-X size\t\tMaximum message size to use for TCP based connections\n"
|
|
"\t \t\t(default is 8388864). Maximum value of 2^32 -1\n"
|
|
"PSK Options (if supported by underlying (D)TLS library)\n"
|
|
"\t-h hint\t\tIdentity Hint to send. Default is CoAP. Zero length is\n"
|
|
"\t \t\tno hint\n"
|
|
"\t-i match_identity_file\n"
|
|
"\t \t\tThis is a file that contains one or more lines of\n"
|
|
"\t \t\tIdentity Hints and (user) Identities to match for\n"
|
|
"\t \t\ta different new Pre-Shared Key (PSK) (comma separated)\n"
|
|
"\t \t\tto be used. E.g., per line\n"
|
|
"\t \t\t hint_to_match,identity_to_match,use_key\n"
|
|
"\t \t\tNote: -k still needs to be defined for the default case\n"
|
|
"\t \t\tNote: A match using the -s option may mean that the\n"
|
|
"\t \t\tcurrent Identity Hint is different to that defined by -h\n"
|
|
"\t-k key \t\tPre-Shared Key. This argument requires (D)TLS with PSK\n"
|
|
"\t \t\tto be available. This cannot be empty if defined.\n"
|
|
"\t \t\tNote that both -c and -k need to be defined for both\n"
|
|
"\t \t\tPSK and PKI to be concurrently supported. If the\n"
|
|
"\t \t\tkey begins with 0x, then the hex text (two [0-9a-f] per\n"
|
|
"\t \t\tbyte) is converted to binary data\n"
|
|
"\t-s match_psk_sni_file\n"
|
|
"\t \t\tThis is a file that contains one or more lines of\n"
|
|
"\t \t\treceived Subject Name Identifier (SNI) to match to use\n"
|
|
"\t \t\ta different Identity Hint and associated Pre-Shared Key\n"
|
|
"\t \t\t(PSK) (comma separated) instead of the '-h hint' and\n"
|
|
"\t \t\t'-k key' options. E.g., per line\n"
|
|
"\t \t\t sni_to_match,use_hint,with_key\n"
|
|
"\t \t\tNote: -k still needs to be defined for the default case\n"
|
|
"\t \t\tif there is not a match\n"
|
|
"\t \t\tNote: The associated Pre-Shared Key will get updated if\n"
|
|
"\t \t\tthere is also a -i match. The update checking order is\n"
|
|
"\t \t\t-s followed by -i\n"
|
|
"\t-u user\t\tUser identity for pre-shared key mode (only used if\n"
|
|
"\t \t\toption -P is set)\n"
|
|
"\t-2 \t\tUse EC-JPAKE negotiation (if supported)\n"
|
|
);
|
|
fprintf(stderr,
|
|
"PKI Options (if supported by underlying (D)TLS library)\n"
|
|
"\tNote: If any one of '-c certfile', '-j keyfile' or '-C cafile' is in\n"
|
|
"\tPKCS11 URI naming format (pkcs11: prefix), then any remaining non\n"
|
|
"\tPKCS11 URI file definitions have to be in DER, not PEM, format.\n"
|
|
"\tOtherwise all of '-c certfile', '-j keyfile' or '-C cafile' are in\n"
|
|
"\tPEM format.\n\n"
|
|
"\t-c certfile\tPEM file or PKCS11 URI for the certificate. The private\n"
|
|
"\t \t\tkey can also be in the PEM file, or has the same PKCS11\n"
|
|
"\t \t\tURI. If not, the private key is defined by '-j keyfile'.\n"
|
|
"\t \t\tNote that both -c and -k need to be defined for both\n"
|
|
"\t \t\tPSK and PKI to be concurrently supported\n"
|
|
"\t-j keyfile\tPEM file or PKCS11 URI for the private key for the\n"
|
|
"\t \t\tcertificate in '-c certfile' if the parameter is\n"
|
|
"\t \t\tdifferent from certfile in '-c certfile'\n"
|
|
"\t-m \t\tUse COAP_PKI_KEY_PEM_BUF instead of COAP_PKI_KEY_PEM i/f\n"
|
|
"\t \t\tby reading into memory the Cert / CA file (for testing)\n"
|
|
"\t-n \t\tDisable remote peer certificate checking. This gives\n"
|
|
"\t \t\tclients the ability to use PKI, but without any defined\n"
|
|
"\t \t\tcertificates\n"
|
|
"\t-C cafile\tPEM file or PKCS11 URI that contains a list of one or\n"
|
|
"\t \t\tmore CAs that are to be passed to the client for the\n"
|
|
"\t \t\tclient to determine what client certificate to use.\n"
|
|
"\t \t\tNormally, this list of CAs would be the root CA and\n"
|
|
"\t \t\tany intermediate CAs. Ideally the server certificate\n"
|
|
"\t \t\tshould be signed by the same CA so that mutual\n"
|
|
"\t \t\tauthentication can take place. The contents of cafile\n"
|
|
"\t \t\tare added to the trusted store of root CAs.\n"
|
|
"\t \t\tUsing the -C or -R options will trigger the\n"
|
|
"\t \t\tvalidation of the client certificate unless overridden\n"
|
|
"\t \t\tby the -n option\n"
|
|
"\t-J pkcs11_pin\tThe user pin to unlock access to the PKCS11 token\n"
|
|
"\t-M rpk_file\tRaw Public Key (RPK) PEM file or PKCS11 URI that\n"
|
|
"\t \t\tcontains both PUBLIC KEY and PRIVATE KEY or just\n"
|
|
"\t \t\tEC PRIVATE KEY. (GnuTLS and TinyDTLS(PEM) support only).\n"
|
|
"\t \t\t'-C cafile' or '-R trust_casfile' are not required\n"
|
|
"\t-R trust_casfile\n"
|
|
"\t \t\tPEM file containing the set of trusted root CAs\n"
|
|
"\t \t\tthat are to be used to validate the client certificate.\n"
|
|
"\t \t\tAlternatively, this can point to a directory containing\n"
|
|
"\t \t\ta set of CA PEM files.\n"
|
|
"\t \t\tUsing '-R trust_casfile' disables common CA mutual\n"
|
|
"\t \t\tauthentication which can only be done by using\n"
|
|
"\t \t\t'-C cafile'.\n"
|
|
"\t \t\tUsing the -C or -R options will trigger the\n"
|
|
"\t \t\tvalidation of the client certificate unless overridden\n"
|
|
"\t \t\tby the -n option\n"
|
|
"\t-S match_pki_sni_file\n"
|
|
"\t \t\tThis option denotes a file that contains one or more\n"
|
|
"\t \t\tlines of Subject Name Identifier (SNI) to match for new\n"
|
|
"\t \t\tCert file and new CA file (comma separated) to be used.\n"
|
|
"\t \t\tE.g., per line\n"
|
|
"\t \t\t sni_to_match,new_cert_file,new_ca_file\n"
|
|
"\t \t\tNote: -c and -C still need to be defined for the default\n"
|
|
"\t \t\tcase\n"
|
|
"\t-Y\n"
|
|
"\t \t\tDo not load the default system Trusted Root CA Store\n"
|
|
);
|
|
}
|
|
|
|
static coap_context_t *
|
|
get_context(const char *node, const char *port) {
|
|
coap_context_t *ctx = NULL;
|
|
coap_addr_info_t *info = NULL;
|
|
coap_addr_info_t *info_list = NULL;
|
|
coap_str_const_t local;
|
|
int have_ep = 0;
|
|
uint16_t u_s_port = 0;
|
|
uint16_t s_port = 0;
|
|
uint32_t scheme_hint_bits = 0;
|
|
|
|
ctx = coap_new_context(NULL);
|
|
if (!ctx) {
|
|
return NULL;
|
|
}
|
|
|
|
/* Need PKI/RPK/PSK set up before we set up (D)TLS endpoints */
|
|
fill_keystore(ctx);
|
|
|
|
if (node) {
|
|
local.s = (const uint8_t *)node;
|
|
local.length = strlen(node);
|
|
}
|
|
|
|
if (port) {
|
|
u_s_port = atoi(port);
|
|
s_port = u_s_port + 1;
|
|
}
|
|
scheme_hint_bits =
|
|
coap_get_available_scheme_hint_bits(cert_file != NULL || key_defined != 0,
|
|
enable_ws, use_unix_proto);
|
|
info_list = coap_resolve_address_info(node ? &local : NULL, u_s_port, s_port,
|
|
ws_port, wss_port,
|
|
AI_PASSIVE | AI_NUMERICHOST,
|
|
scheme_hint_bits,
|
|
COAP_RESOLVE_TYPE_LOCAL);
|
|
for (info = info_list; info != NULL; info = info->next) {
|
|
coap_endpoint_t *ep;
|
|
|
|
ep = coap_new_endpoint(ctx, &info->addr, info->proto);
|
|
if (!ep) {
|
|
coap_log_warn("cannot create endpoint for proto %u\n",
|
|
info->proto);
|
|
} else {
|
|
have_ep = 1;
|
|
}
|
|
}
|
|
coap_free_address_info(info_list);
|
|
if (!have_ep) {
|
|
coap_log_err("No context available for interface '%s'\n", node);
|
|
coap_free_context(ctx);
|
|
return NULL;
|
|
}
|
|
return ctx;
|
|
}
|
|
|
|
#if COAP_PROXY_SUPPORT
|
|
static int
|
|
cmdline_proxy(char *arg) {
|
|
char *host_start = strchr(arg, ',');
|
|
char *next_name = host_start;
|
|
size_t ofs;
|
|
coap_uri_t uri;
|
|
coap_proxy_server_t *new_entry;
|
|
|
|
if (!host_start) {
|
|
coap_log_warn("Zero or more proxy host names not defined\n");
|
|
return 0;
|
|
}
|
|
*host_start = '\000';
|
|
|
|
if (host_start != arg) {
|
|
/* Next upstream proxy is defined */
|
|
if (coap_split_uri((unsigned char *)arg, strlen(arg), &uri) < 0 ||
|
|
uri.path.length != 0 || uri.query.length != 0) {
|
|
coap_log_err("Invalid CoAP Proxy definition\n");
|
|
return 0;
|
|
}
|
|
if (!coap_verify_proxy_scheme_supported(uri.scheme)) {
|
|
coap_log_err("Unsupported CoAP Proxy protocol\n");
|
|
return 0;
|
|
}
|
|
forward_proxy.type = COAP_PROXY_FORWARD_STATIC;
|
|
forward_proxy.idle_timeout_secs = 300;
|
|
} else {
|
|
memset(&uri, 0, sizeof(uri));
|
|
forward_proxy.type = COAP_PROXY_FORWARD_DYNAMIC_STRIP;
|
|
forward_proxy.idle_timeout_secs = 10;
|
|
}
|
|
|
|
new_entry = realloc(forward_proxy.entry,
|
|
(forward_proxy.entry_count + 1)*sizeof(forward_proxy.entry[0]));
|
|
if (!new_entry) {
|
|
coap_log_err("CoAP Proxy realloc() error\n");
|
|
return 0;
|
|
}
|
|
forward_proxy.entry = new_entry;
|
|
memset(&forward_proxy.entry[forward_proxy.entry_count], 0, sizeof(forward_proxy.entry[0]));
|
|
forward_proxy.entry[forward_proxy.entry_count].uri = uri;
|
|
forward_proxy.entry_count++;
|
|
|
|
proxy_host_name_count = 0;
|
|
while (next_name) {
|
|
proxy_host_name_count++;
|
|
next_name = strchr(next_name+1, ',');
|
|
}
|
|
proxy_host_name_list = coap_malloc(proxy_host_name_count * sizeof(char *));
|
|
next_name = host_start;
|
|
ofs = 0;
|
|
while (next_name) {
|
|
proxy_host_name_list[ofs++] = next_name+1;
|
|
next_name = strchr(next_name+1, ',');
|
|
if (next_name)
|
|
*next_name = '\000';
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
cmdline_reverse_proxy(char *arg) {
|
|
/* upstream server is defined */
|
|
coap_uri_t uri;
|
|
coap_proxy_server_t *new_entry;
|
|
|
|
if (coap_split_uri((unsigned char *)arg, strlen(arg), &uri) < 0 ||
|
|
uri.path.length != 0 || uri.query.length != 0) {
|
|
coap_log_err("Invalid CoAP Reverse-Proxy definition\n");
|
|
return 0;
|
|
}
|
|
if (!coap_verify_proxy_scheme_supported(uri.scheme)) {
|
|
coap_log_err("Unsupported CoAP Reverse-Proxy protocol\n");
|
|
return 0;
|
|
}
|
|
|
|
new_entry = realloc(reverse_proxy.entry,
|
|
(reverse_proxy.entry_count + 1)*sizeof(reverse_proxy.entry[0]));
|
|
if (!new_entry) {
|
|
coap_log_err("CoAP Reverse-Proxy realloc() error\n");
|
|
return 0;
|
|
}
|
|
reverse_proxy.entry = new_entry;
|
|
memset(&reverse_proxy.entry[reverse_proxy.entry_count], 0, sizeof(reverse_proxy.entry[0]));
|
|
reverse_proxy.entry[reverse_proxy.entry_count].uri = uri;
|
|
reverse_proxy.entry_count++;
|
|
return 1;
|
|
}
|
|
|
|
static ssize_t
|
|
cmdline_read_user(char *arg, unsigned char **buf, size_t maxlen) {
|
|
size_t len = strnlen(arg, maxlen);
|
|
if (len) {
|
|
*buf = (unsigned char *)arg;
|
|
/* len is the size or less, so 0 terminate to maxlen */
|
|
(*buf)[len] = '\000';
|
|
}
|
|
/* 0 length Identity is valid */
|
|
return len;
|
|
}
|
|
#endif /* COAP_PROXY_SUPPORT */
|
|
|
|
static FILE *oscore_seq_num_fp = NULL;
|
|
static const char *oscore_conf_file = NULL;
|
|
static const char *oscore_seq_save_file = NULL;
|
|
|
|
static int
|
|
oscore_save_seq_num(uint64_t sender_seq_num, void *param COAP_UNUSED) {
|
|
if (oscore_seq_num_fp) {
|
|
rewind(oscore_seq_num_fp);
|
|
fprintf(oscore_seq_num_fp, "%" PRIu64 "\n", sender_seq_num);
|
|
fflush(oscore_seq_num_fp);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static coap_oscore_conf_t *
|
|
get_oscore_conf(coap_context_t *context) {
|
|
uint8_t *buf;
|
|
size_t length;
|
|
coap_str_const_t file_mem;
|
|
uint64_t start_seq_num = 0;
|
|
|
|
/* Need a rw var to free off later and file_mem.s is a const */
|
|
buf = read_file_mem(oscore_conf_file, &length);
|
|
if (buf == NULL) {
|
|
fprintf(stderr, "OSCORE configuration file error: %s\n", oscore_conf_file);
|
|
return NULL;
|
|
}
|
|
file_mem.s = buf;
|
|
file_mem.length = length;
|
|
if (oscore_seq_save_file) {
|
|
oscore_seq_num_fp = fopen(oscore_seq_save_file, "r+");
|
|
if (oscore_seq_num_fp == NULL) {
|
|
/* Try creating it */
|
|
oscore_seq_num_fp = fopen(oscore_seq_save_file, "w+");
|
|
if (oscore_seq_num_fp == NULL) {
|
|
fprintf(stderr, "OSCORE save restart info file error: %s\n",
|
|
oscore_seq_save_file);
|
|
coap_free(buf);
|
|
return NULL;
|
|
}
|
|
}
|
|
if (fscanf(oscore_seq_num_fp, "%" PRIu64, &start_seq_num) != 1) {
|
|
/* Must be empty */
|
|
start_seq_num = 0;
|
|
}
|
|
}
|
|
oscore_conf = coap_new_oscore_conf(file_mem,
|
|
oscore_save_seq_num,
|
|
NULL, start_seq_num);
|
|
coap_free(buf);
|
|
if (oscore_conf == NULL) {
|
|
fprintf(stderr, "OSCORE configuration file error: %s\n", oscore_conf_file);
|
|
return NULL;
|
|
}
|
|
coap_context_oscore_server(context, oscore_conf);
|
|
return oscore_conf;
|
|
}
|
|
|
|
static int
|
|
cmdline_oscore(char *arg) {
|
|
if (coap_oscore_is_supported()) {
|
|
char *sep = strchr(arg, ',');
|
|
|
|
if (sep)
|
|
*sep = '\000';
|
|
oscore_conf_file = arg;
|
|
|
|
if (sep) {
|
|
sep++;
|
|
oscore_seq_save_file = sep;
|
|
}
|
|
return 1;
|
|
}
|
|
fprintf(stderr, "OSCORE support not enabled\n");
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
cmdline_tls_engine(char *arg) {
|
|
uint8_t *buf;
|
|
size_t length;
|
|
coap_str_const_t file_mem;
|
|
|
|
/* Need a rw var to free off later and file_mem.s is a const */
|
|
buf = read_file_mem(arg, &length);
|
|
if (buf == NULL) {
|
|
fprintf(stderr, "Openssl ENGINE configuration file error: %s\n", arg);
|
|
return 0;
|
|
}
|
|
file_mem.s = buf;
|
|
file_mem.length = length;
|
|
if (!coap_tls_engine_configure(&file_mem)) {
|
|
coap_free(buf);
|
|
return 0;
|
|
}
|
|
coap_free(buf);
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Utility function to convert a hex digit to its corresponding
|
|
* numerical value.
|
|
*
|
|
* param c The hex digit to convert. Must be in [0-9A-Fa-f].
|
|
*
|
|
* return The numerical representation of @p c.
|
|
*/
|
|
static uint8_t
|
|
hex2char(char c) {
|
|
assert(isxdigit(c));
|
|
if ('a' <= c && c <= 'f')
|
|
return c - 'a' + 10;
|
|
else if ('A' <= c && c <= 'F')
|
|
return c - 'A' + 10;
|
|
else
|
|
return c - '0';
|
|
}
|
|
|
|
/**
|
|
* Converts the sequence of hex digits in src to a sequence of bytes.
|
|
*
|
|
* This function returns the number of bytes that have been written to
|
|
* @p dst.
|
|
*
|
|
* param[in] src The null-terminated hex string to convert.
|
|
* param[out] dst Conversion result.
|
|
*
|
|
* return The length of @p dst.
|
|
*/
|
|
static size_t
|
|
convert_hex_string(const char *src, uint8_t *dst) {
|
|
uint8_t *p = dst;
|
|
while (isxdigit((int)src[0]) && isxdigit((int)src[1])) {
|
|
*p++ = (hex2char(src[0]) << 4) + hex2char(src[1]);
|
|
src += 2;
|
|
}
|
|
if (src[0] != '\0') { /* error in hex input */
|
|
coap_log_warn("invalid hex string in option '%s'\n", src);
|
|
}
|
|
return p - dst;
|
|
}
|
|
|
|
static ssize_t
|
|
cmdline_read_key(char *arg, unsigned char **buf, size_t maxlen) {
|
|
size_t len = strnlen(arg, maxlen);
|
|
if (len) {
|
|
/* read hex string alternative when arg starts with "0x" */
|
|
if (len >= 4 && arg[0] == '0' && arg[1] == 'x') {
|
|
/* As the command line option is part of our environment we can do
|
|
* the conversion in place. */
|
|
len = convert_hex_string(arg + 2, (uint8_t *)arg);
|
|
}
|
|
*buf = (unsigned char *)arg;
|
|
return len;
|
|
}
|
|
/* Need at least one byte for the pre-shared key */
|
|
coap_log_crit("Invalid Pre-Shared Key specified\n");
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
cmdline_read_psk_sni_check(char *arg) {
|
|
FILE *fp = fopen(arg, "r");
|
|
static char tmpbuf[256];
|
|
if (fp == NULL) {
|
|
coap_log_err("SNI file: %s: Unable to open\n", arg);
|
|
return 0;
|
|
}
|
|
while (fgets(tmpbuf, sizeof(tmpbuf), fp) != NULL) {
|
|
char *cp = tmpbuf;
|
|
char *tcp = strchr(cp, '\n');
|
|
|
|
if (tmpbuf[0] == '#')
|
|
continue;
|
|
if (tcp)
|
|
*tcp = '\000';
|
|
|
|
tcp = strchr(cp, ',');
|
|
if (tcp) {
|
|
psk_sni_def_t *new_psk_sni_list;
|
|
new_psk_sni_list = realloc(valid_psk_snis.psk_sni_list,
|
|
(valid_psk_snis.count + 1)*sizeof(valid_psk_snis.psk_sni_list[0]));
|
|
if (new_psk_sni_list == NULL) {
|
|
break;
|
|
}
|
|
valid_psk_snis.psk_sni_list = new_psk_sni_list;
|
|
valid_psk_snis.psk_sni_list[valid_psk_snis.count].sni_match = strndup(cp, tcp-cp);
|
|
cp = tcp+1;
|
|
tcp = strchr(cp, ',');
|
|
if (tcp) {
|
|
valid_psk_snis.psk_sni_list[valid_psk_snis.count].new_hint =
|
|
coap_new_bin_const((const uint8_t *)cp, tcp-cp);
|
|
cp = tcp+1;
|
|
valid_psk_snis.psk_sni_list[valid_psk_snis.count].new_key =
|
|
coap_new_bin_const((const uint8_t *)cp, strlen(cp));
|
|
valid_psk_snis.count++;
|
|
} else {
|
|
free(valid_psk_snis.psk_sni_list[valid_psk_snis.count].sni_match);
|
|
}
|
|
}
|
|
}
|
|
fclose(fp);
|
|
return valid_psk_snis.count > 0;
|
|
}
|
|
|
|
static int
|
|
cmdline_read_identity_check(char *arg) {
|
|
FILE *fp = fopen(arg, "r");
|
|
static char tmpbuf[256];
|
|
if (fp == NULL) {
|
|
coap_log_err("Identity file: %s: Unable to open\n", arg);
|
|
return 0;
|
|
}
|
|
while (fgets(tmpbuf, sizeof(tmpbuf), fp) != NULL) {
|
|
char *cp = tmpbuf;
|
|
char *tcp = strchr(cp, '\n');
|
|
|
|
if (tmpbuf[0] == '#')
|
|
continue;
|
|
if (tcp)
|
|
*tcp = '\000';
|
|
|
|
tcp = strchr(cp, ',');
|
|
if (tcp) {
|
|
id_def_t *new_id_list;
|
|
new_id_list = realloc(valid_ids.id_list,
|
|
(valid_ids.count + 1)*sizeof(valid_ids.id_list[0]));
|
|
if (new_id_list == NULL) {
|
|
break;
|
|
}
|
|
valid_ids.id_list = new_id_list;
|
|
valid_ids.id_list[valid_ids.count].hint_match = strndup(cp, tcp-cp);
|
|
cp = tcp+1;
|
|
tcp = strchr(cp, ',');
|
|
if (tcp) {
|
|
valid_ids.id_list[valid_ids.count].identity_match =
|
|
coap_new_bin_const((const uint8_t *)cp, tcp-cp);
|
|
cp = tcp+1;
|
|
valid_ids.id_list[valid_ids.count].new_key =
|
|
coap_new_bin_const((const uint8_t *)cp, strlen(cp));
|
|
valid_ids.count++;
|
|
} else {
|
|
free(valid_ids.id_list[valid_ids.count].hint_match);
|
|
}
|
|
}
|
|
}
|
|
fclose(fp);
|
|
return valid_ids.count > 0;
|
|
}
|
|
|
|
static int
|
|
cmdline_unix(char *arg) {
|
|
if (!strcmp("coap", arg)) {
|
|
use_unix_proto = COAP_PROTO_UDP;
|
|
return 1;
|
|
} else if (!strcmp("coaps", arg)) {
|
|
if (!coap_dtls_is_supported()) {
|
|
coap_log_err("unix with dtls is not supported\n");
|
|
return 0;
|
|
}
|
|
use_unix_proto = COAP_PROTO_DTLS;
|
|
return 1;
|
|
} else if (!strcmp("coap+tcp", arg)) {
|
|
if (!coap_tcp_is_supported()) {
|
|
coap_log_err("unix with stream is not supported\n");
|
|
return 0;
|
|
}
|
|
use_unix_proto = COAP_PROTO_TCP;
|
|
return 1;
|
|
} else if (!strcmp("coaps+tcp", arg)) {
|
|
if (!coap_tls_is_supported()) {
|
|
coap_log_err("unix with tls is not supported\n");
|
|
return 0;
|
|
}
|
|
use_unix_proto = COAP_PROTO_TLS;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
cmdline_ws(char *arg) {
|
|
char *cp = strchr(arg, ',');
|
|
|
|
if (cp) {
|
|
if (cp != arg)
|
|
ws_port = atoi(arg);
|
|
cp++;
|
|
if (*cp != '\000')
|
|
wss_port = atoi(cp);
|
|
} else {
|
|
ws_port = atoi(arg);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
cmdline_read_pki_sni_check(char *arg) {
|
|
FILE *fp = fopen(arg, "r");
|
|
static char tmpbuf[256];
|
|
if (fp == NULL) {
|
|
coap_log_err("SNI file: %s: Unable to open\n", arg);
|
|
return 0;
|
|
}
|
|
while (fgets(tmpbuf, sizeof(tmpbuf), fp) != NULL) {
|
|
char *cp = tmpbuf;
|
|
char *tcp = strchr(cp, '\n');
|
|
|
|
if (tmpbuf[0] == '#')
|
|
continue;
|
|
if (tcp)
|
|
*tcp = '\000';
|
|
|
|
tcp = strchr(cp, ',');
|
|
if (tcp) {
|
|
pki_sni_def_t *new_pki_sni_list;
|
|
new_pki_sni_list = realloc(valid_pki_snis.pki_sni_list,
|
|
(valid_pki_snis.count + 1)*sizeof(valid_pki_snis.pki_sni_list[0]));
|
|
if (new_pki_sni_list == NULL) {
|
|
break;
|
|
}
|
|
valid_pki_snis.pki_sni_list = new_pki_sni_list;
|
|
valid_pki_snis.pki_sni_list[valid_pki_snis.count].sni_match =
|
|
strndup(cp, tcp-cp);
|
|
cp = tcp+1;
|
|
tcp = strchr(cp, ',');
|
|
if (tcp) {
|
|
int fail = 0;
|
|
valid_pki_snis.pki_sni_list[valid_pki_snis.count].new_cert =
|
|
strndup(cp, tcp-cp);
|
|
cp = tcp+1;
|
|
valid_pki_snis.pki_sni_list[valid_pki_snis.count].new_ca =
|
|
strndup(cp, strlen(cp));
|
|
if (access(valid_pki_snis.pki_sni_list[valid_pki_snis.count].new_cert,
|
|
R_OK)) {
|
|
coap_log_err("SNI file: Cert File: %s: Unable to access\n",
|
|
valid_pki_snis.pki_sni_list[valid_pki_snis.count].new_cert);
|
|
fail = 1;
|
|
}
|
|
if (access(valid_pki_snis.pki_sni_list[valid_pki_snis.count].new_ca,
|
|
R_OK)) {
|
|
coap_log_err("SNI file: CA File: %s: Unable to access\n",
|
|
valid_pki_snis.pki_sni_list[valid_pki_snis.count].new_ca);
|
|
fail = 1;
|
|
}
|
|
if (fail) {
|
|
free(valid_pki_snis.pki_sni_list[valid_pki_snis.count].sni_match);
|
|
free(valid_pki_snis.pki_sni_list[valid_pki_snis.count].new_cert);
|
|
free(valid_pki_snis.pki_sni_list[valid_pki_snis.count].new_ca);
|
|
} else {
|
|
valid_pki_snis.count++;
|
|
}
|
|
} else {
|
|
coap_log_err("SNI file: SNI_match,Use_Cert_file,Use_CA_file not defined\n");
|
|
free(valid_pki_snis.pki_sni_list[valid_pki_snis.count].sni_match);
|
|
}
|
|
}
|
|
}
|
|
fclose(fp);
|
|
return valid_pki_snis.count > 0;
|
|
}
|
|
|
|
static int
|
|
cmdline_read_extended_token_size(char *arg) {
|
|
extended_token_size = strtoul(arg, NULL, 0);
|
|
if (extended_token_size < COAP_TOKEN_DEFAULT_MAX) {
|
|
coap_log_err("Extended Token Length must be 8 or greater\n");
|
|
return 0;
|
|
} else if (extended_token_size > COAP_TOKEN_EXT_MAX) {
|
|
coap_log_err("Extended Token Length must be 65804 or less\n");
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
#ifndef _WIN32
|
|
|
|
uint32_t syslog_pri = 0;
|
|
|
|
static void
|
|
syslog_handler(coap_log_t level, const char *message) {
|
|
char *cp = strchr(message, '\n');
|
|
|
|
if (cp) {
|
|
char *lcp = strchr(message, '\r');
|
|
if (lcp && lcp < cp)
|
|
cp = lcp;
|
|
}
|
|
syslog(syslog_pri, "%s %*.*s", coap_log_level_desc(level), (int)(cp-message),
|
|
(int)(cp-message), message);
|
|
}
|
|
#endif /* ! _WIN32 */
|
|
|
|
/*
|
|
* This function only initiates an Observe unsolicited response when the time
|
|
* (in seconds) changes.
|
|
*/
|
|
static void
|
|
do_time_observe_code(void *arg) {
|
|
static coap_time_t t_last = 0;
|
|
coap_time_t t_now;
|
|
coap_tick_t now;
|
|
|
|
(void)arg;
|
|
coap_ticks(&now);
|
|
t_now = coap_ticks_to_rt(now);
|
|
if (t_now != t_last) {
|
|
t_last = t_now;
|
|
coap_resource_notify_observers(time_resource, NULL);
|
|
}
|
|
}
|
|
|
|
int
|
|
main(int argc, char **argv) {
|
|
coap_context_t *ctx = NULL;
|
|
char *group = NULL;
|
|
char *group_if = NULL;
|
|
char addr_str[NI_MAXHOST] = "::";
|
|
char *port_str = NULL;
|
|
int opt;
|
|
int mcast_per_resource = 0;
|
|
coap_log_t log_level = COAP_LOG_WARN;
|
|
coap_log_t dtls_log_level = COAP_LOG_ERR;
|
|
unsigned wait_ms;
|
|
size_t i;
|
|
int exit_code = 0;
|
|
uint32_t max_block_size = 0;
|
|
int shutdown_no_observe = 0;
|
|
#ifndef _WIN32
|
|
int use_syslog = 0;
|
|
#endif /* ! _WIN32 */
|
|
uint16_t cache_ignore_options[] = { COAP_OPTION_BLOCK1,
|
|
COAP_OPTION_BLOCK2,
|
|
/* See https://rfc-editor.org/rfc/rfc7959#section-2.10 */
|
|
COAP_OPTION_MAXAGE,
|
|
/* See https://rfc-editor.org/rfc/rfc7959#section-2.10 */
|
|
COAP_OPTION_IF_NONE_MATCH
|
|
};
|
|
#ifndef _WIN32
|
|
struct sigaction sa;
|
|
#endif
|
|
|
|
/* Initialize libcoap library */
|
|
coap_startup();
|
|
|
|
clock_offset = time(NULL);
|
|
|
|
while ((opt = getopt(argc, argv,
|
|
"a:b:c:d:ef:g:h:i:j:k:l:mnop:q:rs:tu:v:w:y:A:C:E:G:J:L:M:NP:R:S:T:U:V:X:Y2")) != -1) {
|
|
switch (opt) {
|
|
#ifndef _WIN32
|
|
case 'a':
|
|
use_syslog = 1;
|
|
syslog_pri = atoi(optarg);
|
|
if (syslog_pri > 7)
|
|
syslog_pri = 7;
|
|
break;
|
|
#endif /* ! _WIN32 */
|
|
case 'A' :
|
|
strncpy(addr_str, optarg, NI_MAXHOST-1);
|
|
addr_str[NI_MAXHOST - 1] = '\0';
|
|
break;
|
|
case 'b':
|
|
max_block_size = atoi(optarg);
|
|
break;
|
|
case 'c' :
|
|
cert_file = optarg;
|
|
break;
|
|
case 'C' :
|
|
ca_file = optarg;
|
|
break;
|
|
case 'd' :
|
|
support_dynamic = atoi(optarg);
|
|
break;
|
|
case 'e':
|
|
echo_back = 1;
|
|
break;
|
|
case 'E':
|
|
doing_oscore = cmdline_oscore(optarg);
|
|
if (!doing_oscore) {
|
|
goto failed;
|
|
}
|
|
break;
|
|
case 'f':
|
|
if (!coap_proxy_is_supported()) {
|
|
fprintf(stderr, "Reverse Proxy support not available as libcoap proxy code not enabled\n");
|
|
goto failed;
|
|
}
|
|
#if COAP_PROXY_SUPPORT
|
|
if (!cmdline_reverse_proxy(optarg)) {
|
|
fprintf(stderr, "Reverse Proxy error specifying upstream address\n");
|
|
goto failed;
|
|
}
|
|
block_mode |= COAP_BLOCK_SINGLE_BODY;
|
|
#endif /* COAP_PROXY_SUPPORT */
|
|
break;
|
|
case 'g' :
|
|
group = optarg;
|
|
break;
|
|
case 'G' :
|
|
group_if = optarg;
|
|
break;
|
|
case 'h' :
|
|
if (!optarg[0]) {
|
|
hint = NULL;
|
|
break;
|
|
}
|
|
hint = optarg;
|
|
break;
|
|
case 'i':
|
|
if (!cmdline_read_identity_check(optarg)) {
|
|
usage(argv[0], LIBCOAP_PACKAGE_VERSION);
|
|
goto failed;
|
|
}
|
|
break;
|
|
case 'j' :
|
|
key_file = optarg;
|
|
break;
|
|
case 'J' :
|
|
pkcs11_pin = optarg;
|
|
break;
|
|
case 'k' :
|
|
key_length = cmdline_read_key(optarg, &key, MAX_KEY);
|
|
if (key_length < 0) {
|
|
break;
|
|
}
|
|
key_defined = 1;
|
|
break;
|
|
case 'l':
|
|
if (!coap_debug_set_packet_loss(optarg)) {
|
|
usage(argv[0], LIBCOAP_PACKAGE_VERSION);
|
|
goto failed;
|
|
}
|
|
break;
|
|
case 'L':
|
|
block_mode = strtoul(optarg, NULL, 0);
|
|
if (!(block_mode & COAP_BLOCK_USE_LIBCOAP)) {
|
|
fprintf(stderr, "Block mode must include COAP_BLOCK_USE_LIBCOAP (1)\n");
|
|
goto failed;
|
|
}
|
|
break;
|
|
case 'm':
|
|
use_pem_buf = 1;
|
|
break;
|
|
case 'M':
|
|
cert_file = optarg;
|
|
is_rpk_not_cert = 1;
|
|
break;
|
|
case 'n':
|
|
verify_peer_cert = 0;
|
|
break;
|
|
case 'N':
|
|
resource_flags = COAP_RESOURCE_FLAGS_NOTIFY_NON;
|
|
break;
|
|
case 'o':
|
|
shutdown_no_observe = 1;
|
|
break;
|
|
case 'p' :
|
|
port_str = optarg;
|
|
break;
|
|
case 'P':
|
|
if (!coap_proxy_is_supported()) {
|
|
fprintf(stderr, "Proxy support not available as libcoap proxy code not enabled\n");
|
|
goto failed;
|
|
}
|
|
#if COAP_PROXY_SUPPORT
|
|
if (!cmdline_proxy(optarg)) {
|
|
fprintf(stderr, "error specifying proxy address or host names\n");
|
|
goto failed;
|
|
}
|
|
block_mode |= COAP_BLOCK_SINGLE_BODY;
|
|
#endif /* COAP_PROXY_SUPPORT */
|
|
break;
|
|
case 'q':
|
|
tls_engine_conf = optarg;
|
|
doing_tls_engine = 1;
|
|
break;
|
|
case 'r' :
|
|
mcast_per_resource = 1;
|
|
break;
|
|
case 'R' :
|
|
root_ca_file = optarg;
|
|
break;
|
|
case 's':
|
|
if (!cmdline_read_psk_sni_check(optarg)) {
|
|
goto failed;
|
|
}
|
|
break;
|
|
case 'S':
|
|
if (!cmdline_read_pki_sni_check(optarg)) {
|
|
goto failed;
|
|
}
|
|
break;
|
|
case 'T':
|
|
if (!cmdline_read_extended_token_size(optarg)) {
|
|
goto failed;
|
|
}
|
|
break;
|
|
case 't':
|
|
track_observes = 1;
|
|
break;
|
|
case 'u':
|
|
if (!coap_proxy_is_supported()) {
|
|
fprintf(stderr, "Proxy support not available as libcoap proxy code not enabled\n");
|
|
goto failed;
|
|
}
|
|
#if COAP_PROXY_SUPPORT
|
|
user_length = cmdline_read_user(optarg, &user, MAX_USER);
|
|
#endif /* COAP_PROXY_SUPPORT */
|
|
break;
|
|
case 'U':
|
|
if (!cmdline_unix(optarg)) {
|
|
usage(argv[0], LIBCOAP_PACKAGE_VERSION);
|
|
goto failed;
|
|
}
|
|
break;
|
|
case 'v' :
|
|
log_level = strtol(optarg, NULL, 10);
|
|
break;
|
|
case 'V':
|
|
dtls_log_level = strtol(optarg, NULL, 10);
|
|
break;
|
|
case 'w':
|
|
if (!coap_ws_is_supported() || !cmdline_ws(optarg)) {
|
|
fprintf(stderr, "WebSockets not enabled in libcoap\n");
|
|
exit(1);
|
|
}
|
|
enable_ws = 1;
|
|
break;
|
|
case 'x':
|
|
coap_enable_pdu_data_output(0);
|
|
break;
|
|
case 'X':
|
|
csm_max_message_size = strtol(optarg, NULL, 10);
|
|
break;
|
|
case 'y':
|
|
reconnect_secs = atoi(optarg);
|
|
break;
|
|
case 'Y':
|
|
no_trust_store = 1;
|
|
break;
|
|
case '2':
|
|
ec_jpake = 1;
|
|
break;
|
|
default:
|
|
usage(argv[0], LIBCOAP_PACKAGE_VERSION);
|
|
goto failed;
|
|
}
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
signal(SIGINT, handle_sigint);
|
|
#else
|
|
memset(&sa, 0, sizeof(sa));
|
|
sigemptyset(&sa.sa_mask);
|
|
sa.sa_handler = handle_sigint;
|
|
sa.sa_flags = 0;
|
|
sigaction(SIGINT, &sa, NULL);
|
|
sigaction(SIGTERM, &sa, NULL);
|
|
sa.sa_handler = handle_sigusr2;
|
|
sigaction(SIGUSR2, &sa, NULL);
|
|
/* So we do not exit on a SIGPIPE */
|
|
sa.sa_handler = SIG_IGN;
|
|
sigaction(SIGPIPE, &sa, NULL);
|
|
#endif
|
|
|
|
#ifndef _WIN32
|
|
if (use_syslog) {
|
|
openlog("coap-server", 0, LOG_DAEMON);
|
|
coap_set_show_pdu_output(0);
|
|
coap_set_log_handler(syslog_handler);
|
|
}
|
|
#endif /* ! _WIN32 */
|
|
coap_set_log_level(log_level);
|
|
coap_dtls_set_log_level(dtls_log_level);
|
|
|
|
ctx = get_context(addr_str, port_str);
|
|
if (!ctx)
|
|
return -1;
|
|
|
|
init_resources(ctx);
|
|
if (mcast_per_resource)
|
|
coap_mcast_per_resource(ctx);
|
|
if (shutdown_no_observe)
|
|
coap_context_set_shutdown_no_observe(ctx);
|
|
coap_context_set_block_mode(ctx, block_mode);
|
|
coap_context_set_max_block_size(ctx, max_block_size);
|
|
coap_context_set_session_reconnect_time(ctx, reconnect_secs);
|
|
coap_context_set_keepalive(ctx, 30);
|
|
if (csm_max_message_size)
|
|
coap_context_set_csm_max_message_size(ctx, csm_max_message_size);
|
|
if (doing_tls_engine) {
|
|
if (!cmdline_tls_engine(tls_engine_conf))
|
|
goto failed;
|
|
}
|
|
if (doing_oscore) {
|
|
if (get_oscore_conf(ctx) == NULL)
|
|
goto failed;
|
|
}
|
|
#if COAP_PROXY_SUPPORT
|
|
if (reverse_proxy.entry_count) {
|
|
proxy_dtls_setup(ctx, &reverse_proxy);
|
|
}
|
|
if (forward_proxy.entry_count) {
|
|
proxy_dtls_setup(ctx, &forward_proxy);
|
|
}
|
|
#endif /* COAP_PROXY_SUPPORT */
|
|
if (extended_token_size > COAP_TOKEN_DEFAULT_MAX)
|
|
coap_context_set_max_token_size(ctx, extended_token_size);
|
|
|
|
/* Define the options to ignore when setting up cache-keys */
|
|
coap_cache_ignore_options(ctx, cache_ignore_options,
|
|
sizeof(cache_ignore_options)/sizeof(cache_ignore_options[0]));
|
|
/* join multicast group if requested at command line */
|
|
if (group)
|
|
coap_join_mcast_group_intf(ctx, group, group_if);
|
|
|
|
if (track_observes) {
|
|
/*
|
|
* Read in and set up appropriate persist information.
|
|
* Note that this should be done after ctx is properly set up.
|
|
*/
|
|
if (!coap_persist_startup(ctx,
|
|
"/tmp/coap_dyn_resource_save_file",
|
|
"/tmp/coap_observe_save_file",
|
|
"/tmp/coap_obs_cnt_save_file", 10)) {
|
|
fprintf(stderr, "Unable to set up persist logic\n");
|
|
goto finish;
|
|
}
|
|
}
|
|
|
|
wait_ms = COAP_RESOURCE_CHECK_TIME * 1000;
|
|
|
|
#if NUM_SERVER_THREADS
|
|
if (!coap_io_process_loop(ctx, time_resource ? do_time_observe_code : NULL,
|
|
NULL, wait_ms, NUM_SERVER_THREADS)) {
|
|
coap_log_err("coap_io_process_loop: Failed\n");
|
|
}
|
|
#else
|
|
int nfds = 0;
|
|
int coap_fd;
|
|
fd_set m_readfds;
|
|
|
|
coap_fd = coap_context_get_coap_fd(ctx);
|
|
if (coap_fd != -1) {
|
|
/* if coap_fd is -1, then epoll is not supported within libcoap */
|
|
FD_ZERO(&m_readfds);
|
|
FD_SET(coap_fd, &m_readfds);
|
|
nfds = coap_fd + 1;
|
|
}
|
|
|
|
while (!quit) {
|
|
int result;
|
|
coap_tick_t now;
|
|
|
|
if (coap_fd != -1) {
|
|
/*
|
|
* Using epoll. It is more usual to call coap_io_process() with wait_ms
|
|
* (as in the non-epoll branch), but doing it this way gives the
|
|
* flexibility of potentially working with other file descriptors that
|
|
* are not a part of libcoap.
|
|
*/
|
|
fd_set readfds = m_readfds;
|
|
struct timeval tv;
|
|
coap_tick_t begin, end;
|
|
|
|
coap_ticks(&begin);
|
|
|
|
tv.tv_sec = wait_ms / 1000;
|
|
tv.tv_usec = (wait_ms % 1000) * 1000;
|
|
/* Wait until any i/o takes place or timeout */
|
|
result = select(nfds, &readfds, NULL, NULL, &tv);
|
|
if (result == -1) {
|
|
if (errno != EAGAIN) {
|
|
coap_log_debug("select: %s (%d)\n", coap_socket_strerror(), errno);
|
|
break;
|
|
}
|
|
}
|
|
if (result > 0) {
|
|
if (FD_ISSET(coap_fd, &readfds)) {
|
|
result = coap_io_process(ctx, COAP_IO_NO_WAIT);
|
|
}
|
|
}
|
|
if (result >= 0) {
|
|
coap_ticks(&end);
|
|
/* Track the overall time spent in select() and coap_io_process() */
|
|
result = (int)(end - begin);
|
|
}
|
|
} else {
|
|
/*
|
|
* epoll is not supported within libcoap
|
|
*
|
|
* result is time spent in coap_io_process()
|
|
*/
|
|
result = coap_io_process(ctx, wait_ms);
|
|
}
|
|
if (result < 0) {
|
|
break;
|
|
} else if (result && (unsigned)result < wait_ms) {
|
|
/* decrement if there is a result wait time returned */
|
|
wait_ms -= result;
|
|
} else {
|
|
/*
|
|
* result == 0, or result >= wait_ms
|
|
* (wait_ms could have decremented to a small value, below
|
|
* the granularity of the timer in coap_io_process() and hence
|
|
* result == 0)
|
|
*/
|
|
wait_ms = COAP_RESOURCE_CHECK_TIME * 1000;
|
|
}
|
|
if (time_resource) {
|
|
unsigned int next_sec_ms;
|
|
|
|
do_time_observe_code(NULL);
|
|
|
|
/* need to wait until next second starts if wait_ms is too large */
|
|
coap_ticks(&now);
|
|
next_sec_ms = 1000 - (now % COAP_TICKS_PER_SECOND) *
|
|
1000 / COAP_TICKS_PER_SECOND;
|
|
if (next_sec_ms && next_sec_ms < wait_ms)
|
|
wait_ms = next_sec_ms;
|
|
}
|
|
}
|
|
#endif /* NUM_SERVER_THREADS */
|
|
exit_code = 0;
|
|
|
|
finish:
|
|
/* Clean up local usage */
|
|
if (keep_persist)
|
|
coap_persist_stop(ctx);
|
|
|
|
coap_free(ca_mem);
|
|
coap_free(cert_mem);
|
|
coap_free(key_mem);
|
|
coap_free(ca_mem_base);
|
|
coap_free(cert_mem_base);
|
|
coap_free(key_mem_base);
|
|
for (i = 0; i < valid_psk_snis.count; i++) {
|
|
free(valid_psk_snis.psk_sni_list[i].sni_match);
|
|
coap_delete_bin_const(valid_psk_snis.psk_sni_list[i].new_hint);
|
|
coap_delete_bin_const(valid_psk_snis.psk_sni_list[i].new_key);
|
|
}
|
|
if (valid_psk_snis.count)
|
|
free(valid_psk_snis.psk_sni_list);
|
|
|
|
for (i = 0; i < valid_ids.count; i++) {
|
|
free(valid_ids.id_list[i].hint_match);
|
|
coap_delete_bin_const(valid_ids.id_list[i].identity_match);
|
|
coap_delete_bin_const(valid_ids.id_list[i].new_key);
|
|
}
|
|
if (valid_ids.count)
|
|
free(valid_ids.id_list);
|
|
|
|
for (i = 0; i < valid_pki_snis.count; i++) {
|
|
free(valid_pki_snis.pki_sni_list[i].sni_match);
|
|
free(valid_pki_snis.pki_sni_list[i].new_cert);
|
|
free(valid_pki_snis.pki_sni_list[i].new_ca);
|
|
}
|
|
if (valid_pki_snis.count)
|
|
free(valid_pki_snis.pki_sni_list);
|
|
|
|
for (i = 0; i < (size_t)dynamic_count; i++) {
|
|
coap_delete_string(dynamic_entry[i].uri_path);
|
|
release_resource_data(NULL, dynamic_entry[i].value);
|
|
}
|
|
free(dynamic_entry);
|
|
release_resource_data(NULL, example_data_value);
|
|
#if COAP_PROXY_SUPPORT
|
|
free(reverse_proxy.entry);
|
|
free(forward_proxy.entry);
|
|
#if defined(_WIN32) && !defined(__MINGW32__)
|
|
#pragma warning( disable : 4090 )
|
|
#endif
|
|
coap_free(proxy_host_name_list);
|
|
#endif /* COAP_PROXY_SUPPORT */
|
|
if (oscore_seq_num_fp)
|
|
fclose(oscore_seq_num_fp);
|
|
|
|
/* Clean up library usage */
|
|
coap_free_context(ctx);
|
|
coap_cleanup();
|
|
|
|
return exit_code;
|
|
|
|
failed:
|
|
exit_code = 1;
|
|
goto finish;
|
|
}
|