1 /*
2   Licensed to the Apache Software Foundation (ASF) under one
3   or more contributor license agreements.  See the NOTICE file
4   distributed with this work for additional information
5   regarding copyright ownership.  The ASF licenses this file
6   to you under the Apache License, Version 2.0 (the
7   "License"); you may not use this file except in compliance
8   with the License.  You may obtain a copy of the License at
9 
10   http://www.apache.org/licenses/LICENSE-2.0
11 
12   Unless required by applicable law or agreed to in writing, software
13   distributed under the License is distributed on an "AS IS" BASIS,
14   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   See the License for the specific language governing permissions and
16   limitations under the License.
17 */
18 
19 #include <ts/ts.h>
20 #include <ts/remap.h>
21 #include <cstdio>
22 #include <unistd.h>
23 
24 #include <mysql/mysql.h>
25 
26 #include "lib/iniparser.h"
27 #include "default.h"
28 
29 MYSQL mysql;
30 
31 using my_data = struct {
32   char *query;
33 };
34 
35 bool
do_mysql_remap(TSCont contp,TSHttpTxn txnp)36 do_mysql_remap(TSCont contp, TSHttpTxn txnp)
37 {
38   TSMBuffer reqp;
39   TSMLoc hdr_loc, url_loc, field_loc;
40   bool ret_val = false;
41 
42   const char *request_host;
43   int request_host_length = 0;
44   const char *request_scheme;
45   int request_scheme_length = 0;
46   int request_port          = 80;
47   char *query;
48 
49   MYSQL_ROW row;
50   MYSQL_RES *res;
51 
52   my_data *data = static_cast<my_data *>(TSContDataGet(contp));
53   query         = data->query;
54 
55   if (TSHttpTxnClientReqGet(txnp, &reqp, &hdr_loc) != TS_SUCCESS) {
56     TSDebug(PLUGIN_NAME, "could not get request data");
57     return false;
58   }
59 
60   TSHttpHdrUrlGet(reqp, hdr_loc, &url_loc);
61 
62   if (!url_loc) {
63     TSDebug(PLUGIN_NAME, "couldn't retrieve request url");
64     goto release_hdr;
65   }
66 
67   field_loc = TSMimeHdrFieldFind(reqp, hdr_loc, TS_MIME_FIELD_HOST, TS_MIME_LEN_HOST);
68 
69   if (!field_loc) {
70     TSDebug(PLUGIN_NAME, "couldn't retrieve request HOST header");
71     goto release_url;
72   }
73 
74   request_host = TSMimeHdrFieldValueStringGet(reqp, hdr_loc, field_loc, -1, &request_host_length);
75   if (!request_host_length) {
76     TSDebug(PLUGIN_NAME, "couldn't find request HOST header");
77     goto release_field;
78   }
79 
80   request_scheme = TSUrlSchemeGet(reqp, url_loc, &request_scheme_length);
81   request_port   = TSUrlPortGet(reqp, url_loc);
82 
83   TSDebug(PLUGIN_NAME, "      +++++MYSQL REMAP+++++      ");
84 
85   TSDebug(PLUGIN_NAME, "\nINCOMING REQUEST ->\n ::: from_scheme_desc: %.*s\n ::: from_hostname: %.*s\n ::: from_port: %d",
86           request_scheme_length, request_scheme, request_host_length, request_host, request_port);
87 
88   snprintf(query, QSIZE, " \
89     SELECT \
90         t_scheme.scheme_desc, \
91         t_host.hostname, \
92         to_port \
93       FROM map \
94         INNER JOIN scheme as t_scheme ON (map.to_scheme_id = t_scheme.id) \
95         INNER JOIN scheme as f_scheme ON (map.from_scheme_id = f_scheme.id) \
96         INNER JOIN hostname as t_host ON (map.to_hostname_id = t_host.id) \
97         INNER JOIN hostname as f_host ON (map.from_hostname_id = f_host.id) \
98       WHERE \
99         is_enabled=1 \
100         AND f_host.hostname = '%.*s' \
101         AND f_scheme.id = %d \
102         AND from_port = %d \
103       LIMIT 1",
104            request_host_length, request_host, (strcmp(request_scheme, "https") == 0) ? 2 : 1, request_port);
105 
106   mysql_real_query(&mysql, query, (unsigned int)strlen(query));
107   res = mysql_use_result(&mysql);
108 
109   if (!res)
110     goto not_found; // TODO: define a fallback
111 
112   do {
113     row = mysql_fetch_row(res);
114     if (!row)
115       goto not_found;
116     TSDebug(PLUGIN_NAME, "\nOUTGOING REQUEST ->\n ::: to_scheme_desc: %s\n ::: to_hostname: %s\n ::: to_port: %s", row[0], row[1],
117             row[2]);
118     TSMimeHdrFieldValueStringSet(reqp, hdr_loc, field_loc, 0, row[1], -1);
119     TSUrlHostSet(reqp, url_loc, row[1], -1);
120     TSUrlSchemeSet(reqp, url_loc, row[0], -1);
121     TSUrlPortSet(reqp, url_loc, atoi(row[2]));
122   } while (false);
123 
124   ret_val = true;
125 
126 not_found:
127   if (!ret_val) {
128     // lets build up a nice 404 message for someone
129     TSHttpHdrStatusSet(reqp, hdr_loc, TS_HTTP_STATUS_NOT_FOUND);
130     TSHttpTxnStatusSet(txnp, TS_HTTP_STATUS_NOT_FOUND);
131   }
132   if (res) {
133     mysql_free_result(res);
134   }
135 release_field:
136   if (field_loc) {
137     TSHandleMLocRelease(reqp, hdr_loc, field_loc);
138   }
139 release_url:
140   if (url_loc) {
141     TSHandleMLocRelease(reqp, hdr_loc, url_loc);
142   }
143 release_hdr:
144   if (hdr_loc) {
145     TSHandleMLocRelease(reqp, TS_NULL_MLOC, hdr_loc);
146   }
147 
148   return ret_val;
149 }
150 
151 static int
mysql_remap(TSCont contp,TSEvent event,void * edata)152 mysql_remap(TSCont contp, TSEvent event, void *edata)
153 {
154   TSHttpTxn txnp   = static_cast<TSHttpTxn>(edata);
155   TSEvent reenable = TS_EVENT_HTTP_CONTINUE;
156 
157   switch (event) {
158   case TS_EVENT_HTTP_READ_REQUEST_HDR:
159     TSDebug(PLUGIN_NAME, "Reading Request");
160     TSSkipRemappingSet(txnp, 1);
161     if (!do_mysql_remap(contp, txnp)) {
162       reenable = TS_EVENT_HTTP_ERROR;
163     }
164     break;
165   default:
166     break;
167   }
168 
169   TSHttpTxnReenable(txnp, reenable);
170   return 1;
171 }
172 
173 void
TSPluginInit(int argc,const char * argv[])174 TSPluginInit(int argc, const char *argv[])
175 {
176   dictionary *ini;
177   const char *host;
178   int port;
179   const char *username;
180   const char *password;
181   const char *db;
182 
183   my_data *data = static_cast<my_data *>(malloc(1 * sizeof(my_data)));
184 
185   TSPluginRegistrationInfo info;
186   bool reconnect = true;
187 
188   info.plugin_name   = const_cast<char *>(PLUGIN_NAME);
189   info.vendor_name   = const_cast<char *>("Apache Software Foundation");
190   info.support_email = const_cast<char *>("dev@trafficserver.apache.org");
191 
192   if (TSPluginRegister(&info) != TS_SUCCESS) {
193     TSError("[mysql_remap] Plugin registration failed");
194   }
195 
196   if (argc != 2) {
197     TSError("[mysql_remap] Usage: %s /path/to/sample.ini", argv[0]);
198     return;
199   }
200 
201   ini = iniparser_load(argv[1]);
202   if (!ini) {
203     TSError("[mysql_remap] Error with ini file (1)");
204     TSDebug(PLUGIN_NAME, "Error parsing ini file(1)");
205     return;
206   }
207 
208   host     = iniparser_getstring(ini, "mysql_remap:mysql_host", (char *)"localhost");
209   port     = iniparser_getint(ini, "mysql_remap:mysql_port", 3306);
210   username = iniparser_getstring(ini, "mysql_remap:mysql_username", nullptr);
211   password = iniparser_getstring(ini, "mysql_remap:mysql_password", nullptr);
212   db       = iniparser_getstring(ini, "mysql_remap:mysql_database", (char *)"mysql_remap");
213 
214   if (mysql_library_init(0, NULL, NULL)) {
215     TSError("[mysql_remap] Error initializing mysql client library");
216     TSDebug(PLUGIN_NAME, "Error initializing mysql client library");
217     return;
218   }
219 
220   if (!mysql_init(&mysql)) {
221     TSError("[mysql_remap] Could not initialize MySQL");
222     TSDebug(PLUGIN_NAME, "Could not initialize MySQL");
223     return;
224   }
225 
226   mysql_options(&mysql, MYSQL_OPT_RECONNECT, &reconnect);
227 
228   if (!mysql_real_connect(&mysql, host, username, password, db, port, NULL, 0)) {
229     TSError("[mysql_remap] Could not connect to mysql");
230     TSDebug(PLUGIN_NAME, "Could not connect to mysql: %s", mysql_error(&mysql));
231     return;
232   }
233 
234   data->query = static_cast<char *>(TSmalloc(QSIZE * sizeof(char))); // TODO: malloc smarter sizes
235 
236   TSDebug(PLUGIN_NAME, "h: %s; u: %s; p: %s; p:%d; d:%s", host, username, password, port, db);
237   TSCont cont = TSContCreate(mysql_remap, TSMutexCreate());
238 
239   TSHttpHookAdd(TS_HTTP_READ_REQUEST_HDR_HOOK, cont);
240 
241   TSContDataSet(cont, (void *)data);
242 
243   TSDebug(PLUGIN_NAME, "plugin is successfully initialized [plugin mode]");
244   iniparser_freedict(ini);
245   return;
246 }
247