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