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 "tscore/ink_defs.h"
22 
23 #include <cstdio>
24 #include <cstring>
25 #include <cctype>
26 #include <cstdlib>
27 #include <string>
28 
29 static const char PLUGIN_NAME[] = "conf_remap";
30 
31 // This makes the plugin depend on the version of traffic server installed, but that's
32 // OK, since this plugin is distributed only with the "core" (it's a core piece).
33 #define MAX_OVERRIDABLE_CONFIGS TS_CONFIG_LAST_ENTRY
34 
35 // Class to hold a set of configurations (one for each remap rule instance)
36 struct RemapConfigs {
37   struct Item {
38     TSOverridableConfigKey _name;
39     TSRecordDataType _type;
40     TSRecordData _data;
41     int _data_len; // Used when data is a string
42   };
43 
RemapConfigsRemapConfigs44   RemapConfigs() { memset(_items, 0, sizeof(_items)); };
45   bool parse_file(const char *filename);
46   bool parse_inline(const char *arg);
47 
48   Item _items[MAX_OVERRIDABLE_CONFIGS];
49   int _current = 0;
50 };
51 
52 // Helper function for the parser
53 inline TSRecordDataType
str_to_datatype(const char * str)54 str_to_datatype(const char *str)
55 {
56   TSRecordDataType type = TS_RECORDDATATYPE_NULL;
57 
58   if (!str || !*str) {
59     return TS_RECORDDATATYPE_NULL;
60   }
61 
62   if (!strcmp(str, "INT")) {
63     type = TS_RECORDDATATYPE_INT;
64   } else if (!strcmp(str, "STRING")) {
65     type = TS_RECORDDATATYPE_STRING;
66   } else if (!strcmp(str, "FLOAT")) {
67     type = TS_RECORDDATATYPE_FLOAT;
68   }
69 
70   return type;
71 }
72 
73 // Parse an inline key=value config pair.
74 bool
parse_inline(const char * arg)75 RemapConfigs::parse_inline(const char *arg)
76 {
77   const char *sep;
78   std::string key;
79   std::string value;
80 
81   TSOverridableConfigKey name;
82   TSRecordDataType type;
83 
84   // Each token should be a configuration variable then a value, separated by '='.
85   sep = strchr(arg, '=');
86   if (sep == nullptr) {
87     return false;
88   }
89 
90   key   = std::string(arg, std::distance(arg, sep));
91   value = std::string(sep + 1, std::distance(sep + 1, arg + strlen(arg)));
92 
93   if (TSHttpTxnConfigFind(key.c_str(), -1 /* len */, &name, &type) != TS_SUCCESS) {
94     TSWarning("[%s] Invalid configuration variable '%s'", PLUGIN_NAME, key.c_str());
95     return true;
96   }
97 
98   switch (type) {
99   case TS_RECORDDATATYPE_INT:
100     _items[_current]._data.rec_int = strtoll(value.c_str(), nullptr, 10);
101     break;
102   case TS_RECORDDATATYPE_STRING:
103     if (strcmp(value.c_str(), "NULL") == 0) {
104       _items[_current]._data.rec_string = nullptr;
105       _items[_current]._data_len        = 0;
106     } else {
107       _items[_current]._data.rec_string = TSstrdup(value.c_str());
108       _items[_current]._data_len        = value.size();
109     }
110     break;
111   case TS_RECORDDATATYPE_FLOAT:
112     _items[_current]._data.rec_float = strtof(value.c_str(), nullptr);
113     break;
114   default:
115     TSError("[%s] Configuration variable '%s' is of an unsupported type", PLUGIN_NAME, key.c_str());
116     return false;
117   }
118 
119   _items[_current]._name = name;
120   _items[_current]._type = type;
121   ++_current;
122   return true;
123 }
124 
125 // Config file parser, somewhat borrowed from P_RecCore.i
126 bool
parse_file(const char * filename)127 RemapConfigs::parse_file(const char *filename)
128 {
129   int line_num = 0;
130   TSFile file;
131   char buf[8192];
132   TSOverridableConfigKey name;
133   TSRecordDataType type, expected_type;
134 
135   std::string path;
136 
137   if (!filename || ('\0' == *filename)) {
138     return false;
139   }
140 
141   if (*filename == '/') {
142     // Absolute path, just use it.
143     path = filename;
144   } else {
145     // Relative path. Make it relative to the configuration directory.
146     path = TSConfigDirGet();
147     path += "/";
148     path += filename;
149   }
150 
151   if (nullptr == (file = TSfopen(path.c_str(), "r"))) {
152     TSError("[%s] Could not open config file %s", PLUGIN_NAME, path.c_str());
153     return false;
154   }
155 
156   TSDebug(PLUGIN_NAME, "loading configuration file %s", path.c_str());
157 
158   while (nullptr != TSfgets(file, buf, sizeof(buf))) {
159     char *ln, *tok;
160     char *s = buf;
161 
162     ++line_num; // First line is #1 ...
163     while (isspace(*s)) {
164       ++s;
165     }
166     tok = strtok_r(s, " \t", &ln);
167 
168     // check for blank lines and comments
169     if ((!tok) || (tok && ('#' == *tok))) {
170       continue;
171     }
172 
173     if (strncmp(tok, "CONFIG", 6)) {
174       TSError("[%s] File %s, line %d: non-CONFIG line encountered", PLUGIN_NAME, path.c_str(), line_num);
175       continue;
176     }
177 
178     // Find the configuration name
179     tok = strtok_r(nullptr, " \t", &ln);
180     if (TSHttpTxnConfigFind(tok, -1, &name, &expected_type) != TS_SUCCESS) {
181       TSError("[%s] File %s, line %d: %s is not a configuration variable or cannot be overridden", PLUGIN_NAME, path.c_str(),
182               line_num, tok);
183       continue;
184     }
185 
186     // Find the type (INT or STRING only)
187     tok = strtok_r(nullptr, " \t", &ln);
188     if (TS_RECORDDATATYPE_NULL == (type = str_to_datatype(tok))) {
189       TSError("[%s] file %s, line %d: only INT, STRING, and FLOAT types supported", PLUGIN_NAME, path.c_str(), line_num);
190       continue;
191     }
192 
193     if (type != expected_type) {
194       TSError("[%s] file %s, line %d: mismatch between provide data type, and expected type", PLUGIN_NAME, path.c_str(), line_num);
195       continue;
196     }
197 
198     // Find the value (which depends on the type above)
199     if (ln) {
200       while (isspace(*ln)) {
201         ++ln;
202       }
203       if ('\0' == *ln) {
204         tok = nullptr;
205       } else {
206         tok = ln;
207         while (*ln != '\0') {
208           ++ln;
209         }
210         --ln;
211         while (isspace(*ln) && (ln > tok)) {
212           --ln;
213         }
214         ++ln;
215         *ln = '\0';
216       }
217     } else {
218       tok = nullptr;
219     }
220     if (!tok) {
221       TSError("[%s] file %s, line %d: the configuration must provide a value", PLUGIN_NAME, path.c_str(), line_num);
222       continue;
223     }
224 
225     // Now store the new config
226     switch (type) {
227     case TS_RECORDDATATYPE_INT:
228       _items[_current]._data.rec_int = strtoll(tok, nullptr, 10);
229       break;
230     case TS_RECORDDATATYPE_STRING:
231       if (strcmp(tok, "NULL") == 0) {
232         _items[_current]._data.rec_string = nullptr;
233         _items[_current]._data_len        = 0;
234       } else {
235         _items[_current]._data.rec_string = TSstrdup(tok);
236         _items[_current]._data_len        = strlen(tok);
237       }
238       break;
239     case TS_RECORDDATATYPE_FLOAT:
240       _items[_current]._data.rec_float = strtof(tok, nullptr);
241       break;
242     default:
243       TSError("[%s] file %s, line %d: type not support (unheard of)", PLUGIN_NAME, path.c_str(), line_num);
244       continue;
245       break;
246     }
247     _items[_current]._name = name;
248     _items[_current]._type = type;
249     ++_current;
250   }
251 
252   TSfclose(file);
253   return (_current > 0);
254 }
255 
256 ///////////////////////////////////////////////////////////////////////////////
257 // Initialize the plugin as a remap plugin.
258 //
259 TSReturnCode
TSRemapInit(TSRemapInterface * api_info,char * errbuf,int errbuf_size)260 TSRemapInit(TSRemapInterface *api_info, char *errbuf, int errbuf_size)
261 {
262   if (!api_info) {
263     TSstrlcpy(errbuf, "[TSRemapInit] - Invalid TSRemapInterface argument", errbuf_size);
264     return TS_ERROR;
265   }
266 
267   if (api_info->size < sizeof(TSRemapInterface)) {
268     TSstrlcpy(errbuf, "[TSRemapInit] - Incorrect size of TSRemapInterface structure", errbuf_size);
269     return TS_ERROR;
270   }
271 
272   TSDebug(PLUGIN_NAME, "remap plugin is successfully initialized");
273   return TS_SUCCESS; /* success */
274 }
275 
276 TSReturnCode
TSRemapNewInstance(int argc,char * argv[],void ** ih,char *,int)277 TSRemapNewInstance(int argc, char *argv[], void **ih, char * /* errbuf ATS_UNUSED */, int /* errbuf_size ATS_UNUSED */)
278 {
279   if (argc < 3) {
280     TSError("[%s] Unable to create remap instance, need configuration file", PLUGIN_NAME);
281     return TS_ERROR;
282   }
283 
284   RemapConfigs *conf = new (RemapConfigs);
285   for (int i = 2; i < argc; ++i) {
286     if (strchr(argv[i], '=') != nullptr) {
287       // Parse as an inline key=value pair ...
288       if (!conf->parse_inline(argv[i])) {
289         goto fail;
290       }
291     } else {
292       // Parse as a config file ...
293       if (!conf->parse_file(argv[i])) {
294         goto fail;
295       }
296     }
297   }
298 
299   *ih = static_cast<void *>(conf);
300   return TS_SUCCESS;
301 
302 fail:
303   delete conf;
304   return TS_ERROR;
305 }
306 
307 void
TSRemapDeleteInstance(void * ih)308 TSRemapDeleteInstance(void *ih)
309 {
310   RemapConfigs *conf = static_cast<RemapConfigs *>(ih);
311 
312   for (int ix = 0; ix < conf->_current; ++ix) {
313     if (TS_RECORDDATATYPE_STRING == conf->_items[ix]._type) {
314       TSfree(conf->_items[ix]._data.rec_string);
315     }
316   }
317 
318   delete conf;
319 }
320 
321 ///////////////////////////////////////////////////////////////////////////////
322 // Main entry point when used as a remap plugin.
323 //
324 TSRemapStatus
TSRemapDoRemap(void * ih,TSHttpTxn rh,TSRemapRequestInfo *)325 TSRemapDoRemap(void *ih, TSHttpTxn rh, TSRemapRequestInfo * /* rri ATS_UNUSED */)
326 {
327   if (nullptr != ih) {
328     RemapConfigs *conf = static_cast<RemapConfigs *>(ih);
329     TSHttpTxn txnp     = static_cast<TSHttpTxn>(rh);
330 
331     for (int ix = 0; ix < conf->_current; ++ix) {
332       switch (conf->_items[ix]._type) {
333       case TS_RECORDDATATYPE_INT:
334         TSHttpTxnConfigIntSet(txnp, conf->_items[ix]._name, conf->_items[ix]._data.rec_int);
335         TSDebug(PLUGIN_NAME, "Setting config id %d to %" PRId64 "", conf->_items[ix]._name, conf->_items[ix]._data.rec_int);
336         break;
337       case TS_RECORDDATATYPE_STRING:
338         TSHttpTxnConfigStringSet(txnp, conf->_items[ix]._name, conf->_items[ix]._data.rec_string, conf->_items[ix]._data_len);
339         TSDebug(PLUGIN_NAME, "Setting config id %d to %s", conf->_items[ix]._name, conf->_items[ix]._data.rec_string);
340         break;
341       case TS_RECORDDATATYPE_FLOAT:
342         TSHttpTxnConfigFloatSet(txnp, conf->_items[ix]._name, conf->_items[ix]._data.rec_int);
343         TSDebug(PLUGIN_NAME, "Setting config id %d to %f", conf->_items[ix]._name, conf->_items[ix]._data.rec_float);
344         break;
345       default:
346         break; // Error ?
347       }
348     }
349   }
350 
351   return TSREMAP_NO_REMAP; // This plugin never rewrites anything.
352 }
353