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