]> git.sesse.net Git - itkacl/blob - itkacl-2.2/itkacl.c
Update for PHP 7.
[itkacl] / itkacl-2.2 / itkacl.c
1 /*
2  * ITKACL control library.
3  *
4  * (C) 2004-2022 Steinar H. Gunderson
5  * GPL, v2.
6  */
7 #include <stdio.h>
8 #include <string.h>
9 #include <stdlib.h>
10 #include <stdarg.h>
11 #include <unistd.h>
12 #include <errno.h>
13 #include <ctype.h>
14 #include <unbound.h>
15
16 #include "itkacl.h"
17
18 struct itkacl_config {
19         char nszone[256];
20         int require_dnssec;
21         char dnssec_public_key[256];
22 };
23
24 struct itkacl_ctx {
25         struct itkacl_config config;
26         struct ub_ctx *ubctx;
27 };
28
29 #define CONFIG_FILENAME "/etc/itkacl.conf"
30
31 static int itkacl_read_config(const char * const filename,
32                               struct itkacl_config *config,
33                               char *errmsg, size_t errmsg_size)
34 {
35         FILE *fp;
36         int lineno = 0;
37
38         /* Defaults. */
39         strcpy(config->nszone, "");
40         config->require_dnssec = 0;
41         strcpy(config->dnssec_public_key, "");
42
43         fp = fopen(CONFIG_FILENAME, "r");
44         if (fp == NULL) {
45                 if (errmsg)
46                         snprintf(errmsg, errmsg_size, "%s: %s",
47                                  CONFIG_FILENAME, strerror(errno));
48                 return -1;
49         }
50
51         while (!feof(fp)) {
52                 char line[256], arg[256], *ptr;
53         
54                 if (fgets(line, sizeof(line), fp) == NULL) {
55                         break;
56                 }
57                 ++lineno;
58
59                 /* Remove trailing newlines and then comments. */
60                 ptr = strchr(line, '\n');
61                 if (ptr != NULL)
62                         *ptr = 0;
63
64                 ptr = strchr(line, '\r');
65                 if (ptr != NULL)
66                         *ptr = 0;
67
68                 ptr = strchr(line, '#');
69                 if (ptr != NULL)
70                         *ptr = 0;
71
72                 /* Remove trailing whitespace, if any. */
73                 ptr = line + strlen(line) - 1;
74                 while (ptr >= line && isspace(*ptr))
75                         *ptr-- = 0;
76
77                 /* Skip lines that now ended up blank. */
78                 if (line[0] == 0)
79                         continue;
80
81                 if (sscanf(line, "zone %255s", arg) == 1) {
82                         strcpy(config->nszone, arg);
83                         continue;
84                 }
85                 if (strcmp(line, "require-dnssec") == 0) {
86                         config->require_dnssec = 1;
87                         continue;
88                 }
89                 if (sscanf(line, "dnssec-public-key %255s", arg) == 1) {
90                         strcpy(config->dnssec_public_key, arg);
91                         continue;
92                 }
93
94                 if (errmsg)
95                         snprintf(errmsg, errmsg_size, "%s: Could not parse line %d",
96                                  CONFIG_FILENAME, lineno);
97                 fclose(fp);
98                 return -1;
99         }
100
101         if (strlen(config->nszone) == 0) {
102                 if (errmsg)
103                         snprintf(errmsg, errmsg_size, "%s: Missing 'zone' directive",
104                                  CONFIG_FILENAME);
105                 fclose(fp);
106                 return -1;
107         }
108
109         fclose(fp);
110         return 0;
111 }
112
113 int itkacl_check(const char * const realm, const char * const user,
114                  char *errmsg, size_t errmsg_size)
115 {
116         struct itkacl_ctx *ctx;
117         int err;
118
119         ctx = itkacl_create_ctx(errmsg, errmsg_size);
120         if (ctx == NULL) {
121                 return -1;
122         }
123
124         err = itkacl_check_with_ctx(ctx, realm, user, errmsg, errmsg_size);
125
126         itkacl_free_ctx(ctx);
127
128         return err;
129 }
130
131 struct itkacl_ctx *itkacl_create_ctx(char *errmsg, size_t errmsg_size)
132 {
133         struct itkacl_ctx *ctx;
134         int ret;
135
136         ctx = (struct itkacl_ctx *)malloc(sizeof(struct itkacl_ctx));
137         if (ctx == NULL) {
138                 if (errmsg)
139                         snprintf(errmsg, errmsg_size, "Memory allocation failed");
140                 return NULL;
141         }
142
143         if (itkacl_read_config(CONFIG_FILENAME, &ctx->config, errmsg, errmsg_size) != 0) {
144                 free(ctx);
145                 return NULL;
146         }
147
148         /* Create the DNS resolver context. */
149         ctx->ubctx = ub_ctx_create();
150         if (ctx->ubctx == NULL) {
151                 if (errmsg)
152                         snprintf(errmsg, errmsg_size, "Host name lookup failure: Could not create DNS context");
153                 free(ctx);
154                 return NULL;
155         }
156
157         ret = ub_ctx_resolvconf(ctx->ubctx, NULL);
158         if (ret != 0) {
159                 if (errmsg)
160                         snprintf(errmsg, errmsg_size,
161                                  "Host name lookup failure: Could not read resolv.conf "
162                                  "(resolver error: %s) (system error: %s)",
163                                  ub_strerror(ret), strerror(errno));
164                 ub_ctx_delete(ctx->ubctx);
165                 free(ctx);
166                 return NULL;
167         }
168
169         ret = ub_ctx_hosts(ctx->ubctx, NULL);
170         if (ret != 0) {
171                 if (errmsg)
172                         snprintf(errmsg, errmsg_size,
173                                  "Host name lookup failure: Could not read hosts file "
174                                  "(resolver error: %s) (system error: %s)",
175                                  ub_strerror(ret), strerror(errno));
176                 ub_ctx_delete(ctx->ubctx);
177                 free(ctx);
178                 return NULL;
179         }
180
181         if (strlen(ctx->config.dnssec_public_key) != 0) {
182                 ret = ub_ctx_add_ta_file(ctx->ubctx, ctx->config.dnssec_public_key);
183                 if (ret != 0) {
184                         if (errmsg)
185                                 snprintf(errmsg, errmsg_size,
186                                          "Host name lookup failure: Error adding keys from %s "
187                                          "(resolver error: %s) (system error: %s)",
188                                          ctx->config.dnssec_public_key,
189                                          ub_strerror(ret), strerror(errno));
190                         ub_ctx_delete(ctx->ubctx);
191                         free(ctx);
192                         return NULL;
193                 }
194         }
195
196         return ctx;
197 }
198
199 void itkacl_free_ctx(struct itkacl_ctx *ctx)
200 {
201         ub_ctx_delete(ctx->ubctx);
202         free(ctx);
203 }
204
205 int itkacl_check_with_ctx(struct itkacl_ctx *ctx,
206                           const char * const realm, const char * const user,
207                           char *errmsg, size_t errmsg_size)
208 {
209         int ret, nxdomain;
210         const char *ptr;
211         char nszone[256];
212         char temp[256];
213         struct ub_result* result;
214
215         if (realm[0] != '/') {
216                 if (errmsg)
217                         snprintf(errmsg, errmsg_size, "Invalid realm '%s' (missing leading /)",
218                                 realm);
219                 return -1;
220         }
221         if (strlen(user) > 64) {
222                 if (errmsg)
223                         snprintf(errmsg, errmsg_size, "Invalid user '%s' (above 64 characters)",
224                                 user);
225                 return -1;
226         }
227         if (strlen(realm) > 64) {
228                 if (errmsg)
229                         snprintf(errmsg, errmsg_size, "Invalid realm '%s' (above 64 characters)",
230                                 realm);
231                 return -1;
232         }
233
234         /* check that the user name is valid */
235         ptr = user;
236         while (*ptr) {
237                 /* only allow [a-z0-9-] */
238                 if (!((*ptr >= 'a' && *ptr <= 'z') ||
239                       (*ptr >= '0' && *ptr <= '9') ||
240                        *ptr == '-')) {
241                         if (errmsg) {
242                                 snprintf(errmsg, errmsg_size, "Invalid realm '%s' (illegal characters)",
243                                         realm);
244                         }
245                         return -1;
246                 }
247                 ++ptr;
248         }
249
250         /* traverse the realm entry by entry from the root,
251          * creating a DNS zone name as we go */
252         strcpy(nszone, ctx->config.nszone);
253         ptr = realm;
254         while (*ptr) {
255                 /* copy all characters to next / or end of string */
256                 char this_part[64];
257                 int i = 0;
258                 this_part[0] = 0;
259                 
260                 ++ptr;
261                 while (*ptr && *ptr != '/') {
262                         /* only allow [a-z0-9-] */
263                         if (!((*ptr >= 'a' && *ptr <= 'z') ||
264                               (*ptr >= '0' && *ptr <= '9') ||
265                                *ptr == '-')) {
266                                 if (errmsg) {
267                                         snprintf(errmsg, errmsg_size, "Invalid realm '%s' (illegal characters)",
268                                                 realm);
269                                 }
270                                 return -1;
271                         }
272                         this_part[i++] = *ptr++;
273                 }
274                 this_part[i] = 0;
275
276                 strcpy(temp, nszone);
277                 snprintf(nszone, 256, "%s.%s", this_part, temp);
278         }
279
280         /* finally, prepend the username */
281         strcpy(temp, nszone);
282         sprintf(nszone, "%s.%s", user, temp);
283
284         /* Do the actual DNS lookup (TYPE A, CLASS IN). */
285         ret = ub_resolve(ctx->ubctx, nszone, 1, 1, &result);
286         if (ret != 0) {
287                 if (errmsg)
288                         snprintf(errmsg, errmsg_size, "Host name lookup failure: %s",
289                                  ub_strerror(ret));
290                 return -1;
291         }
292
293         /* Verify DNSSEC. */
294         if (result->bogus) {
295                 if (errmsg)
296                         snprintf(errmsg, errmsg_size,
297                                  "Host name lookup failure: Bogus DNSSEC result (security failure)");
298                 ub_resolve_free(result);
299                 return -1;
300         }
301         if (ctx->config.require_dnssec && !result->secure) {
302                 if (errmsg)
303                         snprintf(errmsg, errmsg_size,
304                                  "Host name lookup failure: Result was not secured with DNSSEC");
305                 ub_resolve_free(result);
306                 return -1;
307         }
308
309         nxdomain = result->nxdomain;
310
311         ub_resolve_free(result);
312
313         if (nxdomain)
314                 return 1;
315         else
316                 return 0;
317 }