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