1 /** @file
2
3 Plugin init
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 <cstdio>
25 #include "tscore/ink_platform.h"
26 #include "tscore/ink_file.h"
27 #include "tscore/ParseRules.h"
28 #include "records/I_RecCore.h"
29 #include "tscore/I_Layout.h"
30 #include "InkAPIInternal.h"
31 #include "Plugin.h"
32 #include "tscore/ink_cap.h"
33 #include "tscore/Filenames.h"
34
35 #define MAX_PLUGIN_ARGS 64
36
37 static PluginDynamicReloadMode plugin_dynamic_reload_mode = PluginDynamicReloadMode::RELOAD_ON;
38
39 bool
isPluginDynamicReloadEnabled()40 isPluginDynamicReloadEnabled()
41 {
42 return PluginDynamicReloadMode::RELOAD_ON == plugin_dynamic_reload_mode;
43 }
44
45 void
parsePluginDynamicReloadConfig()46 parsePluginDynamicReloadConfig()
47 {
48 int int_plugin_dynamic_reload_mode;
49
50 REC_ReadConfigInteger(int_plugin_dynamic_reload_mode, "proxy.config.plugin.dynamic_reload_mode");
51 plugin_dynamic_reload_mode = static_cast<PluginDynamicReloadMode>(int_plugin_dynamic_reload_mode);
52
53 if (plugin_dynamic_reload_mode < 0 || plugin_dynamic_reload_mode >= PluginDynamicReloadMode::RELOAD_COUNT) {
54 Warning("proxy.config.plugin.dynamic_reload_mode out of range. using default value.");
55 plugin_dynamic_reload_mode = PluginDynamicReloadMode::RELOAD_ON;
56 }
57 Note("Initialized plugin_dynamic_reload_mode: %d", plugin_dynamic_reload_mode);
58 }
59
60 void
parsePluginConfig()61 parsePluginConfig()
62 {
63 parsePluginDynamicReloadConfig();
64 }
65
66 static const char *plugin_dir = ".";
67
68 using init_func_t = void (*)(int, char **);
69
70 // Plugin registration vars
71 //
72 // plugin_reg_list has an entry for each plugin
73 // we've successfully been able to load
74 // plugin_reg_current is used to associate the
75 // plugin we're in the process of loading with
76 // it struct. We need this global pointer since
77 // the API doesn't have any plugin context. Init
78 // is single threaded so we can get away with the
79 // global pointer
80 //
81 DLL<PluginRegInfo> plugin_reg_list;
82 PluginRegInfo *plugin_reg_current = nullptr;
83
84 PluginRegInfo::PluginRegInfo() = default;
85
~PluginRegInfo()86 PluginRegInfo::~PluginRegInfo()
87 {
88 // We don't support unloading plugins once they are successfully loaded, so assert
89 // that we don't accidentally attempt this.
90 ink_release_assert(this->plugin_registered == false);
91 ink_release_assert(this->link.prev == nullptr);
92
93 ats_free(this->plugin_path);
94 ats_free(this->plugin_name);
95 ats_free(this->vendor_name);
96 ats_free(this->support_email);
97 if (dlh) {
98 dlclose(dlh);
99 }
100 }
101
102 bool
plugin_dso_load(const char * path,void * & handle,void * & init,std::string & error)103 plugin_dso_load(const char *path, void *&handle, void *&init, std::string &error)
104 {
105 handle = dlopen(path, RTLD_NOW);
106 init = nullptr;
107 if (!handle) {
108 error.assign("unable to load '").append(path).append("': ").append(dlerror());
109 Error("%s", error.c_str());
110 return false;
111 }
112
113 init = dlsym(handle, "TSPluginInit");
114 if (!init) {
115 error.assign("unable to find TSPluginInit function in '").append(path).append("': ").append(dlerror());
116 Error("%s", error.c_str());
117 return false;
118 }
119
120 return true;
121 }
122
123 static bool
single_plugin_init(int argc,char * argv[],bool validateOnly)124 single_plugin_init(int argc, char *argv[], bool validateOnly)
125 {
126 char path[PATH_NAME_MAX];
127 init_func_t init;
128
129 if (argc < 1) {
130 return true;
131 }
132 ink_filepath_make(path, sizeof(path), plugin_dir, argv[0]);
133
134 Note("loading plugin '%s'", path);
135
136 for (PluginRegInfo *plugin_reg_temp = plugin_reg_list.head; plugin_reg_temp != nullptr;
137 plugin_reg_temp = (plugin_reg_temp->link).next) {
138 if (strcmp(plugin_reg_temp->plugin_path, path) == 0) {
139 Warning("multiple loading of plugin %s", path);
140 break;
141 }
142 }
143
144 // elevate the access to read files as root if compiled with capabilities, if not
145 // change the effective user to root
146 {
147 uint32_t elevate_access = 0;
148 REC_ReadConfigInteger(elevate_access, "proxy.config.plugin.load_elevated");
149 ElevateAccess access(elevate_access ? ElevateAccess::FILE_PRIVILEGE : 0);
150
151 void *handle, *initptr = nullptr;
152 std::string error;
153 bool loaded = plugin_dso_load(path, handle, initptr, error);
154 init = reinterpret_cast<init_func_t>(initptr);
155
156 if (!loaded) {
157 if (validateOnly) {
158 return false;
159 }
160 Fatal("%s", error.c_str());
161 return false; // this line won't get called since Fatal brings down ATS
162 }
163
164 // Allocate a new registration structure for the
165 // plugin we're starting up
166 ink_assert(plugin_reg_current == nullptr);
167 plugin_reg_current = new PluginRegInfo;
168 plugin_reg_current->plugin_path = ats_strdup(path);
169 plugin_reg_current->dlh = handle;
170
171 #if (!defined(kfreebsd) && defined(freebsd)) || defined(darwin)
172 optreset = 1;
173 #endif
174 #if defined(__GLIBC__)
175 optind = 0;
176 #else
177 optind = 1;
178 #endif
179 opterr = 0;
180 optarg = nullptr;
181 init(argc, argv);
182 } // done elevating access
183
184 if (plugin_reg_current->plugin_registered) {
185 plugin_reg_list.push(plugin_reg_current);
186 } else {
187 Fatal("plugin not registered by calling TSPluginRegister");
188 return false; // this line won't get called since Fatal brings down ATS
189 }
190
191 plugin_reg_current = nullptr;
192
193 return true;
194 }
195
196 static char *
plugin_expand(char * arg)197 plugin_expand(char *arg)
198 {
199 RecDataT data_type;
200 char *str = nullptr;
201
202 if (*arg != '$') {
203 return (char *)nullptr;
204 }
205 // skip the $ character
206 arg += 1;
207
208 if (RecGetRecordDataType(arg, &data_type) != REC_ERR_OKAY) {
209 goto not_found;
210 }
211
212 switch (data_type) {
213 case RECD_STRING: {
214 RecString str_val;
215 if (RecGetRecordString_Xmalloc(arg, &str_val) != REC_ERR_OKAY) {
216 goto not_found;
217 }
218 return static_cast<char *>(str_val);
219 break;
220 }
221 case RECD_FLOAT: {
222 RecFloat float_val;
223 if (RecGetRecordFloat(arg, &float_val) != REC_ERR_OKAY) {
224 goto not_found;
225 }
226 str = static_cast<char *>(ats_malloc(128));
227 snprintf(str, 128, "%f", static_cast<float>(float_val));
228 return str;
229 break;
230 }
231 case RECD_INT: {
232 RecInt int_val;
233 if (RecGetRecordInt(arg, &int_val) != REC_ERR_OKAY) {
234 goto not_found;
235 }
236 str = static_cast<char *>(ats_malloc(128));
237 snprintf(str, 128, "%ld", static_cast<long int>(int_val));
238 return str;
239 break;
240 }
241 case RECD_COUNTER: {
242 RecCounter count_val;
243 if (RecGetRecordCounter(arg, &count_val) != REC_ERR_OKAY) {
244 goto not_found;
245 }
246 str = static_cast<char *>(ats_malloc(128));
247 snprintf(str, 128, "%ld", static_cast<long int>(count_val));
248 return str;
249 break;
250 }
251 default:
252 goto not_found;
253 break;
254 }
255
256 not_found:
257 Warning("%s: unable to find parameter %s", ts::filename::PLUGIN, arg);
258 return nullptr;
259 }
260
261 bool
plugin_init(bool validateOnly)262 plugin_init(bool validateOnly)
263 {
264 ats_scoped_str path;
265 char line[1024], *p;
266 char *argv[MAX_PLUGIN_ARGS];
267 char *vars[MAX_PLUGIN_ARGS];
268 int argc;
269 int fd;
270 int i;
271 bool retVal = true;
272 static bool INIT_ONCE = true;
273
274 if (INIT_ONCE) {
275 api_init();
276 plugin_dir = ats_stringdup(RecConfigReadPluginDir());
277 INIT_ONCE = false;
278 }
279
280 Note("%s loading ...", ts::filename::PLUGIN);
281 path = RecConfigReadConfigPath(nullptr, ts::filename::PLUGIN);
282 fd = open(path, O_RDONLY);
283 if (fd < 0) {
284 Warning("%s failed to load: %d, %s", ts::filename::PLUGIN, errno, strerror(errno));
285 return false;
286 }
287
288 while (ink_file_fd_readline(fd, sizeof(line) - 1, line) > 0) {
289 argc = 0;
290 p = line;
291
292 // strip leading white space and test for comment or blank line
293 while (*p && ParseRules::is_wslfcr(*p)) {
294 ++p;
295 }
296 if ((*p == '\0') || (*p == '#')) {
297 continue;
298 }
299
300 // not comment or blank, so rip line into tokens
301 while (true) {
302 if (argc >= MAX_PLUGIN_ARGS) {
303 Warning("Exceeded max number of args (%d) for plugin: [%s]", MAX_PLUGIN_ARGS, argc > 0 ? argv[0] : "???");
304 break;
305 }
306
307 while (*p && ParseRules::is_wslfcr(*p)) {
308 ++p;
309 }
310 if ((*p == '\0') || (*p == '#')) {
311 break; // EOL
312 }
313
314 if (*p == '\"') {
315 p += 1;
316
317 argv[argc++] = p;
318
319 while (*p && (*p != '\"')) {
320 p += 1;
321 }
322 if (*p == '\0') {
323 break;
324 }
325 *p++ = '\0';
326 } else {
327 argv[argc++] = p;
328
329 while (*p && !ParseRules::is_wslfcr(*p) && (*p != '#')) {
330 p += 1;
331 }
332 if ((*p == '\0') || (*p == '#')) {
333 break;
334 }
335 *p++ = '\0';
336 }
337 }
338
339 for (i = 0; i < argc; i++) {
340 vars[i] = plugin_expand(argv[i]);
341 if (vars[i]) {
342 argv[i] = vars[i];
343 }
344 }
345
346 if (argc < MAX_PLUGIN_ARGS) {
347 argv[argc] = nullptr;
348 } else {
349 argv[MAX_PLUGIN_ARGS - 1] = nullptr;
350 }
351 retVal = single_plugin_init(argc, argv, validateOnly);
352
353 for (i = 0; i < argc; i++) {
354 ats_free(vars[i]);
355 }
356 }
357
358 close(fd);
359 if (retVal) {
360 Note("%s finished loading", ts::filename::PLUGIN);
361 } else {
362 Error("%s failed to load", ts::filename::PLUGIN);
363 }
364 return retVal;
365 }
366