From b8a4ca222e751c431d10638e818467839ea67817 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sat, 18 Dec 2021 17:42:23 +0100 Subject: [PATCH] Add adept_remove util --- README.md | 9 +- README_package.md | 9 +- utils/Makefile | 11 +- utils/adept_remove.cpp | 304 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 323 insertions(+), 10 deletions(-) create mode 100644 utils/adept_remove.cpp diff --git a/README.md b/README.md index f4b542a..d81dffd 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Main fucntions to use from gourou::DRMProcessor are : * Get an ePub from an ACSM file : _fulfill()_ and _download()_ * Create a new device : _createDRMProcessor()_ * Register a new device : _signIn()_ and _activateDevice()_ + * Remove DRM : _removeDRM()_ You can import configuration from (at least) : @@ -26,7 +27,7 @@ Or create a new one. Be careful : there is a limited number of devices that can ePub are encrypted using a shared key : one account / multiple devices, so you can create and register a device into your computer and read downloaded (and encrypted) ePub file with your eReader configured using the same AdobeID account. -For those who wants to remove DRM, you can export your private key and import it within [Calibre](https://calibre-ebook.com/) an its DeDRM plugin. +For those who wants to remove DRM without adept_remove, you can export your private key and import it within [Calibre](https://calibre-ebook.com/) an its DeDRM plugin. Dependencies @@ -65,6 +66,7 @@ BUILD_SHARED build libgourou.so if 1, nothing if 0, can be combined with BUILD_S * Default value + Utils ----- @@ -85,6 +87,11 @@ To export your private key : export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD ./utils/acsmdownloader --export-private-key [-o adobekey_1.der] +To remove ADEPT DRM : + + export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD + ./utils/adept_remove -f + Copyright --------- diff --git a/README_package.md b/README_package.md index 9eed9ab..4256db5 100644 --- a/README_package.md +++ b/README_package.md @@ -41,12 +41,10 @@ To export your private key : export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD ./acsmdownloader --export-private-key [-o adobekey_1.der] +To remove ADEPT DRM : -Sources -------- - -http://indefero.soutade.fr/p/libgourou - + export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD + ./adept_remove -f Copyright @@ -69,4 +67,3 @@ Special thanks -------------- * _Jens_ for all test samples and utils testing - diff --git a/utils/Makefile b/utils/Makefile index e9feca0..2ad92f8 100644 --- a/utils/Makefile +++ b/utils/Makefile @@ -1,5 +1,5 @@ -TARGETS=acsmdownloader adept_activate +TARGETS=acsmdownloader adept_activate adept_remove CXXFLAGS=-Wall `pkg-config --cflags Qt5Core Qt5Network` -fPIC -I$(ROOT)/include -I$(ROOT)/lib/pugixml/src/ @@ -18,12 +18,17 @@ else CXXFLAGS += -O2 endif +COMMON_DEPS = drmprocessorclientimpl.cpp $(STATIC_DEP) + all: $(TARGETS) -acsmdownloader: drmprocessorclientimpl.cpp acsmdownloader.cpp $(STATIC_DEP) +acsmdownloader: acsmdownloader.cpp $(COMMON_DEPS) $(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@ -adept_activate: drmprocessorclientimpl.cpp adept_activate.cpp $(STATIC_DEP) +adept_activate: adept_activate.cpp $(COMMON_DEPS) + $(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@ + +adept_remove: adept_remove.cpp $(COMMON_DEPS) $(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@ clean: diff --git a/utils/adept_remove.cpp b/utils/adept_remove.cpp new file mode 100644 index 0000000..ed857da --- /dev/null +++ b/utils/adept_remove.cpp @@ -0,0 +1,304 @@ +/* + Copyright (c) 2021, Grégory Soutadé + + All rights reserved. + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include "drmprocessorclientimpl.h" + +#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0])) + +static const char* deviceFile = "device.xml"; +static const char* activationFile = "activation.xml"; +static const char* devicekeyFile = "devicesalt"; +static const char* inputFile = 0; +static const char* outputFile = 0; +static const char* outputDir = 0; +static const char* defaultDirs[] = { + ".adept/", + "./adobe-digital-editions/", + "./.adobe-digital-editions/" +}; + +static inline bool endsWith(const std::string& s, const std::string& suffix) +{ + return s.rfind(suffix) == std::abs((int)(s.size()-suffix.size())); +} + +class ADEPTRemove: public QRunnable +{ +public: + ADEPTRemove(QCoreApplication* app): + app(app) + { + setAutoDelete(false); + } + + void run() + { + int ret = 0; + try + { + gourou::DRMProcessor::ITEM_TYPE type; + DRMProcessorClientImpl client; + gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile); + + std::string filename; + if (!outputFile) + filename = std::string(inputFile); + else + filename = outputFile; + + if (outputDir) + { + QDir dir(outputDir); + if (!dir.exists(outputDir)) + dir.mkpath(outputDir); + + filename = std::string(outputDir) + "/" + filename; + } + + if (endsWith(filename, ".epub")) + type = gourou::DRMProcessor::ITEM_TYPE::EPUB; + else if (endsWith(filename, ".pdf")) + type = gourou::DRMProcessor::ITEM_TYPE::PDF; + else + { + EXCEPTION(gourou::DRM_FORMAT_NOT_SUPPORTED, "Unsupported file format of " << filename); + } + + if (inputFile != filename) + { + QFile::remove(filename.c_str()); + if (!QFile::copy(inputFile, filename.c_str())) + { + EXCEPTION(gourou::DRM_FILE_ERROR, "Unable to copy " << inputFile << " into " << filename); + } + processor.removeDRM(inputFile, filename, type); + std::cout << "DRM removed into new file " << filename << std::endl; + } + else + { + // Use temp file for PDF + if (type == gourou::DRMProcessor::ITEM_TYPE::PDF) + { + QTemporaryFile tempFile; + tempFile.open(); + tempFile.setAutoRemove(false); // In case of failure + processor.removeDRM(inputFile, tempFile.fileName().toStdString(), type); + /* Original file must be removed before doing a copy... */ + QFile origFile(inputFile); + origFile.remove(); + if (!QFile::copy(tempFile.fileName(), filename.c_str())) + { + EXCEPTION(gourou::DRM_FILE_ERROR, "Unable to copy " << tempFile.fileName().toStdString() << " into " << filename); + } + tempFile.setAutoRemove(true); + } + else + processor.removeDRM(inputFile, filename, type); + std::cout << "DRM removed from " << filename << std::endl; + } + } catch(std::exception& e) + { + std::cout << e.what() << std::endl; + ret = 1; + } + + this->app->exit(ret); + } + +private: + QCoreApplication* app; +}; + +static const char* findFile(const char* filename, bool inDefaultDirs=true) +{ + QFile file(filename); + + if (file.exists()) + return strdup(filename); + + if (!inDefaultDirs) return 0; + + for (int i=0; i<(int)ARRAY_SIZE(defaultDirs); i++) + { + QString path = QString(defaultDirs[i]) + QString(filename); + file.setFileName(path); + if (file.exists()) + return strdup(path.toStdString().c_str()); + } + + return 0; +} + +static void version(void) +{ + std::cout << "Current libgourou version : " << gourou::DRMProcessor::VERSION << std::endl ; +} + +static void usage(const char* cmd) +{ + std::cout << "Remove ADEPT DRM (from Adobe) of EPUB/PDF file" << std::endl; + + std::cout << "Usage: " << cmd << " [(-d|--device-file) device.xml] [(-a|--activation-file) activation.xml] [(-k|--device-key-file) devicesalt] [(-O|--output-dir) dir] [(-o|--output-file) output(.epub|.pdf|.der)] [(-v|--verbose)] [(-h|--help)] (-f|--input-file) file(.epub|pdf)" << std::endl << std::endl; + + std::cout << " " << "-d|--device-file" << "\t" << "device.xml file from eReader" << std::endl; + std::cout << " " << "-a|--activation-file" << "\t" << "activation.xml file from eReader" << std::endl; + std::cout << " " << "-k|--device-key-file" << "\t" << "private device key file (eg devicesalt/devkey.bin) from eReader" << std::endl; + std::cout << " " << "-O|--output-dir" << "\t" << "Optional output directory were to put result (default ./)" << std::endl; + std::cout << " " << "-o|--output-file" << "\t" << "Optional output filename (default inplace DRM removal>)" << std::endl; + std::cout << " " << "-f|--input-file" << "\t" << "EPUB/PDF file to process" << std::endl; + std::cout << " " << "-v|--verbose" << "\t\t" << "Increase verbosity, can be set multiple times" << std::endl; + std::cout << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl; + std::cout << " " << "-h|--help" << "\t\t" << "This help" << std::endl; + + std::cout << std::endl; + std::cout << "Device file, activation file and device key file are optionals. If not set, they are looked into :" << std::endl; + std::cout << " * Current directory" << std::endl; + std::cout << " * .adept" << std::endl; + std::cout << " * adobe-digital-editions directory" << std::endl; + std::cout << " * .adobe-digital-editions directory" << std::endl; +} + +int main(int argc, char** argv) +{ + int c, ret = -1; + + const char** files[] = {&devicekeyFile, &deviceFile, &activationFile}; + int verbose = gourou::DRMProcessor::getLogLevel(); + + while (1) { + int option_index = 0; + static struct option long_options[] = { + {"device-file", required_argument, 0, 'd' }, + {"activation-file", required_argument, 0, 'a' }, + {"device-key-file", required_argument, 0, 'k' }, + {"output-dir", required_argument, 0, 'O' }, + {"output-file", required_argument, 0, 'o' }, + {"input-file", required_argument, 0, 'f' }, + {"export-private-key",no_argument, 0, 'e' }, + {"verbose", no_argument, 0, 'v' }, + {"version", no_argument, 0, 'V' }, + {"help", no_argument, 0, 'h' }, + {0, 0, 0, 0 } + }; + + c = getopt_long(argc, argv, "d:a:k:O:o:f:evVh", + long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'd': + deviceFile = optarg; + break; + case 'a': + activationFile = optarg; + break; + case 'k': + devicekeyFile = optarg; + break; + case 'f': + inputFile = optarg; + break; + case 'O': + outputDir = optarg; + break; + case 'o': + outputFile = optarg; + break; + case 'v': + verbose++; + break; + case 'V': + version(); + return 0; + case 'h': + usage(argv[0]); + return 0; + default: + usage(argv[0]); + return -1; + } + } + + gourou::DRMProcessor::setLogLevel(verbose); + + if (!inputFile || (outputDir && !outputDir[0]) || + (outputFile && !outputFile[0])) + { + usage(argv[0]); + return -1; + } + + QCoreApplication app(argc, argv); + ADEPTRemove remover(&app); + + int i; + bool hasErrors = false; + const char* orig; + for (i=0; i<(int)ARRAY_SIZE(files); i++) + { + orig = *files[i]; + *files[i] = findFile(*files[i]); + if (!*files[i]) + { + std::cout << "Error : " << orig << " doesn't exists, did you activate your device ?" << std::endl; + ret = -1; + hasErrors = true; + } + } + + if (hasErrors) + goto end; + + QThreadPool::globalInstance()->start(&remover); + + ret = app.exec(); + +end: + for (i=0; i<(int)ARRAY_SIZE(files); i++) + { + if (*files[i]) + free((void*)*files[i]); + } + + return ret; +}