Compare commits

..

64 commits
v0.8 ... master

Author SHA1 Message Date
Radon Rosborough
d7bd98e719 Use -fmacro-prefix-map for utils
The __FILE__ macro is used in the EXCEPTION macro to report the file
name alongside error messages. This macro reports the file name
exactly as it is passed to the compiler. For most source files in
libgourou this is a nice relative path such as "src/libgourou.cpp".
However, for EXCEPTION instances in libgourou_common.h, the compiler
flag is "-I$(ROOT)/include", where $(ROOT) is an absolute path passed
from the higher Makefile. This results in an absolute path to the
build directory being hardcoded into the utils shared library and
binaries, and reported in error messages. Besides being less readable
than the more common relative paths, this triggers warnings from
packaging tools that detect inadvertent references to temporary build
directories that end up in compiled binaries and might indicate a bug.

There is a GCC feature -fmacro-prefix-map which allows to perform
substition on the values that are used for __FILE__. Use that option
to strip out the absolute path component, without changing any
functionality.

The feature was added to GCC on 2018-01-18 and released in GCC 8.1.0.

7365279fca
2026-03-14 13:51:40 +01:00
Grégory Soutadé
72cb22ad2a Refresh curl download percent only when updated 2026-02-17 20:07:57 +01:00
Grégory Soutadé
b44b988966 Fix bug in utils with -o and -O options 2026-02-01 08:56:27 +01:00
Grégory Soutadé
76cab18667 Delete all referenced objects deleted by libgourou during PDF DRM removal 2026-01-31 20:31:44 +01:00
Jake Waksbaum
2387dff2cb Add darwin support to Makefile 2026-01-25 10:15:35 +01:00
Jake Waksbaum
a7cfd3ef89 Replace sprintf with snprintf 2026-01-19 15:35:24 -05:00
Jake Waksbaum
772abdd2f9 Remove unused message field 2026-01-19 14:46:49 -05:00
Jake Waksbaum
1605656c73 Exception: fix constructor 2026-01-19 14:45:59 -05:00
Grégory Soutadé
8f0341d0bd Some code formating 2026-01-18 15:44:20 +01:00
Grégory Soutadé
8061681705 Add a warning when ACSM file is expired 2026-01-18 15:22:02 +01:00
Grégory Soutadé
724961566c Update version 2026-01-18 14:35:18 +01:00
Grégory Soutadé
98c511d0ca Fix mkstemp unused result warning 2026-01-18 14:31:52 +01:00
Grégory Soutadé
db3e2179a9 Add PKCS1 Type 2 padding for Signing request 2026-01-18 14:31:52 +01:00
Grégory Soutadé
28aefba6d6 Update README.md 2025-11-17 22:29:35 +01:00
Grégory Soutadé
e0e2bc7430 Update uPDFParser git address 2025-08-25 20:37:52 +02:00
Grégory Soutadé
d3c90f03bb Update README.md 2025-04-21 08:59:51 +02:00
Grégory Soutadé
469d378f9a Update version to 0.8.7 2025-02-16 21:32:51 +01:00
Philipp Pagel
98b531a232 Fix setup.sh
fix path for updfparser
2025-02-11 18:12:36 +01:00
Grégory Soutadé
956bad3068 Update README.md 2024-12-31 13:21:14 +01:00
Ismail Dönmez
d9a920b062 Use $HOME variable if it exists
Signed-off-by: Ismail Dönmez <ismail@i10z.com>
2024-09-22 10:55:54 +02:00
Grégory Soutadé
204500117d Typo fix 2024-09-19 08:41:15 +02:00
Grégory Soutadé
ce2cf4192a Update README.md 2024-08-31 08:27:53 +02:00
Grégory Soutadé
68bf48df27 Update README.md 2024-08-27 21:48:39 +02:00
Grégory Soutadé
81faf1f9be adept_loan_mgt: manage cases were name is empty 2024-04-15 07:37:28 +02:00
nicokosi
f60abf04d8 Fix typos in README.md 2024-04-11 06:05:23 +02:00
Grégory Soutadé
0d77cf55e1 Update version 2024-03-28 21:58:07 +01:00
Grégory Soutadé
86a79cc381 Remove whole EBX objects for PDF when removing DRM 2024-03-28 21:54:23 +01:00
Grégory Soutadé
68bf982b6f Fix use after free in DRMProcessorClientImpl::sendHTTPRequest() 2024-01-24 19:13:22 +01:00
Grégory Soutadé
ef8c2644ca Update version 2024-01-16 11:09:31 +01:00
Grégory Soutadé
e05639c09d Support HTTP error codes != 200 (exception) and cookies in drmprocessorclientimpl 2024-01-06 09:25:11 +01:00
Grégory Soutadé
69865e005b Register operatorURL only when certificate from operator is fetched 2024-01-06 09:23:03 +01:00
Grégory Soutadé
fd38e84da6 Fix for Android for adept_loan_mgt.cpp (strptime format) 2024-01-06 09:22:11 +01:00
Grégory Soutadé
92a67312bd Update README.md 2023-09-06 21:21:43 +02:00
Grégory Soutadé
29d298b373 Update libgourou version 2023-09-06 21:17:06 +02:00
Grégory Soutadé
40dcb7a041 Add --no-notify option to utils 2023-09-06 21:17:06 +02:00
Grégory Soutadé
e0bb1bd4f8 Add notify server feature 2023-09-06 21:17:06 +02:00
Grégory Soutadé
9388d82138 Loan ID must be Fullfilment ID, not <loan> value(s) 2023-09-04 18:28:47 +02:00
Grégory Soutadé
c19279397f Update README.md 2023-08-09 20:58:06 +02:00
Grégory Soutadé
bb5349d710 Update version 2023-08-08 20:14:23 +02:00
Grégory Soutadé
9a75213b49 Fix misuse of DESTDIR and PREFIX in Makefile 2023-08-08 20:13:03 +02:00
Grégory Soutadé
e06d20a392 DRM removal: Forgot to decrypt HexaString objects 2023-08-05 14:43:48 +02:00
Grégory Soutadé
a0f6324999 Try to fix GCC 13 compilation errors 2023-05-03 21:15:31 +02:00
Grégory Soutadé
c259cbd5a3 Add missing libgen.h in utils for basename() call 2023-03-28 20:32:05 +02:00
Grégory Soutadé
46afe771c7 Remove old pugixml include in utils Makefile 2023-01-15 09:51:00 +01:00
Grégory Soutadé
cad2189fc2 Typo fix 2023-01-14 18:21:06 +01:00
Grégory Soutadé
50bc16079f Fix static build 2023-01-14 12:56:06 +01:00
Grégory Soutadé
1213b34250 Fix ADEPT path creation within adept_activate 2023-01-11 19:57:47 +01:00
Grégory Soutadé
a66dcb959c Work on Makefile 2023-01-11 19:57:47 +01:00
Grégory Soutadé
84b01a5de3 Use system version of pugixml, not a checkouted one 2023-01-08 21:15:33 +01:00
Grégory Soutadé
be78d24236 Add man pages for utils 2023-01-08 21:05:22 +01:00
Grégory Soutadé
8aec5be244 Update Makefile to be more GNU/Linux style 2023-01-08 21:05:04 +01:00
Grégory Soutadé
3a0ab4b438 Update --help for utils and README.md 2023-01-08 21:04:23 +01:00
Grégory Soutadé
891ed05926 We can now specify directly file path for acsmdownloader and adept_remove (-f stille keeped for compatibility) 2023-01-08 20:58:41 +01:00
Grégory Soutadé
fc839e671a Manage ACSM files that contains server internal error 2023-01-06 21:17:57 +01:00
Grégory Soutadé
ab5afa5003 Add new default ADEPT directories : /home/<user>/.config/adept and $ADEPT_DIR environment variable 2023-01-05 21:29:55 +01:00
Grégory Soutadé
937c27fc23 Fix some warnings 2023-01-05 21:27:50 +01:00
Grégory Soutadé
34216d1b6e Check for target user before trying to decrypt a file 2023-01-05 21:26:05 +01:00
Berwyn
ffd2004cbb Correct 'any book loaned' message 2023-01-04 20:23:57 +01:00
Grégory Soutadé
c41dd46ca7 Check for potential write error (or not buffer fully consumed) 2022-12-23 17:51:51 +01:00
Grégory Soutadé
e4bd73c03d Add global option -D to utils, allowing to specify .adept directory instead of every single files. WARNING : -D has been changed by -d in adept_loan_mgt ! 2022-12-21 21:23:42 +01:00
Grégory Soutadé
f65e8cd9eb Check for target user before trying to decrypt a file 2022-12-21 21:06:03 +01:00
Grégory Soutadé
24bae89095 Factorize decryptADEPTKey() for ePub and PDF 2022-12-21 20:58:49 +01:00
Grégory Soutadé
afab1c0012 Fix over encrypted RSA key decryption algorithm 2022-12-21 20:15:11 +01:00
Grégory Soutadé
7878f91cdd Add support for MacOS and old compilers (not supporting C++11). Main patch is from Samuel Marks. 2022-11-21 17:56:29 +01:00
35 changed files with 4708 additions and 3940 deletions

View file

@ -1,21 +1,41 @@
PREFIX ?= /usr/local
LIBDIR ?= /lib
INCDIR ?= /include
AR ?= $(CROSS)ar
CXX ?= $(CROSS)g++
UPDFPARSERLIB = ./lib/updfparser/libupdfparser.a
CXXFLAGS=-Wall -fPIC -I./include -I./lib/pugixml/src/ -I./lib/updfparser/include
LDFLAGS = $(UPDFPARSERLIB)
CXXFLAGS += -Wall -fPIC -I./include -I./usr/include/pugixml -I./lib/updfparser/include
LDFLAGS = -lpugixml
VERSION := $(shell cat include/libgourou.h |grep LIBGOUROU_VERSION|cut -d '"' -f2)
UNAME := $(shell uname -s)
BUILD_STATIC ?= 0
BUILD_SHARED ?= 1
BUILD_UTILS ?= 1
TARGETS =
TARGET_LIBRARIES =
ifneq ($(STATIC_UTILS),)
BUILD_STATIC=1
endif
ifneq ($(BUILD_STATIC), 0)
TARGETS += libgourou.a
TARGET_LIBRARIES += libgourou.a
STATIC_UTILS=1
endif
ifneq ($(BUILD_SHARED), 0)
ifeq ($(UNAME), Darwin)
TARGETS += libgourou.dylib
TARGET_LIBRARIES += libgourou.dylib libgourou.dylib.$(VERSION)
else
TARGETS += libgourou.so
TARGET_LIBRARIES += libgourou.so libgourou.so.$(VERSION)
endif
endif
ifneq ($(BUILD_UTILS), 0)
TARGETS += build_utils
@ -33,16 +53,17 @@ CXXFLAGS += -DSTATIC_NONCE=1
endif
SRCDIR := src
INCDIR := inc
BUILDDIR := obj
TARGETDIR := bin
SRCEXT := cpp
OBJEXT := o
SOURCES = src/libgourou.cpp src/user.cpp src/device.cpp src/fulfillment_item.cpp src/loan_token.cpp src/bytearray.cpp src/pugixml.cpp
SOURCES = src/libgourou.cpp src/user.cpp src/device.cpp src/fulfillment_item.cpp src/loan_token.cpp src/bytearray.cpp
OBJECTS := $(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES:.$(SRCEXT)=.$(OBJEXT)))
all: lib obj $(TARGETS)
all: version lib obj $(TARGETS)
version:
@echo "Building libgourou $(VERSION)"
lib:
mkdir lib
@ -57,21 +78,48 @@ obj:
$(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT)
$(CXX) $(CXXFLAGS) -c $^ -o $@
libgourou: libgourou.a libgourou.so
libgourou: $(TARGET_LIBRARIES)
libgourou.a: $(OBJECTS) $(UPDFPARSERLIB)
$(AR) crs $@ obj/*.o $(UPDFPARSERLIB)
$(AR) rcs --thin $@ $^
libgourou.so: $(OBJECTS) $(UPDFPARSERLIB)
$(CXX) obj/*.o $(LDFLAGS) -o $@ -shared
libgourou.so.$(VERSION): $(OBJECTS) $(UPDFPARSERLIB)
$(CXX) $^ -Wl,-soname,$@ $(LDFLAGS) -o $@ -shared
build_utils:
make -C utils ROOT=$(PWD) CXX=$(CXX) AR=$(AR) DEBUG=$(DEBUG) STATIC_UTILS=$(STATIC_UTILS)
libgourou.so: libgourou.so.$(VERSION)
ln -f -s $^ $@
libgourou.dylib.$(VERSION): $(OBJECTS) $(UPDFPARSERLIB)
$(CXX) $^ $(LDFLAGS) -o $@ -shared
libgourou.dylib: libgourou.dylib.$(VERSION)
ln -f -s $^ $@
build_utils: $(TARGET_LIBRARIES)
$(MAKE) -C utils ROOT=$(PWD) CXX=$(CXX) AR=$(AR) DEBUG=$(DEBUG) STATIC_UTILS=$(STATIC_UTILS) DESTDIR=$(DESTDIR) PREFIX=$(PREFIX)
install: $(TARGET_LIBRARIES)
install -d $(DESTDIR)$(PREFIX)$(LIBDIR)
# Use cp to preserver symlinks
cp --no-dereference $(TARGET_LIBRARIES) $(DESTDIR)$(PREFIX)$(LIBDIR)
$(MAKE) -C utils ROOT=$(PWD) CXX=$(CXX) AR=$(AR) DEBUG=$(DEBUG) STATIC_UTILS=$(STATIC_UTILS) DESTDIR=$(DESTDIR) PREFIX=$(PREFIX) install
uninstall:
cd $(DESTDIR)$(PREFIX)/$(LIBDIR)
rm -f $(TARGET_LIBRARIES) libgourou.so.$(VERSION)
cd -
install_headers:
install -d $(DESTDIR)$(PREFIX)/$(INCDIR)/libgourou
cp --no-dereference include/*.h $(DESTDIR)$(PREFIX)/$(INCDIR)/libgourou
uninstall_headers:
rm -rf $(DESTDIR)$(PREFIX)/$(INCDIR)/libgourou
clean:
rm -rf libgourou.a libgourou.so obj
make -C utils clean
rm -rf libgourou.a libgourou.so libgourou.so.$(VERSION)* obj
$(MAKE) -C utils clean
ultraclean: clean
rm -rf lib
make -C utils ultraclean
$(MAKE) -C utils ultraclean

View file

@ -1,16 +1,16 @@
Introduction
------------
libgourou is a free implementation of Adobe's ADEPT protocol used to add DRM on ePub/PDF files. It overcome the lacks of Adobe support for Linux platforms.
libgourou is a free implementation of Adobe's ADEPT protocol used to add DRM on ePub/PDF files. It overcomes the lack of Adobe support for Linux platforms.
Architecture
------------
Like RMSDK, libgourou has a client/server scheme. All platform specific functions (crypto, network...) has to be implemented in a client class (that derives from DRMProcessorClient) while server implements ADEPT protocol.
A reference implementation using Qt, OpenSSL and libzip is provided (in _utils_ directory).
Like RMSDK, libgourou has a client/server scheme. All platform specific functions (crypto, network...) have to be implemented in a client class (that derives from DRMProcessorClient) while server implements ADEPT protocol.
A reference implementation using cURL, OpenSSL and libzip is provided (in _utils_ directory).
Main fucntions to use from gourou::DRMProcessor are :
Main functions to use from gourou::DRMProcessor are:
* Get an ePub from an ACSM file : _fulfill()_ and _download()_
* Create a new device : _createDRMProcessor()_
@ -23,11 +23,11 @@ You can import configuration from (at least) :
* Kobo device : .adept/device.xml, .adept/devicesalt and .adept/activation.xml
* Bookeen device : .adobe-digital-editions/device.xml, root/devkey.bin and .adobe-digital-editions/activation.xml
Or create a new one. Be careful : there is a limited number of devices that can be created bye one account.
Or create a new one. Be careful: there is a limited number of devices that can be created by one account.
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 without adept_remove, you can export your private key and import it within [Calibre](https://calibre-ebook.com/) an its DeDRM plugin.
For those who want 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
@ -37,22 +37,23 @@ For libgourou :
_externals_ :
* None
* libpugixml
_internals_:
* PugiXML
* Base64
* uPDFParser
For utils:
* libcurl
* OpenSSL
* openssl
* libzip
* libpugixml
Internal libraries are automatically fetched and statically compiled during the first run.
External & utils dependencies has to be installed by your package manager (_apt_ for example).
Use _-dev_ flavours to get needed headers.
Internal libraries are automatically fetched and statically compiled during the first compilation.
When you update libgourou's repository, **don't forget to update internal libraries** with:
make update_lib
@ -63,7 +64,7 @@ Compilation
Use _make_ command
make [CROSS=XXX] [DEBUG=(0*|1)] [STATIC_UTILS=(0*|1)] [BUILD_UTILS=(0|1*)] [BUILD_STATIC=(0*|1)] [BUILD_SHARED=(0|1*)] [all*|clean|ultraclean|build_utils]
make [CROSS=XXX] [DEBUG=(0*|1)] [STATIC_UTILS=(0*|1)] [BUILD_UTILS=(0|1*)] [BUILD_STATIC=(0*|1)] [BUILD_SHARED=(0|1*)] [all*|clean|ultraclean|build_utils|install|uninstall]
CROSS can define a cross compiler prefix (ie arm-linux-gnueabihf-)
@ -77,47 +78,60 @@ BUILD_STATIC build libgourou.a if 1, nothing if 0, can be combined with BUILD_SH
BUILD_SHARED build libgourou.so if 1, nothing if 0, can be combined with BUILD_STATIC
other variables are DESTDIR and PREFIX to handle destination install directory
* Default value
Utils
-----
You can import configuration from your eReader or create a new one with _utils/adept\_activate_ :
First, add libgourou.so to your LD_LIBRARY_PATH
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
You can optionaly specify your .adept directory
export ADEPT_DIR=/home/XXX
Then, use utils as following:
You can import configuration from your eReader or create a new one with _utils/adept\_activate_:
./utils/adept_activate -u <AdobeID USERNAME>
Then a _./.adept_ directory is created with all configuration file
Then a _/home/<user>/.config/adept_ directory is created with all configuration file
To download an ePub/PDF:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
./utils/acsmdownloader -f <ACSM_FILE>
./utils/acsmdownloader <ACSM_FILE>
To export your private key (for DeDRM software):
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 <encryptedFile>
./utils/adept_remove <encryptedFile>
To list loaned books:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
./utils/adept_loan_mgt [-l]
To return a loaned book:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
./utils/adept_loan_mgt -r <id>
You can get utils full options description with -h or --help switch
Binary packages
---------------
Compiled version (and AppImage) of libgourou and utils are available in [Release page](https://forge.soutade.fr/soutade/libgourou/releases)
Docker
------
@ -143,3 +157,19 @@ Special thanks
* _Jens_ for all test samples and utils testing
* _Milian_ for debug & code
* _Berwyn H_ for all test samples, feedbacks, patches and kind donation
Donation
--------
https://www.paypal.com/donate/?hosted_button_id=JD3U6XMZCPHKN
Donators
--------
* _Berwyn H_
* _bwitt_
* _Ismail_
* _Radon_

View file

@ -26,6 +26,7 @@
*/
#include <string>
#include <stdint.h>
namespace macaron {
@ -33,7 +34,11 @@ class Base64 {
public:
static std::string Encode(const std::string data) {
static constexpr char sEncodingTable[] = {
static
#if __STDC_VERSION__ >= 201112L
constexpr
#endif /* __STDC_VERSION__ >= 201112L */
char sEncodingTable[] = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
@ -73,7 +78,11 @@ class Base64 {
}
static std::string Decode(const std::string& input, std::string& out) {
static constexpr unsigned char kDecodingTable[] = {
static
#if __STDC_VERSION__ >= 201112L
constexpr
#endif /* __STDC_VERSION__ >= 201112L */
unsigned char kDecodingTable[] = {
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63,

View file

@ -27,20 +27,17 @@
#include "drmprocessorclient.h"
#include <pugixml.hpp>
#include <stdint.h>
#ifndef HOBBES_DEFAULT_VERSION
#define HOBBES_DEFAULT_VERSION "10.0.4"
#endif
#ifndef DEFAULT_ADEPT_DIR
#define DEFAULT_ADEPT_DIR "./.adept"
#endif
#ifndef ACS_SERVER
#define ACS_SERVER "http://adeactivate.adobe.com/adept"
#endif
#define LIBGOUROU_VERSION "0.8"
#define LIBGOUROU_VERSION "0.8.8"
namespace gourou
{
@ -70,10 +67,11 @@ namespace gourou
* @brief Fulfill ACSM file to server in order to retrieve ePub fulfillment item
*
* @param ACSMFile Path of ACSMFile
* @param notify Notify server if requested by response
*
* @return a FulfillmentItem if all is OK
*/
FulfillmentItem* fulfill(const std::string& ACSMFile);
FulfillmentItem* fulfill(const std::string& ACSMFile, bool notify=true);
/**
* @brief Once fulfilled, ePub file needs to be downloaded.
@ -105,8 +103,14 @@ namespace gourou
*
* @param loanID Loan ID received during fulfill
* @param operatorURL URL of operator that loans this book
* @param notify Notify server if requested by response
*/
void returnLoan(const std::string& loanID, const std::string& operatorURL);
void returnLoan(const std::string& loanID, const std::string& operatorURL, bool notify=true);
/**
* @brief Return default ADEPT directory (ie /home/<user>/.config/adept)
*/
static std::string getDefaultAdeptDir(void);
/**
* @brief Create a new ADEPT environment (device.xml, devicesalt and activation.xml).
@ -118,7 +122,7 @@ namespace gourou
* @param ACSServer Override main ACS server (default adeactivate.adobe.com)
*/
static DRMProcessor* createDRMProcessor(DRMProcessorClient* client,
bool randomSerial=false, const std::string& dirName=std::string(DEFAULT_ADEPT_DIR),
bool randomSerial=false, std::string dirName=std::string(""),
const std::string& hobbes=std::string(HOBBES_DEFAULT_VERSION),
const std::string& ACSServer=ACS_SERVER);
@ -231,8 +235,11 @@ namespace gourou
void buildSignInRequest(pugi::xml_document& signInRequest, const std::string& adobeID, const std::string& adobePassword, const std::string& authenticationCertificate);
void fetchLicenseServiceCertificate(const std::string& licenseURL,
const std::string& operatorURL);
std::string encryptedKeyFirstPass(pugi::xml_document& rightsDoc, const std::string& encryptedKey, const std::string& keyType, ITEM_TYPE type);
void decryptADEPTKey(const std::string& encryptedKey, unsigned char* decryptedKey);
void buildNotifyReq(pugi::xml_document& returnReq, pugi::xml_node& body);
void notifyServer(pugi::xml_node& notifyRoot);
void notifyServer(pugi::xml_document& fulfillReply);
std::string encryptedKeyFirstPass(pugi::xml_document& rightsDoc, const std::string& encryptedKey, const std::string& keyType);
void decryptADEPTKey(pugi::xml_document& rightsDoc, unsigned char* decryptedKey, const unsigned char* encryptionKey=0, unsigned encryptionKeySize=0);
void removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut, const unsigned char* encryptionKey, unsigned encryptionKeySize);
void generatePDFObjectKey(int version,
const unsigned char* masterKey, unsigned int masterKeyLength,

View file

@ -64,7 +64,8 @@ namespace gourou
FF_INVALID_ACSM_FILE,
FF_NO_HMAC_IN_ACSM_FILE,
FF_NOT_ACTIVATED,
FF_NO_OPERATOR_URL
FF_NO_OPERATOR_URL,
FF_SERVER_INTERNAL_ERROR
};
enum DOWNLOAD_ERROR {
@ -119,6 +120,7 @@ namespace gourou
CLIENT_OSSL_ERROR,
CLIENT_CRYPT_ERROR,
CLIENT_DIGEST_ERROR,
CLIENT_HTTP_ERROR
};
enum DRM_REMOVAL_ERROR {
@ -129,9 +131,20 @@ namespace gourou
DRM_IN_OUT_EQUALS,
DRM_MISSING_PARAMETER,
DRM_INVALID_KEY_SIZE,
DRM_ERR_ENCRYPTION_KEY_FP
DRM_ERR_ENCRYPTION_KEY_FP,
DRM_INVALID_USER
};
#ifndef _NOEXCEPT
#if __STDC_VERSION__ >= 201112L
# define _NOEXCEPT noexcept
# define _NOEXCEPT_(x) noexcept(x)
#else
# define _NOEXCEPT throw()
# define _NOEXCEPT_(x)
#endif
#endif /* !_NOEXCEPT */
/**
* Generic exception class
*/
@ -152,12 +165,12 @@ namespace gourou
Exception(const Exception& other)
{
this->code = other.code;
this->line = line;
this->file = file;
this->line = other.line;
this->file = other.file;
this->fullmessage = strdup(other.fullmessage);
}
~Exception()
~Exception() _NOEXCEPT
{
free(fullmessage);
}
@ -168,7 +181,7 @@ namespace gourou
private:
int code, line;
const char* message, *file;
const char* file;
char* fullmessage;
};
@ -223,12 +236,7 @@ namespace gourou
return ltrim(rtrim(s, t), t);
}
/**
* @brief Extract text node from tag in document
* It can throw an exception if tag does not exists
* or just return an empty value
*/
static inline std::string extractTextElem(const pugi::xml_node& root, const char* tagName, bool throwOnNull=true)
static inline pugi::xml_node getNode(const pugi::xml_node& root, const char* tagName, bool throwOnNull=true)
{
pugi::xpath_node xpath_node = root.select_node(tagName);
@ -237,10 +245,23 @@ namespace gourou
if (throwOnNull)
EXCEPTION(GOUROU_TAG_NOT_FOUND, "Tag " << tagName << " not found");
return "";
return pugi::xml_node();
}
pugi::xml_node node = xpath_node.node().first_child();
return xpath_node.node();
}
/**
* @brief Extract text node from tag in document
* It can throw an exception if tag does not exists
* or just return an empty value
*/
static inline std::string extractTextElem(const pugi::xml_node& root, const char* tagName, bool throwOnNull=true)
{
pugi::xml_node node = getNode(root, tagName, throwOnNull);
node = node.first_child();
if (!node)
{
@ -254,6 +275,30 @@ namespace gourou
return trim(res);
}
/**
* @brief Set text node of a tag in document
* It can throw an exception if tag does not exists
*/
static inline void setTextElem(const pugi::xml_node& root, const char* tagName,
const std::string& value, bool throwOnNull=true)
{
pugi::xml_node node = getNode(root, tagName, throwOnNull);
if (!node)
{
if (throwOnNull)
EXCEPTION(GOUROU_TAG_NOT_FOUND, "Text element for tag " << tagName << " not found");
return;
}
node = node.first_child();
if (!node)
node.append_child(pugi::node_pcdata).set_value(value.c_str());
else
node.set_value(value.c_str());
}
/**
* @brief Extract text attribute from tag in document
* It can throw an exception if attribute does not exists
@ -261,17 +306,9 @@ namespace gourou
*/
static inline std::string extractTextAttribute(const pugi::xml_node& root, const char* tagName, const char* attributeName, bool throwOnNull=true)
{
pugi::xpath_node xpath_node = root.select_node(tagName);
pugi::xml_node node = getNode(root, tagName, throwOnNull);
if (!xpath_node)
{
if (throwOnNull)
EXCEPTION(GOUROU_TAG_NOT_FOUND, "Tag " << tagName << " not found");
return "";
}
pugi::xml_attribute attr = xpath_node.node().attribute(attributeName);
pugi::xml_attribute attr = node.attribute(attributeName);
if (!attr)
{

View file

@ -1,16 +1,8 @@
#!/bin/bash
# Pugixml
if [ ! -d lib/pugixml ] ; then
git clone https://github.com/zeux/pugixml.git lib/pugixml
pushd lib/pugixml
git checkout latest
popd
fi
# uPDFParser
if [ ! -d lib/updfparser ] ; then
git clone git://soutade.fr/updfparser.git lib/updfparser
git clone https://forge.soutade.fr/soutade/uPDFParser.git lib/updfparser
pushd lib/updfparser
make BUILD_STATIC=1 BUILD_SHARED=0
popd

View file

@ -1,17 +1,12 @@
#!/bin/bash
if [ ! -d lib/pugixml -o ! -d lib/updfparser ] ; then
if [ ! -d lib/updfparser ] ; then
echo "Some libraries are missing"
echo "You must run this script at the top of libgourou working direcotry."
echo "./lib/setup.sh must be called first (make all)"
echo "./scripts/setup.sh must be called first (make all)"
exit 1
fi
# Pugixml
pushd lib/pugixml
git pull origin latest
popd
# uPDFParser
pushd lib/updfparser
git pull origin master

View file

@ -202,7 +202,7 @@ namespace gourou
char* tmp = new char[_length*2+1];
for(int i=0; i<(int)_length; i++)
sprintf(&tmp[i*2], "%02x", _data[i]);
snprintf(&tmp[i*2], (_length-i)*2+1, "%02x", _data[i]);
tmp[_length*2] = 0;

View file

@ -29,13 +29,23 @@
#include <libgourou_log.h>
#include <device.h>
// From https://stackoverflow.com/questions/1779715/how-to-get-mac-address-of-your-machine-using-a-c-program/35242525
#include <string.h>
#if defined(__linux__) || defined(linux) || defined(__linux)
#include <sys/ioctl.h>
#include <net/if.h>
#include <unistd.h>
#include <netinet/in.h>
#include <string.h>
#elif (defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) \
|| defined(__bsdi__) || defined(__DragonFly__) || defined(__APPLE__))
#include <ifaddrs.h>
#include <sys/socket.h>
#include <net/if_dl.h>
#define BSD_HEADERS 1
#endif
#if defined(__linux__) || defined(linux) || defined(__linux)
// From https://stackoverflow.com/questions/1779715/how-to-get-mac-address-of-your-machine-using-a-c-program/35242525
int get_mac_address(unsigned char* mac_address)
{
struct ifreq ifr;
@ -74,6 +84,43 @@ int get_mac_address(unsigned char* mac_address)
return 1;
}
#elif BSD_HEADERS
// https://stackoverflow.com/a/3978293
int get_mac_address(unsigned char* mac_address, const char* if_name = "en0")
{
ifaddrs* iflist;
int found = 0;
if (getifaddrs(&iflist) == 0) {
for (ifaddrs* cur = iflist; cur; cur = cur->ifa_next) {
if ((cur->ifa_addr->sa_family == AF_LINK) &&
(strcmp(cur->ifa_name, if_name) == 0) &&
cur->ifa_addr) {
sockaddr_dl* sdl = (sockaddr_dl*)cur->ifa_addr;
memcpy(mac_address, LLADDR(sdl), sdl->sdl_alen);
found = 1;
break;
}
}
freeifaddrs(iflist);
}
return found;
}
#else
int get_mac_address(unsigned char* mac_address)
{
GOUROU_LOG(INFO, "get_mac_address() not implemented for your platform, using a static address");
mac_address[0] = 0x8D;
mac_address[1] = 0x70;
mac_address[2] = 0x13;
mac_address[3] = 0x8D;
mac_address[4] = 0x43;
mac_address[5] = 0x27;
return 1;
}
#endif /* defined(__linux__) || defined(linux) || defined(__linux) */
namespace gourou

View file

@ -17,6 +17,7 @@
along with libgourou. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cctype>
#include <fulfillment_item.h>
#include <libgourou_common.h>
#include "user.h"
@ -93,8 +94,12 @@ namespace gourou
std::string FulfillmentItem::getMetadata(std::string name)
{
// https://stackoverflow.com/questions/313970/how-to-convert-an-instance-of-stdstring-to-lower-case
#if __STDC_VERSION__ >= 201112L
std::transform(name.begin(), name.end(), name.begin(),
[](unsigned char c){ return std::tolower(c); });
#else
std::transform(name.begin(), name.end(), name.begin(), tolower);
#endif
name = std::string("dc:") + name;
pugi::xpath_node path = metadatas.select_node(name.c_str());

View file

@ -21,6 +21,7 @@
#include <sys/time.h>
#include <time.h>
#include <vector>
#include <ctime>
#include <uPDFParser.h>
@ -28,6 +29,8 @@
#include <libgourou_common.h>
#include <libgourou_log.h>
#define LOCAL_ADEPT_DIR "./.adept"
#define ASN_NONE 0x00
#define ASN_NS_TAG 0x01
#define ASN_CHILD 0x02
@ -68,11 +71,23 @@ namespace gourou
if (user) delete user;
}
DRMProcessor* DRMProcessor::createDRMProcessor(DRMProcessorClient* client, bool randomSerial, const std::string& dirName,
// function to parse a date or time string.
// https://www.geeksforgeeks.org/cpp/date-and-time-parsing-in-cpp/
static time_t parseDateTime(const char* datetimeString, const char* format)
{
struct tm tmStruct;
strptime(datetimeString, format, &tmStruct);
return mktime(&tmStruct);
}
DRMProcessor* DRMProcessor::createDRMProcessor(DRMProcessorClient* client, bool randomSerial, std::string dirName,
const std::string& hobbes, const std::string& ACSServer)
{
DRMProcessor* processor = new DRMProcessor(client);
if (dirName == "")
dirName = getDefaultAdeptDir();
Device* device = Device::createDevice(processor, dirName, hobbes, randomSerial);
processor->device = device;
@ -394,30 +409,6 @@ namespace gourou
}
doOperatorAuth(operatorURL);
// Add new operatorURL to list
pugi::xml_document activationDoc;
user->readActivation(activationDoc);
pugi::xml_node root;
pugi::xpath_node xpathRes = activationDoc.select_node("//adept:operatorURLList");
// Create adept:operatorURLList if it doesn't exists
if (!xpathRes)
{
xpathRes = activationDoc.select_node("/activationInfo");
root = xpathRes.node();
root = root.append_child("adept:operatorURLList");
root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS;
appendTextElem(root, "adept:user", user->getUUID());
}
else
root = xpathRes.node();
appendTextElem(root, "adept:operatorURL", operatorURL);
user->updateActivationFile(activationDoc);
}
void DRMProcessor::buildFulfillRequest(pugi::xml_document& acsmDoc, pugi::xml_document& fulfillReq)
@ -487,10 +478,28 @@ namespace gourou
appendTextElem(root, "adept:licenseURL", licenseURL);
appendTextElem(root, "adept:certificate", certificate);
// Add new operatorURL to list
xpathRes = activationDoc.select_node("//adept:operatorURLList");
// Create adept:operatorURLList if it doesn't exists
if (!xpathRes)
{
xpathRes = activationDoc.select_node("/activationInfo");
root = xpathRes.node();
root = root.append_child("adept:operatorURLList");
root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS;
appendTextElem(root, "adept:user", user->getUUID());
}
else
root = xpathRes.node();
appendTextElem(root, "adept:operatorURL", operatorURL);
user->updateActivationFile(activationDoc);
}
FulfillmentItem* DRMProcessor::fulfill(const std::string& ACSMFile)
FulfillmentItem* DRMProcessor::fulfill(const std::string& ACSMFile, bool notify)
{
if (!user->getPKCS12().length())
EXCEPTION(FF_NOT_ACTIVATED, "Device not activated");
@ -500,14 +509,31 @@ namespace gourou
if (!acsmDoc.load_file(ACSMFile.c_str(), pugi::parse_ws_pcdata_single|pugi::parse_escapes, pugi::encoding_utf8))
EXCEPTION(FF_INVALID_ACSM_FILE, "Invalid ACSM file " << ACSMFile);
// Could be an server internal error
pugi::xml_node rootNode = acsmDoc.first_child();
if (std::string(rootNode.name()) == "error")
{
EXCEPTION(FF_SERVER_INTERNAL_ERROR, rootNode.attribute("data").value());
}
GOUROU_LOG(INFO, "Fulfill " << ACSMFile);
std::string expiration = extractTextElem(rootNode, "expiration", false);
if (expiration != "")
{
time_t expirationTime = parseDateTime(expiration.c_str(), "%Y-%m-%dT%H:%M:%S");
if (time(NULL) > expirationTime)
GOUROU_LOG(WARN, "WARNING: ACSM file expired (" << expiration << "), It may not work.");
}
// Build req file
pugi::xml_document fulfillReq;
buildFulfillRequest(acsmDoc, fulfillReq);
pugi::xpath_node root = fulfillReq.select_node("//adept:fulfill");
pugi::xml_node rootNode = root.node();
rootNode = root.node();
// Remove HMAC
pugi::xpath_node xpathRes = fulfillReq.select_node("//hmac");
@ -568,7 +594,12 @@ namespace gourou
fetchLicenseServiceCertificate(licenseURL, operatorURL);
return new FulfillmentItem(fulfillReply, user);
FulfillmentItem* item = new FulfillmentItem(fulfillReply, user);
if (notify)
notifyServer(fulfillReply);
return item;
}
DRMProcessor::ITEM_TYPE DRMProcessor::download(FulfillmentItem* item, std::string path, bool resume)
@ -845,7 +876,31 @@ namespace gourou
signNode(root);
}
void DRMProcessor::returnLoan(const std::string& loanID, const std::string& operatorURL)
std::string DRMProcessor::getDefaultAdeptDir(void)
{
#ifndef DEFAULT_ADEPT_DIR
const char* home = getenv("HOME");
if (home)
return home + std::string("/.config/adept/");
else
{
const char* user = getenv("USER");
if (user && user[0])
{
return std::string("/home/") + user + std::string("/.config/adept/");
}
else
return LOCAL_ADEPT_DIR;
}
#else
return DEFAULT_ADEPT_DIR "/";
#endif
}
void DRMProcessor::returnLoan(const std::string& loanID, const std::string& operatorURL,
bool notify)
{
pugi::xml_document returnReq;
@ -853,7 +908,71 @@ namespace gourou
buildReturnReq(returnReq, loanID, operatorURL);
sendRequest(returnReq, operatorURL + "/LoanReturn");
ByteArray replyData = sendRequest(returnReq, operatorURL + "/LoanReturn");
pugi::xml_document fulfillReply;
fulfillReply.load_string((const char*)replyData.data());
if (notify)
notifyServer(fulfillReply);
}
void DRMProcessor::buildNotifyReq(pugi::xml_document& returnReq, pugi::xml_node& body)
{
pugi::xml_node decl = returnReq.append_child(pugi::node_declaration);
decl.append_attribute("version") = "1.0";
pugi::xml_node root = returnReq.append_child("adept:notification");
root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS;
appendTextElem(root, "adept:user", user->getUUID());
appendTextElem(root, "adept:device", user->getDeviceUUID());
body = root.append_copy(body);
body.append_attribute("xmlns") = ADOBE_ADEPT_NS;
addNonce(root);
signNode(root);
}
void DRMProcessor::notifyServer(pugi::xml_node& notifyRoot)
{
std::string notifyUrl = extractTextElem(notifyRoot, "//notifyURL", false);
pugi::xml_node notifyBody = getNode(notifyRoot, "//body", false);
if (notifyUrl == "")
{
GOUROU_LOG(INFO, "No notify URL");
return;
}
if (!notifyBody)
{
GOUROU_LOG(INFO, "No notify body");
return;
}
pugi::xml_document notifyReq;
buildNotifyReq(notifyReq, notifyBody);
sendRequest(notifyReq, notifyUrl);
}
void DRMProcessor::notifyServer(pugi::xml_document& fulfillReply)
{
pugi::xpath_node_set notifySet = fulfillReply.select_nodes("//notify");
if (notifySet.empty())
{
GOUROU_LOG(DEBUG, "No notify request");
return;
}
for (pugi::xpath_node_set::const_iterator it = notifySet.begin(); it != notifySet.end(); ++it)
{
pugi::xml_node notifyRoot = it->node();
notifyServer(notifyRoot);
}
}
ByteArray DRMProcessor::encryptWithDeviceKey(const unsigned char* data, unsigned int len)
@ -931,45 +1050,33 @@ namespace gourou
void DRMProcessor::exportPrivateLicenseKey(std::string path)
{
int fd = open(path.c_str(), O_CREAT|O_TRUNC|O_WRONLY, S_IRWXU);
int ret;
if (fd <= 0)
EXCEPTION(GOUROU_FILE_ERROR, "Unable to open " << path);
ByteArray privateLicenseKey = ByteArray::fromBase64(user->getPrivateLicenseKey());
/* In adobekey.py, we get base64 decoded data [26:] */
write(fd, privateLicenseKey.data()+26, privateLicenseKey.length()-26);
ret = write(fd, privateLicenseKey.data()+26, privateLicenseKey.length()-26);
close(fd);
if (ret != (int)(privateLicenseKey.length()-26))
{
EXCEPTION(gourou::GOUROU_FILE_ERROR, "Error writing " << path);
}
}
int DRMProcessor::getLogLevel() {return (int)gourou::logLevel;}
void DRMProcessor::setLogLevel(int logLevel) {gourou::logLevel = (GOUROU_LOG_LEVEL)logLevel;}
void DRMProcessor::decryptADEPTKey(const std::string& encryptedKey, unsigned char* decryptedKey)
{
if (encryptedKey.size() != 172)
EXCEPTION(DRM_INVALID_KEY_SIZE, "Invalid encrypted key size (" << encryptedKey.size() << "). DRM version not supported");
ByteArray arrayEncryptedKey = ByteArray::fromBase64(encryptedKey);
std::string privateKeyData = user->getPrivateLicenseKey();
ByteArray privateRSAKey = ByteArray::fromBase64(privateKeyData);
dumpBuffer(gourou::LG_LOG_DEBUG, "To decrypt : ", arrayEncryptedKey.data(), arrayEncryptedKey.length());
client->RSAPrivateDecrypt(privateRSAKey.data(), privateRSAKey.length(),
RSAInterface::RSA_KEY_PKCS8, "",
arrayEncryptedKey.data(), arrayEncryptedKey.length(), decryptedKey);
}
/**
* RSA Key can be over encrypted with AES128-CBC if keyType attribute is set
* For EPUB, Key = SHA256(keyType)[14:22] || SHA256(keyType)[7:13]
* For PDF, Key = SHA256(keyType)[6:19] || SHA256(keyType)[3:6]
* remainder = keyType % 16
* Key = SHA256(keyType)[remainder*2:remainder*2+(16-remainder)] || SHA256(keyType)[16-remainder:16]
* IV = DeviceID ^ FulfillmentId ^ VoucherId
*
* @return Base64 encoded decrypted key
*/
std::string DRMProcessor::encryptedKeyFirstPass(pugi::xml_document& rightsDoc, const std::string& encryptedKey, const std::string& keyType, ITEM_TYPE type)
std::string DRMProcessor::encryptedKeyFirstPass(pugi::xml_document& rightsDoc, const std::string& encryptedKey, const std::string& keyType)
{
unsigned char digest[32], key[16], iv[16];
unsigned int dataOutLength;
@ -979,17 +1086,11 @@ namespace gourou
dumpBuffer(gourou::LG_LOG_DEBUG, "SHA of KeyType : ", digest, sizeof(digest));
switch(type)
{
case EPUB:
memcpy(key, &digest[14], 9);
memcpy(&key[9], &digest[7], 7);
break;
case PDF:
memcpy(key, &digest[6], 13);
memcpy(&key[13], &digest[3], 3);
break;
}
long nonce = std::stol(keyType);
int remainder = nonce % 16;
memcpy(key, &digest[remainder*2], 16-remainder);
memcpy(&key[16-remainder], &digest[remainder], remainder);
id = extractTextElem(rightsDoc, "/adept:rights/licenseToken/device");
if (id == "")
@ -1015,11 +1116,10 @@ namespace gourou
for(unsigned int i=0; i<sizeof(iv); i++)
iv[i] = _deviceId[i] ^ _fulfillmentId[i] ^ _voucherId[i];
ByteArray arrayEncryptedKey = ByteArray::fromBase64(encryptedKey);
dumpBuffer(gourou::LG_LOG_DEBUG, "First pass key : ", key, sizeof(key));
dumpBuffer(gourou::LG_LOG_DEBUG, "First pass IV : ", iv, sizeof(iv));
ByteArray arrayEncryptedKey = ByteArray::fromBase64(encryptedKey);
unsigned char* clearRSAKey = new unsigned char[arrayEncryptedKey.size()];
client->decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC,
@ -1048,6 +1148,57 @@ namespace gourou
return res.toBase64();
}
void DRMProcessor::decryptADEPTKey(pugi::xml_document& rightsDoc, unsigned char* decryptedKey, const unsigned char* encryptionKey, unsigned encryptionKeySize)
{
unsigned char rsaKey[RSA_KEY_SIZE];
std::string user = extractTextElem(rightsDoc, "/adept:rights/licenseToken/user");
if (!encryptionKey)
{
if (this->user->getUUID() != user)
{
EXCEPTION(DRM_INVALID_USER, "This book has been downloaded for another user (" << user << ")");
}
std::string encryptedKey = extractTextElem(rightsDoc, "/adept:rights/licenseToken/encryptedKey");
std::string keyType = extractTextAttribute(rightsDoc, "/adept:rights/licenseToken/encryptedKey", "keyType", false);
if (keyType != "")
encryptedKey = encryptedKeyFirstPass(rightsDoc, encryptedKey, keyType);
if (encryptedKey.size() != 172)
EXCEPTION(DRM_INVALID_KEY_SIZE, "Invalid encrypted key size (" << encryptedKey.size() << "). DRM version not supported");
ByteArray arrayEncryptedKey = ByteArray::fromBase64(encryptedKey);
std::string privateKeyData = this->user->getPrivateLicenseKey();
ByteArray privateRSAKey = ByteArray::fromBase64(privateKeyData);
dumpBuffer(gourou::LG_LOG_DEBUG, "To decrypt : ", arrayEncryptedKey.data(), arrayEncryptedKey.length());
client->RSAPrivateDecrypt(privateRSAKey.data(), privateRSAKey.length(),
RSAInterface::RSA_KEY_PKCS8, "",
arrayEncryptedKey.data(), arrayEncryptedKey.length(), rsaKey);
dumpBuffer(gourou::LG_LOG_DEBUG, "Decrypted : ", rsaKey, sizeof(rsaKey));
if (rsaKey[0] != 0x00 || rsaKey[1] != 0x02 ||
rsaKey[RSA_KEY_SIZE-16-1] != 0x00)
EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Unable to retrieve encryption key");
memcpy(decryptedKey, &rsaKey[sizeof(rsaKey)-16], 16);
}
else
{
GOUROU_LOG(DEBUG, "Use provided encryption key");
if (encryptionKeySize != 16)
EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Provided encryption key must be 16 bytes");
memcpy(decryptedKey, encryptionKey, encryptionKeySize);
}
}
void DRMProcessor::removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut,
const unsigned char* encryptionKey, unsigned encryptionKeySize)
{
@ -1059,32 +1210,9 @@ namespace gourou
pugi::xml_document rightsDoc;
rightsDoc.load_string((const char*)zipData.data());
std::string encryptedKey = extractTextElem(rightsDoc, "/adept:rights/licenseToken/encryptedKey");
unsigned char decryptedKey[RSA_KEY_SIZE];
unsigned char decryptedKey[16];
if (!encryptionKey)
{
std::string keyType = extractTextAttribute(rightsDoc, "/adept:rights/licenseToken/encryptedKey", "keyType", false);
if (keyType != "")
encryptedKey = encryptedKeyFirstPass(rightsDoc, encryptedKey, keyType, EPUB);
decryptADEPTKey(encryptedKey, decryptedKey);
dumpBuffer(gourou::LG_LOG_DEBUG, "Decrypted : ", decryptedKey, RSA_KEY_SIZE);
if (decryptedKey[0] != 0x00 || decryptedKey[1] != 0x02 ||
decryptedKey[RSA_KEY_SIZE-16-1] != 0x00)
EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Unable to retrieve encryption key");
}
else
{
GOUROU_LOG(DEBUG, "Use provided encryption key");
if (encryptionKeySize != 16)
EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Provided encryption key must be 16 bytes");
memcpy(&decryptedKey[sizeof(decryptedKey)-16], encryptionKey, encryptionKeySize);
}
decryptADEPTKey(rightsDoc, decryptedKey, encryptionKey, encryptionKeySize);
client->zipReadFile(zipHandler, "META-INF/encryption.xml", zipData);
pugi::xml_document encryptionDoc;
@ -1123,7 +1251,7 @@ namespace gourou
unsigned int dataOutLength;
client->decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC,
decryptedKey+sizeof(decryptedKey)-16, 16, /* Key */
decryptedKey, sizeof(decryptedKey), /* Key */
_data, 16, /* IV */
&_data[16], zipData.length()-16,
_clearData, &dataOutLength);
@ -1214,9 +1342,10 @@ namespace gourou
uPDFParser::Integer* ebxVersion;
std::vector<uPDFParser::Object*> objects = parser.objects();
std::vector<uPDFParser::Object*>::iterator it;
std::vector<uPDFParser::Object*>::iterator it, ebxIt;
std::vector<uPDFParser::Object*>::reverse_iterator rIt;
unsigned char decryptedKey[RSA_KEY_SIZE];
std::vector<uPDFParser::Object*> ebxObjects;
unsigned char decryptedKey[16];
int ebxId;
for(rIt = objects.rbegin(); rIt != objects.rend(); rIt++)
@ -1255,31 +1384,7 @@ namespace gourou
pugi::xml_document rightsDoc;
rightsDoc.load_string((const char*)rightsStr.data());
std::string encryptedKey = extractTextElem(rightsDoc, "/adept:rights/licenseToken/encryptedKey");
if (!encryptionKey)
{
std::string keyType = extractTextAttribute(rightsDoc, "/adept:rights/licenseToken/encryptedKey", "keyType", false);
if (keyType != "")
encryptedKey = encryptedKeyFirstPass(rightsDoc, encryptedKey, keyType, PDF);
decryptADEPTKey(encryptedKey, decryptedKey);
dumpBuffer(gourou::LG_LOG_DEBUG, "Decrypted : ", decryptedKey, RSA_KEY_SIZE);
if (decryptedKey[0] != 0x00 || decryptedKey[1] != 0x02 ||
decryptedKey[RSA_KEY_SIZE-16-1] != 0x00)
EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Unable to retrieve encryption key");
}
else
{
GOUROU_LOG(DEBUG, "Use provided encryption key");
if (encryptionKeySize != 16)
EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Provided encryption key must be 16 bytes");
memcpy(&decryptedKey[sizeof(decryptedKey)-16], encryptionKey, encryptionKeySize);
}
decryptADEPTKey(rightsDoc, decryptedKey, encryptionKey, encryptionKeySize);
ebxId = ebx->objectId();
@ -1298,7 +1403,7 @@ namespace gourou
if (object->objectId() == ebxId)
{
// object->deleteKey("Filter");
ebxObjects.push_back(object);
continue;
}
@ -1311,10 +1416,10 @@ namespace gourou
GOUROU_LOG(DEBUG, "Obj " << object->objectId());
unsigned char tmpKey[16];
unsigned char tmpKey[sizeof(decryptedKey)];
generatePDFObjectKey(ebxVersion->value(),
decryptedKey+sizeof(decryptedKey)-16, 16,
decryptedKey, sizeof(decryptedKey),
object->objectId(), object->generationNumber(),
tmpKey);
@ -1350,6 +1455,30 @@ namespace gourou
delete[] clearData;
}
else if (dictData->type() == uPDFParser::DataType::HEXASTRING)
{
string = ((uPDFParser::HexaString*) dictData)->value();
ByteArray hexStr = ByteArray::fromHex(string);
unsigned char* encryptedData = hexStr.data();
unsigned int dataLength = hexStr.size();
unsigned char* clearData = new unsigned char[dataLength];
unsigned int dataOutLength;
GOUROU_LOG(DEBUG, "Decrypt hexa string " << dictIt->first << " " << dataLength);
client->decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB,
tmpKey, sizeof(tmpKey), /* Key */
NULL, 0, /* IV */
encryptedData, dataLength,
clearData, &dataOutLength);
ByteArray clearHexStr = ByteArray(clearData, dataOutLength);
decodedStrings[dictIt->first] = new uPDFParser::HexaString(
clearHexStr.toHex());
delete[] clearData;
}
}
for (dictIt = decodedStrings.begin(); dictIt != decodedStrings.end(); dictIt++)
@ -1384,6 +1513,33 @@ namespace gourou
}
}
/* Delete objects that reference EBX objects, except in trailer */
for(it = objects.begin(); it != objects.end(); it++)
{
uPDFParser::Object* object = *it;
if (object->hasKey("Encrypt") && (*object)["Encrypt"]->type() == uPDFParser::DataType::REFERENCE)
{
uPDFParser::Reference* encrypt = (uPDFParser::Reference*)(*object)["Encrypt"];
/* Delete EBX objects */
for(ebxIt = ebxObjects.begin(); ebxIt != ebxObjects.end(); ebxIt++)
{
if (encrypt->value() == (*ebxIt)->objectId())
{
GOUROU_LOG(ERROR, "Delete stream id " << object->objectId());
parser.removeObject(object);
break;
}
}
}
}
/* Delete EBX objects */
for(it = ebxObjects.begin(); it != ebxObjects.end(); it++)
parser.removeObject(*it);
uPDFParser::Object& trailer = parser.getTrailer();
trailer.deleteKey("Encrypt");

View file

@ -29,24 +29,13 @@ namespace gourou
if (!node)
EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken element in document");
node = doc.select_node("/envelope/loanToken/loan").node();
node = doc.select_node("/envelope/fulfillmentResult/fulfillment").node();
if (node)
properties["id"] = node.first_child().value();
else
{
node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken/permissions/display/loan").node();
if (node)
properties["id"] = node.first_child().value();
else
{
node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken/permissions/play/loan").node();
if (node)
properties["id"] = node.first_child().value();
else
EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken/loan element in document");
}
EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No fulfillment element in document");
}
node = doc.select_node("/envelope/loanToken/operatorURL").node();

View file

@ -1 +0,0 @@
../lib/pugixml/src/pugixml.cpp

View file

@ -1,10 +1,16 @@
BINDIR ?= /bin
MANDIR ?= /share/man
TARGETS=acsmdownloader adept_activate adept_remove adept_loan_mgt launcher
TARGET_BINARIES=acsmdownloader adept_activate adept_remove adept_loan_mgt
TARGETS=$(TARGET_BINARIES) launcher
CXXFLAGS=-Wall -fPIC -I$(ROOT)/include -I$(ROOT)/lib/pugixml/src/
MAN_PAGES=acsmdownloader adept_activate adept_remove adept_loan_mgt
CXXFLAGS=-Wall -fPIC -I$(ROOT)/include -fmacro-prefix-map=$(ROOT)/= -fdata-sections -ffunction-sections
STATIC_DEP=
LDFLAGS += -L$(ROOT) -lcrypto -lzip -lz -lcurl
# LDFLAGS += -Wl,--gc-sections
LDFLAGS += -L$(ROOT) -lcrypto -lzip -lz -lcurl -lpugixml
ifneq ($(STATIC_UTILS),)
STATIC_DEP = $(ROOT)/libgourou.a
@ -25,12 +31,25 @@ COMMON_LIB = utils.a
all: $(TARGETS)
${COMMON_LIB}: ${COMMON_DEPS} ${STATIC_DEP}
$(CXX) $(CXXFLAGS) ${COMMON_DEPS} $(LDFLAGS) -c
$(AR) crs $@ ${COMMON_OBJECTS} $(STATIC_DEP)
${COMMON_LIB}: $(COMMON_DEPS)
$(CXX) $(CXXFLAGS) $(COMMON_DEPS) -c
$(AR) crs $@ $(COMMON_OBJECTS)
%: %.cpp ${COMMON_LIB}
$(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@
%: %.cpp $(COMMON_LIB) $(STATIC_DEP)
$(CXX) $(CXXFLAGS) $^ $(STATIC_DEP) $(LDFLAGS) -o $@
install: $(TARGET_BINARIES)
install -d $(DESTDIR)$(PREFIX)/$(BINDIR)
install -m 755 $(TARGET_BINARIES) $(DESTDIR)$(PREFIX)/$(BINDIR)
install -d $(DESTDIR)$(PREFIX)/$(MANDIR)/man1
install -m 644 man/*.1 $(DESTDIR)$(PREFIX)/$(MANDIR)/man1
uninstall:
cd $(DESTDIR)$(PREFIX)/$(BINDIR)
rm -f $(TARGET_BINARIES)
cd -
cd $(DESTDIR)$(PREFIX)/$(MANDIR)/man1
rm -f $(addsuffix .1,$(TARGET_BINARIES)
clean:
rm -f $(TARGETS) $(COMMON_LIB)

View file

@ -46,6 +46,7 @@ static bool exportPrivateKey = false;
static const char* outputFile = 0;
static const char* outputDir = 0;
static bool resume = false;
static bool notify = true;
class ACSMDownloader
@ -63,29 +64,30 @@ public:
if (exportPrivateKey)
{
std::string filename;
if (!outputFile)
filename = std::string("Adobe_PrivateLicenseKey--") + user->getUsername() + ".der";
else
if (outputFile)
filename = outputFile;
else
{
filename = std::string("Adobe_PrivateLicenseKey--") + user->getUsername() + ".der";
if (outputDir)
{
if (!fileExists(outputDir))
mkpath(outputDir);
filename = std::string(outputDir) + "/" + filename;
}
createPath(filename.c_str());
processor.exportPrivateLicenseKey(filename);
std::cout << "Private license key exported to " << filename << std::endl;
}
else
{
gourou::FulfillmentItem* item = processor.fulfill(acsmFile);
gourou::FulfillmentItem* item = processor.fulfill(acsmFile, notify);
std::string filename;
if (!outputFile)
if (outputFile)
filename = outputFile;
else
{
filename = item->getMetadata("title");
if (filename == "")
@ -95,18 +97,13 @@ public:
// Remove invalid characters
std::replace(filename.begin(), filename.end(), '/', '_');
}
}
else
filename = outputFile;
if (outputDir)
{
if (!fileExists(outputDir))
mkpath(outputDir);
filename = std::string(outputDir) + "/" + filename;
}
createPath(filename.c_str());
gourou::DRMProcessor::ITEM_TYPE type = processor.download(item, filename, resume);
if (!outputFile)
@ -182,24 +179,31 @@ private:
static void usage(const char* cmd)
{
std::cout << "Download EPUB file from ACSM request file" << std::endl;
std::cout << "Usage: " << basename((char*)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)] [(-r|--resume)] [(-v|--verbose)] [(-h|--help)] (-f|--acsm-file) file.acsm|(-e|--export-private-key)" << 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 <title.(epub|pdf|der)>)" << std::endl;
std::cout << " " << "-f|--acsm-file" << "\t" << "ACSM request file for epub download" << std::endl;
std::cout << basename((char*)cmd) << " download EPUB file from ACSM request file" << std::endl << std::endl;
std::cout << "Usage: " << basename((char*)cmd) << " [OPTIONS] file.acsm" << std::endl << std::endl;
std::cout << "Global Options:" << std::endl;
std::cout << " " << "-O|--output-dir" << "\t" << "Optional output directory were to put result (default ./) (not compatible with -o)" << std::endl;
std::cout << " " << "-o|--output-file" << "\t" << "Optional output filename (default <title.(epub|pdf|der)>) (not compatible with -O)" << std::endl;
std::cout << " " << "-f|--acsm-file" << "\t" << "Backward compatibility: ACSM request file for epub download" << std::endl;
std::cout << " " << "-e|--export-private-key"<< "\t" << "Export private key in DER format" << std::endl;
std::cout << " " << "-r|--resume" << "\t\t" << "Try to resume download (in case of previous failure)" << std::endl;
std::cout << " " << "-N|--no-notify" << "\t\t" << "Don't notify server, even if requested" << 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 << "ADEPT Options:" << std::endl;
std::cout << " " << "-D|--adept-directory" << "\t" << ".adept directory that must contains device.xml, activation.xml and devicesalt" << 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 << 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 << "Environment:" << std::endl;
std::cout << "Device file, activation file and device key file are optionals. If not set, they are looked into :" << std::endl << std::endl;
std::cout << " * $ADEPT_DIR environment variable" << std::endl;
std::cout << " * /home/<user>/.config/adept" << std::endl;
std::cout << " * Current directory" << std::endl;
std::cout << " * .adept" << std::endl;
std::cout << " * adobe-digital-editions directory" << std::endl;
@ -209,6 +213,7 @@ static void usage(const char* cmd)
int main(int argc, char** argv)
{
int c, ret = -1;
std::string _deviceFile, _activationFile, _devicekeyFile;
const char** files[] = {&devicekeyFile, &deviceFile, &activationFile};
int verbose = gourou::DRMProcessor::getLogLevel();
@ -216,6 +221,7 @@ int main(int argc, char** argv)
while (1) {
int option_index = 0;
static struct option long_options[] = {
{"adept-directory", required_argument, 0, 'D' },
{"device-file", required_argument, 0, 'd' },
{"activation-file", required_argument, 0, 'a' },
{"device-key-file", required_argument, 0, 'k' },
@ -224,18 +230,27 @@ int main(int argc, char** argv)
{"acsm-file", required_argument, 0, 'f' },
{"export-private-key",no_argument, 0, 'e' },
{"resume", no_argument, 0, 'r' },
{"no-notify", no_argument, 0, 'N' },
{"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:ervVh",
c = getopt_long(argc, argv, "D:d:a:k:O:o:f:erNvVh",
long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 'D':
_deviceFile = std::string(optarg) + "/device.xml";
_activationFile = std::string(optarg) + "/activation.xml";
_devicekeyFile = std::string(optarg) + "/devicesalt";
deviceFile = _deviceFile.c_str();
activationFile = _activationFile.c_str();
devicekeyFile = _devicekeyFile.c_str();
break;
case 'd':
deviceFile = optarg;
break;
@ -260,6 +275,9 @@ int main(int argc, char** argv)
case 'r':
resume = true;
break;
case 'N':
notify = false;
break;
case 'v':
verbose++;
break;
@ -277,6 +295,9 @@ int main(int argc, char** argv)
gourou::DRMProcessor::setLogLevel(verbose);
if (optind == argc-1)
acsmFile = argv[optind];
if ((!acsmFile && !exportPrivateKey) || (outputDir && !outputDir[0]) ||
(outputFile && !outputFile[0]))
{
@ -284,6 +305,12 @@ int main(int argc, char** argv)
return -1;
}
if (outputDir && outputFile)
{
std::cout << "Error : you cannot use both -o and -O" << std::endl;
return -1;
}
ACSMDownloader downloader;
int i;
@ -314,7 +341,7 @@ int main(int argc, char** argv)
}
else
{
if (!fileExists(acsmFile))
if (!pathExists(acsmFile))
{
std::cout << "Error : " << acsmFile << " doesn't exists" << std::endl;
ret = -1;

View file

@ -31,6 +31,7 @@
#include <termios.h>
#include <string.h>
#include <limits.h>
#include <libgen.h>
#include <iostream>
#include <ostream>
@ -124,10 +125,11 @@ public:
static void usage(const char* cmd)
{
std::cout << "Create new device files used by ADEPT DRM" << std::endl;
std::cout << basename((char*)cmd) << " create new device files used by ADEPT DRM" << std::endl << std::endl;
std::cout << "Usage: " << basename((char*)cmd) << " (-a|--anonymous) | ( (-u|--username) username [(-p|--password) password] ) [(-O|--output-dir) dir] [(-r|--random-serial)] [(-v|--verbose)] [(-h|--help)]" << std::endl << std::endl;
std::cout << "Usage: " << basename((char*)cmd) << " OPTIONS" << std::endl << std::endl;
std::cout << "Global Options:" << std::endl;
std::cout << " " << "-a|--anonymous" << "\t" << "Anonymous account, no need for username/password (Use it only with a DRM removal software)" << std::endl;
std::cout << " " << "-u|--username" << "\t\t" << "AdobeID username (ie adobe.com email account)" << std::endl;
std::cout << " " << "-p|--password" << "\t\t" << "AdobeID password (asked if not set via command line) " << std::endl;
@ -230,7 +232,7 @@ int main(int argc, char** argv)
if (!_outputDir || _outputDir[0] == 0)
{
outputDir = strdup(abspath(DEFAULT_ADEPT_DIR));
outputDir = strdup(gourou::DRMProcessor::getDefaultAdeptDir().c_str());
}
else
{
@ -238,7 +240,7 @@ int main(int argc, char** argv)
if (_outputDir[0] == '.' || _outputDir[0] != '/')
{
// realpath doesn't works if file/dir doesn't exists
if (fileExists(_outputDir))
if (pathExists(_outputDir))
outputDir = strdup(realpath(_outputDir, 0));
else
outputDir = strdup(abspath(_outputDir));
@ -248,7 +250,7 @@ int main(int argc, char** argv)
}
std::string pass;
if (fileExists(outputDir))
if (pathExists(outputDir))
{
int key;

View file

@ -45,13 +45,14 @@
#define MAX_SIZE_BOOK_NAME 30
static char* activationDir = 0;
static char* adeptDir = 0;
static const char* deviceFile = "device.xml";
static const char* activationFile = "activation.xml";
static const char* devicekeyFile = "devicesalt";
static bool list = false;
static const char* returnID = 0;
static const char* deleteID = 0;
static bool notify = true;
struct Loan
{
@ -106,9 +107,9 @@ private:
struct Loan* loan;
char * res;
std::string loanDir = std::string(activationDir) + std::string("/") + LOANS_DIR;
std::string loanDir = std::string(adeptDir) + std::string("/") + LOANS_DIR;
if (!fileExists(loanDir.c_str()))
if (!pathExists(loanDir.c_str()))
return;
dp = opendir (loanDir.c_str());
@ -182,8 +183,13 @@ private:
loan->bookName = node.first_child().value();
struct tm tm;
#ifdef __ANDROID__
res = strptime(loan->validity.c_str(), "%Y-%m-%dT%H:%M:%S%z", &tm);
#else
res = strptime(loan->validity.c_str(), "%Y-%m-%dT%H:%M:%S%Z", &tm);
if (*res == 0)
#endif
if (res != NULL && *res == 0)
{
if (mktime(&tm) <= time(NULL))
loan->validity = " (Expired)";
@ -209,7 +215,7 @@ private:
{
if (!loanedBooks.size())
{
std::cout << "Any book loaned" << std::endl;
std::cout << "No books loaned" << std::endl;
return;
}
@ -223,7 +229,12 @@ private:
maxSizeBookName = loan->bookName.size();
}
if (maxSizeBookName > MAX_SIZE_BOOK_NAME)
/* Manage empty names */
if (maxSizeBookName == 0)
maxSizeBookName = sizeof("No name ")-1;
else if (maxSizeBookName < 4)
maxSizeBookName = 4;
else if (maxSizeBookName > MAX_SIZE_BOOK_NAME)
maxSizeBookName = MAX_SIZE_BOOK_NAME;
else if ((maxSizeBookName % 2))
maxSizeBookName++;
@ -252,7 +263,7 @@ private:
std::cout.width (fillExpiration);
std::cout << "";
std::cout << "Exipration";
std::cout << "Expiration";
std::cout.width (fillExpiration);
std::cout << "" << std::endl;
@ -270,7 +281,9 @@ private:
std::cout << kv.first;
std::cout << " ";
if (loan->bookName.size() > MAX_SIZE_BOOK_NAME)
if (loan->bookName.size() == 0)
bookName = std::string("No name ");
else if (loan->bookName.size() > MAX_SIZE_BOOK_NAME)
bookName = std::string(loan->bookName.c_str(), MAX_SIZE_BOOK_NAME);
else
bookName = loan->bookName;
@ -296,7 +309,7 @@ private:
return;
}
processor.returnLoan(loan->id, loan->operatorURL);
processor.returnLoan(loan->id, loan->operatorURL, notify);
deleteID = returnID;
if (deleteLoan(false))
@ -334,20 +347,27 @@ private:
static void usage(const char* cmd)
{
std::cout << "Manage loaned books" << std::endl;
std::cout << basename((char*)cmd) << " manage loaned books" << std::endl << std::endl;
std::cout << "Usage: " << basename((char*)cmd) << " [(-d|--activation-dir) dir] (-l|--list)|(-D|--delete loanID)|(-R|--delete loanID) [(-v|--verbose)] [(-h|--help)]" << std::endl << std::endl;
std::cout << "Usage: " << basename((char*)cmd) << " [OPTIONS]" << std::endl << std::endl;
std::cout << " " << "-d|--activation-dir" << "\t" << "Directory of device.xml/activation.xml and device key" << std::endl;
std::cout << "Global Options:" << std::endl;
std::cout << " " << "-l|--list" << "\t\t" << "List all loaned books" << std::endl;
std::cout << " " << "-r|--return" << "\t\t" << "Return a loaned book" << std::endl;
std::cout << " " << "-D|--delete" << "\t\t" << "Delete a loan entry without returning it" << std::endl;
std::cout << " " << "-d|--delete" << "\t\t" << "Delete a loan entry without returning it" << std::endl;
std::cout << " " << "-N|--no-notify" << "\t\t" << "Don't notify server, even if requested" << 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 << "ADEPT Options:" << std::endl;
std::cout << " " << "-D|--adept-directory" << "\t" << ".adept directory that must contains device.xml, activation.xml and devicesalt" << std::endl;
std::cout << std::endl;
std::cout << "Activation directory is optional. If not set, it's looked into :" << std::endl;
std::cout << "Environment:" << std::endl;
std::cout << "ADEPT directory is optional. If not set, it's looked into :" << std::endl;
std::cout << " * $ADEPT_DIR environment variable" << std::endl;
std::cout << " * /home/<user>/.config/adept" << std::endl;
std::cout << " * Current directory" << std::endl;
std::cout << " * .adept" << std::endl;
std::cout << " * adobe-digital-editions directory" << std::endl;
@ -365,10 +385,11 @@ int main(int argc, char** argv)
while (1) {
int option_index = 0;
static struct option long_options[] = {
{"activation-dir", required_argument, 0, 'd' },
{"adept-directory", required_argument, 0, 'D' },
{"list", no_argument, 0, 'l' },
{"return", no_argument, 0, 'r' },
{"delete", no_argument, 0, 'D' },
{"delete", no_argument, 0, 'd' },
{"no-notify", no_argument, 0, 'N' },
{"verbose", no_argument, 0, 'v' },
{"version", no_argument, 0, 'V' },
{"help", no_argument, 0, 'h' },
@ -381,8 +402,8 @@ int main(int argc, char** argv)
break;
switch (c) {
case 'd':
activationDir = optarg;
case 'D':
adeptDir = optarg;
break;
case 'l':
list = true;
@ -392,10 +413,13 @@ int main(int argc, char** argv)
returnID = optarg;
actions++;
break;
case 'D':
case 'd':
deleteID = optarg;
actions++;
break;
case 'N':
notify = false;
break;
case 'v':
verbose++;
break;
@ -432,9 +456,9 @@ int main(int argc, char** argv)
{
orig = *files[i];
if (activationDir)
if (adeptDir)
{
std::string path = std::string(activationDir) + std::string("/") + orig;
std::string path = std::string(adeptDir) + std::string("/") + orig;
filename = strdup(path.c_str());
}
else
@ -450,17 +474,17 @@ int main(int argc, char** argv)
if (hasErrors)
{
// In case of activation dir was provided by user
activationDir = 0;
// In case of adept dir was provided by user
adeptDir = 0;
goto end;
}
if (activationDir)
activationDir = strdup(activationDir); // For below free
if (adeptDir)
adeptDir = strdup(adeptDir); // For below free
else
{
activationDir = strdup(deviceFile);
activationDir = dirname(activationDir);
adeptDir = strdup(deviceFile);
adeptDir = dirname(adeptDir);
}
ret = loanMGT.run();
@ -472,8 +496,8 @@ end:
free((void*)*files[i]);
}
if (activationDir)
free(activationDir);
if (adeptDir)
free(adeptDir);
return ret;
}

View file

@ -27,6 +27,7 @@
*/
#include <getopt.h>
#include <libgen.h>
#include <iostream>
@ -80,16 +81,13 @@ public:
gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile);
std::string filename;
if (!outputFile)
filename = std::string(inputFile);
else
if (outputFile)
filename = outputFile;
else
{
filename = std::string(inputFile);
if (outputDir)
{
if (!fileExists(outputDir))
mkpath(outputDir);
filename = std::string(outputDir) + "/" + filename;
}
@ -102,6 +100,8 @@ public:
EXCEPTION(gourou::DRM_FORMAT_NOT_SUPPORTED, "Unsupported file format of " << filename);
}
createPath(filename.c_str());
if (inputFile != filename)
{
unlink(filename.c_str());
@ -141,22 +141,30 @@ public:
static void usage(const char* cmd)
{
std::cout << "Remove ADEPT DRM (from Adobe) of EPUB/PDF file" << std::endl;
std::cout << basename((char*)cmd) << " remove ADEPT DRM (from Adobe) of EPUB/PDF file" << std::endl << std::endl;
std::cout << "Usage: " << basename((char*)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 << "Usage: " << basename((char*)cmd) << " [OPTIONS] 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 << "Global Options:" << std::endl;
std::cout << " " << "-O|--output-dir" << "\t" << "Optional output directory were to put result (default ./) (not compatible with -o)" << std::endl;
std::cout << " " << "-o|--output-file" << "\t" << "Optional output filename (default inplace DRM removal>) (not compatible with -O)" << std::endl;
std::cout << " " << "-f|--input-file" << "\t" << "Backward compatibility: 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 << "ADEPT Options:" << std::endl;
std::cout << " " << "-D|--adept-directory" << "\t" << ".adept directory that must contains device.xml, activation.xml and devicesalt" << 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 << std::endl;
std::cout << "Environment:" << 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 << " * $ADEPT_DIR environment variable" << std::endl;
std::cout << " * /home/<user>/.config/adept" << std::endl;
std::cout << " * Current directory" << std::endl;
std::cout << " * .adept" << std::endl;
std::cout << " * adobe-digital-editions directory" << std::endl;
@ -169,10 +177,12 @@ int main(int argc, char** argv)
const char** files[] = {&devicekeyFile, &deviceFile, &activationFile};
int verbose = gourou::DRMProcessor::getLogLevel();
std::string _deviceFile, _activationFile, _devicekeyFile;
while (1) {
int option_index = 0;
static struct option long_options[] = {
{"adept-directory", required_argument, 0, 'D' },
{"device-file", required_argument, 0, 'd' },
{"activation-file", required_argument, 0, 'a' },
{"device-key-file", required_argument, 0, 'k' },
@ -186,12 +196,20 @@ int main(int argc, char** argv)
{0, 0, 0, 0 }
};
c = getopt_long(argc, argv, "d:a:k:O:o:f:K:vVh",
c = getopt_long(argc, argv, "D:d:a:k:O:o:f:K:vVh",
long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 'D':
_deviceFile = std::string(optarg) + "/device.xml";
_activationFile = std::string(optarg) + "/activation.xml";
_devicekeyFile = std::string(optarg) + "/devicesalt";
deviceFile = _deviceFile.c_str();
activationFile = _activationFile.c_str();
devicekeyFile = _devicekeyFile.c_str();
break;
case 'd':
deviceFile = optarg;
break;
@ -230,6 +248,9 @@ int main(int argc, char** argv)
gourou::DRMProcessor::setLogLevel(verbose);
if (optind == argc-1)
inputFile = argv[optind];
if (!inputFile || (outputDir && !outputDir[0]) ||
(outputFile && !outputFile[0]))
{
@ -237,6 +258,12 @@ int main(int argc, char** argv)
return -1;
}
if (outputDir && outputFile)
{
std::cout << "Error : you cannot use both -o and -O" << std::endl;
return -1;
}
ADEPTRemove remover;
int i;

View file

@ -30,6 +30,7 @@
#include <algorithm>
#include <cctype>
#include <locale>
#include <stdlib.h>
#define OPENSSL_NO_DEPRECATED 1
@ -48,18 +49,42 @@
#include <libgourou_common.h>
#include "drmprocessorclientimpl.h"
static int error_cb(const char *str, size_t len, void *u)
{
std::cout << str << std::endl;
return 0;
}
DRMProcessorClientImpl::DRMProcessorClientImpl():
legacy(0), deflt(0)
{
#if OPENSSL_VERSION_MAJOR >= 3
legacy = OSSL_PROVIDER_load(NULL, "legacy");
if (!legacy)
{
ERR_print_errors_cb(error_cb, NULL);
EXCEPTION(gourou::CLIENT_OSSL_ERROR, "Error, OpenSSL legacy provider not available");
}
deflt = OSSL_PROVIDER_load(NULL, "default");
if (!deflt)
EXCEPTION(gourou::CLIENT_OSSL_ERROR, "Error, OpenSSL default provider not available");
OSSL_PROVIDER_load(NULL, "base");
#endif
#ifdef WIN32
strcpy(cookiejar, "C:\\temp\\libgourou_cookie_jar_XXXXXX");
#else
strcpy(cookiejar, "/tmp/libgourou_cookie_jar_XXXXXX");
#endif
int fd = mkstemp(cookiejar);
if (fd >= 0)
close(fd);
else
{
EXCEPTION(gourou::CLIENT_FILE_ERROR, "mkstemp error");
}
}
DRMProcessorClientImpl::~DRMProcessorClientImpl()
@ -71,6 +96,8 @@ DRMProcessorClientImpl::~DRMProcessorClientImpl()
if (deflt)
OSSL_PROVIDER_unload(deflt);
#endif
unlink(cookiejar);
}
/* Digest interface */
@ -120,6 +147,7 @@ void DRMProcessorClientImpl::randBytes(unsigned char* bytesOut, unsigned int len
#define HTTP_REQ_MAX_RETRY 5
#define DISPLAY_THRESHOLD 10*1024 // Threshold to display download progression
static unsigned downloadedBytes;
static int lastPercent = -1;
static int downloadProgress(void *clientp, curl_off_t dltotal, curl_off_t dlnow,
curl_off_t ultotal, curl_off_t ulnow)
@ -131,7 +159,11 @@ static int downloadProgress(void *clientp, curl_off_t dltotal, curl_off_t dlnow,
if (dltotal)
percent = (dlnow * 100) / dltotal;
if (lastPercent != percent)
{
std::cout << "\rDownload " << percent << "%" << std::flush;
lastPercent = percent;
}
}
return 0;
@ -227,6 +259,7 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons
}
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
curl_easy_setopt(curl, CURLOPT_COOKIEJAR, cookiejar);
if (POSTData.size())
{
@ -251,6 +284,7 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, downloadProgress);
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
lastPercent = -1;
for (int i=0; i<HTTP_REQ_MAX_RETRY; i++)
{
@ -286,11 +320,18 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons
}
curl_slist_free_all(list);
long http_code = 400;
curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &http_code);
curl_easy_cleanup(curl);
if (res != CURLE_OK)
EXCEPTION(gourou::CLIENT_NETWORK_ERROR, "Error " << curl_easy_strerror(res));
if (http_code >= 400)
EXCEPTION(gourou::CLIENT_HTTP_ERROR, "HTTP Error code " << http_code);
if ((downloadedBytes >= DISPLAY_THRESHOLD || replyData.size() >= DISPLAY_THRESHOLD) &&
gourou::logLevel >= gourou::LG_LOG_WARN)
std::cout << std::endl;
@ -314,11 +355,39 @@ void DRMProcessorClientImpl::padWithPKCS1(unsigned char* out, unsigned int outLe
0x00 0x01 0xff * n 0x00 dataIn
*/
memset(out, 0xFF, outLength);
memset(out, 0xFF, outLength - inLength - 1);
out[0] = 0x0;
out[1] = 0x1;
out[outLength - inLength - 1] = 0x00;
memcpy(&out[outLength - inLength], in, inLength);
}
void DRMProcessorClientImpl::padWithPKCS1Type2(unsigned char* out, unsigned int outLength,
const unsigned char* in, unsigned int inLength)
{
if (outLength < (inLength + 3))
EXCEPTION(gourou::CLIENT_RSA_ERROR, "Not enough space for PKCS1 padding");
/*
PKCS1v5 type 2 Padding is :
0x00 0x02 0xXX * n 0x00 dataIn
XX is random non zero data
*/
RAND_bytes(&out[2], outLength - inLength - 1);
for(unsigned int i=2; i<outLength - inLength - 1; i++)
{
while (out[i] == 0)
RAND_bytes(&out[i], 1);
}
out[0] = 0x0;
out[1] = 0x2;
out[outLength - inLength - 1] = 0x00;
memcpy(&out[outLength - inLength], in, inLength);
}
@ -410,33 +479,45 @@ void DRMProcessorClientImpl::RSAPublicEncrypt(const unsigned char* RSAKey, unsig
unsigned char* res)
{
size_t outlen;
unsigned char* tmp;
X509 * x509 = d2i_X509(0, &RSAKey, RSAKeyLength);
if (!x509)
EXCEPTION(gourou::CLIENT_INVALID_CERTIFICATE, "Invalid certificate");
EVP_PKEY_CTX *ctx;
EVP_PKEY * evpKey = X509_get_pubkey(x509);
EVP_PKEY * pkey = X509_get_pubkey(x509);
if (!evpKey)
if (!pkey)
EXCEPTION(gourou::CLIENT_NO_PUB_KEY, "No public key in certificate");
ctx = EVP_PKEY_CTX_new(evpKey, NULL);
ctx = EVP_PKEY_CTX_new(pkey, NULL);
if (EVP_PKEY_encrypt_init(ctx) <= 0)
EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL));
if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0)
if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_NO_PADDING) <= 0)
EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL));
int ret = EVP_PKEY_encrypt(ctx, res, &outlen, data, dataLength);
outlen = EVP_PKEY_get_size(pkey);
tmp = (unsigned char*)malloc(outlen);
/*
PKCS1 functions are no more exported.
Some OpenSSL libraries still use type 1
*/
padWithPKCS1Type2(tmp, outlen, data, dataLength);
int ret = EVP_PKEY_encrypt(ctx, res, &outlen, tmp, outlen);
EVP_PKEY_CTX_free(ctx);
free(tmp);
EVP_PKEY_free(pkey);
if (ret < 0)
EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL));
EVP_PKEY_free(evpKey);
}
void* DRMProcessorClientImpl::generateRSAKey(int keyLengthBits)
@ -450,7 +531,6 @@ void* DRMProcessorClientImpl::generateRSAKey(int keyLengthBits)
EVP_PKEY_keygen_init(ctx);
EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, keyLengthBits);
EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING);
EVP_PKEY_CTX_set1_rsa_keygen_pubexp(ctx, bn);
EVP_PKEY_keygen(ctx, &key);

View file

@ -130,12 +130,16 @@ private:
void padWithPKCS1(unsigned char* out, unsigned int outLength,
const unsigned char* in, unsigned int inLength);
void padWithPKCS1Type2(unsigned char* out, unsigned int outLength,
const unsigned char* in, unsigned int inLength);
#if OPENSSL_VERSION_MAJOR >= 3
OSSL_PROVIDER *legacy, *deflt;
#else
void *legacy, *deflt;
#endif
char cookiejar[64];
};
#endif

View file

@ -23,7 +23,7 @@ int main(int argc, char** argv)
fullPath = std::string(mountPoint) + util;
if (std::string(util) == "launcher" || !fileExists(fullPath.c_str()))
if (std::string(util) == "launcher" || !pathExists(fullPath.c_str()))
fullPath = std::string(mountPoint) + DEFAULT_UTIL;
free(argv0);

View file

@ -0,0 +1,61 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH ACSMDOWNLOADER "1" "January 2023" "acsmdownloader download EPUB file from ACSM request file" "User Commands"
.SH NAME
acsmdownloader \- download EPUB file from ACSM request file
.SH SYNOPSIS
.B acsmdownloader
[\fI\,OPTIONS\/\fR] \fI\,file.acsm\/\fR
.SH DESCRIPTION
Download EPUB file from ACSM request file
.SS "Global Options:"
.TP
\fB\-O\fR|\-\-output\-dir
Optional output directory were to put result (default ./)
.TP
\fB\-o\fR|\-\-output\-file
Optional output filename (default <title.(epub|pdf|der)>)
.TP
\fB\-f\fR|\-\-acsm\-file
Backward compatibility: ACSM request file for epub download
.TP
\fB\-e\fR|\-\-export\-private\-key
Export private key in DER format
.TP
\fB\-r\fR|\-\-resume
Try to resume download (in case of previous failure)
.TP
\fB\-v\fR|\-\-verbose
Increase verbosity, can be set multiple times
.TP
\fB\-V\fR|\-\-version
Display libgourou version
.TP
\fB\-h\fR|\-\-help
This help
.SS "ADEPT Options:"
.TP
\fB\-D\fR|\-\-adept\-directory
\&.adept directory that must contains device.xml, activation.xml and devicesalt
.TP
\fB\-d\fR|\-\-device\-file
device.xml file from eReader
.TP
\fB\-a\fR|\-\-activation\-file
activation.xml file from eReader
.TP
\fB\-k\fR|\-\-device\-key\-file
private device key file (eg devicesalt/devkey.bin) from eReader
.SH ENVIRONMENT
Device file, activation file and device key file are optionals. If not set, they are looked into :
.IP
* $ADEPT_DIR environment variable
.IP
* /home/<user>/.config/adept
.IP
* Current directory
.IP
* .adept
.IP
* adobe\-digital\-editions directory
.IP
* .adobe\-digital\-editions directory

View file

@ -0,0 +1,67 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH ADEPT_ACTIVATE "1" "January 2023" "adept_activate create new device files used by ADEPT DRM" "User Commands"
.SH NAME
adept_activate create new device files used by ADEPT DRM
.SH SYNOPSIS
.B adept_activate
\fI\,OPTIONS\/\fR
.SH DESCRIPTION
Create new device files used by ADEPT DRM
.SS "Global Options:"
.TP
\fB\-a\fR|\-\-anonymous
Anonymous account, no need for username/password (Use it only with a DRM removal software)
.TP
\fB\-u\fR|\-\-username
AdobeID username (ie adobe.com email account)
.TP
\fB\-p\fR|\-\-password
AdobeID password (asked if not set via command line)
.TP
\fB\-O\fR|\-\-output\-dir
Optional output directory were to put result (default ./.adept). This directory must not already exists
.TP
\fB\-H\fR|\-\-hobbes\-version
Force RMSDK version to a specific value (default: version of current librmsdk)
.TP
\fB\-r\fR|\-\-random\-serial
Generate a random device serial (if not set, it will be dependent of your current configuration)
.TP
\fB\-v\fR|\-\-verbose
Increase verbosity, can be set multiple times
.TP
\fB\-V\fR|\-\-version
Display libgourou version
.TP
\fB\-h\fR|\-\-help
This help
.PP
Usage: adept_activate OPTIONS
.SS "Global Options:"
.TP
\fB\-a\fR|\-\-anonymous
Anonymous account, no need for username/password (Use it only with a DRM removal software)
.TP
\fB\-u\fR|\-\-username
AdobeID username (ie adobe.com email account)
.TP
\fB\-p\fR|\-\-password
AdobeID password (asked if not set via command line)
.TP
\fB\-O\fR|\-\-output\-dir
Optional output directory were to put result (default ./.adept). This directory must not already exists
.TP
\fB\-H\fR|\-\-hobbes\-version
Force RMSDK version to a specific value (default: version of current librmsdk)
.TP
\fB\-r\fR|\-\-random\-serial
Generate a random device serial (if not set, it will be dependent of your current configuration)
.TP
\fB\-v\fR|\-\-verbose
Increase verbosity, can be set multiple times
.TP
\fB\-V\fR|\-\-version
Display libgourou version
.TP
\fB\-h\fR|\-\-help
This help

View file

@ -0,0 +1,46 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH ADEPT_LOAN_MGT "1" "January 2023" "adept_loan_mgt manage loaned books" "User Commands"
.SH NAME
adept_loan_mgt manage loaned books
.SH SYNOPSIS
.B adept_loan_mgt
[\fI\,OPTIONS\/\fR]
.SH DESCRIPTION
Manage ADEPT loaned books
.SS "Global Options:"
.TP
\fB\-l\fR|\-\-list
List all loaned books
.TP
\fB\-r\fR|\-\-return
Return a loaned book
.TP
\fB\-d\fR|\-\-delete
Delete a loan entry without returning it
.TP
\fB\-v\fR|\-\-verbose
Increase verbosity, can be set multiple times
.TP
\fB\-V\fR|\-\-version
Display libgourou version
.TP
\fB\-h\fR|\-\-help
This help
.SS "ADEPT Options:"
.TP
\fB\-D\fR|\-\-adept\-directory
\&.adept directory that must contains device.xml, activation.xml and devicesalt
.SH ENVIRONMENT
.SS "ADEPT directory is optional. If not set, it's looked into :"
.IP
* $ADEPT_DIR environment variable
.IP
* /home/<user>/.config/adept
.IP
* Current directory
.IP
* .adept
.IP
* adobe\-digital\-editions directory
.IP
* .adobe\-digital\-editions directory

55
utils/man/adept_remove.1 Normal file
View file

@ -0,0 +1,55 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH ADEPT_REMOVE "1" "January 2023" "adept_remove remove ADEPT DRM (from Adobe) of EPUB/PDF file" "User Commands"
.SH NAME
adept_remove remove ADEPT DRM (from Adobe) of EPUB/PDF file
.SH SYNOPSIS
.B adept_remove
[\fI\,OPTIONS\/\fR] \fI\,file(.epub|pdf)\/\fR
.SH DESCRIPTION
Remove ADEPT DRM (from Adobe) of EPUB/PDF file
.SS "Global Options:"
.TP
\fB\-O\fR|\-\-output\-dir
Optional output directory were to put result (default ./)
.TP
\fB\-o\fR|\-\-output\-file
Optional output filename (default inplace DRM removal>)
.TP
\fB\-f\fR|\-\-input\-file
Backward compatibility: EPUB/PDF file to process
.TP
\fB\-v\fR|\-\-verbose
Increase verbosity, can be set multiple times
.TP
\fB\-V\fR|\-\-version
Display libgourou version
.TP
\fB\-h\fR|\-\-help
This help
.SS "ADEPT Options:"
.TP
\fB\-D\fR|\-\-adept\-directory
\&.adept directory that must contains device.xml, activation.xml and devicesalt
.TP
\fB\-d\fR|\-\-device\-file
device.xml file from eReader
.TP
\fB\-a\fR|\-\-activation\-file
activation.xml file from eReader
.TP
\fB\-k\fR|\-\-device\-key\-file
private device key file (eg devicesalt/devkey.bin) from eReader
.SH ENVIRONMENT
.SS "Device file, activation file and device key file are optionals. If not set, they are looked into :"
.IP
* $ADEPT_DIR environment variable
.IP
* /home/<user>/.config/adept
.IP
* Current directory
.IP
* .adept
.IP
* adobe\-digital\-editions directory
.IP
* .adobe\-digital\-editions directory

View file

@ -33,6 +33,7 @@
#include <stdio.h>
#include <fcntl.h>
#include <limits.h>
#include <libgen.h>
#include <iostream>
@ -51,25 +52,39 @@ void version(void)
std::cout << "Current libgourou version : " << gourou::DRMProcessor::VERSION << std::endl ;
}
bool fileExists(const char* filename)
bool pathExists(const char* path)
{
struct stat _stat;
int ret = stat(filename, &_stat);
int ret = stat(path, &_stat);
return (ret == 0);
}
const char* findFile(const char* filename, bool inDefaultDirs)
{
if (fileExists(filename))
std::string path;
const char* adeptDir = getenv("ADEPT_DIR");
if (adeptDir && adeptDir[0])
{
path = adeptDir + std::string("/") + filename;
if (pathExists(path.c_str()))
return strdup(path.c_str());
}
path = gourou::DRMProcessor::getDefaultAdeptDir() + filename;
if (pathExists(path.c_str()))
return strdup(path.c_str());
if (pathExists(filename))
return strdup(filename);
if (!inDefaultDirs) return 0;
for (int i=0; i<(int)ARRAY_SIZE(defaultDirs); i++)
{
std::string path = std::string(defaultDirs[i]) + filename;
if (fileExists(path.c_str()))
path = std::string(defaultDirs[i]) + filename;
if (pathExists(path.c_str()))
return strdup(path.c_str());
}
@ -98,8 +113,8 @@ void mkpath(const char *dir)
void fileCopy(const char* in, const char* out)
{
char buffer[4096];
int ret, fdIn, fdOut;
char buffer[4096], *_buffer;
int ret, ret2, fdIn, fdOut;
fdIn = open(in, O_RDONLY);
@ -119,9 +134,31 @@ void fileCopy(const char* in, const char* out)
ret = ::read(fdIn, buffer, sizeof(buffer));
if (ret <= 0)
break;
::write(fdOut, buffer, ret);
do
{
_buffer = buffer;
ret2 = ::write(fdOut, _buffer, ret);
if (ret2 >= 0)
{
ret -= ret2;
_buffer += ret2;
}
else
{
EXCEPTION(gourou::CLIENT_FILE_ERROR, "Error writing " << out);
}
} while (ret);
}
close (fdIn);
close (fdOut);
}
void createPath(const char* filename)
{
char* basepath = strdup(filename);
char* outputDir = dirname(basepath);
if (outputDir && !pathExists(outputDir))
mkpath(outputDir);
free(basepath);
}

View file

@ -50,9 +50,9 @@ void version(void);
const char* findFile(const char* filename, bool inDefaultDirs=true);
/**
* @brief Does the file (or directory exists)
* @brief Does the file (or directory) exists
*/
bool fileExists(const char* filename);
bool pathExists(const char* path);
/**
* @brief Recursively created dir
@ -64,4 +64,9 @@ void mkpath(const char *dir);
*/
void fileCopy(const char* in, const char* out);
/**
* @brief Create intermediate directories if it does not exists
*/
void createPath(const char* filename);
#endif