Add support for configuring client connections too.
[libradsec.git] / lib / conf.c
1 /* Copyright 2010, 2011, 2013 NORDUnet A/S. All rights reserved.
2    See LICENSE for licensing information.  */
3
4 #if defined HAVE_CONFIG_H
5 #include <config.h>
6 #endif
7
8 #include <confuse.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <assert.h>
12 #include <radsec/radsec.h>
13 #include <radsec/radsec-impl.h>
14 #include "peer.h"
15 #include "util.h"
16 #include "debug.h"
17
18 #if 0 /* Configuration file syntax. */
19   # realm specific configuration
20   realm STRING {
21       type = "UDP"|"TCP"|"TLS"|"DTLS"
22       timeout = INT
23       retries = INT
24   }
25
26   # realm configuration inherited by clients and servers
27   realm STRING {
28       cacertfile = STRING
29       #cacertpath = STRING
30       certfile = STRING
31       certkeyfile = STRING
32       pskstr = STRING    # Transport pre-shared key, UTF-8 form.
33       pskhexstr = STRING # Transport pre-shared key, ASCII hex form.
34       pskid = STRING
35       pskex = "PSK"|"DHE_PSK"|"RSA_PSK"
36   }
37
38   # client configuration
39   realm STRING {
40       server {
41           hostname = STRING
42           service = STRING      # name or port number
43           secret = STRING       # RADIUS secret
44       }
45   }
46
47   # server configuration
48   realm STRING {
49       client {
50           hostname = STRING
51           service = STRING      # name or port number
52           secret = STRING       # RADIUS secret
53       }
54   }
55 #endif
56
57 struct confcommon {
58   struct rs_credentials *transport_cred;
59   char *cacertfile;
60   char *cacertpath;
61   char *certfile;
62   char *certkeyfile;
63   char *pskstr;
64   char *pskhexstr;
65 };
66
67 #define CONFGET_STR(dst,cfg,key,def) do {  \
68         (dst) = cfg_getstr ((cfg), (key)); \
69         if ((dst) == NULL) (dst) = (def);  \
70       } while (0)
71 #define CONFGET_INT(dst,cfg,key,def) do {  \
72         (dst) = cfg_getint ((cfg), (key)); \
73         if ((dst) == -1) (dst) = (def);    \
74       } while (0)
75
76 static int
77 confload_peers (struct rs_context *ctx,
78                 /*const*/ cfg_t *cfg_realm,
79                 enum rs_peer_type type,
80                 struct rs_realm *r)
81 {
82   const char *peer_type_str[] = {"<no type>", "client", "server"};
83   cfg_t *cfg_peer = NULL;
84   int j;
85   char *def_cacertfile = cfg_getstr (cfg_realm, "cacertfile");
86   /*char *def_cacertpath = cfg_getstr (cfg_realm, "cacertpath");*/
87   char *def_certfile = cfg_getstr (cfg_realm, "certfile");
88   char *def_certkeyfile = cfg_getstr (cfg_realm, "certkeyfile");
89   char *def_pskstr = cfg_getstr (cfg_realm, "pskstr");
90   char *def_pskhexstr = cfg_getstr (cfg_realm, "pskhexstr");
91
92   for (j = 0; j < cfg_size (cfg_realm, peer_type_str[type]); j++)
93     {
94       char *pskstr = NULL;
95       char *pskhexstr = NULL;
96       struct rs_peer *p = peer_create (ctx, &r->peers);
97       if (p == NULL)
98         return rs_err_ctx_push_fl (ctx, RSE_NOMEM, __FILE__, __LINE__,
99                                    NULL);
100       p->type = type;
101       p->realm = r;
102
103       cfg_peer = cfg_getnsec (cfg_realm, peer_type_str[type], j);
104       p->hostname = cfg_getstr (cfg_peer, "hostname");
105       p->service = cfg_getstr (cfg_peer, "service");
106       p->secret = cfg_getstr (cfg_peer, "secret");
107
108       CONFGET_STR (p->cacertfile, cfg_peer, "cacertfile", def_cacertfile);
109       CONFGET_STR (p->certfile, cfg_peer, "certfile", def_certfile);
110       CONFGET_STR (p->certkeyfile, cfg_peer, "certkeyfile", def_certkeyfile);
111       CONFGET_STR (pskstr, cfg_peer, "pskstr", def_pskstr);
112       CONFGET_STR (pskhexstr, cfg_peer, "pskhexstr", def_pskhexstr);
113
114       if (pskstr || pskhexstr)
115         {
116 #if defined RS_ENABLE_TLS_PSK
117           char *def_pskex = cfg_getstr (cfg_realm, "pskex");
118           char *tmp_pskex = NULL;
119           rs_cred_type_t type = RS_CRED_NONE;
120           struct rs_credentials *cred = NULL;
121
122           CONFGET_STR (tmp_pskex, cfg_peer, "pskex", def_pskex);
123           if (!strcmp (tmp_pskex, "PSK"))
124             type = RS_CRED_TLS_PSK;
125           else
126             {
127               /* TODO: push a warning on the error stack:*/
128               /*rs_err_ctx_push (ctx, RSE_WARN, "%s: unsupported PSK key exchange"
129                                " algorithm -- PSK not used", kex);*/
130             }
131
132           if (type != RS_CRED_NONE)
133             {
134               char *def_pskid = cfg_getstr (cfg_realm, "pskid");
135               cred = rs_calloc (ctx, 1, sizeof (*cred));
136               if (cred == NULL)
137                 return rs_err_ctx_push_fl (ctx, RSE_NOMEM, __FILE__, __LINE__,
138                                            NULL);
139               cred->type = type;
140               CONFGET_STR (cred->identity, cfg_peer, "pskid", def_pskid);
141               if (pskhexstr)
142                 {
143                   cred->secret_encoding = RS_KEY_ENCODING_ASCII_HEX;
144                   cred->secret = pskhexstr;
145                   if (pskstr)
146                     ;      /* TODO: warn that we're ignoring pskstr */
147                 }
148               else
149                 {
150                   cred->secret_encoding = RS_KEY_ENCODING_UTF8;
151                   cred->secret = pskstr;
152                 }
153
154               p->transport_cred = cred;
155             }
156 #else  /* !RS_ENABLE_TLS_PSK */
157           /* TODO: push a warning on the error stack: */
158           /* rs_err_ctx_push (ctx, RSE_WARN, "libradsec wasn't configured with "
159              "support for TLS preshared keys, ignoring pskstr "
160              "and pskhexstr");*/
161 #endif  /* RS_ENABLE_TLS_PSK */
162         }
163
164
165       /* For a TLS or DTLS client or server, validate that we have either of CA
166          cert file/path or PSK.  */
167       if ((r->type == RS_CONN_TYPE_TLS || r->type == RS_CONN_TYPE_DTLS)
168           && (p->cacertfile == NULL && p->cacertpath == NULL)
169           && p->transport_cred == NULL)
170         return rs_err_ctx_push (ctx, RSE_CONFIG,
171                                 "%s: missing both CA file/path and PSK",
172                                 r->name);
173     }
174
175   return RSE_OK;
176 }
177
178 /* FIXME: Leaking memory in error cases.  */
179 int
180 rs_context_read_config(struct rs_context *ctx, const char *config_file)
181 {
182   cfg_t *cfg, *cfg_realm;
183   int err = 0;
184   int i;
185   const char *s;
186   struct rs_config *config = NULL;
187
188   cfg_opt_t peer_opts[] =
189     {
190       CFG_STR ("hostname", NULL, CFGF_NONE),
191       CFG_STR ("service", "2083", CFGF_NONE),
192       CFG_STR ("secret", "radsec", CFGF_NONE),
193       CFG_STR ("cacertfile", NULL, CFGF_NONE),
194       /*CFG_STR ("cacertpath", NULL, CFGF_NONE),*/
195       CFG_STR ("certfile", NULL, CFGF_NONE),
196       CFG_STR ("certkeyfile", NULL, CFGF_NONE),
197       CFG_STR ("pskstr", NULL, CFGF_NONE),
198       CFG_STR ("pskhexstr", NULL, CFGF_NONE),
199       CFG_STR ("pskid", NULL, CFGF_NONE),
200       CFG_STR ("pskex", "PSK", CFGF_NONE),
201
202       CFG_END ()
203     };
204   cfg_opt_t realm_opts[] =
205     {
206       CFG_STR ("type", "UDP", CFGF_NONE),
207       CFG_INT ("timeout", 2, CFGF_NONE), /* FIXME: Remove?  */
208       CFG_INT ("retries", 2, CFGF_NONE), /* FIXME: Remove?  */
209       CFG_STR ("cacertfile", NULL, CFGF_NONE),
210       /*CFG_STR ("cacertpath", NULL, CFGF_NONE),*/
211       CFG_STR ("certfile", NULL, CFGF_NONE),
212       CFG_STR ("certkeyfile", NULL, CFGF_NONE),
213       CFG_STR ("pskstr", NULL, CFGF_NONE),
214       CFG_STR ("pskhexstr", NULL, CFGF_NONE),
215       CFG_STR ("pskid", NULL, CFGF_NONE),
216       CFG_STR ("pskex", "PSK", CFGF_NONE),
217       CFG_SEC ("server", peer_opts, CFGF_MULTI),
218       CFG_SEC ("client", peer_opts, CFGF_MULTI),
219       CFG_END ()
220     };
221   cfg_opt_t opts[] =
222     {
223       CFG_SEC ("realm", realm_opts, CFGF_TITLE | CFGF_MULTI),
224       CFG_END ()
225     };
226
227   cfg = cfg_init (opts, CFGF_NONE);
228   if (cfg == NULL)
229     return rs_err_ctx_push (ctx, RSE_CONFIG, "unable to initialize libconfuse");
230   err = cfg_parse (cfg, config_file);
231   switch (err)
232     {
233     case  CFG_SUCCESS:
234       break;
235     case CFG_FILE_ERROR:
236       return rs_err_ctx_push (ctx, RSE_CONFIG,
237                               "%s: unable to open configuration file",
238                               config_file);
239     case CFG_PARSE_ERROR:
240       return rs_err_ctx_push (ctx, RSE_CONFIG, "%s: invalid configuration file",
241                               config_file);
242     default:
243         return rs_err_ctx_push (ctx, RSE_CONFIG, "%s: unknown parse error",
244                                 config_file);
245     }
246
247   config = rs_calloc (ctx, 1, sizeof (*config));
248   if (config == NULL)
249     return rs_err_ctx_push_fl (ctx, RSE_NOMEM, __FILE__, __LINE__, NULL);
250   ctx->config = config;
251
252   for (i = 0; i < cfg_size (cfg, "realm"); i++)
253     {
254       struct rs_realm *r = NULL;
255       const char *typestr;
256       struct confcommon cc;
257
258       memset (&cc, 0, sizeof(cc));
259       r = rs_calloc (ctx, 1, sizeof(*r));
260       if (r == NULL)
261         return rs_err_ctx_push_fl (ctx, RSE_NOMEM, __FILE__, __LINE__, NULL);
262       if (config->realms != NULL)
263         {
264           r->next = config->realms->next;
265           config->realms->next = r;
266         }
267       else
268         {
269           config->realms = r;
270         }
271       cfg_realm = cfg_getnsec (cfg, "realm", i);
272       s = cfg_title (cfg_realm);
273       if (s == NULL)
274         return rs_err_ctx_push_fl (ctx, RSE_CONFIG, __FILE__, __LINE__,
275                                    "missing realm name");
276       /* We use a copy of the return value of cfg_title() since it's const.  */
277       r->name = rs_strdup (ctx, s);
278       if (r->name == NULL)
279         return RSE_NOMEM;
280
281       typestr = cfg_getstr (cfg_realm, "type");
282       if (strcmp (typestr, "UDP") == 0)
283         r->type = RS_CONN_TYPE_UDP;
284       else if (strcmp (typestr, "TCP") == 0)
285         r->type = RS_CONN_TYPE_TCP;
286       else if (strcmp (typestr, "TLS") == 0)
287         r->type = RS_CONN_TYPE_TLS;
288       else if (strcmp (typestr, "DTLS") == 0)
289         r->type = RS_CONN_TYPE_DTLS;
290       else
291         return rs_err_ctx_push (ctx, RSE_CONFIG,
292                                 "%s: invalid connection type: %s",
293                                 r->name, typestr);
294
295       r->timeout = cfg_getint (cfg_realm, "timeout");
296       r->retries = cfg_getint (cfg_realm, "retries");
297
298       /* Add client and server peers. */
299       err = confload_peers (ctx, cfg_realm, RS_PEER_TYPE_CLIENT, r);
300       if (err)
301         return err;
302       err = confload_peers (ctx, cfg_realm, RS_PEER_TYPE_SERVER, r);
303       if (err)
304         return err;
305     }
306
307   /* Save config object in context, for freeing in rs_context_destroy().  */
308   ctx->config->cfg = cfg;
309
310   return RSE_OK;
311 }
312
313 struct rs_realm *
314 rs_conf_find_realm(struct rs_context *ctx, const char *name)
315 {
316   struct rs_realm *r;
317   assert (ctx);
318
319   if (ctx->config)
320     for (r = ctx->config->realms; r; r = r->next)
321       if (strcmp (r->name, name) == 0)
322         return r;
323
324   return NULL;
325 }