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