Coverage Report

Created: 2025-06-20 06:45

/src/mosquitto/plugins/dynamic-security/acl.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 "mosquitto.h"
22
23
#include "dynamic_security.h"
24
25
typedef int (*MOSQ_FUNC_acl_check)(struct dynsec__data *data, struct mosquitto_evt_acl_check *, struct dynsec__rolelist *);
26
27
/* FIXME - CACHE! */
28
29
/* ################################################################
30
 * #
31
 * # ACL check - publish broker to client
32
 * #
33
 * ################################################################ */
34
35
static int acl_check_publish_c_recv(struct dynsec__data *data, struct mosquitto_evt_acl_check *ed, struct dynsec__rolelist *base_rolelist)
36
0
{
37
0
  struct dynsec__rolelist *rolelist, *rolelist_tmp = NULL;
38
0
  struct dynsec__acl *acl, *acl_tmp = NULL;
39
0
  bool result;
40
0
  const char *clientid, *username;
41
42
0
  UNUSED(data);
43
44
0
  clientid = mosquitto_client_id(ed->client);
45
0
  username = mosquitto_client_username(ed->client);
46
47
0
  HASH_ITER(hh, base_rolelist, rolelist, rolelist_tmp){
48
0
    HASH_ITER(hh, rolelist->role->acls.publish_c_recv, acl, acl_tmp){
49
0
      if(mosquitto_topic_matches_sub_with_pattern(acl->topic, ed->topic, clientid, username, &result)){
50
0
        return MOSQ_ERR_ACL_DENIED;
51
0
      }
52
0
      if(result){
53
0
        if(acl->allow){
54
0
          return MOSQ_ERR_SUCCESS;
55
0
        }else{
56
0
          return MOSQ_ERR_ACL_DENIED;
57
0
        }
58
0
      }
59
0
    }
60
0
  }
61
0
  return MOSQ_ERR_NOT_FOUND;
62
0
}
63
64
65
/* ################################################################
66
 * #
67
 * # ACL check - publish client to broker
68
 * #
69
 * ################################################################ */
70
71
static int acl_check_publish_c_send(struct dynsec__data *data, struct mosquitto_evt_acl_check *ed, struct dynsec__rolelist *base_rolelist)
72
0
{
73
0
  struct dynsec__rolelist *rolelist, *rolelist_tmp = NULL;
74
0
  struct dynsec__acl *acl, *acl_tmp = NULL;
75
0
  bool result;
76
0
  const char *clientid, *username;
77
78
0
  UNUSED(data);
79
80
0
  clientid = mosquitto_client_id(ed->client);
81
0
  username = mosquitto_client_username(ed->client);
82
83
0
  HASH_ITER(hh, base_rolelist, rolelist, rolelist_tmp){
84
0
    HASH_ITER(hh, rolelist->role->acls.publish_c_send, acl, acl_tmp){
85
0
      if(mosquitto_topic_matches_sub_with_pattern(acl->topic, ed->topic, clientid, username, &result)){
86
0
        return MOSQ_ERR_ACL_DENIED;
87
0
      }
88
0
      if(result){
89
0
        if(acl->allow){
90
0
          return MOSQ_ERR_SUCCESS;
91
0
        }else{
92
0
          return MOSQ_ERR_ACL_DENIED;
93
0
        }
94
0
      }
95
0
    }
96
0
  }
97
0
  return MOSQ_ERR_NOT_FOUND;
98
0
}
99
100
101
/* ################################################################
102
 * #
103
 * # ACL check - subscribe
104
 * #
105
 * ################################################################ */
106
107
static int acl_check_subscribe(struct dynsec__data *data, struct mosquitto_evt_acl_check *ed, struct dynsec__rolelist *base_rolelist)
108
0
{
109
0
  struct dynsec__rolelist *rolelist, *rolelist_tmp = NULL;
110
0
  struct dynsec__acl *acl, *acl_tmp = NULL;
111
0
  size_t len;
112
0
  bool result;
113
0
  const char *clientid, *username;
114
0
  bool has_wildcard;
115
116
0
  UNUSED(data);
117
118
0
  len = strlen(ed->topic);
119
0
  has_wildcard = (strpbrk(ed->topic, "+#") != NULL);
120
121
0
  clientid = mosquitto_client_id(ed->client);
122
0
  username = mosquitto_client_username(ed->client);
123
124
0
  HASH_ITER(hh, base_rolelist, rolelist, rolelist_tmp){
125
0
    if(rolelist->role->allow_wildcard_subs == false && has_wildcard == true){
126
0
      return MOSQ_ERR_ACL_DENIED;
127
0
    }
128
0
    HASH_FIND(hh, rolelist->role->acls.subscribe_literal, ed->topic, len, acl);
129
0
    if(acl){
130
0
      if(acl->allow){
131
0
        return MOSQ_ERR_SUCCESS;
132
0
      }else{
133
0
        return MOSQ_ERR_ACL_DENIED;
134
0
      }
135
0
    }
136
0
    HASH_ITER(hh, rolelist->role->acls.subscribe_pattern, acl, acl_tmp){
137
0
      if(mosquitto_sub_matches_acl_with_pattern(acl->topic, ed->topic, clientid, username, &result)){
138
        /* Invalid input, so deny */
139
0
        return MOSQ_ERR_ACL_DENIED;
140
0
      }
141
0
      if(result){
142
0
        if(acl->allow){
143
0
          return MOSQ_ERR_SUCCESS;
144
0
        }else{
145
0
          return MOSQ_ERR_ACL_DENIED;
146
0
        }
147
0
      }
148
0
    }
149
0
  }
150
0
  return MOSQ_ERR_NOT_FOUND;
151
0
}
152
153
154
/* ################################################################
155
 * #
156
 * # ACL check - unsubscribe
157
 * #
158
 * ################################################################ */
159
160
static int acl_check_unsubscribe(struct dynsec__data *data, struct mosquitto_evt_acl_check *ed, struct dynsec__rolelist *base_rolelist)
161
0
{
162
0
  struct dynsec__rolelist *rolelist, *rolelist_tmp = NULL;
163
0
  struct dynsec__acl *acl, *acl_tmp = NULL;
164
0
  size_t len;
165
0
  bool result;
166
0
  const char *clientid, *username;
167
168
0
  UNUSED(data);
169
170
0
  len = strlen(ed->topic);
171
172
0
  clientid = mosquitto_client_id(ed->client);
173
0
  username = mosquitto_client_username(ed->client);
174
175
0
  HASH_ITER(hh, base_rolelist, rolelist, rolelist_tmp){
176
0
    HASH_FIND(hh, rolelist->role->acls.unsubscribe_literal, ed->topic, len, acl);
177
0
    if(acl){
178
0
      if(acl->allow){
179
0
        return MOSQ_ERR_SUCCESS;
180
0
      }else{
181
0
        return MOSQ_ERR_ACL_DENIED;
182
0
      }
183
0
    }
184
0
    HASH_ITER(hh, rolelist->role->acls.unsubscribe_pattern, acl, acl_tmp){
185
0
      if(mosquitto_sub_matches_acl_with_pattern(acl->topic, ed->topic, clientid, username, &result)){
186
        /* Invalid input, so deny */
187
0
        return MOSQ_ERR_ACL_DENIED;
188
0
      }
189
0
      if(result){
190
0
        if(acl->allow){
191
0
          return MOSQ_ERR_SUCCESS;
192
0
        }else{
193
0
          return MOSQ_ERR_ACL_DENIED;
194
0
        }
195
0
      }
196
0
    }
197
0
  }
198
0
  return MOSQ_ERR_NOT_FOUND;
199
0
}
200
201
202
/* ################################################################
203
 * #
204
 * # ACL check - generic check
205
 * #
206
 * ################################################################ */
207
208
static int acl_check(struct dynsec__data *data, struct mosquitto_evt_acl_check *ed, MOSQ_FUNC_acl_check check, bool acl_default_access)
209
0
{
210
0
  struct dynsec__client *client;
211
0
  struct dynsec__grouplist *grouplist, *grouplist_tmp = NULL;
212
0
  const char *username;
213
0
  int rc;
214
215
0
  username = mosquitto_client_username(ed->client);
216
217
0
  if(username){
218
0
    client = dynsec_clients__find(data, username);
219
0
    if(client == NULL) return MOSQ_ERR_PLUGIN_DEFER;
220
221
    /* Client roles */
222
0
    rc = check(data, ed, client->rolelist);
223
0
    if(rc != MOSQ_ERR_NOT_FOUND){
224
0
      return rc;
225
0
    }
226
227
0
    HASH_ITER(hh, client->grouplist, grouplist, grouplist_tmp){
228
0
      rc = check(data, ed, grouplist->group->rolelist);
229
0
      if(rc != MOSQ_ERR_NOT_FOUND){
230
0
        return rc;
231
0
      }
232
0
    }
233
0
  }else if(data->anonymous_group){
234
    /* If we have a group for anonymous users, use that for checking. */
235
0
    rc = check(data, ed, data->anonymous_group->rolelist);
236
0
    if(rc != MOSQ_ERR_NOT_FOUND){
237
0
      return rc;
238
0
    }
239
0
  }
240
241
0
  if(acl_default_access == false){
242
0
    return MOSQ_ERR_PLUGIN_DEFER;
243
0
  }else{
244
0
    if(!strncmp(ed->topic, "$CONTROL", strlen("$CONTROL"))){
245
      /* We never give fall through access to $CONTROL topics, they must
246
       * be granted explicitly. */
247
0
      return MOSQ_ERR_PLUGIN_DEFER;
248
0
    }else{
249
0
      return MOSQ_ERR_SUCCESS;
250
0
    }
251
0
  }
252
0
}
253
254
255
/* ################################################################
256
 * #
257
 * # ACL check - plugin callback
258
 * #
259
 * ################################################################ */
260
261
int dynsec__acl_check_callback(int event, void *event_data, void *userdata)
262
0
{
263
0
  struct mosquitto_evt_acl_check *ed = event_data;
264
0
  struct dynsec__data *data = userdata;
265
266
0
  UNUSED(event);
267
0
  UNUSED(userdata);
268
269
  /* ACL checks are made in the order below until a match occurs, at which
270
   * point the decision is made.
271
   *
272
   * User roles in priority order highest to lowest.
273
   *    Roles have their ACLs checked in priority order, highest to lowest
274
   * Groups are processed in priority order highest to lowest
275
   *    Group roles are processed in priority order, highest to lowest
276
   *       Roles have their ACLs checked in priority order, highest to lowest
277
   */
278
279
0
  switch(ed->access){
280
0
    case MOSQ_ACL_SUBSCRIBE:
281
0
      return acl_check(data, event_data, acl_check_subscribe, data->default_access.subscribe);
282
0
      break;
283
0
    case MOSQ_ACL_UNSUBSCRIBE:
284
0
      return acl_check(data, event_data, acl_check_unsubscribe, data->default_access.unsubscribe);
285
0
      break;
286
0
    case MOSQ_ACL_WRITE: /* Client to broker */
287
0
      return acl_check(data, event_data, acl_check_publish_c_send, data->default_access.publish_c_send);
288
0
      break;
289
0
    case MOSQ_ACL_READ:
290
0
      return acl_check(data, event_data, acl_check_publish_c_recv, data->default_access.publish_c_recv);
291
0
      break;
292
0
    default:
293
0
      return MOSQ_ERR_PLUGIN_DEFER;
294
0
  }
295
0
  return MOSQ_ERR_PLUGIN_DEFER;
296
0
}