diff --git a/src/sysrest/configure.ac b/src/sysrest/configure.ac index 16b7e3e1..5c0324b0 100644 --- a/src/sysrest/configure.ac +++ b/src/sysrest/configure.ac @@ -17,8 +17,9 @@ AC_PROG_INSTALL # Check for pkg-config first, warn if it's not installed PKG_PROG_PKG_CONFIG -PKG_CHECK_MODULES([sysrepo], [sysrepo >= 2.2.36]) PKG_CHECK_MODULES([fcgi], [fcgi >= 2.4.2]) +PKG_CHECK_MODULES([sysrepo], [sysrepo >= 2.2.36]) +PKG_CHECK_MODULES([libyang], [libyang >= 2.1.55]) # Plugin installation path for sysrepo-plugind dnl PKG_CHECK_VAR([srpdplugindir], [sysrepo], [SRPD_PLUGINS_PATH]) diff --git a/src/sysrest/src/Makefile.am b/src/sysrest/src/Makefile.am index 34439ecf..168296cf 100644 --- a/src/sysrest/src/Makefile.am +++ b/src/sysrest/src/Makefile.am @@ -2,5 +2,5 @@ sbin_PROGRAMS = sysrest sysrest_SOURCES = main.c sysrest_CFLAGS = -Wall -Wextra -Werror -Wno-unused-parameter \ - $(sysrepo_CFLAGS) $(fcgi_CFLAGS) -sysrest_LDADD = $(sysrepo_LIBS) $(fcgi_LIBS) + $(libyang_CFLAGS) $(sysrepo_CFLAGS) $(fcgi_CFLAGS) +sysrest_LDADD = $(libyang_LIBS) $(sysrepo_LIBS) $(fcgi_LIBS) diff --git a/src/sysrest/src/main.c b/src/sysrest/src/main.c index 596338be..156df4d9 100644 --- a/src/sysrest/src/main.c +++ b/src/sysrest/src/main.c @@ -9,8 +9,120 @@ #include +#include +#include + +#include +#include + static const char *rootpath = "/restconf/"; +enum http_method { + HTTP_GET, + HTTP_HEAD, + HTTP_PATCH, + HTTP_POST, + HTTP_PUT, + + HTTP_INVALID +}; + +/* struct memstream { */ +/* FILE *fp; */ +/* char *buf; */ +/* size_t len; */ +/* }; */ + +/* struct res { */ +/* struct memstream head; */ +/* struct memstream entity; */ +/* }; */ + +/* int res_push_header(struct res *res, const char *key, const char *valfmt, ...) */ +/* { */ +/* fprintf */ +/* } */ + +/* int res_respond_to(struct res *res, FCGX_Request *req) */ +/* { */ + +/* } */ + +/* int res_init(struct res *res) */ +/* { */ +/* res->head.fp = open_memstream(&res->head.buf, &res->head.len); */ +/* if (!res->head.fp) */ +/* goto err; */ + +/* res->entity.fp = open_memstream(&res->entity.buf, &res->entity.len); */ +/* if (!res->entity.fp) */ +/* goto err_free_head; */ + +/* return 0; */ + +/* err_free_head: */ +/* fclose(res->head.fp); */ +/* err: */ +/* return -ENOMEM; */ +/* } */ + +struct req { + FCGX_Request r; + + enum http_method method; + char *uri; + + union { + struct { + char *path; + } data; + }; +}; + +static enum http_method req_parse_method(struct req *req) +{ + const char *str = FCGX_GetParam("REQUEST_METHOD", req->r.envp); + + if (!str) + return HTTP_INVALID; + + if (!strcmp(str, "GET")) + return HTTP_GET; + if (!strcmp(str, "HEAD")) + return HTTP_HEAD; + if (!strcmp(str, "PATCH")) + return HTTP_PATCH; + if (!strcmp(str, "POST")) + return HTTP_POST; + if (!strcmp(str, "PUT")) + return HTTP_PUT; + + return HTTP_INVALID; +} + +static int req_init(struct req *req) +{ + const char *uri; + + req->method = req_parse_method(req); + + uri = FCGX_GetParam("REQUEST_URI", req->r.envp); + if (!uri) + return -EINVAL; + + req->uri = strdup(uri); + if (!req->uri) + return -EINVAL; + + return 0; +} + +static int req_fini(struct req *req) +{ + free(req->uri); + return 0; +} + static void dumpenv(char **envp) { fprintf(stderr, "ENV:\n"); @@ -19,16 +131,19 @@ static void dumpenv(char **envp) } } -int serve_host_meta(FCGX_Request *req) +int serve_host_meta(struct req *req) { - const char *method; size_t len; char *buf; FILE *fp; - method = FCGX_GetParam("REQUEST_METHOD", req->envp); - if (!method || (strcmp(method, "GET") && strcmp(method, "HEAD"))) + switch (req->method) { + case HTTP_GET: + case HTTP_HEAD: + break; + default: return -EINVAL; + } fp = open_memstream(&buf, &len); if (!fp) @@ -40,61 +155,203 @@ int serve_host_meta(FCGX_Request *req) "", rootpath); fclose(fp); - FCGX_FPrintF(req->out, "Content-Type: application/xrd+xml\r\n"); - FCGX_FPrintF(req->out, "Content-Length: %zd\r\n", len); - FCGX_FPrintF(req->out, "Connection: close\r\n"); - FCGX_PutS("\r\n", req->out); + FCGX_FPrintF(req->r.out, "Content-Type: application/xrd+xml\r\n"); + FCGX_FPrintF(req->r.out, "Content-Length: %zd\r\n", len); + FCGX_FPrintF(req->r.out, "Connection: close\r\n"); + FCGX_PutS("\r\n", req->r.out); - if (!strcmp(method, "GET")) - FCGX_PutStr(buf, len, req->out); + if (req->method == HTTP_GET) + FCGX_PutStr(buf, len, req->r.out); free(buf); - FCGX_Finish_r(req); + FCGX_Finish_r(&req->r); return 0; } -int serve_restconf(FCGX_Request *req) +int hex2bin(char *hex) { - FCGX_FPrintF(req->out, "Content-type: text/html\r\n\r\n"); - FCGX_FPrintF(req->out, "RESTCONF\r\n"); - FCGX_Finish_r(req); + int i, out; + + for (i = 0, out = 0; i < 2; i++) { + out <<= 4; + + if (hex[i] >= '0' && hex[i] <= '9') + out |= hex[0] - '0'; + else if (hex[i] >= 'a' && hex[i] <= 'f') + out |= hex[0] - 'a'; + else if (hex[i] >= 'A' && hex[i] <= 'F') + out |= hex[0] - 'A'; + else + return -EINVAL; + } + + if (out < 0 || out >= 0x80) + /* No UTF-8 support */ + return -EINVAL; + + return out; +} + +int unpercent(char *tok) +{ + char *in, *out, *end; + int escaped; + + in = out = tok; + for (; *in; in++, out++) { + if (*in != '%') { + *out = *in; + continue; + } + + escaped = hex2bin(in + 1); + if (escaped < 0) + return escaped; + + *out = escaped; + in += 2; + } + + *out = '\0'; return 0; } +int xpath_from_uri(struct ly_ctx *ly, char *uri, char **xpathp) +{ + +} + +int uri_resolve_r(sr_data_t **nodep, char **urip, char **toksave) +{ + char *tok; + int err; + + tok = strtok_r(*toksave ? NULL : *urip, "/", toksave); + if (!tok) + return 0; + + err = unpercent(tok); + if (err) + return err; + + +} + +int uri_resolve(sr_session_ctx_t *sess, char *uri, struct lyd_node **nodep) +{ + sr_data_t *node; + char *toksave = NULL; + int err; + + err = sr_get_subtree(sess, "/", 0, &node); + if (err) + return err; + + err = uri_resolve_r(&node, &uri, &toksave); + if (err) + return err; + + tok = srst_uritok(uri); + if (!tok) + return 0; + +} + +int serve_restconf_data(struct req *req) +{ + sr_session_ctx_t *sess; + sr_conn_ctx_t *conn; + sr_data_t *data; + int err = 0; + + FILE *fp; + char *buf; + size_t len = 0; + + if (req->method != HTTP_GET) { + err = -EINVAL; + goto err; + } + + if (sr_connect(SR_CONN_DEFAULT, &conn)) { + err = -EIO; + goto err; + } + + if (sr_session_start(conn, SR_DS_RUNNING, &sess)) { + err = -EIO; + goto err_disconnect; + } + + + sr_get_data(sess, req->data.path, 0, 0, 0, &data); + + fp = open_memstream(&buf, &len); + lyd_print_file(fp, data ? data->tree : NULL, LYD_JSON, LYD_PRINT_WITHSIBLINGS); + fclose(fp); + + FCGX_FPrintF(req->r.out, "Content-Type: application/yang-data+json\r\n"); + FCGX_FPrintF(req->r.out, "Content-Length: %zd\r\n", len); + FCGX_FPrintF(req->r.out, "Connection: close\r\n"); + FCGX_PutS("\r\n", req->r.out); + + FCGX_PutStr(buf, len, req->r.out); + free(buf); + + FCGX_Finish_r(&req->r); + return 0; + +err_disconnect: + sr_disconnect(conn); +err: + return err; +} + +int serve_restconf(struct req *req) +{ + char *uri = req->uri + strlen(rootpath); + + if (!strncmp(uri, "data/", strlen("data/"))) { + req->data.path = uri + strlen("data/"); + return serve_restconf_data(req); + } + + return -EINVAL; +} + int serve(int sd) { - FCGX_Request req; - const char *uri; + struct req req; int err = 0; - for (FCGX_InitRequest(&req, sd, 0); - FCGX_Accept_r(&req) == 0; - FCGX_InitRequest(&req, sd, 0)) { - dumpenv(req.envp); - - uri = FCGX_GetParam("REQUEST_URI", req.envp); - if (!uri) + for (FCGX_InitRequest(&req.r, sd, 0); + FCGX_Accept_r(&req.r) == 0; + FCGX_InitRequest(&req.r, sd, 0)) { + if (req_init(&req)) continue; - if (!strcmp(uri, "/.well-known/host-meta")) + dumpenv(req.r.envp); + + if (!strcmp(req.uri, "/.well-known/host-meta")) err = serve_host_meta(&req); - else if (!strncmp(uri, rootpath, strlen(rootpath))) + else if (!strncmp(req.uri, rootpath, strlen(rootpath))) err = serve_restconf(&req); else err = -EINVAL; if (err) { - FCGX_FPrintF(req.out, "Status: 400 Bad Request\r\n"); - FCGX_FPrintF(req.out, "Content-Type: text/html\r\n"); - FCGX_FPrintF(req.out, "Connection: close\r\n"); - FCGX_PutS("\r\n", req.out); - FCGX_Finish_r(&req); - break; + FCGX_FPrintF(req.r.out, "Status: 400 Bad Request\r\n"); + FCGX_FPrintF(req.r.out, "Content-Type: text/html\r\n"); + FCGX_FPrintF(req.r.out, "Connection: close\r\n"); + FCGX_PutS("\r\n", req.r.out); + FCGX_Finish_r(&req.r); } + + req_fini(&req); } - FCGX_Free(&req, sd); + FCGX_Free(&req.r, sd); if (err) fprintf(stderr, "Unable to handle request: %d\n", err); diff --git a/src/sysrest/src/util.c b/src/sysrest/src/util.c new file mode 100644 index 00000000..ccbb4184 --- /dev/null +++ b/src/sysrest/src/util.c @@ -0,0 +1,105 @@ +#include +#include +#include +#include +#include +#include + +#include + +int hex2bin(char *hex) +{ + int i, out; + + for (i = 0, out = 0; i < 2; i++) { + out <<= 4; + + if (hex[i] >= '0' && hex[i] <= '9') + out |= hex[0] - '0'; + else if (hex[i] >= 'a' && hex[i] <= 'f') + out |= hex[0] - 'a'; + else if (hex[i] >= 'A' && hex[i] <= 'F') + out |= hex[0] - 'A'; + else + return -EINVAL; + } + + if (out < 0 || out >= 0x80) + /* No UTF-8 support */ + return -EINVAL; + + return out; +} + +int unpercent(char *text) +{ + char *in, *out, *end; + int escaped; + + in = out = text; + for (; *in; in++, out++) { + if (*in != '%') { + *out = *in; + continue; + } + + escaped = hex2bin(in + 1); + if (escaped < 0) + return escaped; + + *out = escaped; + in += 2; + } + + *out = '\0'; + return 0; +} + +struct uri_node { + char *module; + char *name; + char *keyv[8]; +}; + +int uri_node_from_seg(char *seg, struct uri_node *n) +{ + int keyc = 0; + char *delim; + + memset(n, 0, sizeof(*n)); + + delim = strchr(seg, ':'); + if (delim) { + *delim++ = '\0'; + + n->module = seg; + n->name = seg = delim; + } else { + n->name = seg; + } + + delim = strchr(seg, '='); + if (!delim) + return 0; + + do { + if (keyc >= 7) + return -EINVAL; + + *delim++ = '\0'; + n->keyv[keyc++] = seg = delim; + } while ((delim = strchr(seg, ','); keyc++)); + + return 0; +} + +int xpath_from_uri(struct ly_ctx *ly, char *uri, char **xpathp) +{ + +} + + +int main(void) +{ + +}