/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 | } |