/src/mosquitto/plugins/dynamic-security/groups.c
Line | Count | Source |
1 | | /* |
2 | | Copyright (c) 2020-2021 Roger Light <roger@atchoo.org> |
3 | | |
4 | | All rights reserved. This program and the accompanying materials |
5 | | are made available under the terms of the Eclipse Public License 2.0 |
6 | | and Eclipse Distribution License v1.0 which accompany this distribution. |
7 | | |
8 | | The Eclipse Public License is available at |
9 | | https://www.eclipse.org/legal/epl-2.0/ |
10 | | and the Eclipse Distribution License is available at |
11 | | http://www.eclipse.org/org/documents/edl-v10.php. |
12 | | |
13 | | SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause |
14 | | |
15 | | Contributors: |
16 | | Roger Light - initial implementation and documentation. |
17 | | */ |
18 | | |
19 | | #include "config.h" |
20 | | |
21 | | #include <cjson/cJSON.h> |
22 | | #include <stdio.h> |
23 | | #include <uthash.h> |
24 | | |
25 | | #include "dynamic_security.h" |
26 | | #include "json_help.h" |
27 | | |
28 | | /* ################################################################ |
29 | | * # |
30 | | * # Plugin global variables |
31 | | * # |
32 | | * ################################################################ */ |
33 | | |
34 | | /* ################################################################ |
35 | | * # |
36 | | * # Function declarations |
37 | | * # |
38 | | * ################################################################ */ |
39 | | |
40 | | static int dynsec__remove_all_clients_from_group(struct dynsec__group *group); |
41 | | static int dynsec__remove_all_roles_from_group(struct dynsec__group *group); |
42 | | static cJSON *add_group_to_json(struct dynsec__group *group); |
43 | | |
44 | | |
45 | | /* ################################################################ |
46 | | * # |
47 | | * # Local variables |
48 | | * # |
49 | | * ################################################################ */ |
50 | | |
51 | | |
52 | | /* ################################################################ |
53 | | * # |
54 | | * # Utility functions |
55 | | * # |
56 | | * ################################################################ */ |
57 | | |
58 | | |
59 | | static void group__kick_all(struct dynsec__data *data, struct dynsec__group *group) |
60 | 0 | { |
61 | 0 | if(group == data->anonymous_group){ |
62 | 0 | dynsec_kicklist__add(data, NULL); |
63 | 0 | } |
64 | 0 | dynsec_clientlist__kick_all(data, group->clientlist); |
65 | 0 | } |
66 | | |
67 | | |
68 | | static int group_cmp(void *a, void *b) |
69 | 187k | { |
70 | 187k | struct dynsec__group *group_a = a; |
71 | 187k | struct dynsec__group *group_b = b; |
72 | | |
73 | 187k | return strcmp(group_a->groupname, group_b->groupname); |
74 | 187k | } |
75 | | |
76 | | |
77 | | struct dynsec__group *dynsec_groups__find(struct dynsec__data *data, const char *groupname) |
78 | 92.9k | { |
79 | 92.9k | struct dynsec__group *group = NULL; |
80 | | |
81 | 92.9k | if(groupname){ |
82 | 92.9k | HASH_FIND(hh, data->groups, groupname, strlen(groupname), group); |
83 | 92.9k | } |
84 | 92.9k | return group; |
85 | 92.9k | } |
86 | | |
87 | | |
88 | | static void group__free_item(struct dynsec__data *data, struct dynsec__group *group) |
89 | 32.1k | { |
90 | 32.1k | struct dynsec__group *found_group = NULL; |
91 | | |
92 | 32.1k | if(group == NULL){ |
93 | 0 | return; |
94 | 0 | } |
95 | | |
96 | 32.1k | found_group = dynsec_groups__find(data, group->groupname); |
97 | 32.1k | if(found_group){ |
98 | 32.1k | HASH_DEL(data->groups, found_group); |
99 | 32.1k | } |
100 | 32.1k | dynsec__remove_all_clients_from_group(group); |
101 | 32.1k | mosquitto_free(group->text_name); |
102 | 32.1k | mosquitto_free(group->text_description); |
103 | 32.1k | dynsec_rolelist__cleanup(&group->rolelist); |
104 | 32.1k | mosquitto_free(group); |
105 | 32.1k | } |
106 | | |
107 | | |
108 | | int dynsec_groups__process_add_role(struct dynsec__data *data, struct mosquitto_control_cmd *cmd) |
109 | 0 | { |
110 | 0 | const char *groupname, *rolename; |
111 | 0 | struct dynsec__group *group; |
112 | 0 | struct dynsec__role *role; |
113 | 0 | int priority; |
114 | 0 | const char *admin_clientid, *admin_username; |
115 | 0 | int rc; |
116 | |
|
117 | 0 | if(json_get_string(cmd->j_command, "groupname", &groupname, false) != MOSQ_ERR_SUCCESS){ |
118 | 0 | mosquitto_control_command_reply(cmd, "Invalid/missing groupname"); |
119 | 0 | return MOSQ_ERR_INVAL; |
120 | 0 | } |
121 | 0 | if(mosquitto_validate_utf8(groupname, (int)strlen(groupname)) != MOSQ_ERR_SUCCESS){ |
122 | 0 | mosquitto_control_command_reply(cmd, "Group name not valid UTF-8"); |
123 | 0 | return MOSQ_ERR_INVAL; |
124 | 0 | } |
125 | | |
126 | 0 | if(json_get_string(cmd->j_command, "rolename", &rolename, false) != MOSQ_ERR_SUCCESS){ |
127 | 0 | mosquitto_control_command_reply(cmd, "Invalid/missing rolename"); |
128 | 0 | return MOSQ_ERR_INVAL; |
129 | 0 | } |
130 | 0 | if(mosquitto_validate_utf8(rolename, (int)strlen(rolename)) != MOSQ_ERR_SUCCESS){ |
131 | 0 | mosquitto_control_command_reply(cmd, "Role name not valid UTF-8"); |
132 | 0 | return MOSQ_ERR_INVAL; |
133 | 0 | } |
134 | 0 | json_get_int(cmd->j_command, "priority", &priority, true, -1); |
135 | 0 | if(priority > PRIORITY_MAX){ |
136 | 0 | priority = PRIORITY_MAX; |
137 | 0 | } |
138 | 0 | if(priority < -PRIORITY_MAX){ |
139 | 0 | priority = -PRIORITY_MAX; |
140 | 0 | } |
141 | |
|
142 | 0 | group = dynsec_groups__find(data, groupname); |
143 | 0 | if(group == NULL){ |
144 | 0 | mosquitto_control_command_reply(cmd, "Group not found"); |
145 | 0 | return MOSQ_ERR_SUCCESS; |
146 | 0 | } |
147 | | |
148 | 0 | role = dynsec_roles__find(data, rolename); |
149 | 0 | if(role == NULL){ |
150 | 0 | mosquitto_control_command_reply(cmd, "Role not found"); |
151 | 0 | return MOSQ_ERR_SUCCESS; |
152 | 0 | } |
153 | | |
154 | 0 | admin_clientid = mosquitto_client_id(cmd->client); |
155 | 0 | admin_username = mosquitto_client_username(cmd->client); |
156 | |
|
157 | 0 | rc = dynsec_rolelist__group_add(group, role, priority); |
158 | 0 | if(rc == MOSQ_ERR_SUCCESS){ |
159 | | /* Continue */ |
160 | 0 | }else if(rc == MOSQ_ERR_ALREADY_EXISTS){ |
161 | 0 | mosquitto_control_command_reply(cmd, "Group is already in this role"); |
162 | 0 | return MOSQ_ERR_ALREADY_EXISTS; |
163 | 0 | }else{ |
164 | 0 | mosquitto_control_command_reply(cmd, "Internal error"); |
165 | 0 | return MOSQ_ERR_UNKNOWN; |
166 | 0 | } |
167 | | |
168 | 0 | mosquitto_log_printf(MOSQ_LOG_INFO, "dynsec: %s/%s | addGroupRole | groupname=%s | rolename=%s | priority=%d", |
169 | 0 | admin_clientid, admin_username, groupname, rolename, priority); |
170 | |
|
171 | 0 | dynsec__config_batch_save(data); |
172 | 0 | mosquitto_control_command_reply(cmd, NULL); |
173 | | |
174 | | /* Enforce any changes */ |
175 | 0 | group__kick_all(data, group); |
176 | |
|
177 | 0 | return MOSQ_ERR_SUCCESS; |
178 | 0 | } |
179 | | |
180 | | |
181 | | void dynsec_groups__cleanup(struct dynsec__data *data) |
182 | 5.76k | { |
183 | 5.76k | struct dynsec__group *group, *group_tmp = NULL; |
184 | | |
185 | 32.1k | HASH_ITER(hh, data->groups, group, group_tmp){ |
186 | 32.1k | group__free_item(data, group); |
187 | 32.1k | } |
188 | 5.76k | data->anonymous_group = NULL; |
189 | 5.76k | } |
190 | | |
191 | | |
192 | | /* ################################################################ |
193 | | * # |
194 | | * # Config file load |
195 | | * # |
196 | | * ################################################################ */ |
197 | | |
198 | | |
199 | | int dynsec_groups__config_load(struct dynsec__data *data, cJSON *tree) |
200 | 5.60k | { |
201 | 5.60k | cJSON *j_groups, *j_group; |
202 | 5.60k | cJSON *j_clientlist; |
203 | 5.60k | cJSON *j_roles; |
204 | 5.60k | const char *groupname; |
205 | | |
206 | 5.60k | struct dynsec__group *group; |
207 | 5.60k | struct dynsec__role *role; |
208 | 5.60k | int priority; |
209 | | |
210 | 5.60k | j_groups = cJSON_GetObjectItem(tree, "groups"); |
211 | 5.60k | if(j_groups == NULL){ |
212 | 3.51k | return 0; |
213 | 3.51k | } |
214 | | |
215 | 2.08k | if(cJSON_IsArray(j_groups) == false){ |
216 | 2 | return 1; |
217 | 2 | } |
218 | | |
219 | 38.7k | cJSON_ArrayForEach(j_group, j_groups){ |
220 | 38.7k | if(cJSON_IsObject(j_group) == true){ |
221 | | /* Group name */ |
222 | 38.0k | size_t groupname_len; |
223 | 38.0k | if(json_get_string(j_group, "groupname", &groupname, false) != MOSQ_ERR_SUCCESS){ |
224 | 3.71k | continue; |
225 | 3.71k | } |
226 | 34.3k | groupname_len = strlen(groupname); |
227 | 34.3k | if(groupname_len == 0){ |
228 | 205 | continue; |
229 | 205 | } |
230 | 34.1k | if(dynsec_groups__find(data, groupname)){ |
231 | 1.96k | continue; |
232 | 1.96k | } |
233 | | |
234 | 32.1k | group = mosquitto_calloc(1, sizeof(struct dynsec__group) + groupname_len + 1); |
235 | 32.1k | if(group == NULL){ |
236 | 0 | return MOSQ_ERR_NOMEM; |
237 | 0 | } |
238 | 32.1k | strncpy(group->groupname, groupname, groupname_len+1); |
239 | | |
240 | | /* Text name */ |
241 | 32.1k | const char *textname; |
242 | 32.1k | if(json_get_string(j_group, "textname", &textname, false) == MOSQ_ERR_SUCCESS){ |
243 | 1.18k | if(textname){ |
244 | 1.18k | group->text_name = mosquitto_strdup(textname); |
245 | 1.18k | if(group->text_name == NULL){ |
246 | 0 | mosquitto_free(group); |
247 | 0 | continue; |
248 | 0 | } |
249 | 1.18k | } |
250 | 1.18k | } |
251 | | |
252 | | /* Text description */ |
253 | 32.1k | const char *textdescription; |
254 | 32.1k | if(json_get_string(j_group, "textdescription", &textdescription, false) == MOSQ_ERR_SUCCESS){ |
255 | 72 | if(textdescription){ |
256 | 72 | group->text_description = mosquitto_strdup(textdescription); |
257 | 72 | if(group->text_description == NULL){ |
258 | 0 | mosquitto_free(group->text_name); |
259 | 0 | mosquitto_free(group); |
260 | 0 | continue; |
261 | 0 | } |
262 | 72 | } |
263 | 72 | } |
264 | | |
265 | | /* Roles */ |
266 | 32.1k | j_roles = cJSON_GetObjectItem(j_group, "roles"); |
267 | 32.1k | if(j_roles && cJSON_IsArray(j_roles)){ |
268 | 4.74k | cJSON *j_role; |
269 | | |
270 | 22.0k | cJSON_ArrayForEach(j_role, j_roles){ |
271 | 22.0k | if(cJSON_IsObject(j_role)){ |
272 | 21.1k | const char *rolename; |
273 | 21.1k | if(json_get_string(j_role, "rolename", &rolename, false) == MOSQ_ERR_SUCCESS){ |
274 | 16.4k | json_get_int(j_role, "priority", &priority, true, -1); |
275 | 16.4k | if(priority > PRIORITY_MAX){ |
276 | 199 | priority = PRIORITY_MAX; |
277 | 199 | } |
278 | 16.4k | if(priority < -PRIORITY_MAX){ |
279 | 228 | priority = -PRIORITY_MAX; |
280 | 228 | } |
281 | 16.4k | role = dynsec_roles__find(data, rolename); |
282 | 16.4k | dynsec_rolelist__group_add(group, role, priority); |
283 | 16.4k | } |
284 | 21.1k | } |
285 | 22.0k | } |
286 | 4.74k | } |
287 | | |
288 | | /* This must go before clients are loaded, otherwise the group won't be found */ |
289 | 32.1k | HASH_ADD(hh, data->groups, groupname, groupname_len, group); |
290 | | |
291 | | /* Clients */ |
292 | 32.1k | j_clientlist = cJSON_GetObjectItem(j_group, "clients"); |
293 | 32.1k | if(j_clientlist && cJSON_IsArray(j_clientlist)){ |
294 | 4.19k | cJSON *j_client; |
295 | 37.6k | cJSON_ArrayForEach(j_client, j_clientlist){ |
296 | 37.6k | if(cJSON_IsObject(j_client)){ |
297 | 37.1k | const char *username; |
298 | 37.1k | if(json_get_string(j_client, "username", &username, false) == MOSQ_ERR_SUCCESS){ |
299 | 32.0k | json_get_int(j_client, "priority", &priority, true, -1); |
300 | 32.0k | if(priority > PRIORITY_MAX){ |
301 | 254 | priority = PRIORITY_MAX; |
302 | 254 | } |
303 | 32.0k | if(priority < -PRIORITY_MAX){ |
304 | 233 | priority = -PRIORITY_MAX; |
305 | 233 | } |
306 | 32.0k | dynsec_groups__add_client(data, username, group->groupname, priority, false); |
307 | 32.0k | } |
308 | 37.1k | } |
309 | 37.6k | } |
310 | 4.19k | } |
311 | 32.1k | } |
312 | 38.7k | } |
313 | 2.08k | HASH_SORT(data->groups, group_cmp); |
314 | | |
315 | 2.08k | if(json_get_string(tree, "anonymousGroup", &groupname, false) == MOSQ_ERR_SUCCESS){ |
316 | 4 | data->anonymous_group = dynsec_groups__find(data, groupname); |
317 | 4 | } |
318 | | |
319 | 2.08k | return 0; |
320 | 2.08k | } |
321 | | |
322 | | |
323 | | /* ################################################################ |
324 | | * # |
325 | | * # Config load and save |
326 | | * # |
327 | | * ################################################################ */ |
328 | | |
329 | | |
330 | | static int dynsec__config_add_groups(struct dynsec__data *data, cJSON *j_groups) |
331 | 0 | { |
332 | 0 | struct dynsec__group *group, *group_tmp = NULL; |
333 | 0 | cJSON *j_group, *j_clients, *j_roles; |
334 | |
|
335 | 0 | HASH_ITER(hh, data->groups, group, group_tmp){ |
336 | 0 | j_group = cJSON_CreateObject(); |
337 | 0 | if(j_group == NULL){ |
338 | 0 | return 1; |
339 | 0 | } |
340 | 0 | cJSON_AddItemToArray(j_groups, j_group); |
341 | |
|
342 | 0 | if(cJSON_AddStringToObject(j_group, "groupname", group->groupname) == NULL |
343 | 0 | || (group->text_name && cJSON_AddStringToObject(j_group, "textname", group->text_name) == NULL) |
344 | 0 | || (group->text_description && cJSON_AddStringToObject(j_group, "textdescription", group->text_description) == NULL) |
345 | 0 | ){ |
346 | |
|
347 | 0 | return 1; |
348 | 0 | } |
349 | | |
350 | 0 | j_roles = dynsec_rolelist__all_to_json(group->rolelist); |
351 | 0 | if(j_roles == NULL){ |
352 | 0 | return 1; |
353 | 0 | } |
354 | 0 | cJSON_AddItemToObject(j_group, "roles", j_roles); |
355 | |
|
356 | 0 | j_clients = dynsec_clientlist__all_to_json(group->clientlist); |
357 | 0 | if(j_clients == NULL){ |
358 | 0 | return 1; |
359 | 0 | } |
360 | 0 | cJSON_AddItemToObject(j_group, "clients", j_clients); |
361 | 0 | } |
362 | | |
363 | 0 | return 0; |
364 | 0 | } |
365 | | |
366 | | |
367 | | int dynsec_groups__config_save(struct dynsec__data *data, cJSON *tree) |
368 | 0 | { |
369 | 0 | cJSON *j_groups; |
370 | |
|
371 | 0 | j_groups = cJSON_CreateArray(); |
372 | 0 | if(j_groups == NULL){ |
373 | 0 | return 1; |
374 | 0 | } |
375 | 0 | cJSON_AddItemToObject(tree, "groups", j_groups); |
376 | 0 | if(dynsec__config_add_groups(data, j_groups)){ |
377 | 0 | return 1; |
378 | 0 | } |
379 | | |
380 | 0 | if(data->anonymous_group |
381 | 0 | && cJSON_AddStringToObject(tree, "anonymousGroup", data->anonymous_group->groupname) == NULL){ |
382 | |
|
383 | 0 | return 1; |
384 | 0 | } |
385 | | |
386 | 0 | return 0; |
387 | 0 | } |
388 | | |
389 | | |
390 | | int dynsec_groups__process_create(struct dynsec__data *data, struct mosquitto_control_cmd *cmd) |
391 | 0 | { |
392 | 0 | const char *groupname, *text_name, *text_description; |
393 | 0 | struct dynsec__group *group = NULL; |
394 | 0 | int rc = MOSQ_ERR_SUCCESS; |
395 | 0 | const char *admin_clientid, *admin_username; |
396 | 0 | size_t groupname_len; |
397 | |
|
398 | 0 | if(json_get_string(cmd->j_command, "groupname", &groupname, false) != MOSQ_ERR_SUCCESS){ |
399 | 0 | mosquitto_control_command_reply(cmd, "Invalid/missing groupname"); |
400 | 0 | return MOSQ_ERR_INVAL; |
401 | 0 | } |
402 | 0 | groupname_len = strlen(groupname); |
403 | 0 | if(groupname_len == 0){ |
404 | 0 | mosquitto_control_command_reply(cmd, "Empty groupname"); |
405 | 0 | return MOSQ_ERR_INVAL; |
406 | 0 | } |
407 | 0 | if(mosquitto_validate_utf8(groupname, (int)groupname_len) != MOSQ_ERR_SUCCESS){ |
408 | 0 | mosquitto_control_command_reply(cmd, "Group name not valid UTF-8"); |
409 | 0 | return MOSQ_ERR_INVAL; |
410 | 0 | } |
411 | | |
412 | 0 | if(json_get_string(cmd->j_command, "textname", &text_name, true) != MOSQ_ERR_SUCCESS){ |
413 | 0 | mosquitto_control_command_reply(cmd, "Invalid/missing textname"); |
414 | 0 | return MOSQ_ERR_INVAL; |
415 | 0 | } |
416 | | |
417 | 0 | if(json_get_string(cmd->j_command, "textdescription", &text_description, true) != MOSQ_ERR_SUCCESS){ |
418 | 0 | mosquitto_control_command_reply(cmd, "Invalid/missing textdescription"); |
419 | 0 | return MOSQ_ERR_INVAL; |
420 | 0 | } |
421 | | |
422 | 0 | group = dynsec_groups__find(data, groupname); |
423 | 0 | if(group){ |
424 | 0 | mosquitto_control_command_reply(cmd, "Group already exists"); |
425 | 0 | return MOSQ_ERR_SUCCESS; |
426 | 0 | } |
427 | | |
428 | 0 | group = mosquitto_calloc(1, sizeof(struct dynsec__group) + groupname_len + 1); |
429 | 0 | if(group == NULL){ |
430 | 0 | mosquitto_control_command_reply(cmd, "Internal error"); |
431 | 0 | return MOSQ_ERR_NOMEM; |
432 | 0 | } |
433 | 0 | strncpy(group->groupname, groupname, groupname_len+1); |
434 | 0 | if(text_name){ |
435 | 0 | group->text_name = mosquitto_strdup(text_name); |
436 | 0 | if(group->text_name == NULL){ |
437 | 0 | mosquitto_control_command_reply(cmd, "Internal error"); |
438 | 0 | group__free_item(data, group); |
439 | 0 | return MOSQ_ERR_NOMEM; |
440 | 0 | } |
441 | 0 | } |
442 | 0 | if(text_description){ |
443 | 0 | group->text_description = mosquitto_strdup(text_description); |
444 | 0 | if(group->text_description == NULL){ |
445 | 0 | mosquitto_control_command_reply(cmd, "Internal error"); |
446 | 0 | group__free_item(data, group); |
447 | 0 | return MOSQ_ERR_NOMEM; |
448 | 0 | } |
449 | 0 | } |
450 | | |
451 | 0 | rc = dynsec_rolelist__load_from_json(data, cmd->j_command, &group->rolelist); |
452 | 0 | if(rc == MOSQ_ERR_SUCCESS || rc == ERR_LIST_NOT_FOUND){ |
453 | 0 | }else if(rc == MOSQ_ERR_NOT_FOUND){ |
454 | 0 | mosquitto_control_command_reply(cmd, "Role not found"); |
455 | 0 | group__free_item(data, group); |
456 | 0 | return MOSQ_ERR_INVAL; |
457 | 0 | }else{ |
458 | 0 | mosquitto_control_command_reply(cmd, "Internal error"); |
459 | 0 | group__free_item(data, group); |
460 | 0 | return MOSQ_ERR_INVAL; |
461 | 0 | } |
462 | | |
463 | 0 | HASH_ADD_INORDER(hh, data->groups, groupname, groupname_len, group, group_cmp); |
464 | | |
465 | 0 | admin_clientid = mosquitto_client_id(cmd->client); |
466 | 0 | admin_username = mosquitto_client_username(cmd->client); |
467 | 0 | mosquitto_log_printf(MOSQ_LOG_INFO, "dynsec: %s/%s | createGroup | groupname=%s", |
468 | 0 | admin_clientid, admin_username, groupname); |
469 | |
|
470 | 0 | dynsec__config_batch_save(data); |
471 | 0 | mosquitto_control_command_reply(cmd, NULL); |
472 | 0 | return MOSQ_ERR_SUCCESS; |
473 | 0 | } |
474 | | |
475 | | |
476 | | int dynsec_groups__process_delete(struct dynsec__data *data, struct mosquitto_control_cmd *cmd) |
477 | 0 | { |
478 | 0 | const char *groupname; |
479 | 0 | struct dynsec__group *group; |
480 | 0 | const char *admin_clientid, *admin_username; |
481 | |
|
482 | 0 | if(json_get_string(cmd->j_command, "groupname", &groupname, false) != MOSQ_ERR_SUCCESS){ |
483 | 0 | mosquitto_control_command_reply(cmd, "Invalid/missing groupname"); |
484 | 0 | return MOSQ_ERR_INVAL; |
485 | 0 | } |
486 | 0 | if(mosquitto_validate_utf8(groupname, (int)strlen(groupname)) != MOSQ_ERR_SUCCESS){ |
487 | 0 | mosquitto_control_command_reply(cmd, "Group name not valid UTF-8"); |
488 | 0 | return MOSQ_ERR_INVAL; |
489 | 0 | } |
490 | | |
491 | 0 | group = dynsec_groups__find(data, groupname); |
492 | 0 | if(group){ |
493 | 0 | if(group == data->anonymous_group){ |
494 | 0 | mosquitto_control_command_reply(cmd, "Deleting the anonymous group is forbidden"); |
495 | 0 | return MOSQ_ERR_INVAL; |
496 | 0 | } |
497 | | |
498 | | /* Enforce any changes */ |
499 | 0 | group__kick_all(data, group); |
500 | |
|
501 | 0 | dynsec__remove_all_roles_from_group(group); |
502 | 0 | group__free_item(data, group); |
503 | 0 | dynsec__config_batch_save(data); |
504 | 0 | mosquitto_control_command_reply(cmd, NULL); |
505 | |
|
506 | 0 | admin_clientid = mosquitto_client_id(cmd->client); |
507 | 0 | admin_username = mosquitto_client_username(cmd->client); |
508 | 0 | mosquitto_log_printf(MOSQ_LOG_INFO, "dynsec: %s/%s | deleteGroup | groupname=%s", |
509 | 0 | admin_clientid, admin_username, groupname); |
510 | |
|
511 | 0 | return MOSQ_ERR_SUCCESS; |
512 | 0 | }else{ |
513 | 0 | mosquitto_control_command_reply(cmd, "Group not found"); |
514 | 0 | return MOSQ_ERR_SUCCESS; |
515 | 0 | } |
516 | 0 | } |
517 | | |
518 | | |
519 | | int dynsec_groups__add_client(struct dynsec__data *data, const char *username, const char *groupname, int priority, bool update_config) |
520 | 32.0k | { |
521 | 32.0k | struct dynsec__client *client; |
522 | 32.0k | struct dynsec__clientlist *clientlist; |
523 | 32.0k | struct dynsec__group *group; |
524 | 32.0k | int rc; |
525 | | |
526 | 32.0k | client = dynsec_clients__find(data, username); |
527 | 32.0k | if(client == NULL){ |
528 | 5.45k | return ERR_USER_NOT_FOUND; |
529 | 5.45k | } |
530 | | |
531 | 26.5k | group = dynsec_groups__find(data, groupname); |
532 | 26.5k | if(group == NULL){ |
533 | 0 | return ERR_GROUP_NOT_FOUND; |
534 | 0 | } |
535 | | |
536 | 26.5k | HASH_FIND(hh, group->clientlist, username, strlen(username), clientlist); |
537 | 26.5k | if(clientlist != NULL){ |
538 | | /* Client is already in the group */ |
539 | 5.23k | return MOSQ_ERR_ALREADY_EXISTS; |
540 | 5.23k | } |
541 | | |
542 | 21.3k | rc = dynsec_clientlist__add(&group->clientlist, client, priority); |
543 | 21.3k | if(rc){ |
544 | 0 | return rc; |
545 | 0 | } |
546 | 21.3k | rc = dynsec_grouplist__add(&client->grouplist, group, priority); |
547 | 21.3k | if(rc){ |
548 | 0 | dynsec_clientlist__remove(&group->clientlist, client); |
549 | 0 | return rc; |
550 | 0 | } |
551 | | |
552 | 21.3k | if(update_config){ |
553 | 0 | dynsec__config_batch_save(data); |
554 | 0 | } |
555 | | |
556 | 21.3k | return MOSQ_ERR_SUCCESS; |
557 | 21.3k | } |
558 | | |
559 | | |
560 | | int dynsec_groups__process_add_client(struct dynsec__data *data, struct mosquitto_control_cmd *cmd) |
561 | 0 | { |
562 | 0 | const char *username, *groupname; |
563 | 0 | int rc; |
564 | 0 | int priority; |
565 | 0 | const char *admin_clientid, *admin_username; |
566 | |
|
567 | 0 | if(json_get_string(cmd->j_command, "username", &username, false) != MOSQ_ERR_SUCCESS){ |
568 | 0 | mosquitto_control_command_reply(cmd, "Invalid/missing username"); |
569 | 0 | return MOSQ_ERR_INVAL; |
570 | 0 | } |
571 | 0 | if(mosquitto_validate_utf8(username, (int)strlen(username)) != MOSQ_ERR_SUCCESS){ |
572 | 0 | mosquitto_control_command_reply(cmd, "Username not valid UTF-8"); |
573 | 0 | return MOSQ_ERR_INVAL; |
574 | 0 | } |
575 | | |
576 | 0 | if(json_get_string(cmd->j_command, "groupname", &groupname, false) != MOSQ_ERR_SUCCESS){ |
577 | 0 | mosquitto_control_command_reply(cmd, "Invalid/missing groupname"); |
578 | 0 | return MOSQ_ERR_INVAL; |
579 | 0 | } |
580 | 0 | if(mosquitto_validate_utf8(groupname, (int)strlen(groupname)) != MOSQ_ERR_SUCCESS){ |
581 | 0 | mosquitto_control_command_reply(cmd, "Group name not valid UTF-8"); |
582 | 0 | return MOSQ_ERR_INVAL; |
583 | 0 | } |
584 | | |
585 | 0 | json_get_int(cmd->j_command, "priority", &priority, true, -1); |
586 | 0 | if(priority > PRIORITY_MAX){ |
587 | 0 | priority = PRIORITY_MAX; |
588 | 0 | } |
589 | 0 | if(priority < -PRIORITY_MAX){ |
590 | 0 | priority = -PRIORITY_MAX; |
591 | 0 | } |
592 | |
|
593 | 0 | rc = dynsec_groups__add_client(data, username, groupname, priority, true); |
594 | 0 | if(rc == MOSQ_ERR_SUCCESS){ |
595 | 0 | admin_clientid = mosquitto_client_id(cmd->client); |
596 | 0 | admin_username = mosquitto_client_username(cmd->client); |
597 | 0 | mosquitto_log_printf(MOSQ_LOG_INFO, "dynsec: %s/%s | addGroupClient | groupname=%s | username=%s | priority=%d", |
598 | 0 | admin_clientid, admin_username, groupname, username, priority); |
599 | |
|
600 | 0 | mosquitto_control_command_reply(cmd, NULL); |
601 | 0 | }else if(rc == ERR_USER_NOT_FOUND){ |
602 | 0 | mosquitto_control_command_reply(cmd, "Client not found"); |
603 | 0 | }else if(rc == ERR_GROUP_NOT_FOUND){ |
604 | 0 | mosquitto_control_command_reply(cmd, "Group not found"); |
605 | 0 | }else if(rc == MOSQ_ERR_ALREADY_EXISTS){ |
606 | 0 | mosquitto_control_command_reply(cmd, "Client is already in this group"); |
607 | 0 | }else{ |
608 | 0 | mosquitto_control_command_reply(cmd, "Internal error"); |
609 | 0 | } |
610 | | |
611 | | /* Enforce any changes */ |
612 | 0 | dynsec_kicklist__add(data, username); |
613 | |
|
614 | 0 | return rc; |
615 | 0 | } |
616 | | |
617 | | |
618 | | static int dynsec__remove_all_clients_from_group(struct dynsec__group *group) |
619 | 32.1k | { |
620 | 32.1k | struct dynsec__clientlist *clientlist, *clientlist_tmp = NULL; |
621 | | |
622 | 32.1k | HASH_ITER(hh, group->clientlist, clientlist, clientlist_tmp){ |
623 | | /* Remove client stored group reference */ |
624 | 21.3k | dynsec_grouplist__remove(&clientlist->client->grouplist, group); |
625 | | |
626 | 21.3k | HASH_DELETE(hh, group->clientlist, clientlist); |
627 | 21.3k | mosquitto_free(clientlist); |
628 | 21.3k | } |
629 | | |
630 | 32.1k | return MOSQ_ERR_SUCCESS; |
631 | 32.1k | } |
632 | | |
633 | | |
634 | | static int dynsec__remove_all_roles_from_group(struct dynsec__group *group) |
635 | 0 | { |
636 | 0 | struct dynsec__rolelist *rolelist, *rolelist_tmp = NULL; |
637 | |
|
638 | 0 | HASH_ITER(hh, group->rolelist, rolelist, rolelist_tmp){ |
639 | 0 | dynsec_rolelist__group_remove(group, rolelist->role); |
640 | 0 | } |
641 | |
|
642 | 0 | return MOSQ_ERR_SUCCESS; |
643 | 0 | } |
644 | | |
645 | | |
646 | | int dynsec_groups__remove_client(struct dynsec__data *data, const char *username, const char *groupname, bool update_config) |
647 | 0 | { |
648 | 0 | struct dynsec__client *client; |
649 | 0 | struct dynsec__group *group; |
650 | |
|
651 | 0 | client = dynsec_clients__find(data, username); |
652 | 0 | if(client == NULL){ |
653 | 0 | return ERR_USER_NOT_FOUND; |
654 | 0 | } |
655 | | |
656 | 0 | group = dynsec_groups__find(data, groupname); |
657 | 0 | if(group == NULL){ |
658 | 0 | return ERR_GROUP_NOT_FOUND; |
659 | 0 | } |
660 | | |
661 | 0 | dynsec_clientlist__remove(&group->clientlist, client); |
662 | 0 | dynsec_grouplist__remove(&client->grouplist, group); |
663 | |
|
664 | 0 | if(update_config){ |
665 | 0 | dynsec__config_batch_save(data); |
666 | 0 | } |
667 | 0 | return MOSQ_ERR_SUCCESS; |
668 | 0 | } |
669 | | |
670 | | |
671 | | int dynsec_groups__process_remove_client(struct dynsec__data *data, struct mosquitto_control_cmd *cmd) |
672 | 0 | { |
673 | 0 | const char *username, *groupname; |
674 | 0 | int rc; |
675 | 0 | const char *admin_clientid, *admin_username; |
676 | |
|
677 | 0 | if(json_get_string(cmd->j_command, "username", &username, false) != MOSQ_ERR_SUCCESS){ |
678 | 0 | mosquitto_control_command_reply(cmd, "Invalid/missing username"); |
679 | 0 | return MOSQ_ERR_INVAL; |
680 | 0 | } |
681 | 0 | if(mosquitto_validate_utf8(username, (int)strlen(username)) != MOSQ_ERR_SUCCESS){ |
682 | 0 | mosquitto_control_command_reply(cmd, "Username not valid UTF-8"); |
683 | 0 | return MOSQ_ERR_INVAL; |
684 | 0 | } |
685 | | |
686 | 0 | if(json_get_string(cmd->j_command, "groupname", &groupname, false) != MOSQ_ERR_SUCCESS){ |
687 | 0 | mosquitto_control_command_reply(cmd, "Invalid/missing groupname"); |
688 | 0 | return MOSQ_ERR_INVAL; |
689 | 0 | } |
690 | 0 | if(mosquitto_validate_utf8(groupname, (int)strlen(groupname)) != MOSQ_ERR_SUCCESS){ |
691 | 0 | mosquitto_control_command_reply(cmd, "Group name not valid UTF-8"); |
692 | 0 | return MOSQ_ERR_INVAL; |
693 | 0 | } |
694 | | |
695 | 0 | rc = dynsec_groups__remove_client(data, username, groupname, true); |
696 | 0 | if(rc == MOSQ_ERR_SUCCESS){ |
697 | 0 | admin_clientid = mosquitto_client_id(cmd->client); |
698 | 0 | admin_username = mosquitto_client_username(cmd->client); |
699 | 0 | mosquitto_log_printf(MOSQ_LOG_INFO, "dynsec: %s/%s | removeGroupClient | groupname=%s | username=%s", |
700 | 0 | admin_clientid, admin_username, groupname, username); |
701 | |
|
702 | 0 | mosquitto_control_command_reply(cmd, NULL); |
703 | 0 | }else if(rc == ERR_USER_NOT_FOUND){ |
704 | 0 | mosquitto_control_command_reply(cmd, "Client not found"); |
705 | 0 | }else if(rc == ERR_GROUP_NOT_FOUND){ |
706 | 0 | mosquitto_control_command_reply(cmd, "Group not found"); |
707 | 0 | }else{ |
708 | 0 | mosquitto_control_command_reply(cmd, "Internal error"); |
709 | 0 | } |
710 | | |
711 | | /* Enforce any changes */ |
712 | 0 | dynsec_kicklist__add(data, username); |
713 | |
|
714 | 0 | return rc; |
715 | 0 | } |
716 | | |
717 | | |
718 | | static cJSON *add_group_to_json(struct dynsec__group *group) |
719 | 0 | { |
720 | 0 | cJSON *j_group, *jtmp, *j_clientlist, *j_client, *j_rolelist; |
721 | 0 | struct dynsec__clientlist *clientlist, *clientlist_tmp = NULL; |
722 | |
|
723 | 0 | j_group = cJSON_CreateObject(); |
724 | 0 | if(j_group == NULL){ |
725 | 0 | return NULL; |
726 | 0 | } |
727 | | |
728 | 0 | if(cJSON_AddStringToObject(j_group, "groupname", group->groupname) == NULL |
729 | 0 | || (group->text_name && cJSON_AddStringToObject(j_group, "textname", group->text_name) == NULL) |
730 | 0 | || (group->text_description && cJSON_AddStringToObject(j_group, "textdescription", group->text_description) == NULL) |
731 | 0 | || (j_clientlist = cJSON_AddArrayToObject(j_group, "clients")) == NULL |
732 | 0 | ){ |
733 | |
|
734 | 0 | cJSON_Delete(j_group); |
735 | 0 | return NULL; |
736 | 0 | } |
737 | | |
738 | 0 | HASH_ITER(hh, group->clientlist, clientlist, clientlist_tmp){ |
739 | 0 | j_client = cJSON_CreateObject(); |
740 | 0 | if(j_client == NULL){ |
741 | 0 | cJSON_Delete(j_group); |
742 | 0 | return NULL; |
743 | 0 | } |
744 | 0 | cJSON_AddItemToArray(j_clientlist, j_client); |
745 | |
|
746 | 0 | jtmp = cJSON_CreateStringReference(clientlist->client->username); |
747 | 0 | if(jtmp == NULL){ |
748 | 0 | cJSON_Delete(j_group); |
749 | 0 | return NULL; |
750 | 0 | } |
751 | 0 | cJSON_AddItemToObject(j_client, "username", jtmp); |
752 | 0 | } |
753 | | |
754 | 0 | j_rolelist = dynsec_rolelist__all_to_json(group->rolelist); |
755 | 0 | if(j_rolelist == NULL){ |
756 | 0 | cJSON_Delete(j_group); |
757 | 0 | return NULL; |
758 | 0 | } |
759 | 0 | cJSON_AddItemToObject(j_group, "roles", j_rolelist); |
760 | |
|
761 | 0 | return j_group; |
762 | 0 | } |
763 | | |
764 | | |
765 | | int dynsec_groups__process_list(struct dynsec__data *data, struct mosquitto_control_cmd *cmd) |
766 | 0 | { |
767 | 0 | bool verbose; |
768 | 0 | cJSON *tree, *j_groups, *j_group, *j_data; |
769 | 0 | struct dynsec__group *group, *group_tmp = NULL; |
770 | 0 | int i, count, offset; |
771 | 0 | const char *admin_clientid, *admin_username; |
772 | |
|
773 | 0 | json_get_bool(cmd->j_command, "verbose", &verbose, true, false); |
774 | 0 | json_get_int(cmd->j_command, "count", &count, true, -1); |
775 | 0 | json_get_int(cmd->j_command, "offset", &offset, true, 0); |
776 | |
|
777 | 0 | tree = cJSON_CreateObject(); |
778 | 0 | if(tree == NULL){ |
779 | 0 | mosquitto_control_command_reply(cmd, "Internal error"); |
780 | 0 | return MOSQ_ERR_NOMEM; |
781 | 0 | } |
782 | | |
783 | 0 | if(cJSON_AddStringToObject(tree, "command", "listGroups") == NULL |
784 | 0 | || (j_data = cJSON_AddObjectToObject(tree, "data")) == NULL |
785 | 0 | || cJSON_AddIntToObject(j_data, "totalCount", (int)HASH_CNT(hh, data->groups)) == NULL |
786 | 0 | || (j_groups = cJSON_AddArrayToObject(j_data, "groups")) == NULL |
787 | 0 | || (cmd->correlation_data && cJSON_AddStringToObject(tree, "correlationData", cmd->correlation_data) == NULL) |
788 | 0 | ){ |
789 | |
|
790 | 0 | cJSON_Delete(tree); |
791 | 0 | mosquitto_control_command_reply(cmd, "Internal error"); |
792 | 0 | return MOSQ_ERR_NOMEM; |
793 | 0 | } |
794 | | |
795 | 0 | i = 0; |
796 | 0 | HASH_ITER(hh, data->groups, group, group_tmp){ |
797 | 0 | if(i>=offset){ |
798 | 0 | if(verbose){ |
799 | 0 | j_group = add_group_to_json(group); |
800 | 0 | if(j_group == NULL){ |
801 | 0 | cJSON_Delete(tree); |
802 | 0 | mosquitto_control_command_reply(cmd, "Internal error"); |
803 | 0 | return MOSQ_ERR_NOMEM; |
804 | 0 | } |
805 | 0 | cJSON_AddItemToArray(j_groups, j_group); |
806 | |
|
807 | 0 | }else{ |
808 | 0 | j_group = cJSON_CreateString(group->groupname); |
809 | 0 | if(j_group){ |
810 | 0 | cJSON_AddItemToArray(j_groups, j_group); |
811 | 0 | }else{ |
812 | 0 | cJSON_Delete(tree); |
813 | 0 | mosquitto_control_command_reply(cmd, "Internal error"); |
814 | 0 | return MOSQ_ERR_NOMEM; |
815 | 0 | } |
816 | 0 | } |
817 | | |
818 | 0 | if(count >= 0){ |
819 | 0 | count--; |
820 | 0 | if(count <= 0){ |
821 | 0 | break; |
822 | 0 | } |
823 | 0 | } |
824 | 0 | } |
825 | 0 | i++; |
826 | 0 | } |
827 | | |
828 | 0 | cJSON_AddItemToArray(cmd->j_responses, tree); |
829 | |
|
830 | 0 | admin_clientid = mosquitto_client_id(cmd->client); |
831 | 0 | admin_username = mosquitto_client_username(cmd->client); |
832 | 0 | mosquitto_log_printf(MOSQ_LOG_INFO, "dynsec: %s/%s | listGroups | verbose=%s | count=%d | offset=%d", |
833 | 0 | admin_clientid, admin_username, verbose?"true":"false", count, offset); |
834 | |
|
835 | 0 | return MOSQ_ERR_SUCCESS; |
836 | 0 | } |
837 | | |
838 | | |
839 | | int dynsec_groups__process_get(struct dynsec__data *data, struct mosquitto_control_cmd *cmd) |
840 | 0 | { |
841 | 0 | const char *groupname; |
842 | 0 | cJSON *tree, *j_group, *j_data; |
843 | 0 | struct dynsec__group *group; |
844 | 0 | const char *admin_clientid, *admin_username; |
845 | |
|
846 | 0 | if(json_get_string(cmd->j_command, "groupname", &groupname, false) != MOSQ_ERR_SUCCESS){ |
847 | 0 | mosquitto_control_command_reply(cmd, "Invalid/missing groupname"); |
848 | 0 | return MOSQ_ERR_INVAL; |
849 | 0 | } |
850 | 0 | if(mosquitto_validate_utf8(groupname, (int)strlen(groupname)) != MOSQ_ERR_SUCCESS){ |
851 | 0 | mosquitto_control_command_reply(cmd, "Group name not valid UTF-8"); |
852 | 0 | return MOSQ_ERR_INVAL; |
853 | 0 | } |
854 | | |
855 | 0 | tree = cJSON_CreateObject(); |
856 | 0 | if(tree == NULL){ |
857 | 0 | mosquitto_control_command_reply(cmd, "Internal error"); |
858 | 0 | return MOSQ_ERR_NOMEM; |
859 | 0 | } |
860 | | |
861 | 0 | if(cJSON_AddStringToObject(tree, "command", "getGroup") == NULL |
862 | 0 | || (j_data = cJSON_AddObjectToObject(tree, "data")) == NULL |
863 | 0 | || (cmd->correlation_data && cJSON_AddStringToObject(tree, "correlationData", cmd->correlation_data) == NULL) |
864 | 0 | ){ |
865 | |
|
866 | 0 | cJSON_Delete(tree); |
867 | 0 | mosquitto_control_command_reply(cmd, "Internal error"); |
868 | 0 | return MOSQ_ERR_NOMEM; |
869 | 0 | } |
870 | | |
871 | 0 | group = dynsec_groups__find(data, groupname); |
872 | 0 | if(group){ |
873 | 0 | j_group = add_group_to_json(group); |
874 | 0 | if(j_group == NULL){ |
875 | 0 | cJSON_Delete(tree); |
876 | 0 | mosquitto_control_command_reply(cmd, "Internal error"); |
877 | 0 | return MOSQ_ERR_NOMEM; |
878 | 0 | } |
879 | 0 | cJSON_AddItemToObject(j_data, "group", j_group); |
880 | 0 | }else{ |
881 | 0 | cJSON_Delete(tree); |
882 | 0 | mosquitto_control_command_reply(cmd, "Group not found"); |
883 | 0 | return MOSQ_ERR_NOMEM; |
884 | 0 | } |
885 | | |
886 | 0 | cJSON_AddItemToArray(cmd->j_responses, tree); |
887 | |
|
888 | 0 | admin_clientid = mosquitto_client_id(cmd->client); |
889 | 0 | admin_username = mosquitto_client_username(cmd->client); |
890 | 0 | mosquitto_log_printf(MOSQ_LOG_INFO, "dynsec: %s/%s | getGroup | groupname=%s", |
891 | 0 | admin_clientid, admin_username, groupname); |
892 | |
|
893 | 0 | return MOSQ_ERR_SUCCESS; |
894 | 0 | } |
895 | | |
896 | | |
897 | | int dynsec_groups__process_remove_role(struct dynsec__data *data, struct mosquitto_control_cmd *cmd) |
898 | 0 | { |
899 | 0 | const char *groupname, *rolename; |
900 | 0 | struct dynsec__group *group; |
901 | 0 | struct dynsec__role *role; |
902 | 0 | const char *admin_clientid, *admin_username; |
903 | |
|
904 | 0 | if(json_get_string(cmd->j_command, "groupname", &groupname, false) != MOSQ_ERR_SUCCESS){ |
905 | 0 | mosquitto_control_command_reply(cmd, "Invalid/missing groupname"); |
906 | 0 | return MOSQ_ERR_INVAL; |
907 | 0 | } |
908 | 0 | if(mosquitto_validate_utf8(groupname, (int)strlen(groupname)) != MOSQ_ERR_SUCCESS){ |
909 | 0 | mosquitto_control_command_reply(cmd, "Group name not valid UTF-8"); |
910 | 0 | return MOSQ_ERR_INVAL; |
911 | 0 | } |
912 | | |
913 | 0 | if(json_get_string(cmd->j_command, "rolename", &rolename, false) != MOSQ_ERR_SUCCESS){ |
914 | 0 | mosquitto_control_command_reply(cmd, "Invalid/missing rolename"); |
915 | 0 | return MOSQ_ERR_INVAL; |
916 | 0 | } |
917 | 0 | if(mosquitto_validate_utf8(rolename, (int)strlen(rolename)) != MOSQ_ERR_SUCCESS){ |
918 | 0 | mosquitto_control_command_reply(cmd, "Role name not valid UTF-8"); |
919 | 0 | return MOSQ_ERR_INVAL; |
920 | 0 | } |
921 | | |
922 | 0 | group = dynsec_groups__find(data, groupname); |
923 | 0 | if(group == NULL){ |
924 | 0 | mosquitto_control_command_reply(cmd, "Group not found"); |
925 | 0 | return MOSQ_ERR_SUCCESS; |
926 | 0 | } |
927 | | |
928 | 0 | role = dynsec_roles__find(data, rolename); |
929 | 0 | if(role == NULL){ |
930 | 0 | mosquitto_control_command_reply(cmd, "Role not found"); |
931 | 0 | return MOSQ_ERR_SUCCESS; |
932 | 0 | } |
933 | | |
934 | 0 | dynsec_rolelist__group_remove(group, role); |
935 | 0 | dynsec__config_batch_save(data); |
936 | 0 | mosquitto_control_command_reply(cmd, NULL); |
937 | | |
938 | | /* Enforce any changes */ |
939 | 0 | group__kick_all(data, group); |
940 | |
|
941 | 0 | admin_clientid = mosquitto_client_id(cmd->client); |
942 | 0 | admin_username = mosquitto_client_username(cmd->client); |
943 | 0 | mosquitto_log_printf(MOSQ_LOG_INFO, "dynsec: %s/%s | removeGroupRole | groupname=%s | rolename=%s", |
944 | 0 | admin_clientid, admin_username, groupname, rolename); |
945 | |
|
946 | 0 | return MOSQ_ERR_SUCCESS; |
947 | 0 | } |
948 | | |
949 | | |
950 | | int dynsec_groups__process_modify(struct dynsec__data *data, struct mosquitto_control_cmd *cmd) |
951 | 0 | { |
952 | 0 | const char *groupname = NULL; |
953 | 0 | char *text_name = NULL, *text_description = NULL; |
954 | 0 | struct dynsec__client *client = NULL; |
955 | 0 | struct dynsec__group *group = NULL; |
956 | 0 | struct dynsec__rolelist *rolelist = NULL; |
957 | 0 | bool have_text_name = false, have_text_description = false, have_rolelist = false; |
958 | 0 | const char *str; |
959 | 0 | int rc; |
960 | 0 | int priority; |
961 | 0 | cJSON *j_client, *j_clients; |
962 | 0 | const char *admin_clientid, *admin_username; |
963 | |
|
964 | 0 | if(json_get_string(cmd->j_command, "groupname", &groupname, false) != MOSQ_ERR_SUCCESS){ |
965 | 0 | mosquitto_control_command_reply(cmd, "Invalid/missing groupname"); |
966 | 0 | return MOSQ_ERR_INVAL; |
967 | 0 | } |
968 | 0 | if(mosquitto_validate_utf8(groupname, (int)strlen(groupname)) != MOSQ_ERR_SUCCESS){ |
969 | 0 | mosquitto_control_command_reply(cmd, "Group name not valid UTF-8"); |
970 | 0 | return MOSQ_ERR_INVAL; |
971 | 0 | } |
972 | | |
973 | 0 | group = dynsec_groups__find(data, groupname); |
974 | 0 | if(group == NULL){ |
975 | 0 | mosquitto_control_command_reply(cmd, "Group not found"); |
976 | 0 | return MOSQ_ERR_INVAL; |
977 | 0 | } |
978 | | |
979 | 0 | if(json_get_string(cmd->j_command, "textname", &str, false) == MOSQ_ERR_SUCCESS){ |
980 | 0 | have_text_name = true; |
981 | 0 | text_name = mosquitto_strdup(str); |
982 | 0 | if(text_name == NULL){ |
983 | 0 | mosquitto_control_command_reply(cmd, "Internal error"); |
984 | 0 | rc = MOSQ_ERR_NOMEM; |
985 | 0 | goto error; |
986 | 0 | } |
987 | 0 | } |
988 | | |
989 | 0 | if(json_get_string(cmd->j_command, "textdescription", &str, false) == MOSQ_ERR_SUCCESS){ |
990 | 0 | have_text_description = true; |
991 | 0 | text_description = mosquitto_strdup(str); |
992 | 0 | if(text_description == NULL){ |
993 | 0 | mosquitto_control_command_reply(cmd, "Internal error"); |
994 | 0 | rc = MOSQ_ERR_NOMEM; |
995 | 0 | goto error; |
996 | 0 | } |
997 | 0 | } |
998 | | |
999 | 0 | rc = dynsec_rolelist__load_from_json(data, cmd->j_command, &rolelist); |
1000 | 0 | if(rc == MOSQ_ERR_SUCCESS){ |
1001 | | /* Apply changes below */ |
1002 | 0 | have_rolelist = true; |
1003 | 0 | }else if(rc == ERR_LIST_NOT_FOUND){ |
1004 | | /* There was no list in the JSON, so no modification */ |
1005 | 0 | rolelist = NULL; |
1006 | 0 | }else if(rc == MOSQ_ERR_NOT_FOUND){ |
1007 | 0 | mosquitto_control_command_reply(cmd, "Role not found"); |
1008 | 0 | rc = MOSQ_ERR_INVAL; |
1009 | 0 | goto error; |
1010 | 0 | }else{ |
1011 | 0 | if(rc == MOSQ_ERR_INVAL){ |
1012 | 0 | mosquitto_control_command_reply(cmd, "'roles' not an array or missing/invalid rolename"); |
1013 | 0 | }else{ |
1014 | 0 | mosquitto_control_command_reply(cmd, "Internal error"); |
1015 | 0 | } |
1016 | 0 | rc = MOSQ_ERR_INVAL; |
1017 | 0 | goto error; |
1018 | 0 | } |
1019 | | |
1020 | 0 | j_clients = cJSON_GetObjectItem(cmd->j_command, "clients"); |
1021 | 0 | if(j_clients && cJSON_IsArray(j_clients)){ |
1022 | | /* Iterate over array to check clients are valid before proceeding */ |
1023 | 0 | cJSON_ArrayForEach(j_client, j_clients){ |
1024 | 0 | if(cJSON_IsObject(j_client)){ |
1025 | 0 | const char *username; |
1026 | 0 | if(json_get_string(j_client, "username", &username, false) == MOSQ_ERR_SUCCESS){ |
1027 | 0 | client = dynsec_clients__find(data, username); |
1028 | 0 | if(client == NULL){ |
1029 | 0 | mosquitto_control_command_reply(cmd, "'clients' contains an object with a 'username' that does not exist"); |
1030 | 0 | rc = MOSQ_ERR_INVAL; |
1031 | 0 | goto error; |
1032 | 0 | } |
1033 | 0 | }else{ |
1034 | 0 | mosquitto_control_command_reply(cmd, "'clients' contains an object with an invalid 'username'"); |
1035 | 0 | rc = MOSQ_ERR_INVAL; |
1036 | 0 | goto error; |
1037 | 0 | } |
1038 | 0 | } |
1039 | 0 | } |
1040 | | |
1041 | | /* Kick all clients in the *current* group */ |
1042 | 0 | group__kick_all(data, group); |
1043 | 0 | dynsec__remove_all_clients_from_group(group); |
1044 | | |
1045 | | /* Now we can add the new clients to the group */ |
1046 | 0 | cJSON_ArrayForEach(j_client, j_clients){ |
1047 | 0 | if(cJSON_IsObject(j_client)){ |
1048 | 0 | const char *username; |
1049 | 0 | if(json_get_string(j_client, "username", &username, false) == MOSQ_ERR_SUCCESS){ |
1050 | 0 | json_get_int(j_client, "priority", &priority, true, -1); |
1051 | 0 | if(priority > PRIORITY_MAX){ |
1052 | 0 | priority = PRIORITY_MAX; |
1053 | 0 | } |
1054 | 0 | if(priority < -PRIORITY_MAX){ |
1055 | 0 | priority = -PRIORITY_MAX; |
1056 | 0 | } |
1057 | 0 | dynsec_groups__add_client(data, username, groupname, priority, false); |
1058 | 0 | } |
1059 | 0 | } |
1060 | 0 | } |
1061 | 0 | } |
1062 | | |
1063 | | /* Apply remaining changes to group, note that user changes are already applied */ |
1064 | 0 | if(have_text_name){ |
1065 | 0 | mosquitto_free(group->text_name); |
1066 | 0 | group->text_name = text_name; |
1067 | 0 | } |
1068 | |
|
1069 | 0 | if(have_text_description){ |
1070 | 0 | mosquitto_free(group->text_description); |
1071 | 0 | group->text_description = text_description; |
1072 | 0 | } |
1073 | |
|
1074 | 0 | if(have_rolelist){ |
1075 | 0 | dynsec_rolelist__cleanup(&group->rolelist); |
1076 | 0 | group->rolelist = rolelist; |
1077 | 0 | } |
1078 | | |
1079 | | /* And save */ |
1080 | 0 | dynsec__config_batch_save(data); |
1081 | |
|
1082 | 0 | mosquitto_control_command_reply(cmd, NULL); |
1083 | | |
1084 | | /* Enforce any changes - kick any clients in the *new* group */ |
1085 | 0 | group__kick_all(data, group); |
1086 | |
|
1087 | 0 | admin_clientid = mosquitto_client_id(cmd->client); |
1088 | 0 | admin_username = mosquitto_client_username(cmd->client); |
1089 | 0 | mosquitto_log_printf(MOSQ_LOG_INFO, "dynsec: %s/%s | modifyGroup | groupname=%s", |
1090 | 0 | admin_clientid, admin_username, groupname); |
1091 | |
|
1092 | 0 | return MOSQ_ERR_SUCCESS; |
1093 | 0 | error: |
1094 | 0 | mosquitto_free(text_name); |
1095 | 0 | mosquitto_free(text_description); |
1096 | 0 | dynsec_rolelist__cleanup(&rolelist); |
1097 | |
|
1098 | 0 | admin_clientid = mosquitto_client_id(cmd->client); |
1099 | 0 | admin_username = mosquitto_client_username(cmd->client); |
1100 | 0 | mosquitto_log_printf(MOSQ_LOG_INFO, "dynsec: %s/%s | modifyGroup | groupname=%s", |
1101 | 0 | admin_clientid, admin_username, groupname); |
1102 | |
|
1103 | 0 | return rc; |
1104 | 0 | } |
1105 | | |
1106 | | |
1107 | | int dynsec_groups__process_set_anonymous_group(struct dynsec__data *data, struct mosquitto_control_cmd *cmd) |
1108 | 0 | { |
1109 | 0 | const char *groupname; |
1110 | 0 | struct dynsec__group *group = NULL; |
1111 | 0 | const char *admin_clientid, *admin_username; |
1112 | |
|
1113 | 0 | if(json_get_string(cmd->j_command, "groupname", &groupname, false) != MOSQ_ERR_SUCCESS){ |
1114 | 0 | mosquitto_control_command_reply(cmd, "Invalid/missing groupname"); |
1115 | 0 | return MOSQ_ERR_INVAL; |
1116 | 0 | } |
1117 | 0 | if(mosquitto_validate_utf8(groupname, (int)strlen(groupname)) != MOSQ_ERR_SUCCESS){ |
1118 | 0 | mosquitto_control_command_reply(cmd, "Group name not valid UTF-8"); |
1119 | 0 | return MOSQ_ERR_INVAL; |
1120 | 0 | } |
1121 | | |
1122 | 0 | group = dynsec_groups__find(data, groupname); |
1123 | 0 | if(group == NULL){ |
1124 | 0 | mosquitto_control_command_reply(cmd, "Group not found"); |
1125 | 0 | return MOSQ_ERR_SUCCESS; |
1126 | 0 | } |
1127 | | |
1128 | 0 | data->anonymous_group = group; |
1129 | |
|
1130 | 0 | dynsec__config_batch_save(data); |
1131 | 0 | mosquitto_control_command_reply(cmd, NULL); |
1132 | | |
1133 | | /* Enforce any changes */ |
1134 | 0 | dynsec_kicklist__add(data, NULL); |
1135 | |
|
1136 | 0 | admin_clientid = mosquitto_client_id(cmd->client); |
1137 | 0 | admin_username = mosquitto_client_username(cmd->client); |
1138 | 0 | mosquitto_log_printf(MOSQ_LOG_INFO, "dynsec: %s/%s | setAnonymousGroup | groupname=%s", |
1139 | 0 | admin_clientid, admin_username, groupname); |
1140 | |
|
1141 | 0 | return MOSQ_ERR_SUCCESS; |
1142 | 0 | } |
1143 | | |
1144 | | |
1145 | | int dynsec_groups__process_get_anonymous_group(struct dynsec__data *data, struct mosquitto_control_cmd *cmd) |
1146 | 0 | { |
1147 | 0 | cJSON *tree, *j_data, *j_group; |
1148 | 0 | const char *groupname; |
1149 | 0 | const char *admin_clientid, *admin_username; |
1150 | |
|
1151 | 0 | tree = cJSON_CreateObject(); |
1152 | 0 | if(tree == NULL){ |
1153 | 0 | mosquitto_control_command_reply(cmd, "Internal error"); |
1154 | 0 | return MOSQ_ERR_NOMEM; |
1155 | 0 | } |
1156 | | |
1157 | 0 | if(data->anonymous_group){ |
1158 | 0 | groupname = data->anonymous_group->groupname; |
1159 | 0 | }else{ |
1160 | 0 | groupname = ""; |
1161 | 0 | } |
1162 | |
|
1163 | 0 | if(cJSON_AddStringToObject(tree, "command", "getAnonymousGroup") == NULL |
1164 | 0 | || (j_data = cJSON_AddObjectToObject(tree, "data")) == NULL |
1165 | 0 | || (j_group = cJSON_AddObjectToObject(j_data, "group")) == NULL |
1166 | 0 | || cJSON_AddStringToObject(j_group, "groupname", groupname) == NULL |
1167 | 0 | || (cmd->correlation_data && cJSON_AddStringToObject(tree, "correlationData", cmd->correlation_data) == NULL) |
1168 | 0 | ){ |
1169 | |
|
1170 | 0 | cJSON_Delete(tree); |
1171 | 0 | mosquitto_control_command_reply(cmd, "Internal error"); |
1172 | 0 | return MOSQ_ERR_NOMEM; |
1173 | 0 | } |
1174 | | |
1175 | 0 | cJSON_AddItemToArray(cmd->j_responses, tree); |
1176 | |
|
1177 | 0 | admin_clientid = mosquitto_client_id(cmd->client); |
1178 | 0 | admin_username = mosquitto_client_username(cmd->client); |
1179 | 0 | mosquitto_log_printf(MOSQ_LOG_INFO, "dynsec: %s/%s | getAnonymousGroup", |
1180 | 0 | admin_clientid, admin_username); |
1181 | |
|
1182 | 0 | return MOSQ_ERR_SUCCESS; |
1183 | 0 | } |