#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <signal.h>
#ifdef _WIN32
#define strcasecmp _stricmp
#include "getopt.c"
#if !defined(S_ISDIR)
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
#endif
#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>
#endif
#include <coap3/coap.h>
#define COAP_RESOURCE_CHECK_TIME 2
#define RD_ROOT_STR "rd"
#define RD_ROOT_SIZE 2
static char *cert_file = NULL;
static char *ca_file = NULL;
static char *root_ca_file = NULL;
static int verify_peer_cert = 1;
#define MAX_KEY 64
static uint8_t key[MAX_KEY];
static ssize_t key_length = 0;
static int key_defined = 0;
static const char *hint = "CoAP";
#ifndef min
#define min(a,b) ((a) < (b) ? (a) : (b))
#endif
typedef struct rd_t {
size_t etag_len;
unsigned char etag[8];
coap_string_t data;
} rd_t;
rd_t *resources = NULL;
static ssize_t
cmdline_read_key(char *arg, unsigned char *buf, size_t maxlen) {
size_t len = strnlen(arg, maxlen);
if (len) {
memcpy(buf, arg, len);
return len;
}
return -1;
}
static inline rd_t *
rd_new(void) {
rd_t *rd;
rd = (rd_t *)coap_malloc(sizeof(rd_t));
if (rd)
memset(rd, 0, sizeof(rd_t));
return rd;
}
static void
rd_delete(rd_t *rd) {
if (rd) {
coap_free(rd->data.s);
coap_free(rd);
}
}
static void
resource_rd_delete(void *ptr) {
rd_delete(ptr);
}
static int quit = 0;
static void
handle_sigint(int signum COAP_UNUSED) {
quit = 1;
}
static void
hnd_get_resource(coap_resource_t *resource,
coap_session_t *session COAP_UNUSED,
const coap_pdu_t *request COAP_UNUSED,
const coap_string_t *query COAP_UNUSED,
coap_pdu_t *response) {
rd_t *rd = coap_resource_get_userdata(resource);
unsigned char buf[3];
coap_pdu_set_code(response, COAP_RESPONSE_CODE_CONTENT);
coap_add_option(response,
COAP_OPTION_CONTENT_TYPE,
coap_encode_var_safe(buf, sizeof(buf),
COAP_MEDIATYPE_APPLICATION_LINK_FORMAT),
buf);
if (rd && rd->etag_len)
coap_add_option(response, COAP_OPTION_ETAG, rd->etag_len, rd->etag);
if (rd && rd->data.s)
coap_add_data(response, rd->data.length, rd->data.s);
}
static void
hnd_put_resource(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) {
#if 1
coap_pdu_set_code(response, COAP_RESPONSE_CODE_NOT_IMPLEMENTED);
#else#endif
}
static void
hnd_delete_resource(coap_resource_t *resource,
coap_session_t *session COAP_UNUSED,
const coap_pdu_t *request COAP_UNUSED,
const coap_string_t *query COAP_UNUSED,
coap_pdu_t *response) {
rd_t *rd = coap_resource_get_userdata(resource);
if (rd) {
rd_delete(rd);
}
coap_delete_resource(NULL, resource);
coap_pdu_set_code(response, COAP_RESPONSE_CODE_DELETED);
}
static void
hnd_get_rd(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) {
unsigned char buf[3];
coap_pdu_set_code(response, COAP_RESPONSE_CODE_CONTENT);
coap_add_option(response,
COAP_OPTION_CONTENT_TYPE,
coap_encode_var_safe(buf, sizeof(buf),
COAP_MEDIATYPE_APPLICATION_LINK_FORMAT),
buf);
coap_add_option(response,
COAP_OPTION_MAXAGE,
coap_encode_var_safe(buf, sizeof(buf), 0x2ffff), buf);
}
static int
parse_param(const uint8_t *search,
size_t search_len,
unsigned char *data,
size_t data_len,
coap_string_t *result) {
if (result)
memset(result, 0, sizeof(coap_string_t));
if (!search_len)
return 0;
while (search_len <= data_len) {
if (memcmp(search, data, search_len) == 0) {
data += search_len;
data_len -= search_len;
if (!data_len || *data == '=' || *data == '&') {
while (data_len && *data != '=') {
++data; --data_len;
}
if (data_len > 1 && result) {
result->s = ++data;
while (--data_len && *data != '&') {
++data; result->length++;
}
}
return 1;
}
}
while (--data_len && *data++ != '&')
;
}
return 0;
}
static void
add_source_address(coap_resource_t *resource,
const coap_address_t *peer) {
#define BUFSIZE 64
char *buf;
size_t n = 1;
coap_str_const_t attr_val;
buf = (char *)coap_malloc(BUFSIZE);
if (!buf)
return;
buf[0] = '"';
switch(peer->addr.sa.sa_family) {
case AF_INET:
break;
case AF_INET6:
n += snprintf(buf + n, BUFSIZE - n,
"[%02x%02x:%02x%02x:%02x%02x:%02x%02x" \
":%02x%02x:%02x%02x:%02x%02x:%02x%02x]",
peer->addr.sin6.sin6_addr.s6_addr[0],
peer->addr.sin6.sin6_addr.s6_addr[1],
peer->addr.sin6.sin6_addr.s6_addr[2],
peer->addr.sin6.sin6_addr.s6_addr[3],
peer->addr.sin6.sin6_addr.s6_addr[4],
peer->addr.sin6.sin6_addr.s6_addr[5],
peer->addr.sin6.sin6_addr.s6_addr[6],
peer->addr.sin6.sin6_addr.s6_addr[7],
peer->addr.sin6.sin6_addr.s6_addr[8],
peer->addr.sin6.sin6_addr.s6_addr[9],
peer->addr.sin6.sin6_addr.s6_addr[10],
peer->addr.sin6.sin6_addr.s6_addr[11],
peer->addr.sin6.sin6_addr.s6_addr[12],
peer->addr.sin6.sin6_addr.s6_addr[13],
peer->addr.sin6.sin6_addr.s6_addr[14],
peer->addr.sin6.sin6_addr.s6_addr[15]);
if (peer->addr.sin6.sin6_port != htons(COAP_DEFAULT_PORT)) {
n +=
snprintf(buf + n, BUFSIZE - n, ":%d", peer->addr.sin6.sin6_port);
}
break;
default:
;
}
if (n < BUFSIZE)
buf[n++] = '"';
attr_val.s = (const uint8_t *)buf;
attr_val.length = n;
coap_add_attr(resource,
coap_make_str_const("A"),
&attr_val,
0);
coap_free(buf);
#undef BUFSIZE
}
static rd_t *
make_rd(const coap_pdu_t *pdu) {
rd_t *rd;
const uint8_t *data;
coap_opt_iterator_t opt_iter;
coap_opt_t *etag;
rd = rd_new();
if (!rd) {
coap_log(LOG_DEBUG, "hnd_get_rd: cannot allocate storage for rd\n");
return NULL;
}
if (coap_get_data(pdu, &rd->data.length, &data)) {
rd->data.s = (unsigned char *)coap_malloc(rd->data.length);
if (!rd->data.s) {
coap_log(LOG_DEBUG, "hnd_get_rd: cannot allocate storage for rd->data\n");
rd_delete(rd);
return NULL;
}
memcpy(rd->data.s, data, rd->data.length);
}
etag = coap_check_option(pdu, COAP_OPTION_ETAG, &opt_iter);
if (etag) {
rd->etag_len = min(coap_opt_length(etag), sizeof(rd->etag));
memcpy(rd->etag, coap_opt_value(etag), rd->etag_len);
}
return rd;
}
static void
hnd_post_rd(coap_resource_t *resource COAP_UNUSED,
coap_session_t *session,
const coap_pdu_t *request,
const coap_string_t *query COAP_UNUSED,
coap_pdu_t *response) {
coap_resource_t *r;
#define LOCSIZE 68
unsigned char *loc;
size_t loc_size;
coap_string_t h = {0, NULL}, ins = {0, NULL}, rt = {0, NULL}, lt = {0, NULL};
unsigned char *buf;
coap_str_const_t attr_val;
coap_str_const_t resource_val;
loc = (unsigned char *)coap_malloc(LOCSIZE);
if (!loc) {
coap_pdu_set_code(response, COAP_RESPONSE_CODE_INTERNAL_ERROR);
return;
}
memcpy(loc, RD_ROOT_STR, RD_ROOT_SIZE);
loc_size = RD_ROOT_SIZE;
loc[loc_size++] = '/';
if (query) {
parse_param((const uint8_t *)"h", 1, query->s, query->length, &h);
parse_param((const uint8_t *)"ins", 3, query->s, query->length, &ins);
parse_param((const uint8_t *)"lt", 2, query->s, query->length, <);
parse_param((const uint8_t *)"rt", 2, query->s, query->length, &rt);
}
if (h.length) {
memcpy(loc + loc_size, h.s, min(h.length, LOCSIZE - loc_size - 1));
loc_size += min(h.length, LOCSIZE - loc_size - 1);
if (ins.length && loc_size > 1) {
loc[loc_size++] = '-';
memcpy((char *)(loc + loc_size),
ins.s, min(ins.length, LOCSIZE - loc_size - 1));
loc_size += min(ins.length, LOCSIZE - loc_size - 1);
}
} else {
loc_size +=
snprintf((char *)(loc + loc_size), LOCSIZE - loc_size - 1,
"%x", coap_pdu_get_mid(request));
if (loc_size > 1) {
if (ins.length) {
loc[loc_size++] = '-';
memcpy((char *)(loc + loc_size),
ins.s,
min(ins.length, LOCSIZE - loc_size - 1));
loc_size += min(ins.length, LOCSIZE - loc_size - 1);
} else {
coap_tick_t now;
coap_ticks(&now);
loc_size += snprintf((char *)(loc + loc_size),
LOCSIZE - loc_size - 1,
"-%x",
(unsigned int)(now & (unsigned int)-1));
}
}
}
resource_val.s = loc;
resource_val.length = loc_size;
r = coap_resource_init(&resource_val, 0);
coap_register_request_handler(r, COAP_REQUEST_GET, hnd_get_resource);
coap_register_request_handler(r, COAP_REQUEST_PUT, hnd_put_resource);
coap_register_request_handler(r, COAP_REQUEST_DELETE, hnd_delete_resource);
if (ins.s) {
buf = (unsigned char *)coap_malloc(ins.length + 2);
if (buf) {
buf[0] = '"';
memcpy(buf + 1, ins.s, ins.length);
buf[ins.length + 1] = '"';
attr_val.s = buf;
attr_val.length = ins.length + 2;
coap_add_attr(r,
coap_make_str_const("ins"),
&attr_val,
0);
coap_free(buf);
}
}
if (rt.s) {
buf = (unsigned char *)coap_malloc(rt.length + 2);
if (buf) {
buf[0] = '"';
memcpy(buf + 1, rt.s, rt.length);
buf[rt.length + 1] = '"';
attr_val.s = buf;
attr_val.length = rt.length + 2;
coap_add_attr(r,
coap_make_str_const("rt"),
&attr_val,
0);
coap_free(buf);
}
}
add_source_address(r, coap_session_get_addr_remote(session));
{
rd_t *rd;
rd = make_rd(request);
if (rd) {
coap_resource_set_userdata(r, rd);
} else {
}
}
coap_add_resource(coap_session_get_context(session), r);
coap_pdu_set_code(response, COAP_RESPONSE_CODE_CREATED);
{
unsigned char _b[LOCSIZE];
unsigned char *b = _b;
size_t buflen = sizeof(_b);
int nseg;
nseg = coap_split_path(loc, loc_size, b, &buflen);
while (nseg--) {
coap_add_option(response,
COAP_OPTION_LOCATION_PATH,
coap_opt_length(b),
coap_opt_value(b));
b += coap_opt_size(b);
}
}
coap_free(loc);
}
static void
init_resources(coap_context_t *ctx) {
coap_resource_t *r;
r = coap_resource_init(coap_make_str_const(RD_ROOT_STR), 0);
coap_register_request_handler(r, COAP_REQUEST_GET, hnd_get_rd);
coap_register_request_handler(r, COAP_REQUEST_POST, hnd_post_rd);
coap_add_attr(r, coap_make_str_const("ct"), coap_make_str_const("40"), 0);
coap_add_attr(r, coap_make_str_const("rt"), coap_make_str_const("\"core.rd\""), 0);
coap_add_attr(r, coap_make_str_const("ins"), coap_make_str_const("\"default\""), 0);
coap_add_resource(ctx, r);
coap_resource_release_userdata_handler(ctx, resource_rd_delete);
}
static void
usage( const char *program, const char *version) {
const char *p;
char buffer[72];
const char *lib_build = coap_package_build();
p = strrchr( program, '/' );
if ( p )
program = ++p;
fprintf( stderr, "%s v%s -- CoRE Resource Directory implementation\n"
"(c) 2011-2012,2019-2022 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 [-g group] [-G group_if] [-p port] [-v num] [-A address]\n"
"\t [[-h hint] [-k key]]\n"
"\t [[-c certfile] [-C cafile] [-n] [-R trust_casfile]]\n"
"General Options\n"
"\t-g group\tJoin the given multicast group.\n"
"\t \t\tNote: DTLS over multicast is not currently supported\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-p port\t\tListen on specified port\n"
"\t-v num \t\tVerbosity level (default 3, maximum is 9). Above 7,\n"
"\t \t\tthere is increased verbosity in GnuTLS and OpenSSL logging\n"
"\t-A address\tInterface address to bind to\n"
"PSK Options (if supported by underlying (D)TLS library)\n"
"\t-h hint\t\tIdentity Hint. Default is CoAP. Zero length is no hint\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\n"
"\t \t\tfor both PSK and PKI to be concurrently supported\n"
"PKI Options (if supported by underlying (D)TLS library)\n"
"\t-c certfile\tPEM file containing both CERTIFICATE and PRIVATE KEY\n"
"\t \t\tThis argument requires (D)TLS with PKI to be available\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 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 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 will trigger the\n"
"\t \t\tvalidation of the client certificate unless overridden\n"
"\t \t\tby the -n option\n"
"\t-R trust_casfile\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 will trigger the\n"
"\t \t\tvalidation of the client certificate unless overridden\n"
"\t \t\tby the -n option\n"
,
program);
}
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(LOG_DEBUG,
"(D)TLS not enabled as neither -k or -c options specified\n");
}
}
if (cert_file) {
coap_dtls_pki_t dtls_pki;
memset (&dtls_pki, 0, sizeof(dtls_pki));
dtls_pki.version = COAP_DTLS_PKI_SETUP_VERSION;
if (ca_file || root_ca_file) {
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.validate_cn_call_back = NULL;
dtls_pki.cn_call_back_arg = NULL;
dtls_pki.validate_sni_call_back = NULL;
dtls_pki.sni_call_back_arg = NULL;
}
dtls_pki.pki_key.key_type = COAP_PKI_KEY_PEM;
dtls_pki.pki_key.key.pem.public_cert = cert_file;
dtls_pki.pki_key.key.pem.private_key = cert_file;
dtls_pki.pki_key.key.pem.ca_file = ca_file;
if (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);
}
}
coap_context_set_pki(ctx, &dtls_pki);
}
if (key_defined) {
coap_dtls_spsk_t dtls_psk;
memset (&dtls_psk, 0, sizeof(dtls_psk));
dtls_psk.version = COAP_DTLS_SPSK_SETUP_VERSION;
dtls_psk.validate_id_call_back = NULL;
dtls_psk.validate_sni_call_back = NULL;
dtls_psk.psk_info.hint.s = (const uint8_t *)hint;
dtls_psk.psk_info.hint.length = hint ? strlen(hint) : 0;
dtls_psk.psk_info.key.s = key;
dtls_psk.psk_info.key.length = key_length;
coap_context_set_psk2(ctx, &dtls_psk);
}
}
static coap_context_t *
get_context(const char *node, const char *port) {
coap_context_t *ctx = NULL;
int s;
struct addrinfo hints;
struct addrinfo *result, *rp;
ctx = coap_new_context(NULL);
if (!ctx) {
return NULL;
}
fill_keystore(ctx);
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
s = getaddrinfo(node, port, &hints, &result);
if ( s != 0 ) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
coap_free_context(ctx);
return NULL;
}
for (rp = result; rp != NULL; rp = rp->ai_next) {
coap_address_t addr, addrs;
coap_endpoint_t *ep_udp = NULL, *ep_dtls = NULL, *ep_tcp = NULL, *ep_tls = NULL;
if (rp->ai_addrlen <= (socklen_t)sizeof(addr.addr)) {
coap_address_init(&addr);
addr.size = (socklen_t)rp->ai_addrlen;
memcpy(&addr.addr, rp->ai_addr, rp->ai_addrlen);
addrs = addr;
if (addr.addr.sa.sa_family == AF_INET) {
uint16_t temp = ntohs(addr.addr.sin.sin_port) + 1;
addrs.addr.sin.sin_port = htons(temp);
} else if (addr.addr.sa.sa_family == AF_INET6) {
uint16_t temp = ntohs(addr.addr.sin6.sin6_port) + 1;
addrs.addr.sin6.sin6_port = htons(temp);
} else {
goto finish;
}
ep_udp = coap_new_endpoint(ctx, &addr, COAP_PROTO_UDP);
if (ep_udp) {
if (coap_dtls_is_supported() && (key_defined || cert_file)) {
ep_dtls = coap_new_endpoint(ctx, &addrs, COAP_PROTO_DTLS);
if (!ep_dtls)
coap_log(LOG_CRIT, "cannot create DTLS endpoint\n");
}
} else {
coap_log(LOG_CRIT, "cannot create UDP endpoint\n");
continue;
}
ep_tcp = coap_new_endpoint(ctx, &addr, COAP_PROTO_TCP);
if (ep_tcp) {
if (coap_tls_is_supported() && (key_defined || cert_file)) {
ep_tls = coap_new_endpoint(ctx, &addrs, COAP_PROTO_TLS);
if (!ep_tls)
coap_log(LOG_CRIT, "cannot create TLS endpoint\n");
}
} else {
coap_log(LOG_CRIT, "cannot create TCP endpoint\n");
}
if (ep_udp)
goto finish;
}
}
fprintf(stderr, "no context available for interface '%s'\n", node);
finish:
freeaddrinfo(result);
return ctx;
}
int
main(int argc, char **argv) {
coap_context_t *ctx;
int result;
char addr_str[NI_MAXHOST] = "::";
char port_str[NI_MAXSERV] = "5683";
char *group = NULL;
char *group_if = NULL;
int opt;
coap_log_t log_level = LOG_WARNING;
#ifndef _WIN32
struct sigaction sa;
#endif
while ((opt = getopt(argc, argv, "A:c:C:g:G:h:k:n:R:p:v:")) != -1) {
switch (opt) {
case 'A' :
strncpy(addr_str, optarg, NI_MAXHOST-1);
addr_str[NI_MAXHOST - 1] = '\0';
break;
case 'c' :
cert_file = optarg;
break;
case 'C' :
ca_file = optarg;
break;
case 'g' :
group = optarg;
break;
case 'G' :
group_if = optarg;
break;
case 'h' :
if (!optarg[0]) {
hint = NULL;
break;
}
hint = optarg;
break;
case 'k' :
key_length = cmdline_read_key(optarg, key, MAX_KEY);
if (key_length < 0) {
coap_log( LOG_CRIT, "Invalid Pre-Shared Key specified\n" );
break;
}
key_defined = 1;
break;
case 'n':
verify_peer_cert = 0;
break;
case 'R' :
root_ca_file = optarg;
break;
case 'p' :
strncpy(port_str, optarg, NI_MAXSERV-1);
port_str[NI_MAXSERV - 1] = '\0';
break;
case 'v' :
log_level = strtol(optarg, NULL, 10);
break;
default:
usage( argv[0], LIBCOAP_PACKAGE_VERSION );
exit( 1 );
}
}
coap_startup();
coap_dtls_set_log_level(log_level);
coap_set_log_level(log_level);
ctx = get_context(addr_str, port_str);
if (!ctx)
return -1;
if (group)
coap_join_mcast_group_intf(ctx, group, group_if);
init_resources(ctx);
#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 = SIG_IGN;
sigaction (SIGPIPE, &sa, NULL);
#endif
while ( !quit ) {
result = coap_io_process( ctx, COAP_RESOURCE_CHECK_TIME * 1000 );
if ( result >= 0 ) {
}
}
coap_free_context( ctx );
coap_cleanup();
return 0;
}