Coverage Report

Created: 2025-10-24 06:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mosquitto/plugins/acl-file/acl_parse.c
Line
Count
Source
1
/*
2
Copyright (c) 2011-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 <ctype.h>
22
#include <stdbool.h>
23
#include <stdio.h>
24
#include <string.h>
25
#include <utlist.h>
26
27
#include "mosquitto.h"
28
#include "acl_file.h"
29
30
31
static int acl__add_to_user(struct acl__user *acl_user, const char *topic, int access)
32
566k
{
33
566k
  struct acl__entry *acl;
34
35
566k
  acl = mosquitto_calloc(1, sizeof(struct acl__entry));
36
566k
  if(!acl){
37
0
    return MOSQ_ERR_NOMEM;
38
0
  }
39
566k
  acl->access = access;
40
41
566k
  acl->topic = mosquitto_strdup(topic);
42
566k
  if(!acl->topic){
43
0
    return MOSQ_ERR_NOMEM;
44
0
  }
45
46
  /* Add acl to user acl list */
47
566k
  if(access == MOSQ_ACL_NONE){
48
    /* Put "deny" acls at front of the list */
49
544
    DL_PREPEND(acl_user->acl, acl);
50
566k
  }else{
51
566k
    DL_APPEND(acl_user->acl, acl);
52
566k
  }
53
54
566k
  return MOSQ_ERR_SUCCESS;
55
566k
}
56
57
58
static struct acl__user *acl__find_or_create_user(struct acl_file_data *data, const char *user, unsigned user_hashv)
59
566k
{
60
566k
  if(user == NULL){
61
450k
    return &data->acl_anon;
62
450k
  }else{
63
115k
    struct acl__user *acl_user=NULL;
64
65
115k
    HASH_FIND_BYHASHVALUE(hh, data->acl_users, user, strlen(user), user_hashv, acl_user);
66
67
115k
    if(!acl_user){
68
16.5k
      acl_user = mosquitto_calloc(1, sizeof(struct acl__user));
69
16.5k
      if(!acl_user){
70
0
        return NULL;
71
0
      }
72
16.5k
      if(user){
73
16.5k
        acl_user->username = mosquitto_strdup(user);
74
16.5k
        if(!acl_user->username){
75
0
          mosquitto_FREE(acl_user);
76
0
          return NULL;
77
0
        }
78
16.5k
      }
79
16.5k
      HASH_ADD_KEYPTR(hh, data->acl_users, acl_user->username, strlen(acl_user->username), acl_user);
80
16.5k
    }
81
82
115k
    return acl_user;
83
115k
  }
84
566k
}
85
86
87
static int acl__add(struct acl_file_data *data, const char *user, unsigned user_hashv, const char *topic, int access)
88
566k
{
89
566k
  struct acl__user *acl_user=NULL;
90
91
566k
  if(!data || !topic){
92
0
    return MOSQ_ERR_INVAL;
93
0
  }
94
95
566k
  acl_user = acl__find_or_create_user(data, user, user_hashv);
96
566k
  if(!acl_user){
97
0
    return MOSQ_ERR_NOMEM;
98
0
  }
99
100
566k
  return acl__add_to_user(acl_user, topic, access);
101
566k
}
102
103
104
static int acl__add_pattern(struct acl_file_data *data, const char *topic, int access)
105
3.51k
{
106
3.51k
  struct acl__entry *acl, *acl_tail;
107
3.51k
  char *local_topic;
108
3.51k
  char *s;
109
110
3.51k
  if(!data| !topic){
111
0
    return MOSQ_ERR_INVAL;
112
0
  }
113
114
3.51k
  local_topic = mosquitto_strdup(topic);
115
3.51k
  if(!local_topic){
116
0
    return MOSQ_ERR_NOMEM;
117
0
  }
118
119
3.51k
  acl = mosquitto_malloc(sizeof(struct acl__entry));
120
3.51k
  if(!acl){
121
0
    mosquitto_FREE(local_topic);
122
0
    return MOSQ_ERR_NOMEM;
123
0
  }
124
3.51k
  acl->access = access;
125
3.51k
  acl->topic = local_topic;
126
3.51k
  acl->next = NULL;
127
128
3.51k
  acl->ccount = 0;
129
3.51k
  s = local_topic;
130
67.4k
  while(s){
131
63.9k
    s = strstr(s, "%c");
132
63.9k
    if(s){
133
60.3k
      acl->ccount++;
134
60.3k
      s+=2;
135
60.3k
    }
136
63.9k
  }
137
138
3.51k
  acl->ucount = 0;
139
3.51k
  s = local_topic;
140
41.5k
  while(s){
141
37.9k
    s = strstr(s, "%u");
142
37.9k
    if(s){
143
34.4k
      acl->ucount++;
144
34.4k
      s+=2;
145
34.4k
    }
146
37.9k
  }
147
148
3.51k
  if(acl->ccount == 0 && acl->ucount == 0){
149
3.04k
    mosquitto_log_printf(MOSQ_LOG_WARNING,
150
3.04k
        "Warning: ACL pattern '%s' does not contain '%%c' or '%%u'.",
151
3.04k
        topic);
152
3.04k
  }
153
154
3.51k
  if(data->acl_patterns){
155
3.37k
    acl_tail = data->acl_patterns;
156
3.37k
    if(access == MOSQ_ACL_NONE){
157
      /* Put "deny" acls at front of the list */
158
195
      acl->next = acl_tail;
159
195
      data->acl_patterns = acl;
160
3.17k
    }else{
161
569k
      while(acl_tail->next){
162
566k
        acl_tail = acl_tail->next;
163
566k
      }
164
3.17k
      acl_tail->next = acl;
165
3.17k
    }
166
3.37k
  }else{
167
138
    data->acl_patterns = acl;
168
138
  }
169
170
3.51k
  return MOSQ_ERR_SUCCESS;
171
3.51k
}
172
173
174
int acl_file__parse(struct acl_file_data *data)
175
1.25k
{
176
1.25k
  FILE *aclfptr = NULL;
177
1.25k
  char *token;
178
1.25k
  char *user = NULL;
179
1.25k
  char *topic;
180
1.25k
  char *access_s;
181
1.25k
  int access;
182
1.25k
  int rc = MOSQ_ERR_SUCCESS;
183
1.25k
  size_t slen;
184
1.25k
  int topic_pattern;
185
1.25k
  char *saveptr = NULL;
186
1.25k
  char *buf = NULL;
187
1.25k
  int buflen = 256;
188
1.25k
  unsigned user_hashv = 0;
189
190
1.25k
  if(!data){
191
0
    return MOSQ_ERR_INVAL;
192
0
  }
193
1.25k
  if(!data->acl_file){
194
0
    return MOSQ_ERR_SUCCESS;
195
0
  }
196
197
1.25k
  buf = mosquitto_calloc((size_t)buflen, 1);
198
1.25k
  if(buf == NULL){
199
0
    mosquitto_log_printf(MOSQ_LOG_ERR, "Error: Out of memory.");
200
0
    return MOSQ_ERR_NOMEM;
201
0
  }
202
203
1.25k
  aclfptr = mosquitto_fopen(data->acl_file, "rt", true);
204
1.25k
  if(!aclfptr){
205
0
    mosquitto_FREE(buf);
206
0
    mosquitto_log_printf(MOSQ_LOG_ERR, "Error: Unable to open acl_file \"%s\".", data->acl_file);
207
0
    return MOSQ_ERR_UNKNOWN;
208
0
  }
209
210
  /* topic [read|write] <topic>
211
   * user <user>
212
   */
213
214
596k
  while(mosquitto_fgets(&buf, &buflen, aclfptr)){
215
595k
    slen = strlen(buf);
216
1.19M
    while(slen > 0 && isspace(buf[slen-1])){
217
595k
      buf[slen-1] = '\0';
218
595k
      slen = strlen(buf);
219
595k
    }
220
595k
    if(buf[0] == '#'){
221
489
      continue;
222
489
    }
223
595k
    token = strtok_r(buf, " ", &saveptr);
224
595k
    if(token){
225
594k
      if(!strcmp(token, "topic") || !strcmp(token, "pattern")){
226
570k
        if(!strcmp(token, "topic")){
227
567k
          topic_pattern = 0;
228
567k
        }else{
229
3.52k
          topic_pattern = 1;
230
3.52k
        }
231
232
570k
        access_s = strtok_r(NULL, " ", &saveptr);
233
570k
        if(!access_s){
234
19
          mosquitto_log_printf(MOSQ_LOG_ERR, "Error: Empty topic in acl_file \"%s\".", data->acl_file);
235
19
          rc = MOSQ_ERR_INVAL;
236
19
          break;
237
19
        }
238
570k
        token = strtok_r(NULL, "", &saveptr);
239
570k
        if(token){
240
1.69k
          topic = mosquitto_trimblanks(token);
241
568k
        }else{
242
568k
          topic = access_s;
243
568k
          access_s = NULL;
244
568k
        }
245
570k
        if(access_s){
246
1.69k
          if(!strcmp(access_s, "read")){
247
385
            access = MOSQ_ACL_READ;
248
1.31k
          }else if(!strcmp(access_s, "write")){
249
195
            access = MOSQ_ACL_WRITE;
250
1.11k
          }else if(!strcmp(access_s, "readwrite")){
251
212
            access = MOSQ_ACL_READ | MOSQ_ACL_WRITE;
252
903
          }else if(!strcmp(access_s, "deny")){
253
740
            access = MOSQ_ACL_NONE;
254
740
          }else{
255
163
            mosquitto_log_printf(MOSQ_LOG_ERR, "Error: Invalid topic access type \"%s\" in acl_file \"%s\".", access_s, data->acl_file);
256
163
            rc = MOSQ_ERR_INVAL;
257
163
            break;
258
163
          }
259
568k
        }else{
260
568k
          access = MOSQ_ACL_READ | MOSQ_ACL_WRITE;
261
568k
        }
262
570k
        rc = mosquitto_sub_topic_check(topic);
263
570k
        if(rc != MOSQ_ERR_SUCCESS){
264
76
          mosquitto_log_printf(MOSQ_LOG_ERR, "Error: Invalid ACL topic \"%s\" in acl_file \"%s\".", topic, data->acl_file);
265
76
          rc = MOSQ_ERR_INVAL;
266
76
          break;
267
76
        }
268
269
570k
        if(topic_pattern == 0){
270
566k
          rc = acl__add(data, user, user_hashv, topic, access);
271
566k
        }else{
272
3.51k
          rc = acl__add_pattern(data, topic, access);
273
3.51k
        }
274
570k
        if(rc){
275
0
          break;
276
0
        }
277
570k
      }else if(!strcmp(token, "user")){
278
23.2k
        token = strtok_r(NULL, "", &saveptr);
279
23.2k
        if(token){
280
23.2k
          token = mosquitto_trimblanks(token);
281
23.2k
          if(slen == 0){
282
0
            mosquitto_log_printf(MOSQ_LOG_ERR, "Error: Missing username in acl_file \"%s\".", data->acl_file);
283
0
            rc = MOSQ_ERR_INVAL;
284
0
            break;
285
0
          }
286
23.2k
          mosquitto_FREE(user);
287
23.2k
          user = mosquitto_strdup(token);
288
23.2k
          if(!user){
289
0
            rc = MOSQ_ERR_NOMEM;
290
0
            break;
291
0
          }
292
23.2k
          HASH_VALUE(user, strlen(user), user_hashv);
293
23.2k
        }else{
294
1
          mosquitto_log_printf(MOSQ_LOG_ERR, "Error: Missing username in acl_file \"%s\".", data->acl_file);
295
1
          rc = MOSQ_ERR_INVAL;
296
1
          break;
297
1
        }
298
23.2k
      }else{
299
223
        mosquitto_log_printf(MOSQ_LOG_ERR, "Error: Invalid line in acl_file \"%s\": %s.", data->acl_file, buf);
300
223
        rc = MOSQ_ERR_INVAL;
301
223
        break;
302
223
      }
303
594k
    }
304
595k
  }
305
306
1.25k
  mosquitto_FREE(buf);
307
1.25k
  mosquitto_FREE(user);
308
1.25k
  fclose(aclfptr);
309
310
1.25k
  return rc;
311
1.25k
}
312
313
314
static void acl__free_entries(struct acl__entry *entry)
315
22.6k
{
316
592k
  while(entry){
317
570k
    struct acl__entry *next = entry->next;
318
319
570k
    mosquitto_FREE(entry->topic);
320
570k
    mosquitto_FREE(entry);
321
322
570k
    entry = next;
323
570k
  }
324
22.6k
}
325
326
327
void acl_file__cleanup(struct acl_file_data *data)
328
3.05k
{
329
3.05k
  struct acl__user *user, *user_tmp;
330
331
16.5k
  HASH_ITER(hh, data->acl_users, user, user_tmp){
332
16.5k
    HASH_DELETE(hh, data->acl_users, user);
333
16.5k
    mosquitto_FREE(user->username);
334
16.5k
    acl__free_entries(user->acl);
335
16.5k
    mosquitto_FREE(user);
336
16.5k
  }
337
338
3.05k
  acl__free_entries(data->acl_anon.acl);
339
3.05k
  data->acl_anon.acl = NULL;
340
341
3.05k
  acl__free_entries(data->acl_patterns);
342
3.05k
  data->acl_patterns = NULL;
343
3.05k
}
344
345
346
int acl_file__reload(int event, void *event_data, void *userdata)
347
0
{
348
0
  struct acl_file_data *data = userdata;
349
350
0
  UNUSED(event);
351
0
  UNUSED(event_data);
352
353
0
  acl_file__cleanup(data);
354
0
  return acl_file__parse(data);
355
0
}