ARI: Don't complain about missing ARI users when we aren't enabled
[asterisk/asterisk.git] / res / ari / config.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2013, Digium, Inc.
5  *
6  * David M. Lee, II <dlee@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*! \file
20  *
21  * \brief Config framework stuffz for ARI.
22  * \author David M. Lee, II <dlee@digium.com>
23  */
24
25 #include "asterisk.h"
26
27 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
28
29 #include "asterisk/config_options.h"
30 #include "internal.h"
31
32 /*! \brief Locking container for safe configuration access. */
33 static AO2_GLOBAL_OBJ_STATIC(confs);
34
35 /*! \brief Mapping of the ARI conf struct's globals to the
36  *         general context in the config file. */
37 static struct aco_type general_option = {
38         .type = ACO_GLOBAL,
39         .name = "general",
40         .item_offset = offsetof(struct ast_ari_conf, general),
41         .category = "^general$",
42         .category_match = ACO_WHITELIST,
43 };
44
45 static struct aco_type *general_options[] = ACO_TYPES(&general_option);
46
47 /*! \brief Encoding format handler converts from boolean to enum. */
48 static int encoding_format_handler(const struct aco_option *opt,
49         struct ast_variable *var, void *obj)
50 {
51         struct ast_ari_conf_general *general = obj;
52
53         if (!strcasecmp(var->name, "pretty")) {
54                 general->format = ast_true(var->value) ?
55                         AST_JSON_PRETTY : AST_JSON_COMPACT;
56         } else {
57                 return -1;
58         }
59
60         return 0;
61 }
62
63 /*! \brief Parses the ast_ari_password_format enum from a config file */
64 static int password_format_handler(const struct aco_option *opt,
65         struct ast_variable *var, void *obj)
66 {
67         struct ast_ari_conf_user *user = obj;
68
69         if (strcasecmp(var->value, "plain") == 0) {
70                 user->password_format = ARI_PASSWORD_FORMAT_PLAIN;
71         } else if (strcasecmp(var->value, "crypt") == 0) {
72                 user->password_format = ARI_PASSWORD_FORMAT_CRYPT;
73         } else {
74                 return -1;
75         }
76
77         return 0;
78 }
79
80 /*! \brief Destructor for \ref ast_ari_conf_user */
81 static void user_dtor(void *obj)
82 {
83         struct ast_ari_conf_user *user = obj;
84         ast_debug(3, "Disposing of user %s\n", user->username);
85         ast_free(user->username);
86 }
87
88 /*! \brief Allocate an \ref ast_ari_conf_user for config parsing */
89 static void *user_alloc(const char *cat)
90 {
91         RAII_VAR(struct ast_ari_conf_user *, user, NULL, ao2_cleanup);
92
93         if (!cat) {
94                 return NULL;
95         }
96
97         ast_debug(3, "Allocating user %s\n", cat);
98
99         user = ao2_alloc_options(sizeof(*user), user_dtor,
100                 AO2_ALLOC_OPT_LOCK_NOLOCK);
101         if (!user) {
102                 return NULL;
103         }
104
105         user->username = ast_strdup(cat);
106         if (!user->username) {
107                 return NULL;
108         }
109
110         ao2_ref(user, +1);
111         return user;
112 }
113
114 /*! \brief Sorting function for use with red/black tree */
115 static int user_sort_cmp(const void *obj_left, const void *obj_right, int flags)
116 {
117         const struct ast_ari_conf_user *user_left = obj_left;
118
119         if (flags & OBJ_PARTIAL_KEY) {
120                 const char *key_right = obj_right;
121                 return strncasecmp(user_left->username, key_right,
122                         strlen(key_right));
123         } else if (flags & OBJ_KEY) {
124                 const char *key_right = obj_right;
125                 return strcasecmp(user_left->username, key_right);
126         } else {
127                 const struct ast_ari_conf_user *user_right = obj_right;
128                 const char *key_right = user_right->username;
129                 return strcasecmp(user_left->username, key_right);
130         }
131 }
132
133 /*! \brief \ref aco_type item_find function */
134 static void *user_find(struct ao2_container *tmp_container, const char *cat)
135 {
136         if (!cat) {
137                 return NULL;
138         }
139
140         return ao2_find(tmp_container, cat, OBJ_KEY);
141 }
142
143 static struct aco_type user_option = {
144         .type = ACO_ITEM,
145         .name = "user",
146         .category_match = ACO_BLACKLIST,
147         .category = "^general$",
148         .matchfield = "type",
149         .matchvalue = "user",
150         .item_alloc = user_alloc,
151         .item_find = user_find,
152         .item_offset = offsetof(struct ast_ari_conf, users),
153 };
154
155 static struct aco_type *user[] = ACO_TYPES(&user_option);
156
157 /*! \brief \ref ast_ari_conf destructor. */
158 static void conf_destructor(void *obj)
159 {
160         struct ast_ari_conf *cfg = obj;
161
162         ast_string_field_free_memory(cfg->general);
163
164         ao2_cleanup(cfg->general);
165         ao2_cleanup(cfg->users);
166 }
167
168 /*! \brief Allocate an \ref ast_ari_conf for config parsing */
169 static void *conf_alloc(void)
170 {
171         RAII_VAR(struct ast_ari_conf *, cfg, NULL, ao2_cleanup);
172
173         cfg = ao2_alloc_options(sizeof(*cfg), conf_destructor,
174                 AO2_ALLOC_OPT_LOCK_NOLOCK);
175         if (!cfg) {
176                 return NULL;
177         }
178
179         cfg->general = ao2_alloc_options(sizeof(*cfg->general), NULL,
180                 AO2_ALLOC_OPT_LOCK_NOLOCK);
181         if (!cfg->general) {
182                 return NULL;
183         }
184         aco_set_defaults(&general_option, "general", cfg->general);
185
186         if (ast_string_field_init(cfg->general, 64)) {
187                 return NULL;
188         }
189
190         cfg->users = ao2_container_alloc_rbtree(AO2_ALLOC_OPT_LOCK_NOLOCK,
191                 AO2_CONTAINER_ALLOC_OPT_DUPS_REPLACE, user_sort_cmp, NULL);
192
193         ao2_ref(cfg, +1);
194         return cfg;
195 }
196
197 #define CONF_FILENAME "ari.conf"
198
199 /*! \brief The conf file that's processed for the module. */
200 static struct aco_file conf_file = {
201         /*! The config file name. */
202         .filename = CONF_FILENAME,
203         /*! The mapping object types to be processed. */
204         .types = ACO_TYPES(&general_option, &user_option),
205 };
206
207 CONFIG_INFO_STANDARD(cfg_info, confs, conf_alloc,
208                      .files = ACO_FILES(&conf_file));
209
210 struct ast_ari_conf *ast_ari_config_get(void)
211 {
212         struct ast_ari_conf *res = ao2_global_obj_ref(confs);
213         if (!res) {
214                 ast_log(LOG_ERROR,
215                         "Error obtaining config from " CONF_FILENAME "\n");
216         }
217         return res;
218 }
219
220 struct ast_ari_conf_user *ast_ari_config_validate_user(const char *username,
221         const char *password)
222 {
223         RAII_VAR(struct ast_ari_conf *, conf, NULL, ao2_cleanup);
224         RAII_VAR(struct ast_ari_conf_user *, user, NULL, ao2_cleanup);
225         int is_valid = 0;
226
227         conf = ast_ari_config_get();
228         if (!conf) {
229                 return NULL;
230         }
231
232         user = ao2_find(conf->users, username, OBJ_KEY);
233         if (!user) {
234                 return NULL;
235         }
236
237         if (ast_strlen_zero(user->password)) {
238                 ast_log(LOG_WARNING,
239                         "User '%s' missing password; authentication failed\n",
240                         user->username);
241                 return NULL;
242         }
243
244         switch (user->password_format) {
245         case ARI_PASSWORD_FORMAT_PLAIN:
246                 is_valid = strcmp(password, user->password) == 0;
247                 break;
248         case ARI_PASSWORD_FORMAT_CRYPT:
249                 is_valid = ast_crypt_validate(password, user->password);
250                 break;
251         }
252
253         if (!is_valid) {
254                 return NULL;
255         }
256
257         ao2_ref(user, +1);
258         return user;
259 }
260
261 /*! \brief Callback to validate a user object */
262 static int validate_user_cb(void *obj, void *arg, int flags)
263 {
264         struct ast_ari_conf_user *user = obj;
265
266         if (ast_strlen_zero(user->password)) {
267                 ast_log(LOG_WARNING, "User '%s' missing password\n",
268                         user->username);
269         }
270
271         return 0;
272 }
273
274 /*! \brief Load (or reload) configuration. */
275 static int process_config(int reload)
276 {
277         RAII_VAR(struct ast_ari_conf *, conf, NULL, ao2_cleanup);
278
279         switch (aco_process_config(&cfg_info, reload)) {
280         case ACO_PROCESS_ERROR:
281                 return -1;
282         case ACO_PROCESS_OK:
283         case ACO_PROCESS_UNCHANGED:
284                 break;
285         }
286
287         conf = ast_ari_config_get();
288         if (!conf) {
289                 ast_assert(0); /* We just configured; it should be there */
290                 return -1;
291         }
292
293         if (conf->general->enabled) {
294                 if (ao2_container_count(conf->users) == 0) {
295                         ast_log(LOG_ERROR, "No configured users for ARI\n");
296                 } else {
297                         ao2_callback(conf->users, OBJ_NODATA, validate_user_cb, NULL);
298                 }
299         }
300
301         return 0;
302 }
303
304 int ast_ari_config_init(void)
305 {
306         if (aco_info_init(&cfg_info)) {
307                 aco_info_destroy(&cfg_info);
308                 return -1;
309         }
310
311         aco_option_register(&cfg_info, "enabled", ACO_EXACT, general_options,
312                 "yes", OPT_BOOL_T, 1,
313                 FLDSET(struct ast_ari_conf_general, enabled));
314         aco_option_register_custom(&cfg_info, "pretty", ACO_EXACT,
315                 general_options, "no",  encoding_format_handler, 0);
316         aco_option_register(&cfg_info, "auth_realm", ACO_EXACT, general_options,
317                 "Asterisk REST Interface", OPT_CHAR_ARRAY_T, 0,
318                 FLDSET(struct ast_ari_conf_general, auth_realm),
319                 ARI_AUTH_REALM_LEN);
320         aco_option_register(&cfg_info, "allowed_origins", ACO_EXACT, general_options,
321                 "", OPT_STRINGFIELD_T, 0,
322                 STRFLDSET(struct ast_ari_conf_general, allowed_origins));
323
324         aco_option_register(&cfg_info, "type", ACO_EXACT, user, NULL,
325                 OPT_NOOP_T, 0, 0);
326         aco_option_register(&cfg_info, "read_only", ACO_EXACT, user,
327                 "no", OPT_BOOL_T, 1,
328                 FLDSET(struct ast_ari_conf_user, read_only));
329         aco_option_register(&cfg_info, "password", ACO_EXACT, user,
330                 "", OPT_CHAR_ARRAY_T, 0,
331                 FLDSET(struct ast_ari_conf_user, password), ARI_PASSWORD_LEN);
332         aco_option_register_custom(&cfg_info, "password_format", ACO_EXACT,
333                 user, "plain",  password_format_handler, 0);
334
335         return process_config(0);
336 }
337
338 int ast_ari_config_reload(void)
339 {
340         return process_config(1);
341 }
342
343 void ast_ari_config_destroy(void)
344 {
345         aco_info_destroy(&cfg_info);
346         ao2_global_obj_release(confs);
347 }