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