1 /** @file
2
3 An example plugin that denies client access to specified sites (denylist.txt).
4
5 @section license License
6
7 Licensed to the Apache Software Foundation (ASF) under one
8 or more contributor license agreements. See the NOTICE file
9 distributed with this work for additional information
10 regarding copyright ownership. The ASF licenses this file
11 to you under the Apache License, Version 2.0 (the
12 "License"); you may not use this file except in compliance
13 with the License. You may obtain a copy of the License at
14
15 http://www.apache.org/licenses/LICENSE-2.0
16
17 Unless required by applicable law or agreed to in writing, software
18 distributed under the License is distributed on an "AS IS" BASIS,
19 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 See the License for the specific language governing permissions and
21 limitations under the License.
22 */
23
24 #include <stdio.h>
25 #include <string.h>
26
27 #include "ts/ts.h"
28 #include "tscore/ink_defs.h"
29
30 #define PLUGIN_NAME "denylist_1"
31
32 #define MAX_NSITES 500
33 #define RETRY_TIME 10
34
35 static char *sites[MAX_NSITES];
36 static int nsites;
37 static TSMutex sites_mutex;
38 static TSTextLogObject log;
39 static TSCont global_contp;
40
41 static void handle_txn_start(TSCont contp, TSHttpTxn txnp);
42
43 typedef struct contp_data {
44 enum calling_func {
45 HANDLE_DNS,
46 HANDLE_RESPONSE,
47 READ_BLOCKLIST,
48 } cf;
49
50 TSHttpTxn txnp;
51
52 } cdata;
53
54 static void
destroy_continuation(TSHttpTxn txnp,TSCont contp)55 destroy_continuation(TSHttpTxn txnp, TSCont contp)
56 {
57 cdata *cd = NULL;
58
59 cd = (cdata *)TSContDataGet(contp);
60 if (cd != NULL) {
61 TSfree(cd);
62 }
63 TSContDestroy(contp);
64 TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
65 return;
66 }
67
68 static void
handle_dns(TSHttpTxn txnp,TSCont contp)69 handle_dns(TSHttpTxn txnp, TSCont contp)
70 {
71 TSMBuffer bufp;
72 TSMLoc hdr_loc;
73 TSMLoc url_loc;
74 const char *host;
75 int i;
76 int host_length;
77
78 if (TSHttpTxnClientReqGet(txnp, &bufp, &hdr_loc) != TS_SUCCESS) {
79 TSError("[%s] Couldn't retrieve client request header", PLUGIN_NAME);
80 goto done;
81 }
82
83 if (TSHttpHdrUrlGet(bufp, hdr_loc, &url_loc) != TS_SUCCESS) {
84 TSError("[%s] Couldn't retrieve request url", PLUGIN_NAME);
85 TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
86 goto done;
87 }
88
89 host = TSUrlHostGet(bufp, url_loc, &host_length);
90 if (!host) {
91 TSError("[%s] Couldn't retrieve request hostname", PLUGIN_NAME);
92 TSHandleMLocRelease(bufp, hdr_loc, url_loc);
93 TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
94 goto done;
95 }
96
97 /* We need to lock the sites_mutex as that is the mutex that is
98 protecting the global list of all denylisted sites. */
99 if (TSMutexLockTry(sites_mutex) != TS_SUCCESS) {
100 TSDebug(PLUGIN_NAME, "Unable to get lock. Will retry after some time");
101 TSHandleMLocRelease(bufp, hdr_loc, url_loc);
102 TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
103 TSContScheduleOnPool(contp, RETRY_TIME, TS_THREAD_POOL_NET);
104 return;
105 }
106
107 for (i = 0; i < nsites; i++) {
108 if (strncmp(host, sites[i], host_length) == 0) {
109 if (log) {
110 TSTextLogObjectWrite(log, "denylisting site: %s", sites[i]);
111 } else {
112 TSDebug(PLUGIN_NAME, "denylisting site: %s", sites[i]);
113 }
114 TSHttpTxnHookAdd(txnp, TS_HTTP_SEND_RESPONSE_HDR_HOOK, contp);
115 TSHandleMLocRelease(bufp, hdr_loc, url_loc);
116 TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
117 TSHttpTxnReenable(txnp, TS_EVENT_HTTP_ERROR);
118 TSMutexUnlock(sites_mutex);
119 return;
120 }
121 }
122
123 TSMutexUnlock(sites_mutex);
124 TSHandleMLocRelease(bufp, hdr_loc, url_loc);
125 TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
126
127 done:
128 TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
129 }
130
131 static void
handle_response(TSHttpTxn txnp,TSCont contp ATS_UNUSED)132 handle_response(TSHttpTxn txnp, TSCont contp ATS_UNUSED)
133 {
134 TSMBuffer bufp;
135 TSMLoc hdr_loc;
136 TSMLoc url_loc;
137 char *url_str;
138 char *buf;
139 int url_length;
140
141 if (TSHttpTxnClientRespGet(txnp, &bufp, &hdr_loc) != TS_SUCCESS) {
142 TSError("[%s] Couldn't retrieve client response header", PLUGIN_NAME);
143 goto done;
144 }
145
146 TSHttpHdrStatusSet(bufp, hdr_loc, TS_HTTP_STATUS_FORBIDDEN);
147 TSHttpHdrReasonSet(bufp, hdr_loc, TSHttpHdrReasonLookup(TS_HTTP_STATUS_FORBIDDEN),
148 strlen(TSHttpHdrReasonLookup(TS_HTTP_STATUS_FORBIDDEN)));
149
150 if (TSHttpTxnClientReqGet(txnp, &bufp, &hdr_loc) != TS_SUCCESS) {
151 TSError("[%s] Couldn't retrieve client request header", PLUGIN_NAME);
152 TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
153 goto done;
154 }
155
156 if (TSHttpHdrUrlGet(bufp, hdr_loc, &url_loc) != TS_SUCCESS) {
157 TSError("[%s] Couldn't retrieve request url", PLUGIN_NAME);
158 TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
159 goto done;
160 }
161
162 buf = (char *)TSmalloc(4096);
163
164 url_str = TSUrlStringGet(bufp, url_loc, &url_length);
165 sprintf(buf, "You are forbidden from accessing \"%s\"\n", url_str);
166 TSfree(url_str);
167 TSHandleMLocRelease(bufp, hdr_loc, url_loc);
168 TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
169
170 TSHttpTxnErrorBodySet(txnp, buf, strlen(buf), NULL);
171
172 done:
173 TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
174 }
175
176 static void
read_denylist(TSCont contp)177 read_denylist(TSCont contp)
178 {
179 char denylist_file[1024];
180 TSFile file;
181
182 sprintf(denylist_file, "%s/denylist.txt", TSPluginDirGet());
183 file = TSfopen(denylist_file, "r");
184 nsites = 0;
185
186 /* If the Mutex lock is not successful try again in RETRY_TIME */
187 if (TSMutexLockTry(sites_mutex) != TS_SUCCESS) {
188 if (file != NULL) {
189 TSfclose(file);
190 }
191 TSContScheduleOnPool(contp, RETRY_TIME, TS_THREAD_POOL_NET);
192 return;
193 }
194
195 if (file != NULL) {
196 char buffer[1024];
197
198 while (TSfgets(file, buffer, sizeof(buffer) - 1) != NULL && nsites < MAX_NSITES) {
199 char *eol;
200 if ((eol = strstr(buffer, "\r\n")) != NULL) {
201 /* To handle newlines on Windows */
202 *eol = '\0';
203 } else if ((eol = strchr(buffer, '\n')) != NULL) {
204 *eol = '\0';
205 } else {
206 /* Not a valid line, skip it */
207 continue;
208 }
209 if (sites[nsites] != NULL) {
210 TSfree(sites[nsites]);
211 }
212 sites[nsites] = TSstrdup(buffer);
213 nsites++;
214 }
215
216 TSfclose(file);
217 } else {
218 TSError("[%s] Unable to open %s", PLUGIN_NAME, denylist_file);
219 TSError("[%s] All sites will be allowed", PLUGIN_NAME);
220 }
221
222 TSMutexUnlock(sites_mutex);
223 }
224
225 static int
denylist_plugin(TSCont contp,TSEvent event,void * edata)226 denylist_plugin(TSCont contp, TSEvent event, void *edata)
227 {
228 TSHttpTxn txnp;
229 cdata *cd;
230
231 switch (event) {
232 case TS_EVENT_HTTP_TXN_START:
233 txnp = (TSHttpTxn)edata;
234 handle_txn_start(contp, txnp);
235 return 0;
236 case TS_EVENT_HTTP_OS_DNS:
237 if (contp != global_contp) {
238 cd = (cdata *)TSContDataGet(contp);
239 cd->cf = HANDLE_DNS;
240 handle_dns(cd->txnp, contp);
241 return 0;
242 } else {
243 break;
244 }
245 case TS_EVENT_HTTP_TXN_CLOSE:
246 txnp = (TSHttpTxn)edata;
247 if (contp != global_contp) {
248 destroy_continuation(txnp, contp);
249 }
250 break;
251 case TS_EVENT_HTTP_SEND_RESPONSE_HDR:
252 if (contp != global_contp) {
253 cd = (cdata *)TSContDataGet(contp);
254 cd->cf = HANDLE_RESPONSE;
255 handle_response(cd->txnp, contp);
256 return 0;
257 } else {
258 break;
259 }
260 case TS_EVENT_TIMEOUT:
261 /* when mutex lock is not acquired and continuation is rescheduled,
262 the plugin is called back with TS_EVENT_TIMEOUT with a NULL
263 edata. We need to decide, in which function did the MutexLock
264 failed and call that function again */
265 if (contp != global_contp) {
266 cd = (cdata *)TSContDataGet(contp);
267 switch (cd->cf) {
268 case HANDLE_DNS:
269 handle_dns(cd->txnp, contp);
270 return 0;
271 case HANDLE_RESPONSE:
272 handle_response(cd->txnp, contp);
273 return 0;
274 default:
275 TSDebug(PLUGIN_NAME, "This event was unexpected: %d", event);
276 break;
277 }
278 } else {
279 read_denylist(contp);
280 return 0;
281 }
282 default:
283 break;
284 }
285 return 0;
286 }
287
288 static void
handle_txn_start(TSCont contp ATS_UNUSED,TSHttpTxn txnp)289 handle_txn_start(TSCont contp ATS_UNUSED, TSHttpTxn txnp)
290 {
291 TSCont txn_contp;
292 cdata *cd;
293
294 txn_contp = TSContCreate((TSEventFunc)denylist_plugin, TSMutexCreate());
295 /* create the data that'll be associated with the continuation */
296 cd = (cdata *)TSmalloc(sizeof(cdata));
297 TSContDataSet(txn_contp, cd);
298
299 cd->txnp = txnp;
300
301 TSHttpTxnHookAdd(txnp, TS_HTTP_OS_DNS_HOOK, txn_contp);
302 TSHttpTxnHookAdd(txnp, TS_HTTP_TXN_CLOSE_HOOK, txn_contp);
303
304 TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
305 }
306
307 void
TSPluginInit(int argc ATS_UNUSED,const char * argv[]ATS_UNUSED)308 TSPluginInit(int argc ATS_UNUSED, const char *argv[] ATS_UNUSED)
309 {
310 int i;
311 TSPluginRegistrationInfo info;
312 TSReturnCode error;
313
314 info.plugin_name = PLUGIN_NAME;
315 info.vendor_name = "Apache Software Foundation";
316 info.support_email = "dev@trafficserver.apache.org";
317
318 if (TSPluginRegister(&info) != TS_SUCCESS) {
319 TSError("[%s] Plugin registration failed", PLUGIN_NAME);
320 }
321
322 /* create an TSTextLogObject to log denied requests to */
323 error = TSTextLogObjectCreate("denylist", TS_LOG_MODE_ADD_TIMESTAMP, &log);
324 if (!log || error == TS_ERROR) {
325 TSDebug(PLUGIN_NAME, "error while creating log");
326 }
327
328 sites_mutex = TSMutexCreate();
329
330 nsites = 0;
331 for (i = 0; i < MAX_NSITES; i++) {
332 sites[i] = NULL;
333 }
334
335 global_contp = TSContCreate(denylist_plugin, sites_mutex);
336 read_denylist(global_contp);
337
338 /*TSHttpHookAdd (TS_HTTP_OS_DNS_HOOK, contp); */
339 TSHttpHookAdd(TS_HTTP_TXN_START_HOOK, global_contp);
340 }
341