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