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