X-Git-Url: https://git.saurik.com/ldid.git/blobdiff_plain/d4187e5371fa6a656531494ce54cdc43638c3d71..886cb3f1cf485d307519e2024ef141314d42e713:/ldid.cpp diff --git a/ldid.cpp b/ldid.cpp index 40209b5..33b1859 100644 --- a/ldid.cpp +++ b/ldid.cpp @@ -23,19 +23,24 @@ #include #include #include -#include +#include +#include +#include #include #include #include +#include #include #include +#include #include #include #include #include #include +#include #include #include @@ -61,18 +66,19 @@ #define _assert(expr) \ _assert_(expr, "%s", #expr) -#define _syscall(expr) ({ \ - __typeof__(expr) _value; \ - do if ((long) (_value = (expr)) != -1) \ - break; \ - else switch (errno) { \ - case EINTR: \ - continue; \ - default: \ - _assert_(false, "errno=%u", errno); \ - } while (true); \ - _value; \ -}) +#define _syscall(expr, ...) [&] { for (;;) { \ + auto _value(expr); \ + if ((long) _value != -1) \ + return _value; \ + int error(errno); \ + if (error == EINTR) \ + continue; \ + /* XXX: EINTR is included in this list to fix g++ */ \ + for (auto success : (long[]) {EINTR, __VA_ARGS__}) \ + if (error == success) \ + return (decltype(expr)) -success; \ + _assert_(false, "errno=%u", error); \ +} }() #define _trace() \ fprintf(stderr, "_trace(%s:%u): %s\n", __FILE__, __LINE__, __FUNCTION__) @@ -95,6 +101,40 @@ struct Iterator_ { for (bool _suck(true); _suck; _suck = false) \ for (const __typeof__(*_item) &item = *_item; _suck; _suck = false) +class _Scope { +}; + +template +class Scope : + public _Scope +{ + private: + Function_ function_; + + public: + Scope(const Function_ &function) : + function_(function) + { + } + + ~Scope() { + function_(); + } +}; + +template +Scope _scope(const Function_ &function) { + return Scope(function); +} + +#define _scope__(counter, function) \ + __attribute__((__unused__)) \ + const _Scope &_scope ## counter(_scope([&]function)) +#define _scope_(counter, function) \ + _scope__(counter, function) +#define _scope(function) \ + _scope_(__COUNTER__, function) + struct fat_header { uint32_t magic; uint32_t nfat_arch; @@ -1259,14 +1299,123 @@ class Signature { } }; +class NullBuffer : + public std::streambuf +{ + public: + virtual std::streamsize xsputn(const char_type *data, std::streamsize size) { + return size; + } + + virtual int_type overflow(int_type next) { + return next; + } +}; + +class HashBuffer : + public std::streambuf +{ + private: + std::vector &hash_; + SHA_CTX context_; + + public: + HashBuffer(std::vector &hash) : + hash_(hash) + { + SHA1_Init(&context_); + } + + ~HashBuffer() { + hash_.resize(SHA_DIGEST_LENGTH); + SHA1_Final(reinterpret_cast(hash_.data()), &context_); + } + + virtual std::streamsize xsputn(const char_type *data, std::streamsize size) { + SHA1_Update(&context_, data, size); + return size; + } + + virtual int_type overflow(int_type next) { + if (next == traits_type::eof()) + return sync(); + char value(next); + xsputn(&value, 1); + return next; + } +}; + +class HashProxy : + public HashBuffer +{ + private: + std::streambuf &buffer_; + + public: + HashProxy(std::vector &hash, std::streambuf &buffer) : + HashBuffer(hash), + buffer_(buffer) + { + } + + virtual std::streamsize xsputn(const char_type *data, std::streamsize size) { + _assert(HashBuffer::xsputn(data, size) == size); + return buffer_.sputn(data, size); + } +}; + +static bool Starts(const std::string &lhs, const std::string &rhs) { + return lhs.size() >= rhs.size() && lhs.compare(0, rhs.size(), rhs) == 0; +} + +class Split { + public: + std::string dir; + std::string base; + + Split(const std::string &path) { + size_t slash(path.rfind('/')); + if (slash == std::string::npos) + base = path; + else { + dir = path.substr(0, slash + 1); + base = path.substr(slash + 1); + } + } +}; + +static void mkdir_p(const std::string &path) { + if (path.empty()) + return; +#ifdef __WIN32__ + if (_syscall(mkdir(path.c_str()), EEXIST) == -EEXIST) + return; +#else + if (_syscall(mkdir(path.c_str(), 0755), EEXIST) == -EEXIST) + return; +#endif + auto slash(path.rfind('/', path.size() - 1)); + if (slash == std::string::npos) + return; + mkdir_p(path.substr(0, slash)); +} + +static std::string Temporary(std::filebuf &file, const Split &split) { + std::string temp(split.dir + ".ldid." + split.base); + mkdir_p(split.dir); + _assert_(file.open(temp.c_str(), std::ios::out | std::ios::trunc | std::ios::binary) == &file, "open(): %s", temp.c_str()); + return temp; +} + static void Commit(const std::string &path, const std::string &temp) { struct stat info; - _syscall(stat(path.c_str(), &info)); + if (_syscall(stat(path.c_str(), &info), ENOENT) == 0) { #ifndef __WIN32__ - _syscall(chown(temp.c_str(), info.st_uid, info.st_gid)); + _syscall(chown(temp.c_str(), info.st_uid, info.st_gid)); #endif - _syscall(chmod(temp.c_str(), info.st_mode)); - _syscall(unlink(path.c_str())); + _syscall(chmod(temp.c_str(), info.st_mode)); + } + _syscall(rename(temp.c_str(), path.c_str())); } @@ -1406,6 +1555,439 @@ static void Unsign(void *idata, size_t isize, std::streambuf &output) { })); } +std::string DiskFolder::Path(const std::string &path) { + return path_ + "/" + path; +} + +DiskFolder::DiskFolder(const std::string &path) : + path_(path) +{ +} + +DiskFolder::~DiskFolder() { + if (!std::uncaught_exception()) + for (const auto &commit : commit_) + Commit(commit.first, commit.second); +} + +void DiskFolder::Find(const std::string &root, const std::string &base, const Functor &)> &)>&code) { + std::string path(Path(root) + base); + + DIR *dir(opendir(path.c_str())); + _assert(dir != NULL); + _scope({ _syscall(closedir(dir)); }); + + while (auto child = readdir(dir)) { + std::string name(child->d_name); + if (name == "." || name == "..") + continue; + if (Starts(name, ".ldid.")) + continue; + + bool directory; + +#ifdef __WIN32__ + struct stat info; + _syscall(stat(path.c_str(), &info)); + if (false); + else if (S_ISDIR(info.st_mode)) + directory = true; + else if (S_ISREG(info.st_mode)) + directory = false; + else + _assert_(false, "st_mode=%x", info.st_mode); +#else + switch (child->d_type) { + case DT_DIR: + directory = true; + break; + case DT_REG: + directory = false; + break; + default: + _assert_(false, "d_type=%u", child->d_type); + } +#endif + + if (directory) + Find(root, base + name + "/", code); + else + code(base + name, fun([&](const Functor &code) { + std::string access(root + base + name); + _assert_(Open(access, fun([&](std::streambuf &data) { + NullBuffer save; + code(data, save); + })), "open(): %s", access.c_str()); + })); + } +} + +void DiskFolder::Save(const std::string &path, const Functor &code) { + std::filebuf save; + auto from(Path(path)); + commit_[from] = Temporary(save, from); + code(save); +} + +bool DiskFolder::Open(const std::string &path, const Functor &code) { + std::filebuf data; + auto result(data.open(Path(path).c_str(), std::ios::binary | std::ios::in)); + if (result == NULL) + return false; + _assert(result == &data); + code(data); + return true; +} + +void DiskFolder::Find(const std::string &path, const Functor &)> &)>&code) { + Find(path, "", code); +} + +SubFolder::SubFolder(Folder &parent, const std::string &path) : + parent_(parent), + path_(path) +{ +} + +void SubFolder::Save(const std::string &path, const Functor &code) { + return parent_.Save(path_ + path, code); +} + +bool SubFolder::Open(const std::string &path, const Functor &code) { + return parent_.Open(path_ + path, code); +} + +void SubFolder::Find(const std::string &path, const Functor &)> &)> &code) { + return parent_.Find(path_ + path, code); +} + +UnionFolder::UnionFolder(Folder &parent) : + parent_(parent) +{ +} + +void UnionFolder::Save(const std::string &path, const Functor &code) { + return parent_.Save(path, code); +} + +bool UnionFolder::Open(const std::string &path, const Functor &code) { + auto file(files_.find(path)); + if (file == files_.end()) + return parent_.Open(path, code); + + auto &data(file->second); + data.pubseekpos(0, std::ios::in); + code(data); + return true; +} + +void UnionFolder::Find(const std::string &path, const Functor &)> &)> &code) { + parent_.Find(path, fun([&](const std::string &name, const Functor &)> &save) { + if (files_.find(name) == files_.end()) + code(name, save); + })); + + for (auto &file : files_) + code(file.first, fun([&](const Functor &code) { + parent_.Save(file.first, fun([&](std::streambuf &save) { + file.second.pubseekpos(0, std::ios::in); + code(file.second, save); + })); + })); +} + +static size_t copy(std::streambuf &source, std::streambuf &target) { + size_t total(0); + for (;;) { + char data[4096]; + size_t writ(source.sgetn(data, sizeof(data))); + if (writ == 0) + break; + _assert(target.sputn(data, writ) == writ); + total += writ; + } + return total; +} + +static plist_t plist(const std::string &data) { + plist_t plist(NULL); + if (Starts(data, "bplist00")) + plist_from_bin(data.data(), data.size(), &plist); + else + plist_from_xml(data.data(), data.size(), &plist); + _assert(plist != NULL); + return plist; +} + +static void plist_d(std::streambuf &buffer, const Functor &code) { + std::stringbuf data; + copy(buffer, data); + auto node(plist(data.str())); + _scope({ plist_free(node); }); + _assert(plist_get_node_type(node) == PLIST_DICT); + code(node); +} + +static std::string plist_s(plist_t node) { + _assert(node != NULL); + _assert(plist_get_node_type(node) == PLIST_STRING); + char *data; + plist_get_string_val(node, &data); + _scope({ free(data); }); + return data; +} + +enum Mode { + NoMode, + OptionalMode, + OmitMode, + NestedMode, + TopMode, +}; + +class Expression { + private: + regex_t regex_; + + public: + Expression(const std::string &code) { + _assert_(regcomp(®ex_, code.c_str(), REG_EXTENDED | REG_NOSUB) == 0, "regcomp()"); + } + + ~Expression() { + regfree(®ex_); + } + + bool operator ()(const std::string &data) const { + auto value(regexec(®ex_, data.c_str(), 0, NULL, 0)); + if (value == REG_NOMATCH) + return false; + _assert_(value == 0, "regexec()"); + return true; + } +}; + +struct Rule { + unsigned weight_; + Mode mode_; + std::string code_; + + mutable std::auto_ptr regex_; + + Rule(unsigned weight, Mode mode, const std::string &code) : + weight_(weight), + mode_(mode), + code_(code) + { + } + + Rule(const Rule &rhs) : + weight_(rhs.weight_), + mode_(rhs.mode_), + code_(rhs.code_) + { + } + + void Compile() const { + regex_.reset(new Expression(code_)); + } + + bool operator ()(const std::string &data) const { + _assert(regex_.get() != NULL); + return (*regex_)(data); + } + + bool operator <(const Rule &rhs) const { + if (weight_ > rhs.weight_) + return true; + if (weight_ < rhs.weight_) + return false; + return mode_ > rhs.mode_; + } +}; + +struct RuleCode { + bool operator ()(const Rule *lhs, const Rule *rhs) const { + return lhs->code_ < rhs->code_; + } +}; + +std::string Bundle(const std::string &root, Folder &folder, const std::string &key, std::map> &remote) { + std::string executable; + std::string identifier; + + static const std::string info("Info.plist"); + + _assert_(folder.Open(info, fun([&](std::streambuf &buffer) { + plist_d(buffer, fun([&](plist_t node) { + executable = plist_s(plist_dict_get_item(node, "CFBundleExecutable")); + identifier = plist_s(plist_dict_get_item(node, "CFBundleIdentifier")); + })); + })), "open(): Info.plist"); + + std::map> versions; + + auto &rules1(versions[""]); + auto &rules2(versions["2"]); + + static const std::string signature("_CodeSignature/CodeResources"); + + folder.Open(signature, fun([&](std::streambuf &buffer) { + plist_d(buffer, fun([&](plist_t node) { + // XXX: maybe attempt to preserve existing rules + })); + })); + + if (true) { + rules1.insert(Rule{1, NoMode, "^"}); + rules1.insert(Rule{10000, OmitMode, "^(Frameworks/[^/]+\\.framework/|PlugIns/[^/]+\\.appex/|PlugIns/[^/]+\\.appex/Frameworks/[^/]+\\.framework/|())SC_Info/[^/]+\\.(sinf|supf|supp)$"}); + rules1.insert(Rule{1000, OptionalMode, "^.*\\.lproj/"}); + rules1.insert(Rule{1100, OmitMode, "^.*\\.lproj/locversion.plist$"}); + rules1.insert(Rule{10000, OmitMode, "^Watch/[^/]+\\.app/(Frameworks/[^/]+\\.framework/|PlugIns/[^/]+\\.appex/|PlugIns/[^/]+\\.appex/Frameworks/[^/]+\\.framework/)SC_Info/[^/]+\\.(sinf|supf|supp)$"}); + rules1.insert(Rule{1, NoMode, "^version.plist$"}); + } + + if (true) { + rules2.insert(Rule{11, NoMode, ".*\\.dSYM($|/)"}); + rules2.insert(Rule{20, NoMode, "^"}); + rules2.insert(Rule{2000, OmitMode, "^(.*/)?\\.DS_Store$"}); + rules2.insert(Rule{10000, OmitMode, "^(Frameworks/[^/]+\\.framework/|PlugIns/[^/]+\\.appex/|PlugIns/[^/]+\\.appex/Frameworks/[^/]+\\.framework/|())SC_Info/[^/]+\\.(sinf|supf|supp)$"}); + rules2.insert(Rule{10, NestedMode, "^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/"}); + rules2.insert(Rule{1, NoMode, "^.*"}); + rules2.insert(Rule{1000, OptionalMode, "^.*\\.lproj/"}); + rules2.insert(Rule{1100, OmitMode, "^.*\\.lproj/locversion.plist$"}); + rules2.insert(Rule{20, OmitMode, "^Info\\.plist$"}); + rules2.insert(Rule{20, OmitMode, "^PkgInfo$"}); + rules2.insert(Rule{10000, OmitMode, "^Watch/[^/]+\\.app/(Frameworks/[^/]+\\.framework/|PlugIns/[^/]+\\.appex/|PlugIns/[^/]+\\.appex/Frameworks/[^/]+\\.framework/)SC_Info/[^/]+\\.(sinf|supf|supp)$"}); + rules2.insert(Rule{10, NestedMode, "^[^/]+$"}); + rules2.insert(Rule{20, NoMode, "^embedded\\.provisionprofile$"}); + rules2.insert(Rule{20, NoMode, "^version\\.plist$"}); + } + + std::map> local; + + static Expression nested("^PlugIns/[^/]*\\.appex/Info\\.plist$"); + + folder.Find("", fun([&](const std::string &name, const Functor &)> &code) { + if (!nested(name)) + return; + auto bundle(root + Split(name).dir); + SubFolder subfolder(folder, bundle); + Bundle(bundle, subfolder, key, local); + })); + + folder.Find("", fun([&](const std::string &name, const Functor &)> &code) { + if (name == executable || name == signature) + return; + + auto &hash(local[name]); + if (!hash.empty()) + return; + + code(fun([&](std::streambuf &data, std::streambuf &save) { + HashProxy proxy(hash, save); + copy(data, proxy); + })); + + _assert(hash.size() == SHA_DIGEST_LENGTH); + })); + + auto plist(plist_new_dict()); + _scope({ plist_free(plist); }); + + for (const auto &version : versions) { + auto files(plist_new_dict()); + plist_dict_set_item(plist, ("files" + version.first).c_str(), files); + + for (const auto &rule : version.second) + rule.Compile(); + + for (const auto &hash : local) + for (const auto &rule : version.second) + if (rule(hash.first)) { + if (rule.mode_ == NoMode) + plist_dict_set_item(files, hash.first.c_str(), plist_new_data(hash.second.data(), hash.second.size())); + else if (rule.mode_ == OptionalMode) { + auto entry(plist_new_dict()); + plist_dict_set_item(entry, "hash", plist_new_data(hash.second.data(), hash.second.size())); + plist_dict_set_item(entry, "optional", plist_new_bool(true)); + plist_dict_set_item(files, hash.first.c_str(), entry); + } + + break; + } + } + + for (const auto &version : versions) { + auto rules(plist_new_dict()); + plist_dict_set_item(plist, ("rules" + version.first).c_str(), rules); + + std::multiset ordered; + for (const auto &rule : version.second) + ordered.insert(&rule); + + for (const auto &rule : ordered) + if (rule->weight_ == 1 && rule->mode_ == NoMode) + plist_dict_set_item(rules, rule->code_.c_str(), plist_new_bool(true)); + else { + auto entry(plist_new_dict()); + plist_dict_set_item(rules, rule->code_.c_str(), entry); + + switch (rule->mode_) { + case NoMode: + break; + case OmitMode: + plist_dict_set_item(entry, "omit", plist_new_bool(true)); + break; + case OptionalMode: + plist_dict_set_item(entry, "optional", plist_new_bool(true)); + break; + case NestedMode: + plist_dict_set_item(entry, "nested", plist_new_bool(true)); + break; + case TopMode: + plist_dict_set_item(entry, "top", plist_new_bool(true)); + break; + } + + if (rule->weight_ >= 10000) + plist_dict_set_item(entry, "weight", plist_new_uint(rule->weight_)); + else if (rule->weight_ != 1) + plist_dict_set_item(entry, "weight", plist_new_real(rule->weight_)); + } + } + + folder.Save(signature, fun([&](std::streambuf &save) { + HashProxy proxy(local[signature], save); + char *xml(NULL); + uint32_t size; + plist_to_xml(plist, &xml, &size); + _scope({ free(xml); }); + put(proxy, xml, size); + })); + + folder.Open(executable, fun([&](std::streambuf &buffer) { + // XXX: this is a miserable fail + std::stringbuf temp; + copy(buffer, temp); + auto data(temp.str()); + + folder.Save(executable, fun([&](std::streambuf &save) { + Slots slots; + slots[1] = local.at(info); + slots[3] = local.at(signature); + + HashProxy proxy(local[executable], save); + Sign(data.data(), data.size(), proxy, identifier, "", key, slots); + })); + })); + + for (const auto &hash : local) + remote[root + hash.first] = hash.second; + + return executable; +} + } int main(int argc, char *argv[]) { @@ -1473,9 +2055,9 @@ int main(int argc, char *argv[]) { char *arge; unsigned number(strtoul(slot, &arge, 0)); _assert(arge == colon); - std::string &hash(slots[number]); + std::vector &hash(slots[number]); hash.resize(SHA_DIGEST_LENGTH); - sha1(reinterpret_cast(&hash[0]), file.data(), file.size()); + sha1(reinterpret_cast(hash.data()), file.data(), file.size()); } break; case 'D': flag_D = true; break; @@ -1548,28 +2130,25 @@ int main(int argc, char *argv[]) { _foreach (file, files) try { std::string path(file); - if (flag_S || flag_r) { - Map input(path, O_RDONLY, PROT_READ, MAP_PRIVATE); - - std::string dir; - std::string base; + struct stat info; + _syscall(stat(path.c_str(), &info)); - size_t slash(path.rfind('/')); - if (slash == std::string::npos) - base = path; - else { - dir = path.substr(0, slash + 1); - base = path.substr(slash + 1); - } + if (S_ISDIR(info.st_mode)) { + _assert(!flag_r); + ldid::DiskFolder folder(path); + std::map> hashes; + path += "/" + Bundle("", folder, key, hashes); + } else if (flag_S || flag_r) { + Map input(path, O_RDONLY, PROT_READ, MAP_PRIVATE); - std::string temp(dir + "." + base + ".cs"); std::filebuf output; - _assert(output.open(temp.c_str(), std::ios::out | std::ios::trunc | std::ios::binary) == &output); + Split split(path); + auto temp(Temporary(output, split)); if (flag_r) ldid::Unsign(input.data(), input.size(), output); else { - std::string identifier(flag_I ?: base.c_str()); + std::string identifier(flag_I ?: split.base.c_str()); ldid::Sign(input.data(), input.size(), output, identifier, entitlements, key, slots); }