From ec86e9d545049b0dc83cc6c5b9c7c66f74915f7f Mon Sep 17 00:00:00 2001 From: Sergey Poznyakoff Date: Sat, 29 Feb 2020 13:47:43 +0200 Subject: Improve authorization support. * examples/inspect: Special handling for "auth" objects. Print the rest of arrays as here documents. * src/json.c (json_object_filter): new function. * src/json.h (json_object_filter): new proto. * src/ping903.c (try_auth): Don't format HTTP responses if ret_val is NULL. (ept_ident, ept_config): Check individual object attributes using try_auth. Remove those not allowed by auth ACL. (cf_auth): Support for CF_SERIALIZE. --- README | 8 +-- examples/inspect | 35 ++++++++----- src/json.c | 38 ++++++++++++++ src/json.h | 3 ++ src/ping903.c | 153 +++++++++++++++++++++++++++++++++++++++++++++++++------ 5 files changed, 203 insertions(+), 34 deletions(-) diff --git a/README b/README index 15b6b33..2e9486f 100644 --- a/README +++ b/README @@ -364,10 +364,10 @@ without reloading the server. For the purpose of updating the IP list is sectioned in two parts: 1. Immutable IP addresses - These are IP addresses obtained from files supplied using the - "ip-list" keyword in the configuration file. These addresses - cannot be modified using the API described in this section. - An attempt to do so will return an error status. + These are IP addresses specified in the configuration file using + the "ip-list: statement. These addresses cannot be modified using + the API described in this section. An attempt to do so will return + an error status. 2. Mutable IP addresses. These are additional IP addresses configured via this API. diff --git a/examples/inspect b/examples/inspect index f9e21f2..7b10184 100755 --- a/examples/inspect +++ b/examples/inspect @@ -116,25 +116,32 @@ unless ($response->is_success) { my $resp = JSON->new->decode($response->decoded_content); foreach my $kw (grep { $_ ne 'ip-list' } sort keys %$resp) { my $val = $resp->{$kw} or next; - if (ref($val) eq 'ARRAY') { - foreach my $sv (@$val) { - print "$kw $sv\n"; + if ($kw eq 'auth') { + my $delim = ''; + foreach my $acl (@$val) { + print "${delim}auth $acl->{type} $acl->{method} $acl->{url}"; + if (exists($acl->{'passwd-file'})) { + print " $acl->{'passwd-file'}"; + if (exists($acl->{realm})) { + print " $acl->{realm}"; + } + } + $delim = "\n"; } } else { print "$kw "; - if (JSON::is_bool($val)) { + + if (ref($val) eq 'ARRAY') { + print "<{'ip-list'}}) { - print "ip-list <{'ip-list'}}) { - print " $ip\n"; } - print "EOF\n"; + print "\n"; } diff --git a/src/json.c b/src/json.c index 510c7ce..2b143ee 100644 --- a/src/json.c +++ b/src/json.c @@ -697,6 +697,44 @@ json_format_object(struct json_format *fmt, struct json_value *obj, } json_format_writec(fmt, '}'); } + +/* Removes from OBJ all pairs for which the predicate function PRED + returns 1. + */ +int +json_object_filter(struct json_value *obj, + int (*pred)(char const *, struct json_value *, void *), + void *data) +{ + struct json_object *op; + struct json_pair *p, *prev; + + if (obj->type != json_object) { + errno = EINVAL; + return -1; + } + op = obj->v.o; + if (!op->head) + return 0; + + prev = NULL; + for (p = op->head; p; ) { + struct json_pair *next = p->next; + if (pred(p->k, p->v, data)) { + if (prev) + prev->next = next; + else + op->head = next; + if (!next) + op->tail = prev; + free(p->k); + json_value_free(p->v); + } else + prev = p; + p = next; + } + return 0; +} /* Parser */ #define ISSPACE(c) ((c)==' '||(c)=='\t'||(c)=='\n'||(c)=='\r') diff --git a/src/json.h b/src/json.h index 5e320a1..b5fa9d3 100644 --- a/src/json.h +++ b/src/json.h @@ -84,6 +84,9 @@ int json_object_set(struct json_value *obj, char const *name, struct json_value *val); int json_object_get(struct json_value *obj, char const *name, struct json_value **retval); +int json_object_filter(struct json_value *obj, + int (*pred)(char const *, struct json_value *, void *), + void *data); struct json_value *json_new_array(void); static inline size_t json_array_length(struct json_value *j) { diff --git a/src/ping903.c b/src/ping903.c index 3de8e97..72c121d 100644 --- a/src/ping903.c +++ b/src/ping903.c @@ -294,6 +294,35 @@ httpd_json_response(struct MHD_Connection *conn, return ret; } +static int try_auth(struct MHD_Connection *conn, const char *url, + const char *method, int *ret_val); + +struct auth_flt_data +{ + struct MHD_Connection *conn; + const char *url; + const char *method; + int err; +}; + +static int +auth_flt(char const *kw, struct json_value *val, void *data) +{ + int rc; + struct auth_flt_data *adata = data; + char *url = malloc(strlen(adata->url) + strlen(kw) + 1); + if (!url) { + adata->err = 1; + return 0; + } + strcpy(url, adata->url); + strcat(url, "/"); + strcat(url, kw); + rc = try_auth(adata->conn, url, adata->method, NULL); + free(url); + return rc; +} + static struct json_value * ident_to_json(void) { @@ -351,7 +380,20 @@ ept_ident(struct MHD_Connection *conn, } json_value_free(obj); obj = cp; - } + } else { + struct auth_flt_data adata = { + .conn = conn, + .url = url, + .method = method, + .err = 0 + }; + if (json_object_filter(obj, auth_flt, &adata) || adata.err) { + int ret = http_response(conn, method, url, + MHD_HTTP_INTERNAL_SERVER_ERROR); + json_value_free(obj); + return ret; + } + } return httpd_json_response(conn, url, method, MHD_HTTP_OK, obj); } @@ -391,7 +433,20 @@ ept_config(struct MHD_Connection *conn, } json_value_free(val); val = cp; - } + } else { + struct auth_flt_data adata = { + .conn = conn, + .url = url, + .method = method, + .err = 0 + }; + if (json_object_filter(val, auth_flt, &adata) || adata.err) { + int ret = http_response(conn, method, url, + MHD_HTTP_INTERNAL_SERVER_ERROR); + json_value_free(val); + return ret; + } + } return httpd_json_response(conn, url, method, MHD_HTTP_OK, val); } @@ -607,8 +662,8 @@ struct auth_location { static struct auth_location *auth_head, *auth_tail; -int -cf_auth(int mode, union cf_callback_arg *arg, void *data) +static int +cf_auth_parse(union cf_callback_arg *arg, void *data) { int ac; char **av; @@ -618,10 +673,7 @@ cf_auth(int mode, union cf_callback_arg *arg, void *data) char *passwd_file = NULL; char *realm = NULL; - if (mode != CF_PARSE) - return CF_RET_IGNORE; - - // auth basic METHOD URL [FILE [REALM]] + // auth TYPE METHOD URL [FILE [REALM]] enum { i_type, i_method, i_url, i_file, i_realm }; switch (strsplit(arg->input.val, 5, &ac, &av, &endp)) { @@ -695,6 +747,65 @@ cf_auth(int mode, union cf_callback_arg *arg, void *data) return CF_RET_OK; } +static int +cf_auth_serialize(union cf_callback_arg *arg, void *data) +{ + struct json_value *ar, *obj = NULL, *jv = NULL; + struct auth_location *auth; + static char const *auth_type_str[] = { + [AUTH_NONE] = "none", + [AUTH_BASIC] = "basic" + }; + + if (!(ar = json_new_array())) + return CF_RET_FAIL; + + for (auth = auth_head; auth; auth = auth->next) { + if (!(obj = json_new_object())) + goto err; + if (!(jv = json_new_string(auth_type_str[auth->type])) + || json_object_set(obj, "type", jv)) + goto err; + if (!(jv = json_new_string(auth->url)) + || json_object_set(obj, "url", jv)) + goto err; + if (!(jv = json_new_string(auth->method)) + || json_object_set(obj, "method", jv)) + goto err; + if (auth->type == AUTH_BASIC) { + if (auth->passwd_file + && (!(jv = json_new_string(auth->passwd_file)) + || json_object_set(obj, "passwd-file", jv))) + goto err; + if (auth->realm + && (!(jv = json_new_string(auth->realm)) + || json_object_set(obj, "realm", jv))) + goto err; + } + if (json_array_append(ar, obj)) + goto err; + } + arg->output = ar; + return CF_RET_OK; +err: + json_value_free(jv); + json_value_free(obj); + json_value_free(ar); + return CF_RET_FAIL; +} + +int +cf_auth(int mode, union cf_callback_arg *arg, void *data) +{ + switch (mode) { + case CF_PARSE: + return cf_auth_parse(arg, data); + case CF_SERIALIZE: + return cf_auth_serialize(arg, data); + } + abort(); +} + #define WWW_AUTH_PFX "Basic realm=\"" #define WWW_AUTH_SFX "\"" @@ -765,8 +876,9 @@ try_auth(struct MHD_Connection *conn, const char *url, const char *method, --url_len; url_buf = malloc(url_len + 1); if (!url_buf) { - *ret_val = http_response(conn, method, url, - MHD_HTTP_INTERNAL_SERVER_ERROR); + if (ret_val) + *ret_val = http_response(conn, method, url, + MHD_HTTP_INTERNAL_SERVER_ERROR); return 1; } memcpy(url_buf, url, url_len); @@ -791,15 +903,21 @@ try_auth(struct MHD_Connection *conn, const char *url, const char *method, MHD_HEADER_KIND, MHD_HTTP_HEADER_AUTHORIZATION); if (!auth) { - *ret_val = http_unauthorized(conn, method, url, - loc->realm); + if (ret_val) + *ret_val = http_unauthorized(conn, + method, + url, + loc->realm); return 1; } switch (basicauth(loc->passwd_file, auth)) { case BASICAUTH_DENY: - case BASICAUTH_BAD_INPUT: - *ret_val = http_unauthorized(conn, method, url, - loc->realm); + case BASICAUTH_BAD_INPUT: + if (ret_val) + *ret_val = http_unauthorized(conn, + method, + url, + loc->realm); return 1; case BASICAUTH_ALLOW: return 0; @@ -808,7 +926,10 @@ try_auth(struct MHD_Connection *conn, const char *url, const char *method, loc->passwd_file, strerror(errno)); /* fall through */ default: - *ret_val = http_response(conn, method, url, + if (ret_val) + *ret_val = http_response(conn, + method, + url, MHD_HTTP_INTERNAL_SERVER_ERROR); return 1; } -- cgit v1.2.1