Coverage Report

Created: 2025-06-20 06:45

/src/mosquitto/plugins/dynamic-security/clients.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 <uthash.h>
24
25
#include "dynamic_security.h"
26
#include "json_help.h"
27
28
struct connection_array_context{
29
  const char *username;
30
  cJSON *j_connections;
31
};
32
33
/* ################################################################
34
 * #
35
 * # Function declarations
36
 * #
37
 * ################################################################ */
38
39
static int dynsec__remove_client_from_all_groups(struct dynsec__data *data, const char *username);
40
static void client__remove_all_roles(struct dynsec__client *client);
41
42
/* ################################################################
43
 * #
44
 * # Local variables
45
 * #
46
 * ################################################################ */
47
48
/* ################################################################
49
 * #
50
 * # Utility functions
51
 * #
52
 * ################################################################ */
53
54
static int client_cmp(void *a, void *b)
55
193k
{
56
193k
  struct dynsec__client *client_a = a;
57
193k
  struct dynsec__client *client_b = b;
58
59
193k
  return strcmp(client_a->username, client_b->username);
60
193k
}
61
62
struct dynsec__client *dynsec_clients__find(struct dynsec__data *data, const char *username)
63
132k
{
64
132k
  struct dynsec__client *client = NULL;
65
66
132k
  if(username){
67
132k
    HASH_FIND(hh, data->clients, username, strlen(username), client);
68
132k
  }
69
132k
  return client;
70
132k
}
71
72
73
static void client__free_item(struct dynsec__data *data, struct dynsec__client *client)
74
34.4k
{
75
34.4k
  struct dynsec__client *client_found;
76
34.4k
  if(client == NULL) return;
77
78
34.4k
  client_found = dynsec_clients__find(data, client->username);
79
34.4k
  if(client_found){
80
34.4k
    HASH_DEL(data->clients, client_found);
81
34.4k
  }
82
34.4k
  dynsec_rolelist__cleanup(&client->rolelist);
83
34.4k
  dynsec__remove_client_from_all_groups(data, client->username);
84
34.4k
  mosquitto_pw_cleanup(client->pw);
85
34.4k
  mosquitto_free(client->text_name);
86
34.4k
  mosquitto_free(client->text_description);
87
34.4k
  mosquitto_free(client->clientid);
88
34.4k
  mosquitto_free(client);
89
34.4k
}
90
91
void dynsec_clients__cleanup(struct dynsec__data *data)
92
5.29k
{
93
5.29k
  struct dynsec__client *client, *client_tmp;
94
95
34.4k
  HASH_ITER(hh, data->clients, client, client_tmp){
96
34.4k
    client__free_item(data, client);
97
34.4k
  }
98
5.29k
}
99
100
/* ################################################################
101
 * #
102
 * # Config file load and save
103
 * #
104
 * ################################################################ */
105
106
int dynsec_clients__config_load(struct dynsec__data *data, cJSON *tree)
107
5.15k
{
108
5.15k
  cJSON *j_clients, *j_client = NULL, *j_roles, *j_role;
109
5.15k
  struct dynsec__client *client;
110
5.15k
  struct dynsec__role *role;
111
5.15k
  int priority;
112
113
5.15k
  j_clients = cJSON_GetObjectItem(tree, "clients");
114
5.15k
  if(j_clients == NULL){
115
2.79k
    return 0;
116
2.79k
  }
117
118
2.35k
  if(cJSON_IsArray(j_clients) == false){
119
1
    return 1;
120
1
  }
121
40.6k
  cJSON_ArrayForEach(j_client, j_clients){
122
40.6k
    if(cJSON_IsObject(j_client) == true){
123
      /* Username */
124
40.1k
      const char *username;
125
40.1k
      if(json_get_string(j_client, "username", &username, false) != MOSQ_ERR_SUCCESS){
126
3.44k
        continue;
127
3.44k
      }
128
36.7k
      size_t username_len = strlen(username);
129
36.7k
      if(username_len == 0){
130
204
        continue;
131
204
      }
132
36.5k
      if(dynsec_clients__find(data, username)){
133
2.09k
        continue;
134
2.09k
      }
135
136
34.4k
      client = mosquitto_calloc(1, sizeof(struct dynsec__client) + username_len + 1);
137
34.4k
      if(client == NULL){
138
0
        return MOSQ_ERR_NOMEM;
139
0
      }
140
34.4k
      strncpy(client->username, username, username_len);
141
142
34.4k
      bool disabled;
143
34.4k
      if(json_get_bool(j_client, "disabled", &disabled, false, false) == MOSQ_ERR_SUCCESS){
144
0
        client->disabled = disabled;
145
0
      }
146
147
34.4k
      const char *password;
148
34.4k
      if(json_get_string(j_client, "encoded_password", &password, false) == MOSQ_ERR_SUCCESS){
149
0
        if(!client->pw && mosquitto_pw_new(&client->pw, MOSQ_PW_DEFAULT)){
150
0
          return MOSQ_ERR_NOMEM;
151
0
        }
152
0
        if(mosquitto_pw_decode(client->pw, password) == MOSQ_ERR_NOMEM){
153
0
          return MOSQ_ERR_NOMEM;
154
0
        }
155
34.4k
      }else{
156
        /* sha512-pbkdf2 only */
157
34.4k
        int iterations;
158
34.4k
        const char *salt;
159
34.4k
        json_get_int(j_client, "iterations", &iterations, true, 0);
160
34.4k
        if(json_get_string(j_client, "salt", &salt, false) == MOSQ_ERR_SUCCESS
161
34.4k
            && json_get_string(j_client, "password", &password, false) == MOSQ_ERR_SUCCESS
162
34.4k
            && iterations > 0){
163
164
540
          char buf[1024];
165
540
          if(!client->pw && mosquitto_pw_new(&client->pw, MOSQ_PW_SHA512_PBKDF2)){
166
0
            return MOSQ_ERR_NOMEM;
167
0
          }
168
540
          snprintf(buf, sizeof(buf), "$7$%d$%s$%s", iterations, salt, password);
169
540
          mosquitto_pw_decode(client->pw, buf);
170
33.8k
        }else{
171
33.8k
          mosquitto_pw_set_valid(client->pw, false);
172
33.8k
        }
173
34.4k
      }
174
175
      /* Client id */
176
34.4k
      const char *clientid;
177
34.4k
      if(json_get_string(j_client, "clientid", &clientid, false) == MOSQ_ERR_SUCCESS){
178
0
        client->clientid = mosquitto_strdup(clientid);
179
0
        if(client->clientid == NULL){
180
0
          mosquitto_free(client);
181
0
          continue;
182
0
        }
183
0
      }
184
185
      /* Text name */
186
34.4k
      const char *textname;
187
34.4k
      if(json_get_string(j_client, "textname", &textname, false) == MOSQ_ERR_SUCCESS){
188
109
        client->text_name = mosquitto_strdup(textname);
189
109
        if(client->text_name == NULL){
190
0
          mosquitto_free(client->clientid);
191
0
          mosquitto_free(client);
192
0
          continue;
193
0
        }
194
109
      }
195
196
      /* Text description */
197
34.4k
      const char *textdescription;
198
34.4k
      if(json_get_string(j_client, "textdescription", &textdescription, false) == MOSQ_ERR_SUCCESS){
199
67
        client->text_description = mosquitto_strdup(textdescription);
200
67
        if(client->text_description == NULL){
201
0
          mosquitto_free(client->text_name);
202
0
          mosquitto_free(client->clientid);
203
0
          mosquitto_free(client);
204
0
          continue;
205
0
        }
206
67
      }
207
208
      /* Roles */
209
34.4k
      j_roles = cJSON_GetObjectItem(j_client, "roles");
210
34.4k
      if(j_roles && cJSON_IsArray(j_roles)){
211
41.7k
        cJSON_ArrayForEach(j_role, j_roles){
212
41.7k
          if(cJSON_IsObject(j_role)){
213
41.5k
            const char *rolename;
214
41.5k
            if(json_get_string(j_role, "rolename", &rolename, false) == MOSQ_ERR_SUCCESS){
215
34.8k
              json_get_int(j_role, "priority", &priority, true, -1);
216
34.8k
              if(priority > PRIORITY_MAX) priority = PRIORITY_MAX;
217
34.8k
              if(priority < -PRIORITY_MAX) priority = -PRIORITY_MAX;
218
34.8k
              role = dynsec_roles__find(data, rolename);
219
34.8k
              dynsec_rolelist__client_add(client, role, priority);
220
34.8k
            }
221
41.5k
          }
222
41.7k
        }
223
4.23k
      }
224
225
34.4k
      HASH_ADD(hh, data->clients, username, username_len, client);
226
34.4k
    }
227
40.6k
  }
228
2.35k
  HASH_SORT(data->clients, client_cmp);
229
230
2.35k
  return 0;
231
2.35k
}
232
233
234
static int dynsec__config_add_clients(struct dynsec__data *data, cJSON *j_clients)
235
0
{
236
0
  struct dynsec__client *client, *client_tmp;
237
0
  cJSON *j_client, *j_roles;
238
239
0
  HASH_ITER(hh, data->clients, client, client_tmp){
240
0
    j_client = cJSON_CreateObject();
241
0
    if(j_client == NULL) return 1;
242
0
    cJSON_AddItemToArray(j_clients, j_client);
243
244
0
    if(cJSON_AddStringToObject(j_client, "username", client->username) == NULL
245
0
        || (client->clientid && cJSON_AddStringToObject(j_client, "clientid", client->clientid) == NULL)
246
0
        || (client->text_name && cJSON_AddStringToObject(j_client, "textname", client->text_name) == NULL)
247
0
        || (client->text_description && cJSON_AddStringToObject(j_client, "textdescription", client->text_description) == NULL)
248
0
        || (client->disabled && cJSON_AddBoolToObject(j_client, "disabled", true) == NULL)
249
0
        ){
250
251
0
      return 1;
252
0
    }
253
254
0
    j_roles = dynsec_rolelist__all_to_json(client->rolelist);
255
0
    if(j_roles == NULL){
256
0
      return 1;
257
0
    }
258
0
    cJSON_AddItemToObject(j_client, "roles", j_roles);
259
260
0
    if(mosquitto_pw_is_valid(client->pw)){
261
0
      if(cJSON_AddStringToObject(j_client, "encoded_password", mosquitto_pw_get_encoded(client->pw)) == NULL){
262
0
        return 1;
263
0
      }
264
0
    }
265
0
  }
266
267
0
  return 0;
268
0
}
269
270
271
int dynsec_clients__config_save(struct dynsec__data *data, cJSON *tree)
272
0
{
273
0
  cJSON *j_clients;
274
275
0
  if((j_clients = cJSON_AddArrayToObject(tree, "clients")) == NULL){
276
0
    return 1;
277
0
  }
278
0
  if(dynsec__config_add_clients(data, j_clients)){
279
0
    return 1;
280
0
  }
281
282
0
  return 0;
283
0
}
284
285
286
int dynsec_clients__process_create(struct dynsec__data *data, struct mosquitto_control_cmd *cmd)
287
0
{
288
0
  const char *username, *password, *clientid = NULL;
289
0
  const char *text_name, *text_description;
290
0
  struct dynsec__client *client;
291
0
  int rc;
292
0
  cJSON *j_groups, *j_group;
293
0
  int priority;
294
0
  const char *admin_clientid, *admin_username;
295
0
  size_t username_len;
296
297
0
  if(json_get_string(cmd->j_command, "username", &username, false) != MOSQ_ERR_SUCCESS){
298
0
    mosquitto_control_command_reply(cmd, "Invalid/missing username");
299
0
    return MOSQ_ERR_INVAL;
300
0
  }
301
0
  username_len = strlen(username);
302
0
  if(username_len == 0){
303
0
    mosquitto_control_command_reply(cmd, "Empty username");
304
0
    return MOSQ_ERR_INVAL;
305
0
  }
306
0
  if(mosquitto_validate_utf8(username, (int)username_len) != MOSQ_ERR_SUCCESS){
307
0
    mosquitto_control_command_reply(cmd, "Username not valid UTF-8");
308
0
    return MOSQ_ERR_INVAL;
309
0
  }
310
311
0
  if(json_get_string(cmd->j_command, "password", &password, true) != MOSQ_ERR_SUCCESS){
312
0
    mosquitto_control_command_reply(cmd, "Invalid/missing password");
313
0
    return MOSQ_ERR_INVAL;
314
0
  }
315
316
0
  if(json_get_string(cmd->j_command, "clientid", &clientid, true) != MOSQ_ERR_SUCCESS){
317
0
    mosquitto_control_command_reply(cmd, "Invalid/missing client id");
318
0
    return MOSQ_ERR_INVAL;
319
0
  }
320
0
  if(clientid && mosquitto_validate_utf8(clientid, (int)strlen(clientid)) != MOSQ_ERR_SUCCESS){
321
0
    mosquitto_control_command_reply(cmd, "Client ID not valid UTF-8");
322
0
    return MOSQ_ERR_INVAL;
323
0
  }
324
325
326
0
  if(json_get_string(cmd->j_command, "textname", &text_name, true) != MOSQ_ERR_SUCCESS){
327
0
    mosquitto_control_command_reply(cmd, "Invalid/missing textname");
328
0
    return MOSQ_ERR_INVAL;
329
0
  }
330
331
0
  if(json_get_string(cmd->j_command, "textdescription", &text_description, true) != MOSQ_ERR_SUCCESS){
332
0
    mosquitto_control_command_reply(cmd, "Invalid/missing textdescription");
333
0
    return MOSQ_ERR_INVAL;
334
0
  }
335
336
0
  client = dynsec_clients__find(data, username);
337
0
  if(client){
338
0
    mosquitto_control_command_reply(cmd, "Client already exists");
339
0
    return MOSQ_ERR_SUCCESS;
340
0
  }
341
342
0
  client = mosquitto_calloc(1, sizeof(struct dynsec__client) + username_len + 1);
343
0
  if(client == NULL){
344
0
    mosquitto_control_command_reply(cmd, "Internal error");
345
0
    return MOSQ_ERR_NOMEM;
346
0
  }
347
0
  strncpy(client->username, username, username_len);
348
349
0
  if(text_name){
350
0
    client->text_name = mosquitto_strdup(text_name);
351
0
    if(client->text_name == NULL){
352
0
      mosquitto_control_command_reply(cmd, "Internal error");
353
0
      client__free_item(data, client);
354
0
      return MOSQ_ERR_NOMEM;
355
0
    }
356
0
  }
357
0
  if(text_description){
358
0
    client->text_description = mosquitto_strdup(text_description);
359
0
    if(client->text_description == NULL){
360
0
      mosquitto_control_command_reply(cmd, "Internal error");
361
0
      client__free_item(data, client);
362
0
      return MOSQ_ERR_NOMEM;
363
0
    }
364
0
  }
365
366
0
  if(password){
367
0
    if(mosquitto_pw_new(&client->pw, MOSQ_PW_DEFAULT)
368
0
        || mosquitto_pw_hash_encoded(client->pw, password)
369
0
        ){
370
371
0
      mosquitto_control_command_reply(cmd, "Internal error");
372
0
      client__free_item(data, client);
373
0
      return MOSQ_ERR_NOMEM;
374
0
    }
375
0
  }
376
0
  if(clientid && strlen(clientid) > 0){
377
0
    client->clientid = mosquitto_strdup(clientid);
378
0
    if(client->clientid == NULL){
379
0
      mosquitto_control_command_reply(cmd, "Internal error");
380
0
      client__free_item(data, client);
381
0
      return MOSQ_ERR_NOMEM;
382
0
    }
383
0
  }
384
385
0
  rc = dynsec_rolelist__load_from_json(data, cmd->j_command, &client->rolelist);
386
0
  if(rc == MOSQ_ERR_SUCCESS || rc == ERR_LIST_NOT_FOUND){
387
0
  }else if(rc == MOSQ_ERR_NOT_FOUND){
388
0
    mosquitto_control_command_reply(cmd, "Role not found");
389
0
    client__free_item(data, client);
390
0
    return MOSQ_ERR_INVAL;
391
0
  }else{
392
0
    if(rc == MOSQ_ERR_INVAL){
393
0
      mosquitto_control_command_reply(cmd, "'roles' not an array or missing/invalid rolename");
394
0
    }else{
395
0
      mosquitto_control_command_reply(cmd, "Internal error");
396
0
    }
397
0
    client__free_item(data, client);
398
0
    return MOSQ_ERR_INVAL;
399
0
  }
400
401
  /* Must add user before groups, otherwise adding groups will fail */
402
0
  HASH_ADD_INORDER(hh, data->clients, username, username_len, client, client_cmp);
403
404
0
  j_groups = cJSON_GetObjectItem(cmd->j_command, "groups");
405
0
  if(j_groups && cJSON_IsArray(j_groups)){
406
0
    cJSON_ArrayForEach(j_group, j_groups){
407
0
      if(cJSON_IsObject(j_group)){
408
0
        const char *groupname;
409
0
        if(json_get_string(j_group, "groupname", &groupname, false) == MOSQ_ERR_SUCCESS){
410
0
          json_get_int(j_group, "priority", &priority, true, -1);
411
0
          if(priority > PRIORITY_MAX) priority = PRIORITY_MAX;
412
0
          rc = dynsec_groups__add_client(data, username, groupname, priority, false);
413
0
          if(rc == ERR_GROUP_NOT_FOUND){
414
0
            mosquitto_control_command_reply(cmd, "Group not found");
415
0
            client__free_item(data, client);
416
0
            return MOSQ_ERR_INVAL;
417
0
          }else if(rc != MOSQ_ERR_SUCCESS){
418
0
            mosquitto_control_command_reply(cmd, "Internal error");
419
0
            client__free_item(data, client);
420
0
            return MOSQ_ERR_INVAL;
421
0
          }
422
0
        }
423
0
      }
424
0
    }
425
0
  }
426
427
0
  dynsec__config_batch_save(data);
428
429
0
  mosquitto_control_command_reply(cmd, NULL);
430
431
0
  admin_clientid = mosquitto_client_id(cmd->client);
432
0
  admin_username = mosquitto_client_username(cmd->client);
433
0
  mosquitto_log_printf(MOSQ_LOG_INFO, "dynsec: %s/%s | createClient | username=%s | password=%s",
434
0
      admin_clientid, admin_username, username, password?"*****":"no password");
435
436
0
  return MOSQ_ERR_SUCCESS;
437
0
}
438
439
440
int dynsec_clients__process_delete(struct dynsec__data *data, struct mosquitto_control_cmd *cmd)
441
0
{
442
0
  const char *username;
443
0
  struct dynsec__client *client;
444
0
  const char *admin_clientid, *admin_username;
445
446
0
  if(json_get_string(cmd->j_command, "username", &username, false) != MOSQ_ERR_SUCCESS){
447
0
    mosquitto_control_command_reply(cmd, "Invalid/missing username");
448
0
    return MOSQ_ERR_INVAL;
449
0
  }
450
451
0
  client = dynsec_clients__find(data, username);
452
0
  if(client){
453
0
    dynsec__remove_client_from_all_groups(data, username);
454
0
    client__remove_all_roles(client);
455
0
    client__free_item(data, client);
456
0
    dynsec__config_batch_save(data);
457
0
    mosquitto_control_command_reply(cmd, NULL);
458
459
    /* Enforce any changes */
460
0
    dynsec_kicklist__add(data, username);
461
462
0
    admin_clientid = mosquitto_client_id(cmd->client);
463
0
    admin_username = mosquitto_client_username(cmd->client);
464
0
    mosquitto_log_printf(MOSQ_LOG_INFO, "dynsec: %s/%s | deleteClient | username=%s",
465
0
        admin_clientid, admin_username, username);
466
467
0
    return MOSQ_ERR_SUCCESS;
468
0
  }else{
469
0
    mosquitto_control_command_reply(cmd, "Client not found");
470
0
    return MOSQ_ERR_SUCCESS;
471
0
  }
472
0
}
473
474
int dynsec_clients__process_disable(struct dynsec__data *data, struct mosquitto_control_cmd *cmd)
475
0
{
476
0
  const char *username;
477
0
  struct dynsec__client *client;
478
0
  const char *admin_clientid, *admin_username;
479
480
0
  if(json_get_string(cmd->j_command, "username", &username, false) != MOSQ_ERR_SUCCESS){
481
0
    mosquitto_control_command_reply(cmd, "Invalid/missing username");
482
0
    return MOSQ_ERR_INVAL;
483
0
  }
484
0
  if(mosquitto_validate_utf8(username, (int)strlen(username)) != MOSQ_ERR_SUCCESS){
485
0
    mosquitto_control_command_reply(cmd, "Username not valid UTF-8");
486
0
    return MOSQ_ERR_INVAL;
487
0
  }
488
489
0
  client = dynsec_clients__find(data, username);
490
0
  if(client == NULL){
491
0
    mosquitto_control_command_reply(cmd, "Client not found");
492
0
    return MOSQ_ERR_SUCCESS;
493
0
  }
494
495
0
  client->disabled = true;
496
497
0
  dynsec_kicklist__add(data, username);
498
499
0
  dynsec__config_batch_save(data);
500
0
  mosquitto_control_command_reply(cmd, NULL);
501
502
0
  admin_clientid = mosquitto_client_id(cmd->client);
503
0
  admin_username = mosquitto_client_username(cmd->client);
504
0
  mosquitto_log_printf(MOSQ_LOG_INFO, "dynsec: %s/%s | disableClient | username=%s",
505
0
      admin_clientid, admin_username, username);
506
507
0
  return MOSQ_ERR_SUCCESS;
508
0
}
509
510
511
int dynsec_clients__process_enable(struct dynsec__data *data, struct mosquitto_control_cmd *cmd)
512
0
{
513
0
  const char *username;
514
0
  struct dynsec__client *client;
515
0
  const char *admin_clientid, *admin_username;
516
517
0
  if(json_get_string(cmd->j_command, "username", &username, false) != MOSQ_ERR_SUCCESS){
518
0
    mosquitto_control_command_reply(cmd, "Invalid/missing username");
519
0
    return MOSQ_ERR_INVAL;
520
0
  }
521
0
  if(mosquitto_validate_utf8(username, (int)strlen(username)) != MOSQ_ERR_SUCCESS){
522
0
    mosquitto_control_command_reply(cmd, "Username not valid UTF-8");
523
0
    return MOSQ_ERR_INVAL;
524
0
  }
525
526
0
  client = dynsec_clients__find(data, username);
527
0
  if(client == NULL){
528
0
    mosquitto_control_command_reply(cmd, "Client not found");
529
0
    return MOSQ_ERR_SUCCESS;
530
0
  }
531
532
0
  client->disabled = false;
533
534
0
  dynsec__config_batch_save(data);
535
0
  mosquitto_control_command_reply(cmd, NULL);
536
537
0
  admin_clientid = mosquitto_client_id(cmd->client);
538
0
  admin_username = mosquitto_client_username(cmd->client);
539
0
  mosquitto_log_printf(MOSQ_LOG_INFO, "dynsec: %s/%s | enableClient | username=%s",
540
0
      admin_clientid, admin_username, username);
541
542
0
  return MOSQ_ERR_SUCCESS;
543
0
}
544
545
546
int dynsec_clients__process_set_id(struct dynsec__data *data, struct mosquitto_control_cmd *cmd)
547
0
{
548
0
  const char *username, *clientid;
549
0
  char *clientid_heap = NULL;
550
0
  struct dynsec__client *client;
551
0
  size_t slen;
552
0
  const char *admin_clientid, *admin_username;
553
554
0
  if(json_get_string(cmd->j_command, "username", &username, false) != MOSQ_ERR_SUCCESS){
555
0
    mosquitto_control_command_reply(cmd, "Invalid/missing username");
556
0
    return MOSQ_ERR_INVAL;
557
0
  }
558
0
  if(mosquitto_validate_utf8(username, (int)strlen(username)) != MOSQ_ERR_SUCCESS){
559
0
    mosquitto_control_command_reply(cmd, "Username not valid UTF-8");
560
0
    return MOSQ_ERR_INVAL;
561
0
  }
562
563
0
  if(json_get_string(cmd->j_command, "clientid", &clientid, true) != MOSQ_ERR_SUCCESS){
564
0
    mosquitto_control_command_reply(cmd, "Invalid/missing client ID");
565
0
    return MOSQ_ERR_INVAL;
566
0
  }
567
0
  if(clientid){
568
0
    slen = strlen(clientid);
569
0
    if(mosquitto_validate_utf8(clientid, (int)slen) != MOSQ_ERR_SUCCESS){
570
0
      mosquitto_control_command_reply(cmd, "Client ID not valid UTF-8");
571
0
      return MOSQ_ERR_INVAL;
572
0
    }
573
0
    if(slen > 0){
574
0
      clientid_heap = mosquitto_strdup(clientid);
575
0
      if(clientid_heap == NULL){
576
0
        mosquitto_control_command_reply(cmd, "Internal error");
577
0
        return MOSQ_ERR_NOMEM;
578
0
      }
579
0
    }else{
580
0
      clientid_heap = NULL;
581
0
    }
582
0
  }
583
584
0
  client = dynsec_clients__find(data, username);
585
0
  if(client == NULL){
586
0
    mosquitto_free(clientid_heap);
587
0
    mosquitto_control_command_reply(cmd, "Client not found");
588
0
    return MOSQ_ERR_SUCCESS;
589
0
  }
590
591
0
  mosquitto_free(client->clientid);
592
0
  client->clientid = clientid_heap;
593
594
0
  dynsec__config_batch_save(data);
595
0
  mosquitto_control_command_reply(cmd, NULL);
596
597
  /* Enforce any changes */
598
0
  dynsec_kicklist__add(data, username);
599
600
0
  admin_clientid = mosquitto_client_id(cmd->client);
601
0
  admin_username = mosquitto_client_username(cmd->client);
602
0
  mosquitto_log_printf(MOSQ_LOG_INFO, "dynsec: %s/%s | setClientId | username=%s | clientid=%s",
603
0
      admin_clientid, admin_username, username, client->clientid);
604
605
0
  return MOSQ_ERR_SUCCESS;
606
0
}
607
608
609
static int client__set_password(struct dynsec__client *client, const char *password)
610
0
{
611
0
  if(!client->pw){
612
0
    if(mosquitto_pw_new(&client->pw, MOSQ_PW_DEFAULT)){
613
0
      return MOSQ_ERR_NOMEM;
614
0
    }
615
0
  }
616
0
  return mosquitto_pw_hash_encoded(client->pw, password);
617
0
}
618
619
int dynsec_clients__process_set_password(struct dynsec__data *data, struct mosquitto_control_cmd *cmd)
620
0
{
621
0
  const char *username, *password;
622
0
  struct dynsec__client *client;
623
0
  int rc;
624
0
  const char *admin_clientid, *admin_username;
625
626
0
  if(json_get_string(cmd->j_command, "username", &username, false) != MOSQ_ERR_SUCCESS){
627
0
    mosquitto_control_command_reply(cmd, "Invalid/missing username");
628
0
    return MOSQ_ERR_INVAL;
629
0
  }
630
0
  if(mosquitto_validate_utf8(username, (int)strlen(username)) != MOSQ_ERR_SUCCESS){
631
0
    mosquitto_control_command_reply(cmd, "Username not valid UTF-8");
632
0
    return MOSQ_ERR_INVAL;
633
0
  }
634
635
0
  if(json_get_string(cmd->j_command, "password", &password, false) != MOSQ_ERR_SUCCESS){
636
0
    mosquitto_control_command_reply(cmd, "Invalid/missing password");
637
0
    return MOSQ_ERR_INVAL;
638
0
  }
639
0
  if(strlen(password) == 0){
640
0
    mosquitto_control_command_reply(cmd, "Empty password is not allowed");
641
0
    return MOSQ_ERR_INVAL;
642
0
  }
643
644
0
  client = dynsec_clients__find(data, username);
645
0
  if(client == NULL){
646
0
    mosquitto_control_command_reply(cmd, "Client not found");
647
0
    return MOSQ_ERR_SUCCESS;
648
0
  }
649
0
  rc = client__set_password(client, password);
650
0
  if(rc == MOSQ_ERR_SUCCESS){
651
0
    dynsec__config_batch_save(data);
652
0
    mosquitto_control_command_reply(cmd, NULL);
653
654
    /* Enforce any changes */
655
0
    dynsec_kicklist__add(data, username);
656
657
0
    admin_clientid = mosquitto_client_id(cmd->client);
658
0
    admin_username = mosquitto_client_username(cmd->client);
659
0
    mosquitto_log_printf(MOSQ_LOG_INFO, "dynsec: %s/%s | setClientPassword | username=%s | password=******",
660
0
        admin_clientid, admin_username, username);
661
0
  }else{
662
0
    mosquitto_control_command_reply(cmd, "Internal error");
663
0
  }
664
0
  return rc;
665
0
}
666
667
668
static void client__add_new_roles(struct dynsec__client *client, struct dynsec__rolelist *base_rolelist)
669
0
{
670
0
  struct dynsec__rolelist *rolelist, *rolelist_tmp;
671
672
0
  HASH_ITER(hh, base_rolelist, rolelist, rolelist_tmp){
673
0
    dynsec_rolelist__client_add(client, rolelist->role, rolelist->priority);
674
0
  }
675
0
}
676
677
static void client__remove_all_roles(struct dynsec__client *client)
678
0
{
679
0
  struct dynsec__rolelist *rolelist, *rolelist_tmp;
680
681
0
  HASH_ITER(hh, client->rolelist, rolelist, rolelist_tmp){
682
0
    dynsec_rolelist__client_remove(client, rolelist->role);
683
0
  }
684
0
}
685
686
int dynsec_clients__process_modify(struct dynsec__data *data, struct mosquitto_control_cmd *cmd)
687
0
{
688
0
  const char *username;
689
0
  char *clientid = NULL;
690
0
  const char *password = NULL;
691
0
  char *text_name = NULL, *text_description = NULL;
692
0
  bool have_clientid = false, have_text_name = false, have_text_description = false, have_rolelist = false, have_password = false;
693
0
  struct dynsec__client *client;
694
0
  struct dynsec__group *group;
695
0
  struct dynsec__rolelist *rolelist = NULL;
696
0
  const char *str;
697
0
  int rc;
698
0
  int priority;
699
0
  cJSON *j_group, *j_groups;
700
0
  const char *admin_clientid, *admin_username;
701
702
0
  if(json_get_string(cmd->j_command, "username", &username, false) != MOSQ_ERR_SUCCESS){
703
0
    mosquitto_control_command_reply(cmd, "Invalid/missing username");
704
0
    return MOSQ_ERR_INVAL;
705
0
  }
706
0
  if(mosquitto_validate_utf8(username, (int)strlen(username)) != MOSQ_ERR_SUCCESS){
707
0
    mosquitto_control_command_reply(cmd, "Username not valid UTF-8");
708
0
    return MOSQ_ERR_INVAL;
709
0
  }
710
711
0
  client = dynsec_clients__find(data, username);
712
0
  if(client == NULL){
713
0
    mosquitto_control_command_reply(cmd, "Client not found");
714
0
    return MOSQ_ERR_INVAL;
715
0
  }
716
717
0
  if(json_get_string(cmd->j_command, "clientid", &str, false) == MOSQ_ERR_SUCCESS){
718
0
    have_clientid = true;
719
0
    if(str && strlen(str) > 0){
720
0
      clientid = mosquitto_strdup(str);
721
0
      if(clientid == NULL){
722
0
        mosquitto_control_command_reply(cmd, "Internal error");
723
0
        rc = MOSQ_ERR_NOMEM;
724
0
        goto error;
725
0
      }
726
0
    }else{
727
0
      clientid = NULL;
728
0
    }
729
0
  }
730
731
0
  if(json_get_string(cmd->j_command, "password", &password, false) == MOSQ_ERR_SUCCESS){
732
0
    if(strlen(password) > 0){
733
0
      have_password = true;
734
0
    }
735
0
  }
736
737
0
  if(json_get_string(cmd->j_command, "textname", &str, false) == MOSQ_ERR_SUCCESS){
738
0
    have_text_name = true;
739
0
    text_name = mosquitto_strdup(str);
740
0
    if(text_name == NULL){
741
0
      mosquitto_control_command_reply(cmd, "Internal error");
742
0
      rc = MOSQ_ERR_NOMEM;
743
0
      goto error;
744
0
    }
745
0
  }
746
747
0
  if(json_get_string(cmd->j_command, "textdescription", &str, false) == MOSQ_ERR_SUCCESS){
748
0
    have_text_description = true;
749
0
    text_description = mosquitto_strdup(str);
750
0
    if(text_description == NULL){
751
0
      mosquitto_control_command_reply(cmd, "Internal error");
752
0
      rc = MOSQ_ERR_NOMEM;
753
0
      goto error;
754
0
    }
755
0
  }
756
757
0
  rc = dynsec_rolelist__load_from_json(data, cmd->j_command, &rolelist);
758
0
  if(rc == MOSQ_ERR_SUCCESS){
759
0
    have_rolelist = true;
760
0
  }else if(rc == ERR_LIST_NOT_FOUND){
761
    /* There was no list in the JSON, so no modification */
762
0
  }else if(rc == MOSQ_ERR_NOT_FOUND){
763
0
    mosquitto_control_command_reply(cmd, "Role not found");
764
0
    rc = MOSQ_ERR_INVAL;
765
0
    goto error;
766
0
  }else{
767
0
    if(rc == MOSQ_ERR_INVAL){
768
0
      mosquitto_control_command_reply(cmd, "'roles' not an array or missing/invalid rolename");
769
0
    }else{
770
0
      mosquitto_control_command_reply(cmd, "Internal error");
771
0
    }
772
0
    rc = MOSQ_ERR_INVAL;
773
0
    goto error;
774
0
  }
775
776
0
  j_groups = cJSON_GetObjectItem(cmd->j_command, "groups");
777
0
  if(j_groups && cJSON_IsArray(j_groups)){
778
    /* Iterate through list to check all groups are valid */
779
0
    cJSON_ArrayForEach(j_group, j_groups){
780
0
      if(cJSON_IsObject(j_group)){
781
0
        const char *groupname;
782
0
        if(json_get_string(j_group, "groupname", &groupname, false) == MOSQ_ERR_SUCCESS){
783
0
          group = dynsec_groups__find(data, groupname);
784
0
          if(group == NULL){
785
0
            mosquitto_control_command_reply(cmd, "'groups' contains an object with a 'groupname' that does not exist");
786
0
            rc = MOSQ_ERR_INVAL;
787
0
            goto error;
788
0
          }
789
0
        }else{
790
0
          mosquitto_control_command_reply(cmd, "'groups' contains an object with an invalid 'groupname'");
791
0
          rc = MOSQ_ERR_INVAL;
792
0
          goto error;
793
0
        }
794
0
      }
795
0
    }
796
797
0
    dynsec__remove_client_from_all_groups(data, username);
798
0
    cJSON_ArrayForEach(j_group, j_groups){
799
0
      if(cJSON_IsObject(j_group)){
800
0
        const char *groupname;
801
0
        if(json_get_string(j_group, "groupname", &groupname, false) == MOSQ_ERR_SUCCESS){
802
0
          json_get_int(j_group, "priority", &priority, true, -1);
803
0
          if(priority > PRIORITY_MAX) priority = PRIORITY_MAX;
804
0
          dynsec_groups__add_client(data, username, groupname, priority, false);
805
0
        }
806
0
      }
807
0
    }
808
0
  }
809
810
0
  if(have_password){
811
    /* FIXME - This is the one call that will result in modification on internal error - note that groups have already been modified */
812
0
    rc = client__set_password(client, password);
813
0
    if(rc != MOSQ_ERR_SUCCESS){
814
0
      mosquitto_control_command_reply(cmd, "Internal error");
815
0
      mosquitto_kick_client_by_username(username, false);
816
      /* If this fails we have the situation that the password is set as
817
       * invalid, but the config isn't saved, so restarting the broker
818
       * *now* will mean the client can log in again. This might be
819
       * "good", but is inconsistent, so save the config to be
820
       * consistent. */
821
0
      dynsec__config_batch_save(data);
822
0
      rc = MOSQ_ERR_NOMEM;
823
0
      goto error;
824
0
    }
825
0
  }
826
827
0
  if(have_clientid){
828
0
    mosquitto_free(client->clientid);
829
0
    client->clientid = clientid;
830
0
  }
831
832
0
  if(have_text_name){
833
0
    mosquitto_free(client->text_name);
834
0
    client->text_name = text_name;
835
0
  }
836
837
0
  if(have_text_description){
838
0
    mosquitto_free(client->text_description);
839
0
    client->text_description = text_description;
840
0
  }
841
842
0
  if(have_rolelist){
843
0
    client__remove_all_roles(client);
844
0
    client__add_new_roles(client, rolelist);
845
0
    dynsec_rolelist__cleanup(&rolelist);
846
0
  }
847
848
0
  dynsec__config_batch_save(data);
849
0
  mosquitto_control_command_reply(cmd, NULL);
850
851
  /* Enforce any changes */
852
0
  dynsec_kicklist__add(data, username);
853
854
0
  admin_clientid = mosquitto_client_id(cmd->client);
855
0
  admin_username = mosquitto_client_username(cmd->client);
856
0
  mosquitto_log_printf(MOSQ_LOG_INFO, "dynsec: %s/%s | modifyClient | username=%s",
857
0
      admin_clientid, admin_username, username);
858
0
  return MOSQ_ERR_SUCCESS;
859
0
error:
860
0
  mosquitto_free(clientid);
861
0
  mosquitto_free(text_name);
862
0
  mosquitto_free(text_description);
863
0
  dynsec_rolelist__cleanup(&rolelist);
864
0
  return rc;
865
0
}
866
867
868
static int dynsec__remove_client_from_all_groups(struct dynsec__data *data, const char *username)
869
34.4k
{
870
34.4k
  struct dynsec__grouplist *grouplist, *grouplist_tmp;
871
34.4k
  struct dynsec__client *client;
872
873
34.4k
  client = dynsec_clients__find(data, username);
874
34.4k
  if(client){
875
0
    HASH_ITER(hh, client->grouplist, grouplist, grouplist_tmp){
876
0
      dynsec_groups__remove_client(data, username, grouplist->group->groupname, false);
877
0
    }
878
0
  }
879
880
34.4k
  return MOSQ_ERR_SUCCESS;
881
34.4k
}
882
883
884
static int dynsec__add_client_address(const struct mosquitto *client, void *context_ptr)
885
0
{
886
0
  struct connection_array_context *functor_context = (struct connection_array_context*)context_ptr;
887
0
  const char *username = mosquitto_client_username(client);
888
889
0
  if((username == NULL && functor_context->username == NULL)
890
0
      || (username && functor_context->username && !strcmp(functor_context->username, username))){
891
892
0
    cJSON *j_connection = cJSON_CreateObject();
893
0
    const char *address;
894
0
    if(!j_connection){
895
0
      return MOSQ_ERR_NOMEM;
896
0
    }
897
0
    if((address = mosquitto_client_address(client)) && !cJSON_AddStringToObject(j_connection, "address", address)){
898
0
      cJSON_Delete(j_connection);
899
0
      return MOSQ_ERR_NOMEM;
900
0
    }
901
0
    cJSON_AddItemToArray(functor_context->j_connections, j_connection);
902
0
  }
903
0
  return MOSQ_ERR_SUCCESS;
904
0
}
905
906
907
static cJSON *dynsec_connections__all_to_json(const char *username, const char *clientid)
908
0
{
909
0
  struct connection_array_context functor_context = { username, cJSON_CreateArray()};
910
911
0
  if(functor_context.j_connections == NULL){
912
0
    return NULL;
913
0
  }
914
915
0
  if(clientid){
916
0
    const struct mosquitto *client = mosquitto_client(clientid);
917
0
    if(client && dynsec__add_client_address(client, &functor_context) != MOSQ_ERR_SUCCESS){
918
0
      cJSON_Delete(functor_context.j_connections);
919
0
      return NULL;
920
0
    }
921
0
  }else{
922
0
    if(mosquitto_apply_on_all_clients(&dynsec__add_client_address, &functor_context) != MOSQ_ERR_SUCCESS){
923
0
      cJSON_Delete(functor_context.j_connections);
924
0
      return NULL;
925
0
    }
926
0
  }
927
0
  return functor_context.j_connections;
928
0
}
929
930
931
static cJSON *add_client_to_json(struct dynsec__client *client, bool verbose)
932
0
{
933
0
  cJSON *j_client = NULL;
934
935
0
  if(verbose){
936
0
    cJSON *j_groups, *j_roles, *j_connections;
937
938
0
    j_client = cJSON_CreateObject();
939
0
    if(j_client == NULL){
940
0
      return NULL;
941
0
    }
942
943
0
    if(cJSON_AddStringToObject(j_client, "username", client->username) == NULL
944
0
        || (client->clientid && cJSON_AddStringToObject(j_client, "clientid", client->clientid) == NULL)
945
0
        || (client->text_name && cJSON_AddStringToObject(j_client, "textname", client->text_name) == NULL)
946
0
        || (client->text_description && cJSON_AddStringToObject(j_client, "textdescription", client->text_description) == NULL)
947
0
        || (client->disabled && cJSON_AddBoolToObject(j_client, "disabled", client->disabled) == NULL)
948
0
        ){
949
950
0
      cJSON_Delete(j_client);
951
0
      return NULL;
952
0
    }
953
954
0
    j_roles = dynsec_rolelist__all_to_json(client->rolelist);
955
0
    if(j_roles == NULL){
956
0
      cJSON_Delete(j_client);
957
0
      return NULL;
958
0
    }
959
0
    cJSON_AddItemToObject(j_client, "roles", j_roles);
960
961
0
    j_groups = dynsec_grouplist__all_to_json(client->grouplist);
962
0
    if(j_groups == NULL){
963
0
      cJSON_Delete(j_client);
964
0
      return NULL;
965
0
    }
966
0
    cJSON_AddItemToObject(j_client, "groups", j_groups);
967
968
0
    j_connections = dynsec_connections__all_to_json(client->username, client->clientid);
969
0
    if(j_connections == NULL){
970
0
      cJSON_Delete(j_client);
971
0
      return NULL;
972
0
    }
973
0
    cJSON_AddItemToObject(j_client, "connections", j_connections);
974
0
  }else{
975
0
    j_client = cJSON_CreateString(client->username);
976
0
    if(j_client == NULL){
977
0
      return NULL;
978
0
    }
979
0
  }
980
0
  return j_client;
981
0
}
982
983
984
int dynsec_clients__process_get(struct dynsec__data *data, struct mosquitto_control_cmd *cmd)
985
0
{
986
0
  const char *username;
987
0
  struct dynsec__client *client;
988
0
  cJSON *tree, *j_client, *j_data;
989
0
  const char *admin_clientid, *admin_username;
990
991
0
  if(json_get_string(cmd->j_command, "username", &username, false) != MOSQ_ERR_SUCCESS){
992
0
    mosquitto_control_command_reply(cmd, "Invalid/missing username");
993
0
    return MOSQ_ERR_INVAL;
994
0
  }
995
0
  if(mosquitto_validate_utf8(username, (int)strlen(username)) != MOSQ_ERR_SUCCESS){
996
0
    mosquitto_control_command_reply(cmd, "Username not valid UTF-8");
997
0
    return MOSQ_ERR_INVAL;
998
0
  }
999
1000
0
  client = dynsec_clients__find(data, username);
1001
0
  if(client == NULL){
1002
0
    mosquitto_control_command_reply(cmd, "Client not found");
1003
0
    return MOSQ_ERR_SUCCESS;
1004
0
  }
1005
1006
0
  tree = cJSON_CreateObject();
1007
0
  if(tree == NULL){
1008
0
    mosquitto_control_command_reply(cmd, "Internal error");
1009
0
    return MOSQ_ERR_NOMEM;
1010
0
  }
1011
1012
0
  if(cJSON_AddStringToObject(tree, "command", "getClient") == NULL
1013
0
      || (j_data = cJSON_AddObjectToObject(tree, "data")) == NULL
1014
0
      || (cmd->correlation_data && cJSON_AddStringToObject(tree, "correlationData", cmd->correlation_data) == NULL)
1015
0
      ){
1016
1017
0
    cJSON_Delete(tree);
1018
0
    mosquitto_control_command_reply(cmd, "Internal error");
1019
0
    return MOSQ_ERR_NOMEM;
1020
0
  }
1021
1022
0
  j_client = add_client_to_json(client, true);
1023
0
  if(j_client == NULL){
1024
0
    cJSON_Delete(tree);
1025
0
    mosquitto_control_command_reply(cmd, "Internal error");
1026
0
    return MOSQ_ERR_NOMEM;
1027
0
  }
1028
0
  cJSON_AddItemToObject(j_data, "client", j_client);
1029
0
  cJSON_AddItemToArray(cmd->j_responses, tree);
1030
1031
0
  admin_clientid = mosquitto_client_id(cmd->client);
1032
0
  admin_username = mosquitto_client_username(cmd->client);
1033
0
  mosquitto_log_printf(MOSQ_LOG_INFO, "dynsec: %s/%s | getClient | username=%s",
1034
0
      admin_clientid, admin_username, username);
1035
1036
0
  return MOSQ_ERR_SUCCESS;
1037
0
}
1038
1039
1040
int dynsec_clients__process_list(struct dynsec__data *data, struct mosquitto_control_cmd *cmd)
1041
0
{
1042
0
  bool verbose;
1043
0
  struct dynsec__client *client, *client_tmp;
1044
0
  cJSON *tree, *j_clients, *j_client, *j_data;
1045
0
  int i, count, offset;
1046
0
  const char *admin_clientid, *admin_username;
1047
1048
0
  json_get_bool(cmd->j_command, "verbose", &verbose, true, false);
1049
0
  json_get_int(cmd->j_command, "count", &count, true, -1);
1050
0
  json_get_int(cmd->j_command, "offset", &offset, true, 0);
1051
1052
0
  tree = cJSON_CreateObject();
1053
0
  if(tree == NULL){
1054
0
    mosquitto_control_command_reply(cmd, "Internal error");
1055
0
    return MOSQ_ERR_NOMEM;
1056
0
  }
1057
1058
0
  if(cJSON_AddStringToObject(tree, "command", "listClients") == NULL
1059
0
      || (j_data = cJSON_AddObjectToObject(tree, "data")) == NULL
1060
0
      || cJSON_AddIntToObject(j_data, "totalCount", (int)HASH_CNT(hh, data->clients)) == NULL
1061
0
      || (j_clients = cJSON_AddArrayToObject(j_data, "clients")) == NULL
1062
0
      || (cmd->correlation_data && cJSON_AddStringToObject(tree, "correlationData", cmd->correlation_data) == NULL)
1063
0
      ){
1064
1065
0
    cJSON_Delete(tree);
1066
0
    mosquitto_control_command_reply(cmd, "Internal error");
1067
0
    return MOSQ_ERR_NOMEM;
1068
0
  }
1069
1070
0
  i = 0;
1071
0
  HASH_ITER(hh, data->clients, client, client_tmp){
1072
0
    if(i>=offset){
1073
0
      j_client = add_client_to_json(client, verbose);
1074
0
      if(j_client == NULL){
1075
0
        cJSON_Delete(tree);
1076
0
        mosquitto_control_command_reply(cmd, "Internal error");
1077
0
        return MOSQ_ERR_NOMEM;
1078
0
      }
1079
0
      cJSON_AddItemToArray(j_clients, j_client);
1080
1081
0
      if(count >= 0){
1082
0
        count--;
1083
0
        if(count <= 0){
1084
0
          break;
1085
0
        }
1086
0
      }
1087
0
    }
1088
0
    i++;
1089
0
  }
1090
0
  cJSON_AddItemToArray(cmd->j_responses, tree);
1091
1092
0
  admin_clientid = mosquitto_client_id(cmd->client);
1093
0
  admin_username = mosquitto_client_username(cmd->client);
1094
0
  mosquitto_log_printf(MOSQ_LOG_INFO, "dynsec: %s/%s | listClients | verbose=%s | count=%d | offset=%d",
1095
0
      admin_clientid, admin_username, verbose?"true":"false", count, offset);
1096
1097
0
  return MOSQ_ERR_SUCCESS;
1098
0
}
1099
1100
1101
int dynsec_clients__process_add_role(struct dynsec__data *data, struct mosquitto_control_cmd *cmd)
1102
0
{
1103
0
  const char *username, *rolename;
1104
0
  struct dynsec__client *client;
1105
0
  struct dynsec__role *role;
1106
0
  int priority;
1107
0
  const char *admin_clientid, *admin_username;
1108
1109
0
  if(json_get_string(cmd->j_command, "username", &username, false) != MOSQ_ERR_SUCCESS){
1110
0
    mosquitto_control_command_reply(cmd, "Invalid/missing username");
1111
0
    return MOSQ_ERR_INVAL;
1112
0
  }
1113
0
  if(mosquitto_validate_utf8(username, (int)strlen(username)) != MOSQ_ERR_SUCCESS){
1114
0
    mosquitto_control_command_reply(cmd, "Username not valid UTF-8");
1115
0
    return MOSQ_ERR_INVAL;
1116
0
  }
1117
1118
0
  if(json_get_string(cmd->j_command, "rolename", &rolename, false) != MOSQ_ERR_SUCCESS){
1119
0
    mosquitto_control_command_reply(cmd, "Invalid/missing rolename");
1120
0
    return MOSQ_ERR_INVAL;
1121
0
  }
1122
0
  if(mosquitto_validate_utf8(rolename, (int)strlen(rolename)) != MOSQ_ERR_SUCCESS){
1123
0
    mosquitto_control_command_reply(cmd, "Role name not valid UTF-8");
1124
0
    return MOSQ_ERR_INVAL;
1125
0
  }
1126
0
  json_get_int(cmd->j_command, "priority", &priority, true, -1);
1127
0
  if(priority > PRIORITY_MAX) priority = PRIORITY_MAX;
1128
1129
0
  client = dynsec_clients__find(data, username);
1130
0
  if(client == NULL){
1131
0
    mosquitto_control_command_reply(cmd, "Client not found");
1132
0
    return MOSQ_ERR_SUCCESS;
1133
0
  }
1134
1135
0
  role = dynsec_roles__find(data, rolename);
1136
0
  if(role == NULL){
1137
0
    mosquitto_control_command_reply(cmd, "Role not found");
1138
0
    return MOSQ_ERR_SUCCESS;
1139
0
  }
1140
1141
0
  if(dynsec_rolelist__client_add(client, role, priority) != MOSQ_ERR_SUCCESS){
1142
0
    mosquitto_control_command_reply(cmd, "Internal error");
1143
0
    return MOSQ_ERR_UNKNOWN;
1144
0
  }
1145
0
  dynsec__config_batch_save(data);
1146
0
  mosquitto_control_command_reply(cmd, NULL);
1147
1148
  /* Enforce any changes */
1149
0
  dynsec_kicklist__add(data, username);
1150
1151
0
  admin_clientid = mosquitto_client_id(cmd->client);
1152
0
  admin_username = mosquitto_client_username(cmd->client);
1153
0
  mosquitto_log_printf(MOSQ_LOG_INFO, "dynsec: %s/%s | addClientRole | username=%s | rolename=%s | priority=%d",
1154
0
      admin_clientid, admin_username, username, rolename, priority);
1155
1156
0
  return MOSQ_ERR_SUCCESS;
1157
0
}
1158
1159
1160
int dynsec_clients__process_remove_role(struct dynsec__data *data, struct mosquitto_control_cmd *cmd)
1161
0
{
1162
0
  const char *username, *rolename;
1163
0
  struct dynsec__client *client;
1164
0
  struct dynsec__role *role;
1165
0
  const char *admin_clientid, *admin_username;
1166
1167
0
  if(json_get_string(cmd->j_command, "username", &username, false) != MOSQ_ERR_SUCCESS){
1168
0
    mosquitto_control_command_reply(cmd, "Invalid/missing username");
1169
0
    return MOSQ_ERR_INVAL;
1170
0
  }
1171
0
  if(mosquitto_validate_utf8(username, (int)strlen(username)) != MOSQ_ERR_SUCCESS){
1172
0
    mosquitto_control_command_reply(cmd, "Username not valid UTF-8");
1173
0
    return MOSQ_ERR_INVAL;
1174
0
  }
1175
1176
0
  if(json_get_string(cmd->j_command, "rolename", &rolename, false) != MOSQ_ERR_SUCCESS){
1177
0
    mosquitto_control_command_reply(cmd, "Invalid/missing rolename");
1178
0
    return MOSQ_ERR_INVAL;
1179
0
  }
1180
0
  if(mosquitto_validate_utf8(rolename, (int)strlen(rolename)) != MOSQ_ERR_SUCCESS){
1181
0
    mosquitto_control_command_reply(cmd, "Role name not valid UTF-8");
1182
0
    return MOSQ_ERR_INVAL;
1183
0
  }
1184
1185
1186
0
  client = dynsec_clients__find(data, username);
1187
0
  if(client == NULL){
1188
0
    mosquitto_control_command_reply(cmd, "Client not found");
1189
0
    return MOSQ_ERR_SUCCESS;
1190
0
  }
1191
1192
0
  role = dynsec_roles__find(data, rolename);
1193
0
  if(role == NULL){
1194
0
    mosquitto_control_command_reply(cmd, "Role not found");
1195
0
    return MOSQ_ERR_SUCCESS;
1196
0
  }
1197
1198
0
  dynsec_rolelist__client_remove(client, role);
1199
0
  dynsec__config_batch_save(data);
1200
0
  mosquitto_control_command_reply(cmd, NULL);
1201
1202
  /* Enforce any changes */
1203
0
  dynsec_kicklist__add(data, username);
1204
1205
0
  admin_clientid = mosquitto_client_id(cmd->client);
1206
0
  admin_username = mosquitto_client_username(cmd->client);
1207
0
  mosquitto_log_printf(MOSQ_LOG_INFO, "dynsec: %s/%s | removeClientRole | username=%s | rolename=%s",
1208
0
      admin_clientid, admin_username, username, rolename);
1209
1210
0
  return MOSQ_ERR_SUCCESS;
1211
0
}