You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

432 lines
10 KiB

  1. /*
  2. * private-json-hooks.cc - 2nd generation, JSON-RPC, hooks for APT
  3. *
  4. * Copyright (c) 2018 Canonical Ltd
  5. *
  6. * SPDX-License-Identifier: GPL-2.0+
  7. */
  8. #include <apt-pkg/debsystem.h>
  9. #include <apt-pkg/macros.h>
  10. #include <apt-private/private-json-hooks.h>
  11. #include <ostream>
  12. #include <sstream>
  13. #include <stack>
  14. #include <signal.h>
  15. #include <sys/socket.h>
  16. #include <sys/types.h>
  17. /**
  18. * @brief Simple JSON writer
  19. *
  20. * This performs no error checking, or string escaping, be careful.
  21. */
  22. class APT_HIDDEN JsonWriter
  23. {
  24. std::ostream &os;
  25. std::locale old_locale;
  26. enum write_state
  27. {
  28. empty,
  29. in_array_first_element,
  30. in_array,
  31. in_object_first_key,
  32. in_object_key,
  33. in_object_val
  34. } state = empty;
  35. std::stack<write_state> old_states;
  36. void maybeComma()
  37. {
  38. switch (state)
  39. {
  40. case empty:
  41. break;
  42. case in_object_val:
  43. state = in_object_key;
  44. break;
  45. case in_object_key:
  46. state = in_object_val;
  47. os << ',';
  48. break;
  49. case in_array:
  50. os << ',';
  51. break;
  52. case in_array_first_element:
  53. state = in_array;
  54. break;
  55. case in_object_first_key:
  56. state = in_object_val;
  57. break;
  58. default:
  59. abort();
  60. }
  61. }
  62. void pushState(write_state state)
  63. {
  64. old_states.push(this->state);
  65. this->state = state;
  66. }
  67. void popState()
  68. {
  69. this->state = old_states.top();
  70. }
  71. public:
  72. JsonWriter(std::ostream &os) : os(os) { old_locale = os.imbue(std::locale::classic()); }
  73. ~JsonWriter() { os.imbue(old_locale); }
  74. JsonWriter &beginArray()
  75. {
  76. maybeComma();
  77. pushState(in_array_first_element);
  78. os << '[';
  79. return *this;
  80. }
  81. JsonWriter &endArray()
  82. {
  83. popState();
  84. os << ']';
  85. return *this;
  86. }
  87. JsonWriter &beginObject()
  88. {
  89. maybeComma();
  90. pushState(in_object_first_key);
  91. os << '{';
  92. return *this;
  93. }
  94. JsonWriter &endObject()
  95. {
  96. popState();
  97. os << '}';
  98. return *this;
  99. }
  100. JsonWriter &name(std::string const &name)
  101. {
  102. maybeComma();
  103. os << '"' << name << '"' << ':';
  104. return *this;
  105. }
  106. JsonWriter &value(std::string const &value)
  107. {
  108. maybeComma();
  109. os << '"' << value << '"';
  110. return *this;
  111. }
  112. JsonWriter &value(const char *value)
  113. {
  114. maybeComma();
  115. os << '"' << value << '"';
  116. return *this;
  117. }
  118. JsonWriter &value(int value)
  119. {
  120. maybeComma();
  121. os << value;
  122. return *this;
  123. }
  124. JsonWriter &value(long value)
  125. {
  126. maybeComma();
  127. os << value;
  128. return *this;
  129. }
  130. JsonWriter &value(long long value)
  131. {
  132. maybeComma();
  133. os << value;
  134. return *this;
  135. }
  136. JsonWriter &value(unsigned long long value)
  137. {
  138. maybeComma();
  139. os << value;
  140. return *this;
  141. }
  142. JsonWriter &value(unsigned long value)
  143. {
  144. maybeComma();
  145. os << value;
  146. return *this;
  147. }
  148. JsonWriter &value(unsigned int value)
  149. {
  150. maybeComma();
  151. os << value;
  152. return *this;
  153. }
  154. JsonWriter &value(bool value)
  155. {
  156. maybeComma();
  157. os << (value ? "true" : "false");
  158. return *this;
  159. }
  160. JsonWriter &value(double value)
  161. {
  162. maybeComma();
  163. os << value;
  164. return *this;
  165. }
  166. };
  167. /**
  168. * @brief Wrtie a VerIterator to a JsonWriter
  169. */
  170. static void verIterToJson(JsonWriter &writer, CacheFile &Cache, pkgCache::VerIterator const &Ver)
  171. {
  172. writer.beginObject();
  173. writer.name("id").value(Ver->ID);
  174. writer.name("version").value(Ver.VerStr());
  175. writer.name("architecture").value(Ver.Arch());
  176. writer.name("pin").value(Cache->GetPolicy().GetPriority(Ver));
  177. writer.endObject();
  178. }
  179. /**
  180. * @brief Copy of debSystem::DpkgChrootDirectory()
  181. * @todo Remove
  182. */
  183. static void DpkgChrootDirectory()
  184. {
  185. std::string const chrootDir = _config->FindDir("DPkg::Chroot-Directory");
  186. if (chrootDir == "/")
  187. return;
  188. std::cerr << "Chrooting into " << chrootDir << std::endl;
  189. if (chroot(chrootDir.c_str()) != 0)
  190. _exit(100);
  191. if (chdir("/") != 0)
  192. _exit(100);
  193. }
  194. /**
  195. * @brief Send a notification to the hook's stream
  196. */
  197. static void NotifyHook(std::ostream &os, std::string const &method, const char **FileList, CacheFile &Cache, std::set<std::string> const &UnknownPackages)
  198. {
  199. SortedPackageUniverse Universe(Cache);
  200. JsonWriter jsonWriter{os};
  201. jsonWriter.beginObject();
  202. jsonWriter.name("jsonrpc").value("2.0");
  203. jsonWriter.name("method").value(method);
  204. /* Build params */
  205. jsonWriter.name("params").beginObject();
  206. jsonWriter.name("command").value(FileList[0]);
  207. jsonWriter.name("search-terms").beginArray();
  208. for (int i = 1; FileList[i] != NULL; i++)
  209. jsonWriter.value(FileList[i]);
  210. jsonWriter.endArray();
  211. jsonWriter.name("unknown-packages").beginArray();
  212. for (auto const &PkgName : UnknownPackages)
  213. jsonWriter.value(PkgName);
  214. jsonWriter.endArray();
  215. jsonWriter.name("packages").beginArray();
  216. for (auto const &Pkg : Universe)
  217. {
  218. switch (Cache[Pkg].Mode)
  219. {
  220. case pkgDepCache::ModeInstall:
  221. case pkgDepCache::ModeDelete:
  222. break;
  223. default:
  224. continue;
  225. }
  226. jsonWriter.beginObject();
  227. jsonWriter.name("id").value(Pkg->ID);
  228. jsonWriter.name("name").value(Pkg.Name());
  229. jsonWriter.name("architecture").value(Pkg.Arch());
  230. switch (Cache[Pkg].Mode)
  231. {
  232. case pkgDepCache::ModeInstall:
  233. jsonWriter.name("mode").value("install");
  234. break;
  235. case pkgDepCache::ModeDelete:
  236. jsonWriter.name("mode").value(Cache[Pkg].Purge() ? "purge" : "deinstall");
  237. break;
  238. default:
  239. continue;
  240. }
  241. jsonWriter.name("automatic").value(bool(Cache[Pkg].Flags & pkgCache::Flag::Auto));
  242. jsonWriter.name("versions").beginObject();
  243. if (Cache[Pkg].CandidateVer != nullptr)
  244. verIterToJson(jsonWriter.name("candidate"), Cache, Cache[Pkg].CandidateVerIter(Cache));
  245. if (Cache[Pkg].InstallVer != nullptr)
  246. verIterToJson(jsonWriter.name("install"), Cache, Cache[Pkg].InstVerIter(Cache));
  247. if (Pkg->CurrentVer != 0)
  248. verIterToJson(jsonWriter.name("current"), Cache, Pkg.CurrentVer());
  249. jsonWriter.endObject();
  250. jsonWriter.endObject();
  251. }
  252. jsonWriter.endArray(); // packages
  253. jsonWriter.endObject(); // params
  254. jsonWriter.endObject(); // main
  255. }
  256. /// @brief Build the hello handshake message for 0.1 protocol
  257. static std::string BuildHelloMessage()
  258. {
  259. std::stringstream Hello;
  260. JsonWriter(Hello).beginObject().name("jsonrpc").value("2.0").name("method").value("org.debian.apt.hooks.hello").name("id").value(0).name("params").beginObject().name("versions").beginArray().value("0.1").endArray().endObject().endObject();
  261. return Hello.str();
  262. }
  263. /// @brief Build the bye notification for 0.1 protocol
  264. static std::string BuildByeMessage()
  265. {
  266. std::stringstream Bye;
  267. JsonWriter(Bye).beginObject().name("jsonrpc").value("2.0").name("method").value("org.debian.apt.hooks.bye").name("params").beginObject().endObject().endObject();
  268. return Bye.str();
  269. }
  270. /// @brief Run the Json hook processes in the given option.
  271. bool RunJsonHook(std::string const &option, std::string const &method, const char **FileList, CacheFile &Cache, std::set<std::string> const &UnknownPackages)
  272. {
  273. std::stringstream ss;
  274. NotifyHook(ss, method, FileList, Cache, UnknownPackages);
  275. std::string TheData = ss.str();
  276. std::string HelloData = BuildHelloMessage();
  277. std::string ByeData = BuildByeMessage();
  278. bool result = true;
  279. Configuration::Item const *Opts = _config->Tree(option.c_str());
  280. if (Opts == 0 || Opts->Child == 0)
  281. return true;
  282. Opts = Opts->Child;
  283. sighandler_t old_sigpipe = signal(SIGPIPE, SIG_IGN);
  284. sighandler_t old_sigint = signal(SIGINT, SIG_IGN);
  285. sighandler_t old_sigquit = signal(SIGQUIT, SIG_IGN);
  286. unsigned int Count = 1;
  287. for (; Opts != 0; Opts = Opts->Next, Count++)
  288. {
  289. if (Opts->Value.empty() == true)
  290. continue;
  291. if (_config->FindB("Debug::RunScripts", false) == true)
  292. std::clog << "Running external script with list of all .deb file: '"
  293. << Opts->Value << "'" << std::endl;
  294. // Create the pipes
  295. std::set<int> KeepFDs;
  296. MergeKeepFdsFromConfiguration(KeepFDs);
  297. int Pipes[2];
  298. if (socketpair(AF_UNIX, SOCK_STREAM, 0, Pipes) != 0)
  299. {
  300. result = _error->Errno("pipe", "Failed to create IPC pipe to subprocess");
  301. break;
  302. }
  303. int InfoFD = 3;
  304. if (InfoFD != Pipes[0])
  305. SetCloseExec(Pipes[0], true);
  306. else
  307. KeepFDs.insert(Pipes[0]);
  308. SetCloseExec(Pipes[1], true);
  309. // Purified Fork for running the script
  310. pid_t Process = ExecFork(KeepFDs);
  311. if (Process == 0)
  312. {
  313. // Setup the FDs
  314. dup2(Pipes[0], InfoFD);
  315. SetCloseExec(STDOUT_FILENO, false);
  316. SetCloseExec(STDIN_FILENO, false);
  317. SetCloseExec(STDERR_FILENO, false);
  318. string hookfd;
  319. strprintf(hookfd, "%d", InfoFD);
  320. setenv("APT_HOOK_SOCKET", hookfd.c_str(), 1);
  321. DpkgChrootDirectory();
  322. const char *Args[4];
  323. Args[0] = "/bin/sh";
  324. Args[1] = "-c";
  325. Args[2] = Opts->Value.c_str();
  326. Args[3] = 0;
  327. execv(Args[0], (char **)Args);
  328. _exit(100);
  329. }
  330. close(Pipes[0]);
  331. FILE *F = fdopen(Pipes[1], "w+");
  332. if (F == 0)
  333. {
  334. result = _error->Errno("fdopen", "Failed to open new FD");
  335. break;
  336. }
  337. fwrite(HelloData.data(), HelloData.size(), 1, F);
  338. fwrite("\n\n", 2, 1, F);
  339. fflush(F);
  340. char *line = nullptr;
  341. size_t linesize = 0;
  342. ssize_t size = getline(&line, &linesize, F);
  343. if (size < 0)
  344. {
  345. if (errno != ECONNRESET)
  346. _error->Error("Could not read response to hello message from hook %s: %s", Opts->Value.c_str(), strerror(errno));
  347. goto out;
  348. }
  349. else if (strstr(line, "error") != nullptr)
  350. {
  351. _error->Error("Hook %s reported an error during hello: %s", Opts->Value.c_str(), line);
  352. goto out;
  353. }
  354. size = getline(&line, &linesize, F);
  355. if (size < 0)
  356. {
  357. _error->Error("Could not read message separator line after handshake from %s: %s", Opts->Value.c_str(), feof(F) ? "end of file" : strerror(errno));
  358. goto out;
  359. }
  360. else if (size == 0 || line[0] != '\n')
  361. {
  362. _error->Error("Expected empty line after handshake from %s, received %s", Opts->Value.c_str(), line);
  363. goto out;
  364. }
  365. fwrite(TheData.data(), TheData.size(), 1, F);
  366. fwrite("\n\n", 2, 1, F);
  367. fwrite(ByeData.data(), ByeData.size(), 1, F);
  368. fwrite("\n\n", 2, 1, F);
  369. out:
  370. fclose(F);
  371. // Clean up the sub process
  372. if (ExecWait(Process, Opts->Value.c_str()) == false)
  373. {
  374. result = _error->Error("Failure running hook %s", Opts->Value.c_str());
  375. break;
  376. }
  377. }
  378. signal(SIGINT, old_sigint);
  379. signal(SIGPIPE, old_sigpipe);
  380. signal(SIGQUIT, old_sigquit);
  381. return result;
  382. }