Coverage Report

Created: 2025-08-24 06:43

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