| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * This file is part of the Falcon Player (FPP) and is Copyright (C) | ||
| 3 | * 2013-2022 by the Falcon Player Developers. | ||
| 4 | * | ||
| 5 | * The Falcon Player (FPP) is free software, and is covered under | ||
| 6 | * multiple Open Source licenses. Please see the included 'LICENSES' | ||
| 7 | * file for descriptions of what files are covered by each license. | ||
| 8 | * | ||
| 9 | * This source file is covered under the LGPL v2.1 as described in the | ||
| 10 | * included LICENSE.LGPL file. | ||
| 11 | */ | ||
| 12 | |||
| 13 | #include "fpp-pch.h" | ||
| 14 | |||
| 15 | #include <cstring> | ||
| 16 | #include <errno.h> | ||
| 17 | #include <memory> | ||
| 18 | #include <stdlib.h> | ||
| 19 | #include <string> | ||
| 20 | #include <unistd.h> | ||
| 21 | #include <vector> | ||
| 22 | |||
| 23 | #include "Warnings.h" | ||
| 24 | #include "common.h" | ||
| 25 | #include "log.h" | ||
| 26 | #include "settings.h" | ||
| 27 | #include "commands/Commands.h" | ||
| 28 | |||
| 29 | #include "scripts.h" | ||
| 30 | |||
| 31 | extern char** environ; | ||
| 32 | |||
| 33 | /* | ||
| 34 | * Fork and run a script with accompanying args | ||
| 35 | */ | ||
| 36 | 24 | pid_t RunScript(std::string script, std::string scriptArgs) { | |
| 37 | 24 | std::vector<std::pair<std::string, std::string>> envVars; | |
| 38 | |||
| 39 |
4/8✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 24 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 24 times.
✗ Branch 7 not taken.
|
48 | return RunScript(script, scriptArgs, envVars); |
| 40 | 24 | } | |
| 41 | |||
| 42 | // Refused a script path outside the scripts directory. Timed: it is raised | ||
| 43 | // per attempt, so it ages out once nothing asks for that path any more. | ||
| 44 | static constexpr int WARNING_ID_SCRIPT_OUTSIDE_DIR = 70; | ||
| 45 | |||
| 46 | 53 | pid_t RunScript(std::string script, std::string scriptArgs, std::vector<std::pair<std::string, std::string>> envVars) { | |
| 47 | 53 | pid_t pid = 0; | |
| 48 | char userScript[1024]; | ||
| 49 | char eventScript[1024]; | ||
| 50 | |||
| 51 |
1/2✓ Branch 0 taken 53 times.
✗ Branch 1 not taken.
|
53 | LogDebug(VB_COMMAND, "Script %s: Args: %s\n", script.c_str(), scriptArgs.c_str()); |
| 52 | |||
| 53 | // Only scripts in the scripts directory run. The name is appended to that | ||
| 54 | // directory, so a ".." component was the one way out of it -- and names | ||
| 55 | // arrive from dynamic playlist sources (a remote URL) and from commands, | ||
| 56 | // not just from the UI's script picker. Everything up to the first space | ||
| 57 | // is the path; the rest are arguments. | ||
| 58 |
1/2✓ Branch 0 taken 53 times.
✗ Branch 1 not taken.
|
53 | std::string scriptPath = script.substr(0, script.find(' ')); |
| 59 |
3/4✓ Branch 0 taken 53 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 53 times.
✓ Branch 3 taken 31 times.
|
84 | for (auto& part : split(scriptPath, '/')) { |
| 60 |
3/4✓ Branch 0 taken 53 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 22 times.
✓ Branch 3 taken 31 times.
|
53 | if (part == "..") { |
| 61 |
1/2✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
|
22 | LogErr(VB_COMMAND, "RunScript: refusing script outside the scripts directory: %s\n", script.c_str()); |
| 62 |
3/6✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 22 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
|
22 | WarningHolder::AddWarningTimeout(60, WARNING_ID_SCRIPT_OUTSIDE_DIR, "Refused to run a script outside the scripts directory: " + scriptPath); |
| 63 | 22 | return -1; | |
| 64 | } | ||
| 65 |
2/2✓ Branch 0 taken 31 times.
✓ Branch 1 taken 22 times.
|
53 | } |
| 66 | |||
| 67 | // Setup the script from our user | ||
| 68 |
3/6✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 31 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 31 times.
✗ Branch 5 not taken.
|
46 | int n = snprintf(userScript, sizeof(userScript), "%s%s", FPP_DIR_SCRIPT("/").c_str(), script.c_str()); |
| 69 |
2/4✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 31 times.
|
31 | if (n < 0 || n >= (int)sizeof(userScript)) { |
| 70 | // Truncated: running a prefix of the requested path is never right. | ||
| 71 | ✗ | LogErr(VB_COMMAND, "RunScript: script path too long: %s\n", script.c_str()); | |
| 72 | ✗ | return -1; | |
| 73 | } | ||
| 74 | |||
| 75 | // Setup the wrapper | ||
| 76 |
2/4✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 31 times.
✗ Branch 3 not taken.
|
31 | n = snprintf(eventScript, sizeof(eventScript), "%s/scripts/eventScript", FPP_DIR.c_str()); |
| 77 |
2/4✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 31 times.
|
31 | if (n < 0 || n >= (int)sizeof(eventScript)) { |
| 78 | ✗ | LogWarn(VB_COMMAND, "RunScript: eventScript path truncated\n"); | |
| 79 | ✗ | eventScript[sizeof(eventScript) - 1] = '\0'; | |
| 80 | } | ||
| 81 | |||
| 82 | // Everything the child needs is built here, before fork(). fppd is | ||
| 83 | // multithreaded, and after fork() the child may only make async-signal- | ||
| 84 | // safe calls: it used to read a setting (a lock), log (locks), strdup and | ||
| 85 | // setenv (malloc) and exit() (static destructors) -- if another thread | ||
| 86 | // held any of those locks at the fork, the child deadlocked before exec, | ||
| 87 | // and a blocking script entry waited on it forever. | ||
| 88 | 62 | std::vector<std::string> argStrs; | |
| 89 | { | ||
| 90 |
1/2✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
|
31 | std::string userScriptStr = userScript; |
| 91 |
2/4✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 31 times.
✗ Branch 3 not taken.
|
31 | argStrs.push_back(userScriptStr.substr(0, userScriptStr.find(' '))); |
| 92 |
3/4✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 31 times.
✓ Branch 3 taken 31 times.
|
62 | for (auto& t : split(userScriptStr, ' ')) { |
| 93 |
3/6✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 31 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 31 times.
✗ Branch 5 not taken.
|
31 | if (!t.empty() && argStrs.size() < 126) |
| 94 |
1/2✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
|
31 | argStrs.push_back(t); |
| 95 | 31 | } | |
| 96 | |||
| 97 |
1/2✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
|
31 | std::vector<std::string> parts = split(scriptArgs, ' '); |
| 98 |
1/2✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
|
62 | std::string tmpPart = ""; |
| 99 |
1/2✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
|
31 | std::string quote = ""; |
| 100 |
2/2✓ Branch 0 taken 172 times.
✓ Branch 1 taken 30 times.
|
202 | for (int p = 0; p < parts.size(); p++) { |
| 101 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 171 times.
|
172 | if (argStrs.size() >= 127) { |
| 102 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | LogWarn(VB_COMMAND, "RunScript: too many args, truncating\n"); |
| 103 | 1 | break; | |
| 104 | } | ||
| 105 |
3/4✓ Branch 0 taken 171 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 164 times.
✓ Branch 3 taken 7 times.
|
171 | if (tmpPart == "") { |
| 106 |
4/6✓ Branch 0 taken 164 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 164 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 161 times.
|
356 | if (startsWith(parts[p], "\"")) { |
| 107 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | quote = std::string("\""); |
| 108 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | tmpPart = parts[p].substr(1); |
| 109 |
4/6✓ Branch 0 taken 161 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 161 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✓ Branch 5 taken 157 times.
|
350 | } else if (startsWith(parts[p], "'")) { |
| 110 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | quote = std::string("'"); |
| 111 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | tmpPart = parts[p].substr(1); |
| 112 | } else { | ||
| 113 |
1/2✓ Branch 0 taken 157 times.
✗ Branch 1 not taken.
|
157 | argStrs.push_back(parts[p]); |
| 114 | } | ||
| 115 | |||
| 116 |
8/14✓ Branch 0 taken 164 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✓ Branch 3 taken 157 times.
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 7 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 7 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 7 times.
✗ Branch 12 not taken.
✓ Branch 13 taken 164 times.
|
164 | if ((tmpPart != "") && (quote != "") && (endsWith(tmpPart, quote))) { |
| 117 | // Chop off the ending quote | ||
| 118 | ✗ | argStrs.push_back(tmpPart.substr(0, tmpPart.size() - 1)); | |
| 119 | ✗ | quote = ""; | |
| 120 | ✗ | tmpPart = ""; | |
| 121 | } | ||
| 122 | } else { | ||
| 123 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | tmpPart += " "; |
| 124 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | tmpPart += parts[p]; |
| 125 | |||
| 126 |
2/4✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
|
7 | if (endsWith(parts[p], quote)) { |
| 127 |
2/4✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
|
7 | argStrs.push_back(tmpPart.substr(0, tmpPart.size() - 1)); |
| 128 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | quote = ""; |
| 129 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | tmpPart = ""; |
| 130 | } | ||
| 131 | } | ||
| 132 | } | ||
| 133 | 31 | } | |
| 134 |
2/2✓ Branch 0 taken 226 times.
✓ Branch 1 taken 31 times.
|
257 | for (int x = 0; x < argStrs.size(); x++) { |
| 135 |
1/2✓ Branch 0 taken 226 times.
✗ Branch 1 not taken.
|
226 | LogExcess(VB_COMMAND, "Script Arg %d: %s\n", x, argStrs[x].c_str()); |
| 136 | } | ||
| 137 | |||
| 138 | // The child's environment: ours, plus FPP_SCRIPT, FPP_SCRIPTARGS and the | ||
| 139 | // caller's variables -- each only if not already set, as setenv(..., 0) | ||
| 140 | // did in the child. | ||
| 141 | 31 | std::vector<std::string> envStrs; | |
| 142 |
3/4✓ Branch 0 taken 994 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 963 times.
✓ Branch 3 taken 31 times.
|
994 | for (char** e = environ; e && *e; e++) { |
| 143 |
2/4✓ Branch 0 taken 963 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 963 times.
✗ Branch 3 not taken.
|
2521 | envStrs.push_back(*e); |
| 144 | } | ||
| 145 | 65 | auto addEnv = [&envStrs](const std::string& k, const std::string& v) { | |
| 146 |
1/2✓ Branch 0 taken 65 times.
✗ Branch 1 not taken.
|
65 | std::string prefix = k + "="; |
| 147 |
2/2✓ Branch 0 taken 2028 times.
✓ Branch 1 taken 64 times.
|
2092 | for (auto& e : envStrs) { |
| 148 |
3/4✓ Branch 0 taken 2028 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 2027 times.
|
2028 | if (e.compare(0, prefix.size(), prefix) == 0) |
| 149 | 1 | return; | |
| 150 | } | ||
| 151 |
2/4✓ Branch 0 taken 64 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 64 times.
✗ Branch 3 not taken.
|
64 | envStrs.push_back(prefix + v); |
| 152 |
2/2✓ Branch 0 taken 64 times.
✓ Branch 1 taken 1 times.
|
96 | }; |
| 153 |
2/4✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 31 times.
✗ Branch 3 not taken.
|
62 | addEnv("FPP_SCRIPT", script); |
| 154 |
2/4✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 31 times.
✗ Branch 3 not taken.
|
31 | addEnv("FPP_SCRIPTARGS", scriptArgs); |
| 155 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 31 times.
|
34 | for (auto& ev : envVars) { |
| 156 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | addEnv(ev.first, ev.second); |
| 157 | } | ||
| 158 | |||
| 159 | 31 | std::vector<char*> argv; | |
| 160 |
1/2✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
|
31 | argv.reserve(argStrs.size() + 1); |
| 161 |
2/2✓ Branch 0 taken 226 times.
✓ Branch 1 taken 31 times.
|
257 | for (auto& a : argStrs) |
| 162 |
1/2✓ Branch 0 taken 226 times.
✗ Branch 1 not taken.
|
226 | argv.push_back(const_cast<char*>(a.c_str())); |
| 163 |
1/2✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
|
31 | argv.push_back(nullptr); |
| 164 | 31 | std::vector<char*> envp; | |
| 165 |
1/2✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
|
31 | envp.reserve(envStrs.size() + 1); |
| 166 |
2/2✓ Branch 0 taken 1027 times.
✓ Branch 1 taken 31 times.
|
1058 | for (auto& e : envStrs) |
| 167 |
1/2✓ Branch 0 taken 1027 times.
✗ Branch 1 not taken.
|
1027 | envp.push_back(const_cast<char*>(e.c_str())); |
| 168 |
1/2✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
|
31 | envp.push_back(nullptr); |
| 169 | |||
| 170 |
3/6✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 31 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 31 times.
✗ Branch 5 not taken.
|
31 | const std::string scriptDir = FPP_DIR_SCRIPT(""); |
| 171 |
1/2✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
|
31 | const int daemonize = getSettingInt("daemonize"); |
| 172 | static const char chdirFailed[] = "RunScript: unable to change to the scripts directory\n"; | ||
| 173 | static const char execFailed[] = "RunScript: execve() of the eventScript wrapper failed\n"; | ||
| 174 | |||
| 175 | 31 | pid = fork(); | |
| 176 | |||
| 177 |
2/2✓ Branch 0 taken 31 times.
✓ Branch 1 taken 31 times.
|
62 | if (pid == 0) // Event Script process: async-signal-safe calls only |
| 178 | { | ||
| 179 |
1/2✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
|
31 | CloseOpenFiles(daemonize); |
| 180 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 31 times.
|
31 | if (chdir(scriptDir.c_str())) { |
| 181 | ✗ | if (!daemonize) | |
| 182 | ✗ | (void)!write(2, chdirFailed, sizeof(chdirFailed) - 1); | |
| 183 | ✗ | _exit(EXIT_FAILURE); | |
| 184 | } | ||
| 185 | 31 | execve(eventScript, argv.data(), envp.data()); | |
| 186 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 31 times.
|
31 | if (!daemonize) |
| 187 | ✗ | (void)!write(2, execFailed, sizeof(execFailed) - 1); | |
| 188 | 31 | _exit(EXIT_FAILURE); | |
| 189 | } | ||
| 190 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 31 times.
|
31 | if (pid < 0) { |
| 191 | ✗ | LogErr(VB_COMMAND, "RunScript: fork() failed: %s\n", FPPstrerror(errno)); | |
| 192 | } | ||
| 193 | 31 | return pid; | |
| 194 | 53 | } | |
| 195 |