]> git.sesse.net Git - itkacl/blobdiff - itkacl-2.2/itkacl.c
Add an API for keeping a permanent library context, for performance reasons
[itkacl] / itkacl-2.2 / itkacl.c
diff --git a/itkacl-2.2/itkacl.c b/itkacl-2.2/itkacl.c
new file mode 100644 (file)
index 0000000..d7bdfa0
--- /dev/null
@@ -0,0 +1,317 @@
+/*
+ * ITKACL control library.
+ *
+ * (C) 2004-2022 Steinar H. Gunderson
+ * GPL, v2.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <errno.h>
+#include <ctype.h>
+#include <unbound.h>
+
+#include "itkacl.h"
+
+struct itkacl_config {
+       char nszone[256];
+       int require_dnssec;
+       char dnssec_public_key[256];
+};
+
+struct itkacl_ctx {
+       struct itkacl_config config;
+       struct ub_ctx *ubctx;
+};
+
+#define CONFIG_FILENAME "/etc/itkacl.conf"
+
+static int itkacl_read_config(const char * const filename,
+                              struct itkacl_config *config,
+                              char *errmsg, size_t errmsg_size)
+{
+       FILE *fp;
+       int lineno = 0;
+
+       /* Defaults. */
+       strcpy(config->nszone, "");
+       config->require_dnssec = 0;
+       strcpy(config->dnssec_public_key, "");
+
+       fp = fopen(CONFIG_FILENAME, "r");
+       if (fp == NULL) {
+               if (errmsg)
+                       snprintf(errmsg, errmsg_size, "%s: %s",
+                                CONFIG_FILENAME, strerror(errno));
+               return -1;
+       }
+
+       while (!feof(fp)) {
+               char line[256], arg[256], *ptr;
+       
+               if (fgets(line, sizeof(line), fp) == NULL) {
+                       break;
+               }
+               ++lineno;
+
+               /* Remove trailing newlines and then comments. */
+               ptr = strchr(line, '\n');
+               if (ptr != NULL)
+                       *ptr = 0;
+
+               ptr = strchr(line, '\r');
+               if (ptr != NULL)
+                       *ptr = 0;
+
+               ptr = strchr(line, '#');
+               if (ptr != NULL)
+                       *ptr = 0;
+
+               /* Remove trailing whitespace, if any. */
+               ptr = line + strlen(line) - 1;
+               while (ptr >= line && isspace(*ptr))
+                       *ptr-- = 0;
+
+               /* Skip lines that now ended up blank. */
+               if (line[0] == 0)
+                       continue;
+
+               if (sscanf(line, "zone %255s", arg) == 1) {
+                       strcpy(config->nszone, arg);
+                       continue;
+               }
+               if (strcmp(line, "require-dnssec") == 0) {
+                       config->require_dnssec = 1;
+                       continue;
+               }
+               if (sscanf(line, "dnssec-public-key %255s", arg) == 1) {
+                       strcpy(config->dnssec_public_key, arg);
+                       continue;
+               }
+
+               if (errmsg)
+                       snprintf(errmsg, errmsg_size, "%s: Could not parse line %d",
+                                CONFIG_FILENAME, lineno);
+               fclose(fp);
+               return -1;
+       }
+
+       if (strlen(config->nszone) == 0) {
+               if (errmsg)
+                       snprintf(errmsg, errmsg_size, "%s: Missing 'zone' directive",
+                                CONFIG_FILENAME);
+               fclose(fp);
+               return -1;
+       }
+
+       fclose(fp);
+       return 0;
+}
+
+int itkacl_check(const char * const realm, const char * const user,
+                char *errmsg, size_t errmsg_size)
+{
+       struct itkacl_ctx *ctx;
+       int err;
+
+       ctx = itkacl_create_ctx(errmsg, errmsg_size);
+       if (ctx == NULL) {
+               return -1;
+       }
+
+       err = itkacl_check_with_ctx(ctx, realm, user, errmsg, errmsg_size);
+
+       itkacl_free_ctx(ctx);
+
+       return err;
+}
+
+struct itkacl_ctx *itkacl_create_ctx(char *errmsg, size_t errmsg_size)
+{
+       struct itkacl_ctx *ctx;
+       int ret;
+
+       ctx = (struct itkacl_ctx *)malloc(sizeof(struct itkacl_ctx));
+       if (ctx == NULL) {
+               if (errmsg)
+                       snprintf(errmsg, errmsg_size, "Memory allocation failed");
+               return NULL;
+       }
+
+       if (itkacl_read_config(CONFIG_FILENAME, &ctx->config, errmsg, errmsg_size) != 0) {
+               free(ctx);
+               return NULL;
+       }
+
+       /* Create the DNS resolver context. */
+       ctx->ubctx = ub_ctx_create();
+       if (ctx->ubctx == NULL) {
+               if (errmsg)
+                       snprintf(errmsg, errmsg_size, "Host name lookup failure: Could not create DNS context");
+               free(ctx);
+               return NULL;
+       }
+
+       ret = ub_ctx_resolvconf(ctx->ubctx, NULL);
+       if (ret != 0) {
+               if (errmsg)
+                       snprintf(errmsg, errmsg_size,
+                                "Host name lookup failure: Could not read resolv.conf "
+                                "(resolver error: %s) (system error: %s)",
+                                ub_strerror(ret), strerror(errno));
+               ub_ctx_delete(ctx->ubctx);
+               free(ctx);
+               return NULL;
+       }
+
+       ret = ub_ctx_hosts(ctx->ubctx, NULL);
+       if (ret != 0) {
+               if (errmsg)
+                       snprintf(errmsg, errmsg_size,
+                                "Host name lookup failure: Could not read hosts file "
+                                "(resolver error: %s) (system error: %s)",
+                                ub_strerror(ret), strerror(errno));
+               ub_ctx_delete(ctx->ubctx);
+               free(ctx);
+               return NULL;
+       }
+
+       if (strlen(ctx->config.dnssec_public_key) != 0) {
+               ret = ub_ctx_add_ta_file(ctx->ubctx, ctx->config.dnssec_public_key);
+               if (ret != 0) {
+                       if (errmsg)
+                               snprintf(errmsg, errmsg_size,
+                                        "Host name lookup failure: Error adding keys from %s "
+                                        "(resolver error: %s) (system error: %s)",
+                                        ctx->config.dnssec_public_key,
+                                        ub_strerror(ret), strerror(errno));
+                       ub_ctx_delete(ctx->ubctx);
+                       free(ctx);
+                       return NULL;
+               }
+       }
+
+       return ctx;
+}
+
+void itkacl_free_ctx(struct itkacl_ctx *ctx)
+{
+       ub_ctx_delete(ctx->ubctx);
+       free(ctx);
+}
+
+int itkacl_check_with_ctx(struct itkacl_ctx *ctx,
+                          const char * const realm, const char * const user,
+                         char *errmsg, size_t errmsg_size)
+{
+       int ret, nxdomain;
+       const char *ptr;
+       char nszone[256];
+       char temp[256];
+       struct ub_result* result;
+
+       if (realm[0] != '/') {
+               if (errmsg)
+                       snprintf(errmsg, errmsg_size, "Invalid realm '%s' (missing leading /)",
+                               realm);
+               return -1;
+       }
+       if (strlen(user) > 64) {
+               if (errmsg)
+                       snprintf(errmsg, errmsg_size, "Invalid user '%s' (above 64 characters)",
+                               user);
+               return -1;
+       }
+       if (strlen(realm) > 64) {
+               if (errmsg)
+                       snprintf(errmsg, errmsg_size, "Invalid realm '%s' (above 64 characters)",
+                               realm);
+               return -1;
+       }
+
+       /* check that the user name is valid */
+       ptr = user;
+       while (*ptr) {
+               /* only allow [a-z0-9-] */
+               if (!((*ptr >= 'a' && *ptr <= 'z') ||
+                     (*ptr >= '0' && *ptr <= '9') ||
+                      *ptr == '-')) {
+                       if (errmsg) {
+                               snprintf(errmsg, errmsg_size, "Invalid realm '%s' (illegal characters)",
+                                       realm);
+                       }
+                       return -1;
+               }
+               ++ptr;
+       }
+
+       /* traverse the realm entry by entry from the root,
+        * creating a DNS zone name as we go */
+       strcpy(nszone, ctx->config.nszone);
+       ptr = realm;
+       while (*ptr) {
+               /* copy all characters to next / or end of string */
+               char this_part[64];
+               int i = 0;
+               this_part[0] = 0;
+               
+               ++ptr;
+               while (*ptr && *ptr != '/') {
+                       /* only allow [a-z0-9-] */
+                       if (!((*ptr >= 'a' && *ptr <= 'z') ||
+                             (*ptr >= '0' && *ptr <= '9') ||
+                              *ptr == '-')) {
+                               if (errmsg) {
+                                       snprintf(errmsg, errmsg_size, "Invalid realm '%s' (illegal characters)",
+                                               realm);
+                               }
+                               return -1;
+                       }
+                       this_part[i++] = *ptr++;
+               }
+               this_part[i] = 0;
+
+               strcpy(temp, nszone);
+               snprintf(nszone, 256, "%s.%s", this_part, temp);
+       }
+
+       /* finally, prepend the username */
+       strcpy(temp, nszone);
+       sprintf(nszone, "%s.%s", user, temp);
+
+       /* Do the actual DNS lookup (TYPE A, CLASS IN). */
+       ret = ub_resolve(ctx->ubctx, nszone, 1, 1, &result);
+       if (ret != 0) {
+               if (errmsg)
+                       snprintf(errmsg, errmsg_size, "Host name lookup failure: %s",
+                                ub_strerror(ret));
+               return -1;
+       }
+
+       /* Verify DNSSEC. */
+       if (result->bogus) {
+               if (errmsg)
+                       snprintf(errmsg, errmsg_size,
+                                "Host name lookup failure: Bogus DNSSEC result (security failure)");
+               ub_resolve_free(result);
+               return -1;
+       }
+       if (ctx->config.require_dnssec && !result->secure) {
+               if (errmsg)
+                       snprintf(errmsg, errmsg_size,
+                                "Host name lookup failure: Result was not secured with DNSSEC");
+               ub_resolve_free(result);
+               return -1;
+       }
+
+       nxdomain = result->nxdomain;
+
+       ub_resolve_free(result);
+
+       if (nxdomain)
+               return 1;
+       else
+               return 0;
+}