]>
Commit | Line | Data |
---|---|---|
33cb80ed AF |
1 | /* This file is part of the IPFire Firewall. |
2 | * | |
3 | * This program is distributed under the terms of the GNU General Public | |
4 | * Licence. See the file COPYING for details. | |
5 | * | |
6 | */ | |
7 | ||
8 | #include <stdlib.h> | |
9 | #include <stdio.h> | |
10 | #include <string.h> | |
11 | #include <unistd.h> | |
12 | #include <sys/types.h> | |
24168c88 | 13 | #include <sys/stat.h> |
33cb80ed | 14 | #include <fcntl.h> |
24168c88 RR |
15 | #include <dirent.h> |
16 | #include <fnmatch.h> | |
17 | #include <errno.h> | |
33cb80ed AF |
18 | #include "setuid.h" |
19 | ||
20 | #define BUFFER_SIZE 1024 | |
21 | ||
24168c88 RR |
22 | const char *initd_path = "/etc/rc.d/init.d"; |
23 | const char *enabled_path = "/etc/rc.d/rc3.d"; | |
24 | const char *disabled_path = "/etc/rc.d/rc3.d/off"; | |
25 | ||
26 | const char *usage = | |
27 | "Usage\n" | |
28 | " addonctrl <addon> (start|stop|restart|reload|enable|disable|status|boot-status|list-services) [<service>]\n" | |
29 | "\n" | |
30 | "Options:\n" | |
31 | " <addon>\t\tName of the addon to control\n" | |
32 | " <service>\t\tSpecific service of the addon to control (optional)\n" | |
33 | " \t\t\tBy default the requested action is performed on all related services. See also 'list-services'.\n" | |
34 | " start\t\t\tStart service(s) of the addon\n" | |
35 | " stop\t\t\tStop service(s) of the addon\n" | |
36 | " restart\t\tRestart service(s) of the addon\n" | |
37 | " enable\t\tEnable service(s) of the addon to start at boot\n" | |
38 | " disable\t\tDisable service(s) of the addon to start at boot\n" | |
39 | " status\t\tDisplay current state of addon service(s)\n" | |
40 | " boot-status\t\tDisplay wether service(s) is enabled on boot or not\n" | |
41 | " list-services\t\tDisplay a list of services related to the addon"; | |
42 | ||
43 | // Find a file <path> using <filepattern> as glob pattern. | |
44 | // Returns the found filename or NULL if not found | |
45 | char *find_file_in_dir(const char *path, const char *filepattern) | |
46 | { | |
47 | struct dirent *entry; | |
48 | DIR *dp; | |
49 | char *found = NULL; | |
50 | ||
51 | dp = opendir(path); | |
52 | if (dp) { | |
53 | entry = readdir(dp); | |
54 | while(!found && entry) { | |
55 | if (fnmatch(filepattern, entry->d_name, FNM_PATHNAME) == 0) | |
56 | found = strdup(entry->d_name); | |
57 | else | |
58 | entry = readdir(dp); | |
59 | } | |
60 | ||
61 | closedir(dp); | |
62 | } | |
63 | ||
64 | return found; | |
65 | } | |
66 | ||
67 | // Reads Services metadata for <addon>. | |
68 | // Returns pointer to array of strings containing the services for <addon>, | |
69 | // sets <servicescnt> to the number of found services and | |
70 | // sets <returncode> to | |
71 | // -1 - system error occured, check errno | |
72 | // 0 - success - if returned array is NULL, there are no services for <addon> | |
73 | // 1 - addon was not found | |
74 | char **get_addon_services(const char *addon, int *servicescnt, const char *filter, int *returncode) { | |
75 | const char *metafile_prefix = "/opt/pakfire/db/installed/meta-"; | |
76 | const char *metadata_key = "Services"; | |
77 | const char *keyvalue_delim = ":"; | |
78 | const char *service_delim = " "; | |
79 | char *token; | |
80 | char **services = NULL; | |
81 | char *service; | |
82 | char *line = NULL; | |
83 | size_t line_len = 0; | |
84 | int i = 0; | |
85 | char *metafile = NULL; | |
86 | ||
87 | *returncode = 0; | |
88 | ||
89 | if (!addon) { | |
90 | errno = EINVAL; | |
91 | *returncode = 1; | |
92 | return NULL; | |
93 | } | |
94 | ||
95 | *returncode = asprintf(&metafile, "%s%s", metafile_prefix, addon); | |
96 | if (*returncode == -1) | |
97 | return NULL; | |
98 | ||
99 | FILE *fp = fopen(metafile,"r"); | |
100 | if (!fp) { | |
101 | if (errno == ENOENT) { | |
102 | *returncode = 1; | |
103 | } else { | |
104 | *returncode = -1; | |
105 | } | |
106 | return NULL; | |
107 | } | |
108 | ||
109 | // Get initscript(s) for addon from meta-file | |
110 | while (getline(&line, &line_len, fp) != -1 && !services) { | |
111 | // Strip newline | |
112 | char *newline = strchr(line, '\n'); | |
113 | if (newline) *newline = 0; | |
114 | ||
115 | // Split line in key and values; Check for required key. | |
116 | token = strtok(line, keyvalue_delim); | |
117 | if (!token || strcmp(token, metadata_key) != 0) | |
118 | continue; | |
119 | ||
120 | // Get values for matched key. Stop if no values are present. | |
121 | token = strtok(NULL, keyvalue_delim); | |
122 | if (!token) | |
123 | break; | |
124 | ||
125 | // Split values and put each service in services array | |
126 | service = strtok(token, service_delim); | |
127 | while (service) { | |
128 | if (!filter || strcmp(filter, service) == 0) { | |
129 | services = reallocarray(services, i+1 ,sizeof (char *)); | |
130 | if (!services) { | |
131 | *returncode = -1; | |
132 | break; | |
133 | } | |
134 | ||
135 | services[i] = strdup(service); | |
136 | if (!services[i++]) { | |
137 | *returncode = -1; | |
138 | break; | |
139 | } | |
140 | } | |
141 | ||
142 | service = strtok(NULL, service_delim); | |
143 | } | |
144 | } | |
145 | ||
146 | if (line) free(line); | |
147 | fclose(fp); | |
148 | free(metafile); | |
149 | ||
150 | *servicescnt = i; | |
151 | ||
152 | return services; | |
153 | } | |
154 | ||
155 | // Calls initscript <service> with parameter <action> | |
156 | int initscript_action(const char *service, const char *action) { | |
157 | char *initscript = NULL; | |
158 | char *argv[] = { | |
159 | action, | |
160 | NULL | |
161 | }; | |
162 | int r = 0; | |
163 | ||
164 | r = asprintf(&initscript, "%s/%s", initd_path, service); | |
165 | if (r != -1) | |
166 | r = run(initscript, argv); | |
167 | ||
168 | if (initscript) free(initscript); | |
169 | ||
170 | return r; | |
171 | } | |
172 | ||
173 | // Move an initscript with filepattern from <src_path> to <dest_path> | |
174 | // Returns: | |
175 | // -1: Error during move or memory allocation. Details in errno | |
176 | // 0: Success | |
177 | // 1: file was not moved, but is already in <dest_path> | |
178 | // 2: file does not exist in either in <src_path> or <dest_path> | |
179 | int move_initscript_by_pattern(const char *src_path, const char *dest_path, const char *filepattern) { | |
180 | char *src = NULL; | |
181 | char *dest = NULL; | |
182 | int r = 2; | |
183 | char *filename = NULL; | |
184 | ||
185 | filename = find_file_in_dir(src_path, filepattern); | |
186 | if (filename) { | |
187 | // Move file | |
188 | r = asprintf(&src, "%s/%s", src_path, filename); | |
189 | if (r != -1) { | |
190 | r = asprintf(&dest, "%s/%s", dest_path, filename); | |
191 | if (r != -1) | |
192 | r = rename(src, dest); | |
193 | } | |
194 | ||
195 | if (src) free(src); | |
196 | if (dest) free(dest); | |
197 | } else { | |
198 | // check if file is already in dest | |
199 | filename = find_file_in_dir(dest_path, filepattern); | |
200 | if (filename) | |
201 | r = 1; | |
202 | } | |
203 | ||
204 | if (filename) free(filename); | |
205 | ||
206 | return r; | |
207 | } | |
208 | ||
209 | // Enable/Disable addon service(s) by moving initscript symlink from/to disabled_path | |
210 | // Returns: | |
211 | // -1 - System error occured. Check errno. | |
212 | // 0 - Success | |
213 | // 1 - Service was already enabled/disabled | |
214 | // 2 - Service has no valid runlevel symlink | |
215 | int toggle_service(const char *service, const char *action) { | |
216 | const char *src_path, *dest_path; | |
217 | char *filepattern = NULL; | |
218 | int r = 0; | |
219 | ||
220 | if (asprintf(&filepattern, "S??%s", service) == -1) | |
221 | return -1; | |
222 | ||
223 | if (strcmp(action, "enable") == 0) { | |
224 | src_path = disabled_path; | |
225 | dest_path = enabled_path; | |
226 | } else { | |
227 | src_path = enabled_path; | |
228 | dest_path = disabled_path; | |
229 | } | |
230 | ||
231 | // Ensure disabled_path exists | |
232 | r = mkdir(disabled_path, S_IRWXU + S_IRGRP + S_IXGRP + S_IROTH + S_IXOTH); | |
233 | if (r != -1 || errno == EEXIST) | |
234 | r = move_initscript_by_pattern(src_path, dest_path, filepattern); | |
235 | ||
236 | free(filepattern); | |
237 | ||
238 | return r; | |
239 | } | |
240 | ||
241 | // Return whether <service> is enabled or disabled on boot | |
242 | // Returns: | |
243 | // -1 - System error occured. Check errno. | |
244 | // 0 - <service> is disabled on boot | |
245 | // 1 - <service> is enabled on boot | |
246 | // 2 - Runlevel suymlink for <service> was not found | |
247 | int get_boot_status(char *service) { | |
248 | char *filepattern = NULL; | |
249 | char *filename = NULL; | |
250 | int r = 2; | |
251 | ||
252 | if (asprintf(&filepattern, "S??%s", service) == -1) | |
253 | return -1; | |
254 | ||
255 | filename = find_file_in_dir(enabled_path, filepattern); | |
256 | if (filename) | |
257 | r = 1; | |
258 | else { | |
259 | filename = find_file_in_dir(disabled_path, filepattern); | |
260 | if (filename) | |
261 | r = 0; | |
262 | else | |
263 | r = 2; | |
264 | } | |
265 | ||
266 | if (filename) free(filename); | |
267 | free(filepattern); | |
268 | ||
269 | return r; | |
270 | } | |
271 | ||
33cb80ed | 272 | int main(int argc, char *argv[]) { |
24168c88 RR |
273 | char **services = NULL; |
274 | int servicescnt = 0; | |
275 | char *addon = argv[1]; | |
276 | char *action = argv[2]; | |
277 | char *service_filter = NULL; | |
278 | int r = 0; | |
279 | ||
280 | if (!(initsetuid())) | |
281 | exit(1); | |
282 | ||
283 | if (argc < 3) { | |
284 | fprintf(stderr, "\nMissing arguments.\n\n%s\n\n", usage); | |
285 | exit(1); | |
286 | } | |
287 | ||
288 | // Ignore filter when list of services is requested | |
289 | if (argc == 4 && strcmp(action, "list-services") != 0) | |
290 | service_filter = argv[3]; | |
291 | ||
292 | if (strlen(addon) > 32) { | |
293 | fprintf(stderr, "\nString too large.\n\n%s\n\n", usage); | |
294 | exit(1); | |
295 | } | |
296 | ||
297 | // Check if the input argument is valid | |
298 | if (!is_valid_argument_alnum(addon)) { | |
299 | fprintf(stderr, "Invalid add-on name: %s.\n", addon); | |
300 | exit(2); | |
301 | } | |
302 | ||
303 | // Get initscript name(s) from addon metadata | |
304 | int rc = 0; | |
305 | services = get_addon_services(addon, &servicescnt, service_filter, &rc); | |
306 | if (!services) { | |
307 | switch (rc) { | |
308 | case -1: | |
309 | fprintf(stderr, "\nSystem error occured. (Error: %m)\n\n"); | |
310 | break; | |
311 | ||
312 | case 0: | |
313 | if (service_filter) | |
314 | fprintf(stderr, "\nNo service '%s' found for addon '%s'. Use 'list-services' to get a list of available services\n\n%s\n\n", service_filter, addon, usage); | |
315 | else | |
316 | fprintf(stderr, "\nAddon '%s' has no services.\n\n", addon); | |
317 | break; | |
318 | ||
319 | case 1: | |
320 | fprintf(stderr, "\nAddon '%s' not found.\n\n%s\n\n", addon, usage); | |
321 | break; | |
322 | } | |
323 | exit(1); | |
324 | } | |
325 | ||
326 | // Handle requested action | |
327 | if (strcmp(action, "start") == 0 || | |
328 | strcmp(action, "stop") == 0 || | |
329 | strcmp(action, "restart") == 0 || | |
330 | strcmp(action, "reload") == 0 || | |
331 | strcmp(action, "status") == 0) { | |
332 | ||
333 | for(int i = 0; i < servicescnt; i++) { | |
334 | if (initscript_action(services[i], action) < 0) { | |
335 | r = 1; | |
336 | fprintf(stderr, "\nSystem error occured. (Error: %m)\n\n"); | |
337 | break; | |
338 | } | |
339 | } | |
340 | ||
341 | } else if (strcmp(action, "enable") == 0 || | |
342 | strcmp(action, "disable") == 0) { | |
343 | ||
344 | for(int i = 0; i < servicescnt; i++) { | |
345 | switch (r = toggle_service(services[i], action)) { | |
346 | case -1: | |
347 | fprintf(stderr, "\nSystem error occured. (Error: %m)\n\n"); | |
348 | break; | |
349 | ||
350 | case 0: | |
351 | printf("%sd service %s\n", action, services[i]); | |
352 | break; | |
353 | ||
354 | case 1: | |
355 | fprintf(stderr, "Service %s is already %sd. Skipping...\n", services[i], action); | |
356 | break; | |
357 | ||
358 | case 2: | |
359 | fprintf(stderr, "\nUnable to %s service %s. (Service has no valid runlevel symlink).\n\n", action, services[i]); | |
360 | break; | |
361 | } | |
362 | ||
363 | // Break from for loop in case of a system error. | |
364 | if (r == -1) { | |
365 | r = 1; | |
366 | break; | |
367 | } | |
368 | } | |
369 | ||
370 | } else if (strcmp(action, "boot-status") == 0) { | |
371 | // Print boot status for each service | |
372 | for(int i = 0; i < servicescnt; i++) { | |
373 | switch (get_boot_status(services[i])) { | |
374 | case -1: | |
375 | r = 1; | |
376 | fprintf(stderr, "\nSystem error occured. (Error: %m)\n\n"); | |
377 | break; | |
378 | ||
379 | case 0: | |
380 | printf("%s is disabled on boot.\n", services[i]); | |
381 | break; | |
382 | ||
383 | case 1: | |
384 | printf("%s is enabled on boot.\n", services[i]); | |
385 | break; | |
386 | ||
387 | case 2: | |
388 | printf("%s is not available for boot. (Service has no valid symlink in either %s or %s).\n", services[i], enabled_path, disabled_path); | |
389 | break; | |
390 | } | |
391 | ||
392 | // Break from for loop in case of an error | |
393 | if (r == 1) { | |
394 | break; | |
395 | } | |
396 | } | |
397 | ||
398 | } else if (strcmp(action, "list-services") == 0) { | |
399 | // List all services for addon | |
400 | printf("\nServices for addon %s:\n", addon); | |
401 | for(int i = 0; i < servicescnt; i++) { | |
402 | printf(" %s\n", services[i]); | |
403 | } | |
404 | printf("\n"); | |
405 | ||
406 | } else { | |
407 | fprintf(stderr, "\nBad argument given.\n\n%s\n\n", usage); | |
408 | r = 1; | |
409 | } | |
410 | ||
411 | // Cleanup | |
412 | for(int i = 0; i < servicescnt; i++) | |
413 | free(services[i]); | |
414 | free(services); | |
415 | ||
416 | return r; | |
33cb80ed | 417 | } |