Coverage Report

Created: 2025-11-14 07:11

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