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