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