Coverage Report

Created: 2025-10-24 06:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mosquitto/plugins/dynamic-security/roles.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 <string.h>
24
#include <uthash.h>
25
#include <utlist.h>
26
27
#ifndef WIN32
28
#  include <strings.h>
29
#endif
30
31
#include "dynamic_security.h"
32
#include "json_help.h"
33
34
static cJSON *add_role_to_json(struct dynsec__role *role, bool verbose);
35
static void role__remove_all_clients(struct dynsec__data *data, struct dynsec__role *role);
36
37
38
/* ################################################################
39
 * #
40
 * # Utility functions
41
 * #
42
 * ################################################################ */
43
44
45
static int role_cmp(void *a, void *b)
46
251k
{
47
251k
  struct dynsec__role *role_a = a;
48
251k
  struct dynsec__role *role_b = b;
49
50
251k
  return strcmp(role_a->rolename, role_b->rolename);
51
251k
}
52
53
54
static void role__free_acl(struct dynsec__acl **acl, struct dynsec__acl *item)
55
21.0k
{
56
21.0k
  HASH_DELETE(hh, *acl, item);
57
21.0k
  mosquitto_free(item);
58
21.0k
}
59
60
61
static void role__free_all_acls(struct dynsec__acl **acl)
62
263k
{
63
263k
  struct dynsec__acl *iter, *tmp = NULL;
64
65
263k
  HASH_ITER(hh, *acl, iter, tmp){
66
21.0k
    role__free_acl(acl, iter);
67
21.0k
  }
68
263k
}
69
70
71
static void role__free_item(struct dynsec__data *data, struct dynsec__role *role, bool remove_from_hash)
72
43.8k
{
73
43.8k
  if(remove_from_hash){
74
43.8k
    HASH_DEL(data->roles, role);
75
43.8k
  }
76
43.8k
  dynsec_clientlist__cleanup(&role->clientlist);
77
43.8k
  dynsec_grouplist__cleanup(&role->grouplist);
78
43.8k
  mosquitto_free(role->text_name);
79
43.8k
  mosquitto_free(role->text_description);
80
43.8k
  role__free_all_acls(&role->acls.publish_c_send);
81
43.8k
  role__free_all_acls(&role->acls.publish_c_recv);
82
43.8k
  role__free_all_acls(&role->acls.subscribe_literal);
83
43.8k
  role__free_all_acls(&role->acls.subscribe_pattern);
84
43.8k
  role__free_all_acls(&role->acls.unsubscribe_literal);
85
43.8k
  role__free_all_acls(&role->acls.unsubscribe_pattern);
86
43.8k
  mosquitto_free(role);
87
43.8k
}
88
89
struct dynsec__role *dynsec_roles__find(struct dynsec__data *data, const char *rolename)
90
107k
{
91
107k
  struct dynsec__role *role = NULL;
92
93
107k
  if(rolename){
94
107k
    HASH_FIND(hh, data->roles, rolename, strlen(rolename), role);
95
107k
  }
96
107k
  return role;
97
107k
}
98
99
100
void dynsec_roles__cleanup(struct dynsec__data *data)
101
5.70k
{
102
5.70k
  struct dynsec__role *role, *role_tmp = NULL;
103
104
43.8k
  HASH_ITER(hh, data->roles, role, role_tmp){
105
43.8k
    role__free_item(data, role, true);
106
43.8k
  }
107
5.70k
}
108
109
110
static void role__kick_all(struct dynsec__data *data, struct dynsec__role *role)
111
0
{
112
0
  struct dynsec__grouplist *grouplist, *grouplist_tmp = NULL;
113
114
0
  dynsec_clientlist__kick_all(data, role->clientlist);
115
116
0
  HASH_ITER(hh, role->grouplist, grouplist, grouplist_tmp){
117
0
    if(grouplist->group == data->anonymous_group){
118
0
      dynsec_kicklist__add(data, NULL);
119
0
    }
120
0
    dynsec_clientlist__kick_all(data, grouplist->group->clientlist);
121
0
  }
122
0
}
123
124
125
/* ################################################################
126
 * #
127
 * # Config file load and save
128
 * #
129
 * ################################################################ */
130
131
132
static int add_single_acl_to_json(cJSON *j_array, const char *acl_type, struct dynsec__acl *acl)
133
0
{
134
0
  struct dynsec__acl *iter, *tmp = NULL;
135
0
  cJSON *j_acl;
136
137
0
  HASH_ITER(hh, acl, iter, tmp){
138
0
    j_acl = cJSON_CreateObject();
139
0
    if(j_acl == NULL){
140
0
      return 1;
141
0
    }
142
0
    cJSON_AddItemToArray(j_array, j_acl);
143
144
0
    if(cJSON_AddStringToObject(j_acl, "acltype", acl_type) == NULL
145
0
        || cJSON_AddStringToObject(j_acl, "topic", iter->topic) == NULL
146
0
        || cJSON_AddIntToObject(j_acl, "priority", iter->priority) == NULL
147
0
        || cJSON_AddBoolToObject(j_acl, "allow", iter->allow) == NULL
148
0
        ){
149
150
0
      return 1;
151
0
    }
152
0
  }
153
154
155
0
  return 0;
156
0
}
157
158
159
static int add_acls_to_json(cJSON *j_role, struct dynsec__role *role)
160
0
{
161
0
  cJSON *j_acls;
162
163
0
  if((j_acls = cJSON_AddArrayToObject(j_role, "acls")) == NULL){
164
0
    return 1;
165
0
  }
166
167
0
  if(add_single_acl_to_json(j_acls, ACL_TYPE_PUB_C_SEND, role->acls.publish_c_send) != MOSQ_ERR_SUCCESS
168
0
      || add_single_acl_to_json(j_acls, ACL_TYPE_PUB_C_RECV, role->acls.publish_c_recv) != MOSQ_ERR_SUCCESS
169
0
      || add_single_acl_to_json(j_acls, ACL_TYPE_SUB_LITERAL, role->acls.subscribe_literal) != MOSQ_ERR_SUCCESS
170
0
      || add_single_acl_to_json(j_acls, ACL_TYPE_SUB_PATTERN, role->acls.subscribe_pattern) != MOSQ_ERR_SUCCESS
171
0
      || add_single_acl_to_json(j_acls, ACL_TYPE_UNSUB_LITERAL, role->acls.unsubscribe_literal) != MOSQ_ERR_SUCCESS
172
0
      || add_single_acl_to_json(j_acls, ACL_TYPE_UNSUB_PATTERN, role->acls.unsubscribe_pattern) != MOSQ_ERR_SUCCESS
173
0
      ){
174
175
0
    return 1;
176
0
  }
177
0
  return 0;
178
0
}
179
180
181
int dynsec_roles__config_save(struct dynsec__data *data, cJSON *tree)
182
0
{
183
0
  cJSON *j_roles, *j_role;
184
0
  struct dynsec__role *role, *role_tmp = NULL;
185
186
0
  if((j_roles = cJSON_AddArrayToObject(tree, "roles")) == NULL){
187
0
    return 1;
188
0
  }
189
190
0
  HASH_ITER(hh, data->roles, role, role_tmp){
191
0
    j_role = add_role_to_json(role, true);
192
0
    if(j_role == NULL){
193
0
      return 1;
194
0
    }
195
0
    cJSON_AddItemToArray(j_roles, j_role);
196
0
  }
197
198
0
  return 0;
199
0
}
200
201
202
static int insert_acl_cmp(struct dynsec__acl *a, struct dynsec__acl *b)
203
375k
{
204
375k
  return b->priority - a->priority;
205
375k
}
206
207
208
static int dynsec_roles__acl_load(cJSON *j_acls, const char *key, struct dynsec__acl **acllist)
209
13.8k
{
210
13.8k
  cJSON *j_acl;
211
13.8k
  struct dynsec__acl *acl;
212
213
206k
  cJSON_ArrayForEach(j_acl, j_acls){
214
206k
    const char *acltype;
215
206k
    const char *topic;
216
206k
    size_t topic_len;
217
218
206k
    if(json_get_string(j_acl, "acltype", &acltype, false) != MOSQ_ERR_SUCCESS){
219
26.2k
      continue;
220
26.2k
    }
221
179k
    if(strcasecmp(acltype, key) != 0){
222
155k
      continue;
223
155k
    }
224
24.8k
    if(json_get_string(j_acl, "topic", &topic, false) != MOSQ_ERR_SUCCESS){
225
778
      continue;
226
778
    }
227
228
24.0k
    topic_len = strlen(topic);
229
24.0k
    if(topic_len == 0){
230
200
      continue;
231
200
    }
232
233
23.8k
    HASH_FIND(hh, *acllist, topic, strlen(topic), acl);
234
23.8k
    if(acl){
235
2.80k
      continue;
236
2.80k
    }
237
238
21.0k
    acl = mosquitto_calloc(1, sizeof(struct dynsec__acl) + topic_len + 1);
239
21.0k
    if(acl == NULL){
240
0
      return 1;
241
0
    }
242
21.0k
    strncpy(acl->topic, topic, topic_len+1);
243
244
21.0k
    json_get_int(j_acl, "priority", &acl->priority, true, 0);
245
21.0k
    if(acl->priority > PRIORITY_MAX){
246
379
      acl->priority = PRIORITY_MAX;
247
379
    }
248
21.0k
    if(acl->priority < -PRIORITY_MAX){
249
326
      acl->priority = -PRIORITY_MAX;
250
326
    }
251
21.0k
    json_get_bool(j_acl, "allow", &acl->allow, true, false);
252
253
21.0k
    bool allow;
254
21.0k
    if(json_get_bool(j_acl, "allow", &allow, false, false) == MOSQ_ERR_SUCCESS){
255
833
      acl->allow = allow;
256
833
    }
257
258
21.0k
    HASH_ADD_INORDER(hh, *acllist, topic, topic_len, acl, insert_acl_cmp);
259
21.0k
  }
260
261
13.8k
  return 0;
262
13.8k
}
263
264
265
int dynsec_roles__config_load(struct dynsec__data *data, cJSON *tree)
266
5.58k
{
267
5.58k
  cJSON *j_roles, *j_role, *j_acls;
268
5.58k
  struct dynsec__role *role;
269
5.58k
  size_t rolename_len;
270
271
5.58k
  j_roles = cJSON_GetObjectItem(tree, "roles");
272
5.58k
  if(j_roles == NULL){
273
2.46k
    return 0;
274
2.46k
  }
275
276
3.11k
  if(cJSON_IsArray(j_roles) == false){
277
1
    return 1;
278
1
  }
279
280
56.8k
  cJSON_ArrayForEach(j_role, j_roles){
281
56.8k
    if(cJSON_IsObject(j_role) == true){
282
      /* Role name */
283
56.4k
      const char *rolename;
284
56.4k
      if(json_get_string(j_role, "rolename", &rolename, false) != MOSQ_ERR_SUCCESS){
285
7.00k
        continue;
286
7.00k
      }
287
49.4k
      rolename_len = strlen(rolename);
288
49.4k
      if(rolename_len == 0){
289
228
        continue;
290
228
      }
291
49.1k
      if(dynsec_roles__find(data, rolename)){
292
5.32k
        continue;
293
5.32k
      }
294
295
43.8k
      role = mosquitto_calloc(1, sizeof(struct dynsec__role) + rolename_len + 1);
296
43.8k
      if(role == NULL){
297
0
        return MOSQ_ERR_NOMEM;
298
0
      }
299
43.8k
      strncpy(role->rolename, rolename, rolename_len+1);
300
301
      /* Text name */
302
43.8k
      const char *textname;
303
43.8k
      if(json_get_string(j_role, "textname", &textname, false) == MOSQ_ERR_SUCCESS){
304
186
        role->text_name = mosquitto_strdup(textname);
305
186
        if(role->text_name == NULL){
306
0
          mosquitto_free(role);
307
0
          continue;
308
0
        }
309
186
      }
310
311
      /* Text description */
312
43.8k
      const char *textdescription;
313
43.8k
      if(json_get_string(j_role, "textdescription", &textdescription, false) == MOSQ_ERR_SUCCESS){
314
254
        role->text_description = mosquitto_strdup(textdescription);
315
254
        if(role->text_description == NULL){
316
0
          mosquitto_free(role->text_name);
317
0
          mosquitto_free(role);
318
0
          continue;
319
0
        }
320
254
      }
321
322
      /* Allow wildcard subs */
323
43.8k
      json_get_bool(j_role, "allowwildcardsubs", &role->allow_wildcard_subs, true, true);
324
325
      /* ACLs */
326
43.8k
      j_acls = cJSON_GetObjectItem(j_role, "acls");
327
43.8k
      if(j_acls && cJSON_IsArray(j_acls)){
328
2.30k
        if(dynsec_roles__acl_load(j_acls, ACL_TYPE_PUB_C_SEND, &role->acls.publish_c_send) != 0
329
2.30k
            || dynsec_roles__acl_load(j_acls, ACL_TYPE_PUB_C_RECV, &role->acls.publish_c_recv) != 0
330
2.30k
            || dynsec_roles__acl_load(j_acls, ACL_TYPE_SUB_LITERAL, &role->acls.subscribe_literal) != 0
331
2.30k
            || dynsec_roles__acl_load(j_acls, ACL_TYPE_SUB_PATTERN, &role->acls.subscribe_pattern) != 0
332
2.30k
            || dynsec_roles__acl_load(j_acls, ACL_TYPE_UNSUB_LITERAL, &role->acls.unsubscribe_literal) != 0
333
2.30k
            || dynsec_roles__acl_load(j_acls, ACL_TYPE_UNSUB_PATTERN, &role->acls.unsubscribe_pattern) != 0
334
2.30k
            ){
335
336
0
          mosquitto_free(role->text_name);
337
0
          mosquitto_free(role->text_description);
338
0
          mosquitto_free(role);
339
0
          continue;
340
0
        }
341
2.30k
      }
342
343
43.8k
      HASH_ADD(hh, data->roles, rolename, rolename_len, role);
344
43.8k
    }
345
56.8k
  }
346
3.11k
  HASH_SORT(data->roles, role_cmp);
347
348
3.11k
  return 0;
349
3.11k
}
350
351
352
int dynsec_roles__process_create(struct dynsec__data *data, struct mosquitto_control_cmd *cmd)
353
0
{
354
0
  const char *rolename;
355
0
  const char *text_name, *text_description;
356
0
  bool allow_wildcard_subs;
357
0
  struct dynsec__role *role;
358
0
  int rc = MOSQ_ERR_SUCCESS;
359
0
  cJSON *j_acls;
360
0
  const char *admin_clientid, *admin_username;
361
0
  size_t rolename_len;
362
363
0
  if(json_get_string(cmd->j_command, "rolename", &rolename, false) != MOSQ_ERR_SUCCESS){
364
0
    mosquitto_control_command_reply(cmd, "Invalid/missing rolename");
365
0
    return MOSQ_ERR_INVAL;
366
0
  }
367
0
  rolename_len = strlen(rolename);
368
0
  if(rolename_len == 0){
369
0
    mosquitto_control_command_reply(cmd, "Empty rolename");
370
0
    return MOSQ_ERR_INVAL;
371
0
  }
372
0
  if(mosquitto_validate_utf8(rolename, (int)rolename_len) != MOSQ_ERR_SUCCESS){
373
0
    mosquitto_control_command_reply(cmd, "Role name not valid UTF-8");
374
0
    return MOSQ_ERR_INVAL;
375
0
  }
376
377
0
  if(json_get_string(cmd->j_command, "textname", &text_name, true) != MOSQ_ERR_SUCCESS){
378
0
    mosquitto_control_command_reply(cmd, "Invalid/missing textname");
379
0
    return MOSQ_ERR_INVAL;
380
0
  }
381
382
0
  if(json_get_string(cmd->j_command, "textdescription", &text_description, true) != MOSQ_ERR_SUCCESS){
383
0
    mosquitto_control_command_reply(cmd, "Invalid/missing textdescription");
384
0
    return MOSQ_ERR_INVAL;
385
0
  }
386
387
0
  if(json_get_bool(cmd->j_command, "allowwildcardsubs", &allow_wildcard_subs, true, true) != MOSQ_ERR_SUCCESS){
388
0
    mosquitto_control_command_reply(cmd, "Invalid allowwildcardsubs");
389
0
    return MOSQ_ERR_INVAL;
390
0
  }
391
392
0
  role = dynsec_roles__find(data, rolename);
393
0
  if(role){
394
0
    mosquitto_control_command_reply(cmd, "Role already exists");
395
0
    return MOSQ_ERR_SUCCESS;
396
0
  }
397
398
0
  role = mosquitto_calloc(1, sizeof(struct dynsec__role) + rolename_len + 1);
399
0
  if(role == NULL){
400
0
    mosquitto_control_command_reply(cmd, "Internal error");
401
0
    return MOSQ_ERR_NOMEM;
402
0
  }
403
0
  strncpy(role->rolename, rolename, rolename_len+1);
404
0
  if(text_name){
405
0
    role->text_name = mosquitto_strdup(text_name);
406
0
    if(role->text_name == NULL){
407
0
      mosquitto_control_command_reply(cmd, "Internal error");
408
0
      rc = MOSQ_ERR_NOMEM;
409
0
      goto error;
410
0
    }
411
0
  }
412
0
  if(text_description){
413
0
    role->text_description = mosquitto_strdup(text_description);
414
0
    if(role->text_description == NULL){
415
0
      mosquitto_control_command_reply(cmd, "Internal error");
416
0
      rc = MOSQ_ERR_NOMEM;
417
0
      goto error;
418
0
    }
419
0
  }
420
0
  role->allow_wildcard_subs = allow_wildcard_subs;
421
422
  /* ACLs */
423
0
  j_acls = cJSON_GetObjectItem(cmd->j_command, "acls");
424
0
  if(j_acls && cJSON_IsArray(j_acls)){
425
0
    if(dynsec_roles__acl_load(j_acls, ACL_TYPE_PUB_C_SEND, &role->acls.publish_c_send) != 0
426
0
        || dynsec_roles__acl_load(j_acls, ACL_TYPE_PUB_C_RECV, &role->acls.publish_c_recv) != 0
427
0
        || dynsec_roles__acl_load(j_acls, ACL_TYPE_SUB_LITERAL, &role->acls.subscribe_literal) != 0
428
0
        || dynsec_roles__acl_load(j_acls, ACL_TYPE_SUB_PATTERN, &role->acls.subscribe_pattern) != 0
429
0
        || dynsec_roles__acl_load(j_acls, ACL_TYPE_UNSUB_LITERAL, &role->acls.unsubscribe_literal) != 0
430
0
        || dynsec_roles__acl_load(j_acls, ACL_TYPE_UNSUB_PATTERN, &role->acls.unsubscribe_pattern) != 0
431
0
        ){
432
433
0
      mosquitto_control_command_reply(cmd, "Internal error");
434
0
      rc = MOSQ_ERR_NOMEM;
435
0
      goto error;
436
0
    }
437
0
  }
438
439
440
0
  HASH_ADD_INORDER(hh, data->roles, rolename, rolename_len, role, role_cmp);
441
442
0
  dynsec__config_batch_save(data);
443
444
0
  mosquitto_control_command_reply(cmd, NULL);
445
446
0
  admin_clientid = mosquitto_client_id(cmd->client);
447
0
  admin_username = mosquitto_client_username(cmd->client);
448
0
  mosquitto_log_printf(MOSQ_LOG_INFO, "dynsec: %s/%s | createRole | rolename=%s",
449
0
      admin_clientid, admin_username, rolename);
450
451
0
  return MOSQ_ERR_SUCCESS;
452
0
error:
453
0
  if(role){
454
0
    role__free_item(data, role, false);
455
0
  }
456
0
  return rc;
457
0
}
458
459
460
static void role__remove_all_clients(struct dynsec__data *data, struct dynsec__role *role)
461
0
{
462
0
  struct dynsec__clientlist *clientlist, *clientlist_tmp = NULL;
463
464
0
  HASH_ITER(hh, role->clientlist, clientlist, clientlist_tmp){
465
0
    dynsec_kicklist__add(data, clientlist->client->username);
466
467
0
    dynsec_rolelist__client_remove(clientlist->client, role);
468
0
  }
469
0
}
470
471
472
static void role__remove_all_groups(struct dynsec__data *data, struct dynsec__role *role)
473
0
{
474
0
  struct dynsec__grouplist *grouplist, *grouplist_tmp = NULL;
475
476
0
  HASH_ITER(hh, role->grouplist, grouplist, grouplist_tmp){
477
0
    if(grouplist->group == data->anonymous_group){
478
0
      dynsec_kicklist__add(data, NULL);
479
0
    }
480
0
    dynsec_clientlist__kick_all(data, grouplist->group->clientlist);
481
482
0
    dynsec_rolelist__group_remove(grouplist->group, role);
483
0
  }
484
0
}
485
486
487
int dynsec_roles__process_delete(struct dynsec__data *data, struct mosquitto_control_cmd *cmd)
488
0
{
489
0
  const char *rolename;
490
0
  struct dynsec__role *role;
491
0
  const char *admin_clientid, *admin_username;
492
493
0
  if(json_get_string(cmd->j_command, "rolename", &rolename, false) != MOSQ_ERR_SUCCESS){
494
0
    mosquitto_control_command_reply(cmd, "Invalid/missing rolename");
495
0
    return MOSQ_ERR_INVAL;
496
0
  }
497
0
  if(mosquitto_validate_utf8(rolename, (int)strlen(rolename)) != MOSQ_ERR_SUCCESS){
498
0
    mosquitto_control_command_reply(cmd, "Role name not valid UTF-8");
499
0
    return MOSQ_ERR_INVAL;
500
0
  }
501
502
0
  role = dynsec_roles__find(data, rolename);
503
0
  if(role){
504
0
    role__remove_all_clients(data, role);
505
0
    role__remove_all_groups(data, role);
506
0
    role__free_item(data, role, true);
507
0
    dynsec__config_batch_save(data);
508
0
    mosquitto_control_command_reply(cmd, NULL);
509
510
0
    admin_clientid = mosquitto_client_id(cmd->client);
511
0
    admin_username = mosquitto_client_username(cmd->client);
512
0
    mosquitto_log_printf(MOSQ_LOG_INFO, "dynsec: %s/%s | deleteRole | rolename=%s",
513
0
        admin_clientid, admin_username, rolename);
514
515
0
    return MOSQ_ERR_SUCCESS;
516
0
  }else{
517
0
    mosquitto_control_command_reply(cmd, "Role not found");
518
0
    return MOSQ_ERR_SUCCESS;
519
0
  }
520
0
}
521
522
523
static cJSON *add_role_to_json(struct dynsec__role *role, bool verbose)
524
0
{
525
0
  cJSON *j_role = NULL;
526
527
0
  if(verbose){
528
0
    j_role = cJSON_CreateObject();
529
0
    if(j_role == NULL){
530
0
      return NULL;
531
0
    }
532
533
0
    if(cJSON_AddStringToObject(j_role, "rolename", role->rolename) == NULL
534
0
        || (role->text_name && cJSON_AddStringToObject(j_role, "textname", role->text_name) == NULL)
535
0
        || (role->text_description && cJSON_AddStringToObject(j_role, "textdescription", role->text_description) == NULL)
536
0
        || cJSON_AddBoolToObject(j_role, "allowwildcardsubs", role->allow_wildcard_subs) == NULL
537
0
        ){
538
539
0
      cJSON_Delete(j_role);
540
0
      return NULL;
541
0
    }
542
0
    if(add_acls_to_json(j_role, role)){
543
0
      cJSON_Delete(j_role);
544
0
      return NULL;
545
0
    }
546
0
  }else{
547
0
    j_role = cJSON_CreateString(role->rolename);
548
0
    if(j_role == NULL){
549
0
      return NULL;
550
0
    }
551
0
  }
552
0
  return j_role;
553
0
}
554
555
556
int dynsec_roles__process_list(struct dynsec__data *data, struct mosquitto_control_cmd *cmd)
557
0
{
558
0
  bool verbose;
559
0
  struct dynsec__role *role, *role_tmp = NULL;
560
0
  cJSON *tree, *j_roles, *j_role, *j_data;
561
0
  int i, count, offset;
562
0
  const char *admin_clientid, *admin_username;
563
564
0
  json_get_bool(cmd->j_command, "verbose", &verbose, true, false);
565
0
  json_get_int(cmd->j_command, "count", &count, true, -1);
566
0
  json_get_int(cmd->j_command, "offset", &offset, true, 0);
567
568
0
  tree = cJSON_CreateObject();
569
0
  if(tree == NULL){
570
0
    mosquitto_control_command_reply(cmd, "Internal error");
571
0
    return MOSQ_ERR_NOMEM;
572
0
  }
573
574
0
  if(cJSON_AddStringToObject(tree, "command", "listRoles") == NULL
575
0
      || (j_data = cJSON_AddObjectToObject(tree, "data")) == NULL
576
0
      || cJSON_AddIntToObject(j_data, "totalCount", (int)HASH_CNT(hh, data->roles)) == NULL
577
0
      || (j_roles = cJSON_AddArrayToObject(j_data, "roles")) == NULL
578
0
      || (cmd->correlation_data && cJSON_AddStringToObject(tree, "correlationData", cmd->correlation_data) == NULL)
579
0
      ){
580
581
0
    cJSON_Delete(tree);
582
0
    mosquitto_control_command_reply(cmd, "Internal error");
583
0
    return MOSQ_ERR_NOMEM;
584
0
  }
585
586
0
  i = 0;
587
0
  HASH_ITER(hh, data->roles, role, role_tmp){
588
0
    if(i>=offset){
589
0
      j_role = add_role_to_json(role, verbose);
590
0
      if(j_role == NULL){
591
0
        cJSON_Delete(tree);
592
0
        mosquitto_control_command_reply(cmd, "Internal error");
593
0
        return MOSQ_ERR_NOMEM;
594
0
      }
595
0
      cJSON_AddItemToArray(j_roles, j_role);
596
597
0
      if(count >= 0){
598
0
        count--;
599
0
        if(count <= 0){
600
0
          break;
601
0
        }
602
0
      }
603
0
    }
604
0
    i++;
605
0
  }
606
607
0
  cJSON_AddItemToArray(cmd->j_responses, tree);
608
609
0
  admin_clientid = mosquitto_client_id(cmd->client);
610
0
  admin_username = mosquitto_client_username(cmd->client);
611
0
  mosquitto_log_printf(MOSQ_LOG_INFO, "dynsec: %s/%s | listRoles | verbose=%s | count=%d | offset=%d",
612
0
      admin_clientid, admin_username, verbose?"true":"false", count, offset);
613
614
0
  return MOSQ_ERR_SUCCESS;
615
0
}
616
617
618
int dynsec_roles__process_add_acl(struct dynsec__data *data, struct mosquitto_control_cmd *cmd)
619
0
{
620
0
  const char *rolename;
621
0
  struct dynsec__role *role;
622
0
  struct dynsec__acl **acllist, *acl;
623
0
  int rc;
624
0
  const char *admin_clientid, *admin_username;
625
0
  const char *topic;
626
0
  size_t topic_len;
627
0
  const char *acltype;
628
629
0
  if(json_get_string(cmd->j_command, "rolename", &rolename, false) != MOSQ_ERR_SUCCESS){
630
0
    mosquitto_control_command_reply(cmd, "Invalid/missing rolename");
631
0
    return MOSQ_ERR_INVAL;
632
0
  }
633
0
  if(mosquitto_validate_utf8(rolename, (int)strlen(rolename)) != MOSQ_ERR_SUCCESS){
634
0
    mosquitto_control_command_reply(cmd, "Role name not valid UTF-8");
635
0
    return MOSQ_ERR_INVAL;
636
0
  }
637
638
0
  role = dynsec_roles__find(data, rolename);
639
0
  if(role == NULL){
640
0
    mosquitto_control_command_reply(cmd, "Role not found");
641
0
    return MOSQ_ERR_SUCCESS;
642
0
  }
643
644
0
  if(json_get_string(cmd->j_command, "acltype", &acltype, false) != MOSQ_ERR_SUCCESS){
645
0
    mosquitto_control_command_reply(cmd, "Invalid/missing acltype");
646
0
    return MOSQ_ERR_SUCCESS;
647
0
  }
648
0
  if(!strcasecmp(acltype, ACL_TYPE_PUB_C_SEND)){
649
0
    acllist = &role->acls.publish_c_send;
650
0
  }else if(!strcasecmp(acltype, ACL_TYPE_PUB_C_RECV)){
651
0
    acllist = &role->acls.publish_c_recv;
652
0
  }else if(!strcasecmp(acltype, ACL_TYPE_SUB_LITERAL)){
653
0
    acllist = &role->acls.subscribe_literal;
654
0
  }else if(!strcasecmp(acltype, ACL_TYPE_SUB_PATTERN)){
655
0
    acllist = &role->acls.subscribe_pattern;
656
0
  }else if(!strcasecmp(acltype, ACL_TYPE_UNSUB_LITERAL)){
657
0
    acllist = &role->acls.unsubscribe_literal;
658
0
  }else if(!strcasecmp(acltype, ACL_TYPE_UNSUB_PATTERN)){
659
0
    acllist = &role->acls.unsubscribe_pattern;
660
0
  }else{
661
0
    mosquitto_control_command_reply(cmd, "Unknown acltype");
662
0
    return MOSQ_ERR_SUCCESS;
663
0
  }
664
665
0
  if(json_get_string(cmd->j_command, "topic", &topic, false) == MOSQ_ERR_SUCCESS){
666
0
    topic_len = strlen(topic);
667
0
    if(mosquitto_validate_utf8(topic, (int)topic_len) != MOSQ_ERR_SUCCESS){
668
0
      mosquitto_control_command_reply(cmd, "Topic not valid UTF-8");
669
0
      return MOSQ_ERR_INVAL;
670
0
    }
671
0
    rc = mosquitto_sub_topic_check(topic);
672
0
    if(rc != MOSQ_ERR_SUCCESS){
673
0
      mosquitto_control_command_reply(cmd, "Invalid ACL topic");
674
0
      return MOSQ_ERR_INVAL;
675
0
    }
676
0
  }else{
677
0
    mosquitto_control_command_reply(cmd, "Invalid/missing topic");
678
0
    return MOSQ_ERR_SUCCESS;
679
0
  }
680
681
0
  HASH_FIND(hh, *acllist, topic, topic_len, acl);
682
0
  if(acl){
683
0
    mosquitto_control_command_reply(cmd, "ACL with this topic already exists");
684
0
    return MOSQ_ERR_SUCCESS;
685
0
  }
686
687
0
  acl = mosquitto_calloc(1, sizeof(struct dynsec__acl) + topic_len + 1);
688
0
  if(acl == NULL){
689
0
    mosquitto_control_command_reply(cmd, "Internal error");
690
0
    return MOSQ_ERR_SUCCESS;
691
0
  }
692
0
  strncpy(acl->topic, topic, topic_len+1);
693
694
0
  json_get_int(cmd->j_command, "priority", &acl->priority, true, 0);
695
0
  if(acl->priority > PRIORITY_MAX){
696
0
    acl->priority = PRIORITY_MAX;
697
0
  }
698
0
  if(acl->priority < -PRIORITY_MAX){
699
0
    acl->priority = -PRIORITY_MAX;
700
0
  }
701
0
  json_get_bool(cmd->j_command, "allow", &acl->allow, true, false);
702
703
0
  HASH_ADD_INORDER(hh, *acllist, topic, topic_len, acl, insert_acl_cmp);
704
0
  dynsec__config_batch_save(data);
705
0
  mosquitto_control_command_reply(cmd, NULL);
706
707
0
  role__kick_all(data, role);
708
709
0
  admin_clientid = mosquitto_client_id(cmd->client);
710
0
  admin_username = mosquitto_client_username(cmd->client);
711
0
  mosquitto_log_printf(MOSQ_LOG_INFO, "dynsec: %s/%s | addRoleACL | rolename=%s | acltype=%s | topic=%s | priority=%d | allow=%s",
712
0
      admin_clientid, admin_username, rolename, acltype, topic, acl->priority, acl->allow?"true":"false");
713
714
0
  return MOSQ_ERR_SUCCESS;
715
0
}
716
717
718
int dynsec_roles__process_remove_acl(struct dynsec__data *data, struct mosquitto_control_cmd *cmd)
719
0
{
720
0
  const char *rolename;
721
0
  struct dynsec__role *role;
722
0
  struct dynsec__acl **acllist, *acl;
723
0
  const char *topic;
724
0
  int rc;
725
0
  const char *admin_clientid, *admin_username;
726
0
  const char *acltype;
727
728
0
  if(json_get_string(cmd->j_command, "rolename", &rolename, false) != MOSQ_ERR_SUCCESS){
729
0
    mosquitto_control_command_reply(cmd, "Invalid/missing rolename");
730
0
    return MOSQ_ERR_INVAL;
731
0
  }
732
0
  if(mosquitto_validate_utf8(rolename, (int)strlen(rolename)) != MOSQ_ERR_SUCCESS){
733
0
    mosquitto_control_command_reply(cmd, "Role name not valid UTF-8");
734
0
    return MOSQ_ERR_INVAL;
735
0
  }
736
737
0
  role = dynsec_roles__find(data, rolename);
738
0
  if(role == NULL){
739
0
    mosquitto_control_command_reply(cmd, "Role not found");
740
0
    return MOSQ_ERR_SUCCESS;
741
0
  }
742
743
0
  if(json_get_string(cmd->j_command, "acltype", &acltype, false) != MOSQ_ERR_SUCCESS){
744
0
    mosquitto_control_command_reply(cmd, "Invalid/missing acltype");
745
0
    return MOSQ_ERR_SUCCESS;
746
0
  }
747
0
  if(!strcasecmp(acltype, ACL_TYPE_PUB_C_SEND)){
748
0
    acllist = &role->acls.publish_c_send;
749
0
  }else if(!strcasecmp(acltype, ACL_TYPE_PUB_C_RECV)){
750
0
    acllist = &role->acls.publish_c_recv;
751
0
  }else if(!strcasecmp(acltype, ACL_TYPE_SUB_LITERAL)){
752
0
    acllist = &role->acls.subscribe_literal;
753
0
  }else if(!strcasecmp(acltype, ACL_TYPE_SUB_PATTERN)){
754
0
    acllist = &role->acls.subscribe_pattern;
755
0
  }else if(!strcasecmp(acltype, ACL_TYPE_UNSUB_LITERAL)){
756
0
    acllist = &role->acls.unsubscribe_literal;
757
0
  }else if(!strcasecmp(acltype, ACL_TYPE_UNSUB_PATTERN)){
758
0
    acllist = &role->acls.unsubscribe_pattern;
759
0
  }else{
760
0
    mosquitto_control_command_reply(cmd, "Unknown acltype");
761
0
    return MOSQ_ERR_SUCCESS;
762
0
  }
763
764
0
  if(json_get_string(cmd->j_command, "topic", &topic, false)){
765
0
    mosquitto_control_command_reply(cmd, "Invalid/missing topic");
766
0
    return MOSQ_ERR_SUCCESS;
767
0
  }
768
0
  if(mosquitto_validate_utf8(topic, (int)strlen(topic)) != MOSQ_ERR_SUCCESS){
769
0
    mosquitto_control_command_reply(cmd, "Topic not valid UTF-8");
770
0
    return MOSQ_ERR_INVAL;
771
0
  }
772
0
  rc = mosquitto_sub_topic_check(topic);
773
0
  if(rc != MOSQ_ERR_SUCCESS){
774
0
    mosquitto_control_command_reply(cmd, "Invalid ACL topic");
775
0
    return MOSQ_ERR_INVAL;
776
0
  }
777
778
0
  HASH_FIND(hh, *acllist, topic, strlen(topic), acl);
779
0
  if(acl){
780
0
    role__free_acl(acllist, acl);
781
0
    dynsec__config_batch_save(data);
782
0
    mosquitto_control_command_reply(cmd, NULL);
783
784
0
    role__kick_all(data, role);
785
786
0
    admin_clientid = mosquitto_client_id(cmd->client);
787
0
    admin_username = mosquitto_client_username(cmd->client);
788
0
    mosquitto_log_printf(MOSQ_LOG_INFO, "dynsec: %s/%s | removeRoleACL | rolename=%s | acltype=%s | topic=%s",
789
0
        admin_clientid, admin_username, rolename, acltype, topic);
790
791
0
  }else{
792
0
    mosquitto_control_command_reply(cmd, "ACL not found");
793
0
  }
794
795
0
  return MOSQ_ERR_SUCCESS;
796
0
}
797
798
799
int dynsec_roles__process_get(struct dynsec__data *data, struct mosquitto_control_cmd *cmd)
800
0
{
801
0
  const char *rolename;
802
0
  struct dynsec__role *role;
803
0
  cJSON *tree, *j_role, *j_data;
804
0
  const char *admin_clientid, *admin_username;
805
806
0
  if(json_get_string(cmd->j_command, "rolename", &rolename, false) != MOSQ_ERR_SUCCESS){
807
0
    mosquitto_control_command_reply(cmd, "Invalid/missing rolename");
808
0
    return MOSQ_ERR_INVAL;
809
0
  }
810
0
  if(mosquitto_validate_utf8(rolename, (int)strlen(rolename)) != MOSQ_ERR_SUCCESS){
811
0
    mosquitto_control_command_reply(cmd, "Role name not valid UTF-8");
812
0
    return MOSQ_ERR_INVAL;
813
0
  }
814
815
0
  role = dynsec_roles__find(data, rolename);
816
0
  if(role == NULL){
817
0
    mosquitto_control_command_reply(cmd, "Role not found");
818
0
    return MOSQ_ERR_SUCCESS;
819
0
  }
820
821
0
  tree = cJSON_CreateObject();
822
0
  if(tree == NULL){
823
0
    mosquitto_control_command_reply(cmd, "Internal error");
824
0
    return MOSQ_ERR_NOMEM;
825
0
  }
826
827
0
  if(cJSON_AddStringToObject(tree, "command", "getRole") == NULL
828
0
      || (j_data = cJSON_AddObjectToObject(tree, "data")) == NULL
829
0
      || (cmd->correlation_data && cJSON_AddStringToObject(tree, "correlationData", cmd->correlation_data) == NULL)
830
0
      ){
831
832
0
    cJSON_Delete(tree);
833
0
    mosquitto_control_command_reply(cmd, "Internal error");
834
0
    return MOSQ_ERR_NOMEM;
835
0
  }
836
837
0
  j_role = add_role_to_json(role, true);
838
0
  if(j_role == NULL){
839
0
    cJSON_Delete(tree);
840
0
    mosquitto_control_command_reply(cmd, "Internal error");
841
0
    return MOSQ_ERR_NOMEM;
842
0
  }
843
0
  cJSON_AddItemToObject(j_data, "role", j_role);
844
0
  cJSON_AddItemToArray(cmd->j_responses, tree);
845
846
0
  admin_clientid = mosquitto_client_id(cmd->client);
847
0
  admin_username = mosquitto_client_username(cmd->client);
848
0
  mosquitto_log_printf(MOSQ_LOG_INFO, "dynsec: %s/%s | getRole | rolename=%s",
849
0
      admin_clientid, admin_username, rolename);
850
851
0
  return MOSQ_ERR_SUCCESS;
852
0
}
853
854
855
int dynsec_roles__process_modify(struct dynsec__data *data, struct mosquitto_control_cmd *cmd)
856
0
{
857
0
  const char *rolename;
858
0
  const char *text_name, *text_description;
859
0
  struct dynsec__role *role;
860
0
  cJSON *j_acls;
861
0
  bool allow_wildcard_subs;
862
0
  bool do_kick = false;
863
0
  struct dynsec__acl *tmp_publish_c_send = NULL, *tmp_publish_c_recv = NULL;
864
0
  struct dynsec__acl *tmp_subscribe_literal = NULL, *tmp_subscribe_pattern = NULL;
865
0
  struct dynsec__acl *tmp_unsubscribe_literal = NULL, *tmp_unsubscribe_pattern = NULL;
866
0
  const char *admin_clientid, *admin_username;
867
868
0
  if(json_get_string(cmd->j_command, "rolename", &rolename, false) != MOSQ_ERR_SUCCESS){
869
0
    mosquitto_control_command_reply(cmd, "Invalid/missing rolename");
870
0
    return MOSQ_ERR_INVAL;
871
0
  }
872
0
  if(mosquitto_validate_utf8(rolename, (int)strlen(rolename)) != MOSQ_ERR_SUCCESS){
873
0
    mosquitto_control_command_reply(cmd, "Role name not valid UTF-8");
874
0
    return MOSQ_ERR_INVAL;
875
0
  }
876
877
0
  role = dynsec_roles__find(data, rolename);
878
0
  if(role == NULL){
879
0
    mosquitto_control_command_reply(cmd, "Role does not exist");
880
0
    return MOSQ_ERR_INVAL;
881
0
  }
882
883
0
  if(json_get_string(cmd->j_command, "textname", &text_name, false) == MOSQ_ERR_SUCCESS){
884
0
    char *str = mosquitto_strdup(text_name);
885
0
    if(str == NULL){
886
0
      mosquitto_control_command_reply(cmd, "Internal error");
887
0
      return MOSQ_ERR_NOMEM;
888
0
    }
889
0
    mosquitto_free(role->text_name);
890
0
    role->text_name = str;
891
0
  }
892
893
0
  if(json_get_string(cmd->j_command, "textdescription", &text_description, false) == MOSQ_ERR_SUCCESS){
894
0
    char *str = mosquitto_strdup(text_description);
895
0
    if(str == NULL){
896
0
      mosquitto_control_command_reply(cmd, "Internal error");
897
0
      return MOSQ_ERR_NOMEM;
898
0
    }
899
0
    mosquitto_free(role->text_description);
900
0
    role->text_description = str;
901
0
  }
902
903
0
  if(json_get_bool(cmd->j_command, "allowwildcardsubs", &allow_wildcard_subs, false, true) == MOSQ_ERR_SUCCESS){
904
0
    if(role->allow_wildcard_subs != allow_wildcard_subs){
905
0
      role->allow_wildcard_subs = allow_wildcard_subs;
906
0
      do_kick = true;
907
0
    }
908
0
  }
909
910
0
  j_acls = cJSON_GetObjectItem(cmd->j_command, "acls");
911
0
  if(j_acls && cJSON_IsArray(j_acls)){
912
0
    if(dynsec_roles__acl_load(j_acls, ACL_TYPE_PUB_C_SEND, &tmp_publish_c_send) != 0
913
0
        || dynsec_roles__acl_load(j_acls, ACL_TYPE_PUB_C_RECV, &tmp_publish_c_recv) != 0
914
0
        || dynsec_roles__acl_load(j_acls, ACL_TYPE_SUB_LITERAL, &tmp_subscribe_literal) != 0
915
0
        || dynsec_roles__acl_load(j_acls, ACL_TYPE_SUB_PATTERN, &tmp_subscribe_pattern) != 0
916
0
        || dynsec_roles__acl_load(j_acls, ACL_TYPE_UNSUB_LITERAL, &tmp_unsubscribe_literal) != 0
917
0
        || dynsec_roles__acl_load(j_acls, ACL_TYPE_UNSUB_PATTERN, &tmp_unsubscribe_pattern) != 0
918
0
        ){
919
920
      /* Free any that were successful */
921
0
      role__free_all_acls(&tmp_publish_c_send);
922
0
      role__free_all_acls(&tmp_publish_c_recv);
923
0
      role__free_all_acls(&tmp_subscribe_literal);
924
0
      role__free_all_acls(&tmp_subscribe_pattern);
925
0
      role__free_all_acls(&tmp_unsubscribe_literal);
926
0
      role__free_all_acls(&tmp_unsubscribe_pattern);
927
928
0
      mosquitto_control_command_reply(cmd, "Internal error");
929
0
      return MOSQ_ERR_NOMEM;
930
0
    }
931
932
0
    role__free_all_acls(&role->acls.publish_c_send);
933
0
    role__free_all_acls(&role->acls.publish_c_recv);
934
0
    role__free_all_acls(&role->acls.subscribe_literal);
935
0
    role__free_all_acls(&role->acls.subscribe_pattern);
936
0
    role__free_all_acls(&role->acls.unsubscribe_literal);
937
0
    role__free_all_acls(&role->acls.unsubscribe_pattern);
938
939
0
    role->acls.publish_c_send = tmp_publish_c_send;
940
0
    role->acls.publish_c_recv = tmp_publish_c_recv;
941
0
    role->acls.subscribe_literal = tmp_subscribe_literal;
942
0
    role->acls.subscribe_pattern = tmp_subscribe_pattern;
943
0
    role->acls.unsubscribe_literal = tmp_unsubscribe_literal;
944
0
    role->acls.unsubscribe_pattern = tmp_unsubscribe_pattern;
945
0
    do_kick = true;
946
0
  }
947
948
0
  if(do_kick){
949
0
    role__kick_all(data, role);
950
0
  }
951
0
  dynsec__config_batch_save(data);
952
953
0
  mosquitto_control_command_reply(cmd, NULL);
954
955
0
  admin_clientid = mosquitto_client_id(cmd->client);
956
0
  admin_username = mosquitto_client_username(cmd->client);
957
0
  mosquitto_log_printf(MOSQ_LOG_INFO, "dynsec: %s/%s | modifyRole | rolename=%s",
958
0
      admin_clientid, admin_username, rolename);
959
960
0
  return MOSQ_ERR_SUCCESS;
961
0
}