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 AR ?= $(CROSS)ar
CXX ?= $(CROSS)g++ CXX ?= $(CROSS)g++
UPDFPARSERLIB = ./lib/updfparser/libupdfparser.a UPDFPARSERLIB = ./lib/updfparser/libupdfparser.a
CXXFLAGS=-Wall -fPIC -I./include -I./lib/pugixml/src/ -I./lib/updfparser/include CXXFLAGS += -Wall -fPIC -I./include -I./usr/include/pugixml -I./lib/updfparser/include
LDFLAGS = $(UPDFPARSERLIB) LDFLAGS = -lpugixml
VERSION := $(shell cat include/libgourou.h |grep LIBGOUROU_VERSION|cut -d '"' -f2)
UNAME := $(shell uname -s)
BUILD_STATIC ?= 0 BUILD_STATIC ?= 0
BUILD_SHARED ?= 1 BUILD_SHARED ?= 1
BUILD_UTILS ?= 1 BUILD_UTILS ?= 1
TARGETS = TARGETS =
TARGET_LIBRARIES =
ifneq ($(STATIC_UTILS),)
BUILD_STATIC=1
endif
ifneq ($(BUILD_STATIC), 0) ifneq ($(BUILD_STATIC), 0)
TARGETS += libgourou.a TARGETS += libgourou.a
TARGET_LIBRARIES += libgourou.a
STATIC_UTILS=1
endif endif
ifneq ($(BUILD_SHARED), 0) ifneq ($(BUILD_SHARED), 0)
TARGETS += libgourou.so 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 endif
ifneq ($(BUILD_UTILS), 0) ifneq ($(BUILD_UTILS), 0)
TARGETS += build_utils TARGETS += build_utils
@ -33,16 +53,17 @@ CXXFLAGS += -DSTATIC_NONCE=1
endif endif
SRCDIR := src SRCDIR := src
INCDIR := inc
BUILDDIR := obj BUILDDIR := obj
TARGETDIR := bin
SRCEXT := cpp SRCEXT := cpp
OBJEXT := o 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))) OBJECTS := $(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES:.$(SRCEXT)=.$(OBJEXT)))
all: lib obj $(TARGETS) all: version lib obj $(TARGETS)
version:
@echo "Building libgourou $(VERSION)"
lib: lib:
mkdir lib mkdir lib
@ -57,21 +78,48 @@ obj:
$(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT) $(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT)
$(CXX) $(CXXFLAGS) -c $^ -o $@ $(CXX) $(CXXFLAGS) -c $^ -o $@
libgourou: libgourou.a libgourou.so libgourou: $(TARGET_LIBRARIES)
libgourou.a: $(OBJECTS) $(UPDFPARSERLIB) libgourou.a: $(OBJECTS) $(UPDFPARSERLIB)
$(AR) crs $@ obj/*.o $(UPDFPARSERLIB) $(AR) rcs --thin $@ $^
libgourou.so: $(OBJECTS) $(UPDFPARSERLIB) libgourou.so.$(VERSION): $(OBJECTS) $(UPDFPARSERLIB)
$(CXX) obj/*.o $(LDFLAGS) -o $@ -shared $(CXX) $^ -Wl,-soname,$@ $(LDFLAGS) -o $@ -shared
build_utils: libgourou.so: libgourou.so.$(VERSION)
make -C utils ROOT=$(PWD) CXX=$(CXX) AR=$(AR) DEBUG=$(DEBUG) STATIC_UTILS=$(STATIC_UTILS) 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: clean:
rm -rf libgourou.a libgourou.so obj rm -rf libgourou.a libgourou.so libgourou.so.$(VERSION)* obj
make -C utils clean $(MAKE) -C utils clean
ultraclean: clean ultraclean: clean
rm -rf lib rm -rf lib
make -C utils ultraclean $(MAKE) -C utils ultraclean

View file

@ -1,16 +1,16 @@
Introduction 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 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. 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 Qt, OpenSSL and libzip is provided (in _utils_ directory). 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()_ * Get an ePub from an ACSM file : _fulfill()_ and _download()_
* Create a new device : _createDRMProcessor()_ * Create a new device : _createDRMProcessor()_
@ -18,42 +18,43 @@ Main fucntions to use from gourou::DRMProcessor are :
* Remove DRM : _removeDRM()_ * Remove DRM : _removeDRM()_
* Return loaned book : _returnLoan()_ * Return loaned book : _returnLoan()_
You can import configuration from (at least) : You can import configuration from (at least):
* Kobo device : .adept/device.xml, .adept/devicesalt and .adept/activation.xml * 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 * 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. 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 Dependencies
------------ ------------
For libgourou : For libgourou:
_externals_ : _externals_ :
* None * libpugixml
_internals_ : _internals_:
* PugiXML
* Base64
* uPDFParser * uPDFParser
For utils : For utils:
* libcurl * libcurl
* OpenSSL * openssl
* libzip * 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).
When you update libgourou's repository, **don't forget to update internal libraries** with : 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 make update_lib
@ -63,7 +64,7 @@ Compilation
Use _make_ command 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-) 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 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 * Default value
Utils 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 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> ./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 : To download an ePub/PDF:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD ./utils/acsmdownloader <ACSM_FILE>
./utils/acsmdownloader -f <ACSM_FILE>
To export your private key (for DeDRM software) : 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] ./utils/acsmdownloader --export-private-key [-o adobekey_1.der]
To remove ADEPT DRM : To remove ADEPT DRM:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD ./utils/adept_remove <encryptedFile>
./utils/adept_remove -f <encryptedFile>
To list loaned books : To list loaned books:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
./utils/adept_loan_mgt [-l] ./utils/adept_loan_mgt [-l]
To return a loaned book : To return a loaned book:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
./utils/adept_loan_mgt -r <id> ./utils/adept_loan_mgt -r <id>
You can get utils full options description with -h or --help switch 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 Docker
------ ------
@ -143,3 +157,19 @@ Special thanks
* _Jens_ for all test samples and utils testing * _Jens_ for all test samples and utils testing
* _Milian_ for debug & code * _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,99 +26,108 @@
*/ */
#include <string> #include <string>
#include <stdint.h>
namespace macaron { namespace macaron {
class Base64 { class Base64 {
public: public:
static std::string Encode(const std::string data) {
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',
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3',
'4', '5', '6', '7', '8', '9', '+', '/'
};
size_t in_len = data.size();
size_t out_len = 4 * ((in_len + 2) / 3);
std::string ret(out_len, '\0');
size_t i;
char *p = const_cast<char*>(ret.c_str());
for (i = 0; i < in_len - 2; i += 3) {
*p++ = sEncodingTable[(data[i] >> 2) & 0x3F];
*p++ = sEncodingTable[((data[i] & 0x3) << 4) | ((int) (data[i + 1] & 0xF0) >> 4)];
*p++ = sEncodingTable[((data[i + 1] & 0xF) << 2) | ((int) (data[i + 2] & 0xC0) >> 6)];
*p++ = sEncodingTable[data[i + 2] & 0x3F];
}
if (i < in_len) {
*p++ = sEncodingTable[(data[i] >> 2) & 0x3F];
if (i == (in_len - 1)) {
*p++ = sEncodingTable[((data[i] & 0x3) << 4)];
*p++ = '=';
}
else {
*p++ = sEncodingTable[((data[i] & 0x3) << 4) | ((int) (data[i + 1] & 0xF0) >> 4)];
*p++ = sEncodingTable[((data[i + 1] & 0xF) << 2)];
}
*p++ = '=';
}
return ret;
}
static std::string Decode(const std::string& input, std::string& out) {
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,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64,
64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64,
64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 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, 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, 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, 64, 64, 64, 64
};
size_t in_len = input.size();
if (in_len % 4 != 0) return "Input data size is not a multiple of 4";
size_t out_len = in_len / 4 * 3;
if (input[in_len - 1] == '=') out_len--;
if (input[in_len - 2] == '=') out_len--;
out.resize(out_len);
for (size_t i = 0, j = 0; i < in_len;) {
uint32_t a = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
uint32_t b = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
uint32_t c = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
uint32_t d = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
uint32_t triple = (a << 3 * 6) + (b << 2 * 6) + (c << 1 * 6) + (d << 0 * 6);
if (j < out_len) out[j++] = (triple >> 2 * 8) & 0xFF;
if (j < out_len) out[j++] = (triple >> 1 * 8) & 0xFF;
if (j < out_len) out[j++] = (triple >> 0 * 8) & 0xFF;
}
return "";
}
static std::string Encode(const std::string data) {
static constexpr 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',
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3',
'4', '5', '6', '7', '8', '9', '+', '/'
}; };
size_t in_len = data.size();
size_t out_len = 4 * ((in_len + 2) / 3);
std::string ret(out_len, '\0');
size_t i;
char *p = const_cast<char*>(ret.c_str());
for (i = 0; i < in_len - 2; i += 3) {
*p++ = sEncodingTable[(data[i] >> 2) & 0x3F];
*p++ = sEncodingTable[((data[i] & 0x3) << 4) | ((int) (data[i + 1] & 0xF0) >> 4)];
*p++ = sEncodingTable[((data[i + 1] & 0xF) << 2) | ((int) (data[i + 2] & 0xC0) >> 6)];
*p++ = sEncodingTable[data[i + 2] & 0x3F];
}
if (i < in_len) {
*p++ = sEncodingTable[(data[i] >> 2) & 0x3F];
if (i == (in_len - 1)) {
*p++ = sEncodingTable[((data[i] & 0x3) << 4)];
*p++ = '=';
}
else {
*p++ = sEncodingTable[((data[i] & 0x3) << 4) | ((int) (data[i + 1] & 0xF0) >> 4)];
*p++ = sEncodingTable[((data[i + 1] & 0xF) << 2)];
}
*p++ = '=';
}
return ret;
}
static std::string Decode(const std::string& input, std::string& out) {
static constexpr 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,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64,
64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64,
64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 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, 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, 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, 64, 64, 64, 64
};
size_t in_len = input.size();
if (in_len % 4 != 0) return "Input data size is not a multiple of 4";
size_t out_len = in_len / 4 * 3;
if (input[in_len - 1] == '=') out_len--;
if (input[in_len - 2] == '=') out_len--;
out.resize(out_len);
for (size_t i = 0, j = 0; i < in_len;) {
uint32_t a = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
uint32_t b = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
uint32_t c = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
uint32_t d = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
uint32_t triple = (a << 3 * 6) + (b << 2 * 6) + (c << 1 * 6) + (d << 0 * 6);
if (j < out_len) out[j++] = (triple >> 2 * 8) & 0xFF;
if (j < out_len) out[j++] = (triple >> 1 * 8) & 0xFF;
if (j < out_len) out[j++] = (triple >> 0 * 8) & 0xFF;
}
return "";
}
};
} }
#endif /* _MACARON_BASE64_H_ */ #endif /* _MACARON_BASE64_H_ */

View file

@ -38,149 +38,149 @@ namespace gourou
{ {
public: public:
/** /**
* @brief Create an empty byte array * @brief Create an empty byte array
* *
* @param useMalloc If true, use malloc() instead of new[] for allocation * @param useMalloc If true, use malloc() instead of new[] for allocation
*/ */
ByteArray(bool useMalloc=false); ByteArray(bool useMalloc=false);
/** /**
* @brief Create an empty byte array of length bytes * @brief Create an empty byte array of length bytes
* *
* @param length Length of data * @param length Length of data
* @param useMalloc If true, use malloc() instead of new[] for allocation * @param useMalloc If true, use malloc() instead of new[] for allocation
*/ */
ByteArray(unsigned int length, bool useMalloc=false); ByteArray(unsigned int length, bool useMalloc=false);
/** /**
* @brief Initialize ByteArray with a copy of data * @brief Initialize ByteArray with a copy of data
* *
* @param data Data to be copied * @param data Data to be copied
* @param length Length of data * @param length Length of data
*/ */
ByteArray(const unsigned char* data, unsigned int length); ByteArray(const unsigned char* data, unsigned int length);
/** /**
* @brief Initialize ByteArray with a copy of data * @brief Initialize ByteArray with a copy of data
* *
* @param data Data to be copied * @param data Data to be copied
* @param length Optional length of data. If length == -1, it use strlen(data) as length * @param length Optional length of data. If length == -1, it use strlen(data) as length
*/ */
ByteArray(const char* data, int length=-1); ByteArray(const char* data, int length=-1);
/** /**
* @brief Initialize ByteArray with a copy of str * @brief Initialize ByteArray with a copy of str
* *
* @param str Use internal data of str * @param str Use internal data of str
*/ */
ByteArray(const std::string& str); ByteArray(const std::string& str);
ByteArray(const ByteArray& other); ByteArray(const ByteArray& other);
~ByteArray(); ~ByteArray();
/** /**
* @brief Encode "other" data into base64 and put it into a ByteArray * @brief Encode "other" data into base64 and put it into a ByteArray
*/ */
static ByteArray fromBase64(const ByteArray& other); static ByteArray fromBase64(const ByteArray& other);
/** /**
* @brief Encode data into base64 and put it into a ByteArray * @brief Encode data into base64 and put it into a ByteArray
* *
* @param data Data to be encoded * @param data Data to be encoded
* @param length Optional length of data. If length == -1, it use strlen(data) as length * @param length Optional length of data. If length == -1, it use strlen(data) as length
*/ */
static ByteArray fromBase64(const char* data, int length=-1); static ByteArray fromBase64(const char* data, int length=-1);
/** /**
* @brief Encode str into base64 and put it into a ByteArray * @brief Encode str into base64 and put it into a ByteArray
* *
* @param str Use internal data of str * @param str Use internal data of str
*/ */
static ByteArray fromBase64(const std::string& str); static ByteArray fromBase64(const std::string& str);
/** /**
* @brief Return a string with base64 encoded internal data * @brief Return a string with base64 encoded internal data
*/ */
std::string toBase64(); std::string toBase64();
/** /**
* @brief Convert hex string into bytes * @brief Convert hex string into bytes
* *
* @param str Hex string * @param str Hex string
*/ */
static ByteArray fromHex(const std::string& str); static ByteArray fromHex(const std::string& str);
/** /**
* @brief Return a string with human readable hex encoded internal data * @brief Return a string with human readable hex encoded internal data
*/ */
std::string toHex(); std::string toHex();
/** /**
* @brief Append a byte to internal data * @brief Append a byte to internal data
*/ */
void append(unsigned char c); void append(unsigned char c);
/** /**
* @brief Append data to internal data * @brief Append data to internal data
*/ */
void append(const unsigned char* data, unsigned int length); void append(const unsigned char* data, unsigned int length);
/** /**
* @brief Append str to internal data * @brief Append str to internal data
*/ */
void append(const char* str); void append(const char* str);
/** /**
* @brief Append str to internal data * @brief Append str to internal data
*/ */
void append(const std::string& str); void append(const std::string& str);
/** /**
* @brief Get internal data. Must not be freed * @brief Get internal data. Must not be freed
*/ */
unsigned char* data() {return _data;} unsigned char* data() {return _data;}
/** /**
* @brief Get internal data and increment internal reference counter. * @brief Get internal data and increment internal reference counter.
* Must bot be freed * Must bot be freed
*/ */
unsigned char* takeShadowData() {addRef() ; return _data;} unsigned char* takeShadowData() {addRef() ; return _data;}
/** /**
* @brief Release shadow data. It can now be freed by ByteArray * @brief Release shadow data. It can now be freed by ByteArray
*/ */
void releaseShadowData() {delRef();} void releaseShadowData() {delRef();}
/** /**
* @brief Get internal data length * @brief Get internal data length
*/ */
unsigned int length() const {return _length;} unsigned int length() const {return _length;}
/** /**
* @brief Get internal data length * @brief Get internal data length
*/ */
unsigned int size() const {return length();} unsigned int size() const {return length();}
/** /**
* @brief Increase or decrease internal buffer * @brief Increase or decrease internal buffer
* @param length New length of internal buffer * @param length New length of internal buffer
* @param keepData If true copy old data on new buffer, if false, * @param keepData If true copy old data on new buffer, if false,
* create a new buffer with random data * create a new buffer with random data
*/ */
void resize(unsigned int length, bool keepData=true); void resize(unsigned int length, bool keepData=true);
ByteArray& operator=(const ByteArray& other);
ByteArray& operator=(const ByteArray& other);
private: private:
void initData(const unsigned char* data, unsigned int length); void initData(const unsigned char* data, unsigned int length);
void addRef(); void addRef();
void delRef(); void delRef();
bool _useMalloc; bool _useMalloc;
unsigned char* _data; unsigned char* _data;
unsigned int _length; unsigned int _length;
static std::map<unsigned char*, int> refCounter; static std::map<unsigned char*, int> refCounter;
}; };
} }
#endif #endif

View file

@ -30,54 +30,54 @@ namespace gourou
class Device class Device
{ {
public: public:
static const int DEVICE_KEY_SIZE = 16; static const int DEVICE_KEY_SIZE = 16;
static const int DEVICE_SERIAL_LEN = 10; static const int DEVICE_SERIAL_LEN = 10;
/** /**
* @brief Main Device constructor * @brief Main Device constructor
* *
* @param processor Instance of DRMProcessor * @param processor Instance of DRMProcessor
* @param deviceFile Path of device.xml * @param deviceFile Path of device.xml
* @param deviceKeyFile Path of devicesalt * @param deviceKeyFile Path of devicesalt
*/ */
Device(DRMProcessor* processor, const std::string& deviceFile, const std::string& deviceKeyFile); Device(DRMProcessor* processor, const std::string& deviceFile, const std::string& deviceKeyFile);
/** /**
* @brief Return value of devicesalt file (DEVICE_KEY_SIZE len) * @brief Return value of devicesalt file (DEVICE_KEY_SIZE len)
*/ */
const unsigned char* getDeviceKey(); const unsigned char* getDeviceKey();
/** /**
* @brief Get one value of device.xml (deviceClass, deviceSerial, deviceName, deviceType, hobbes, clientOS, clientLocale) * @brief Get one value of device.xml (deviceClass, deviceSerial, deviceName, deviceType, hobbes, clientOS, clientLocale)
*/ */
std::string getProperty(const std::string& property, const std::string& _default=std::string("")); std::string getProperty(const std::string& property, const std::string& _default=std::string(""));
std::string operator[](const std::string& property); std::string operator[](const std::string& property);
/**
* @brief Create device.xml and devicesalt files when they did not exists
*
* @param processor Instance of DRMProcessor
* @param dirName Directory where to put files (.adept)
* @param hobbes Hobbes (client version) to set
* @param randomSerial Create a random serial (new device each time) or not (serial computed from machine specs)
*/
static Device* createDevice(DRMProcessor* processor, const std::string& dirName, const std::string& hobbes, bool randomSerial);
/**
* @brief Create device.xml and devicesalt files when they did not exists
*
* @param processor Instance of DRMProcessor
* @param dirName Directory where to put files (.adept)
* @param hobbes Hobbes (client version) to set
* @param randomSerial Create a random serial (new device each time) or not (serial computed from machine specs)
*/
static Device* createDevice(DRMProcessor* processor, const std::string& dirName, const std::string& hobbes, bool randomSerial);
private: private:
DRMProcessor* processor; DRMProcessor* processor;
std::string deviceFile; std::string deviceFile;
std::string deviceKeyFile; std::string deviceKeyFile;
unsigned char deviceKey[DEVICE_KEY_SIZE]; unsigned char deviceKey[DEVICE_KEY_SIZE];
std::map<std::string, std::string> properties; std::map<std::string, std::string> properties;
Device(DRMProcessor* processor); Device(DRMProcessor* processor);
std::string makeFingerprint(const std::string& serial); std::string makeFingerprint(const std::string& serial);
std::string makeSerial(bool random); std::string makeSerial(bool random);
void parseDeviceFile(); void parseDeviceFile();
void parseDeviceKeyFile(); void parseDeviceKeyFile();
void createDeviceFile(const std::string& hobbes, bool randomSerial); void createDeviceFile(const std::string& hobbes, bool randomSerial);
void createDeviceKeyFile(); void createDeviceKeyFile();
}; };
} }

View file

@ -34,376 +34,376 @@ namespace gourou
class DigestInterface class DigestInterface
{ {
public: public:
/** /**
* @brief Create a digest handler * @brief Create a digest handler
* *
* @param digestName Digest name to instanciate * @param digestName Digest name to instanciate
*/ */
virtual void* createDigest(const std::string& digestName) = 0; virtual void* createDigest(const std::string& digestName) = 0;
/** /**
* @brief Update digest engine with new data * @brief Update digest engine with new data
* *
* @param handler Digest handler * @param handler Digest handler
* @param data Data to digest * @param data Data to digest
* @param length Length of data * @param length Length of data
*/ */
virtual void digestUpdate(void* handler, unsigned char* data, unsigned int length) = 0; virtual void digestUpdate(void* handler, unsigned char* data, unsigned int length) = 0;
/** /**
* @brief Finalize digest with remained buffered data and destroy handler * @brief Finalize digest with remained buffered data and destroy handler
* *
* @param handler Digest handler * @param handler Digest handler
* @param digestOut Digest result (buffer must be pre allocated with right size) * @param digestOut Digest result (buffer must be pre allocated with right size)
*/ */
virtual void digestFinalize(void* handler, unsigned char* digestOut) = 0; virtual void digestFinalize(void* handler, unsigned char* digestOut) = 0;
/** /**
* @brief Global digest function * @brief Global digest function
* *
* @param digestName Digest name to instanciate * @param digestName Digest name to instanciate
* @param data Data to digest * @param data Data to digest
* @param length Length of data * @param length Length of data
* @param digestOut Digest result (buffer must be pre allocated with right size) * @param digestOut Digest result (buffer must be pre allocated with right size)
*/ */
virtual void digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut) = 0; virtual void digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut) = 0;
}; };
class RandomInterface class RandomInterface
{ {
public: public:
/** /**
* @brief Generate random bytes * @brief Generate random bytes
* *
* @param bytesOut Buffer to fill with random bytes * @param bytesOut Buffer to fill with random bytes
* @param length Length of bytesOut * @param length Length of bytesOut
*/ */
virtual void randBytes(unsigned char* bytesOut, unsigned int length) = 0; virtual void randBytes(unsigned char* bytesOut, unsigned int length) = 0;
}; };
class HTTPInterface class HTTPInterface
{ {
public: public:
/** /**
* @brief Send HTTP (GET or POST) request * @brief Send HTTP (GET or POST) request
* *
* @param URL HTTP URL * @param URL HTTP URL
* @param POSTData POST data if needed, if not set, a GET request is done * @param POSTData POST data if needed, if not set, a GET request is done
* @param contentType Optional content type of POST Data * @param contentType Optional content type of POST Data
* @param responseHeaders Optional Response headers of HTTP request * @param responseHeaders Optional Response headers of HTTP request
* @param fd Optional file descriptor to write request result * @param fd Optional file descriptor to write request result
* @param resume false if target file should be truncated, true to try resume download (works only in combination with a valid fd) * @param resume false if target file should be truncated, true to try resume download (works only in combination with a valid fd)
* *
* @return data of HTTP response * @return data of HTTP response
*/ */
virtual std::string sendHTTPRequest(const std::string& URL, const std::string& POSTData=std::string(""), const std::string& contentType=std::string(""), std::map<std::string, std::string>* responseHeaders=0, int fd=0, bool resume=false) = 0; virtual std::string sendHTTPRequest(const std::string& URL, const std::string& POSTData=std::string(""), const std::string& contentType=std::string(""), std::map<std::string, std::string>* responseHeaders=0, int fd=0, bool resume=false) = 0;
}; };
class RSAInterface class RSAInterface
{ {
public: public:
enum RSA_KEY_TYPE { enum RSA_KEY_TYPE {
RSA_KEY_PKCS12 = 0, RSA_KEY_PKCS12 = 0,
RSA_KEY_PKCS8, RSA_KEY_PKCS8,
RSA_KEY_X509 RSA_KEY_X509
}; };
/** /**
* @brief Encrypt data with RSA private key. Data is padded using PKCS1.5 * @brief Encrypt data with RSA private key. Data is padded using PKCS1.5
* *
* @param RSAKey RSA key in binary form * @param RSAKey RSA key in binary form
* @param RSAKeyLength RSA key length * @param RSAKeyLength RSA key length
* @param keyType Key type * @param keyType Key type
* @param password Optional password for RSA PKCS12 certificate * @param password Optional password for RSA PKCS12 certificate
* @param data Data to encrypt * @param data Data to encrypt
* @param dataLength Data length * @param dataLength Data length
* @param res Encryption result (pre allocated buffer) * @param res Encryption result (pre allocated buffer)
*/ */
virtual void RSAPrivateEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength, virtual void RSAPrivateEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength,
const RSA_KEY_TYPE keyType, const std::string& password, const RSA_KEY_TYPE keyType, const std::string& password,
const unsigned char* data, unsigned dataLength, const unsigned char* data, unsigned dataLength,
unsigned char* res) = 0; unsigned char* res) = 0;
/**
* @brief Decrypt data with RSA private key. Data is padded using PKCS1.5
*
* @param RSAKey RSA key in binary form
* @param RSAKeyLength RSA key length
* @param keyType Key type
* @param password Optional password for RSA PKCS12 certificate
* @param data Data to encrypt
* @param dataLength Data length
* @param res Encryption result (pre allocated buffer)
*/
virtual void RSAPrivateDecrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength,
const RSA_KEY_TYPE keyType, const std::string& password,
const unsigned char* data, unsigned dataLength,
unsigned char* res) = 0;
/** /**
* @brief Encrypt data with RSA public key. Data is padded using PKCS1.5 * @brief Decrypt data with RSA private key. Data is padded using PKCS1.5
* *
* @param RSAKey RSA key in binary form * @param RSAKey RSA key in binary form
* @param RSAKeyLength RSA key length * @param RSAKeyLength RSA key length
* @param keyType Key type * @param keyType Key type
* @param password Optional password for RSA PKCS12 certificate * @param password Optional password for RSA PKCS12 certificate
* @param data Data to encrypt * @param data Data to encrypt
* @param dataLength Data length * @param dataLength Data length
* @param res Encryption result (pre allocated buffer) * @param res Encryption result (pre allocated buffer)
*/ */
virtual void RSAPublicEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength, virtual void RSAPrivateDecrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength,
const RSA_KEY_TYPE keyType, const RSA_KEY_TYPE keyType, const std::string& password,
const unsigned char* data, unsigned dataLength, const unsigned char* data, unsigned dataLength,
unsigned char* res) = 0; unsigned char* res) = 0;
/** /**
* @brief Generate RSA key. Expnonent is fixed (65537 / 0x10001) * @brief Encrypt data with RSA public key. Data is padded using PKCS1.5
* *
* @param keyLengthBits Length of key (in bits) to generate * @param RSAKey RSA key in binary form
* * @param RSAKeyLength RSA key length
* @return generatedKey * @param keyType Key type
*/ * @param password Optional password for RSA PKCS12 certificate
virtual void* generateRSAKey(int keyLengthBits) = 0; * @param data Data to encrypt
* @param dataLength Data length
* @param res Encryption result (pre allocated buffer)
*/
virtual void RSAPublicEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength,
const RSA_KEY_TYPE keyType,
const unsigned char* data, unsigned dataLength,
unsigned char* res) = 0;
/** /**
* @brief Destroy key previously generated * @brief Generate RSA key. Expnonent is fixed (65537 / 0x10001)
* *
* @param handler Key to destroy * @param keyLengthBits Length of key (in bits) to generate
*/ *
virtual void destroyRSAHandler(void* handler) = 0; * @return generatedKey
*/
virtual void* generateRSAKey(int keyLengthBits) = 0;
/** /**
* @brief Extract public key (big number) from RSA handler * @brief Destroy key previously generated
* *
* @param handler RSA handler (generated key) * @param handler Key to destroy
* @param keyOut Pre allocated buffer (if *keyOut != 0). If *keyOut is 0, memory is internally allocated (must be freed) */
* @param keyOutLength Length of result virtual void destroyRSAHandler(void* handler) = 0;
*/
virtual void extractRSAPublicKey(void* handler, unsigned char** keyOut, unsigned int* keyOutLength) = 0;
/** /**
* @brief Extract private key (big number) from RSA handler * @brief Extract public key (big number) from RSA handler
* *
* @param handler RSA handler (generated key) * @param handler RSA handler (generated key)
* @param keyOut Pre allocated buffer (if *keyOut != 0). If *keyOut is 0, memory is internally allocated (must be freed) * @param keyOut Pre allocated buffer (if *keyOut != 0). If *keyOut is 0, memory is internally allocated (must be freed)
* @param keyOutLength Length of result * @param keyOutLength Length of result
*/ */
virtual void extractRSAPrivateKey(void* handler, unsigned char** keyOut, unsigned int* keyOutLength) = 0; virtual void extractRSAPublicKey(void* handler, unsigned char** keyOut, unsigned int* keyOutLength) = 0;
/** /**
* @brief Extract certificate from PKCS12 blob * @brief Extract private key (big number) from RSA handler
* *
* @param RSAKey RSA key in binary form * @param handler RSA handler (generated key)
* @param RSAKeyLength RSA key length * @param keyOut Pre allocated buffer (if *keyOut != 0). If *keyOut is 0, memory is internally allocated (must be freed)
* @param keyType Key type * @param keyOutLength Length of result
* @param password Optional password for RSA PKCS12 certificate */
* @param certOut Result certificate virtual void extractRSAPrivateKey(void* handler, unsigned char** keyOut, unsigned int* keyOutLength) = 0;
* @param certOutLength Result certificate length
*/ /**
virtual void extractCertificate(const unsigned char* RSAKey, unsigned int RSAKeyLength, * @brief Extract certificate from PKCS12 blob
const RSA_KEY_TYPE keyType, const std::string& password, *
unsigned char** certOut, unsigned int* certOutLength) = 0; * @param RSAKey RSA key in binary form
* @param RSAKeyLength RSA key length
* @param keyType Key type
* @param password Optional password for RSA PKCS12 certificate
* @param certOut Result certificate
* @param certOutLength Result certificate length
*/
virtual void extractCertificate(const unsigned char* RSAKey, unsigned int RSAKeyLength,
const RSA_KEY_TYPE keyType, const std::string& password,
unsigned char** certOut, unsigned int* certOutLength) = 0;
}; };
class CryptoInterface class CryptoInterface
{ {
public: public:
enum CRYPTO_ALGO { enum CRYPTO_ALGO {
ALGO_AES=0, ALGO_AES=0,
ALGO_RC4 ALGO_RC4
}; };
enum CHAINING_MODE { enum CHAINING_MODE {
CHAIN_ECB=0, CHAIN_ECB=0,
CHAIN_CBC CHAIN_CBC
}; };
/**
* @brief Do encryption. If length of data is not multiple of block size, PKCS#5 padding is done
*
* @param algo Algorithm to use
* @param chaining Chaining mode
* @param key AES key
* @param keyLength AES key length
* @param iv IV key
* @param ivLength IV key length
* @param dataIn Data to encrypt
* @param dataInLength Data length
* @param dataOut Encrypted data
* @param dataOutLength Length of encrypted data
*/
virtual void encrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength,
const unsigned char* iv, unsigned int ivLength,
const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength) = 0;
/** /**
* @brief Init encryption * @brief Do encryption. If length of data is not multiple of block size, PKCS#5 padding is done
* *
* @param chaining Chaining mode * @param algo Algorithm to use
* @param key Key * @param chaining Chaining mode
* @param keyLength Key length * @param key AES key
* @param iv Optional IV key * @param keyLength AES key length
* @param ivLength Optional IV key length * @param iv IV key
* * @param ivLength IV key length
* @return AES handler * @param dataIn Data to encrypt
*/ * @param dataInLength Data length
virtual void* encryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, * @param dataOut Encrypted data
const unsigned char* key, unsigned int keyLength, * @param dataOutLength Length of encrypted data
const unsigned char* iv=0, unsigned int ivLength=0) = 0; */
virtual void encrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength,
const unsigned char* iv, unsigned int ivLength,
const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength) = 0;
/** /**
* @brief Encrypt data * @brief Init encryption
* *
* @param handler Crypto handler * @param chaining Chaining mode
* @param dataIn Data to encrypt * @param key Key
* @param dataInLength Data length * @param keyLength Key length
* @param dataOut Encrypted data * @param iv Optional IV key
* @param dataOutLength Length of encrypted data * @param ivLength Optional IV key length
*/ *
virtual void encryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, * @return AES handler
unsigned char* dataOut, unsigned int* dataOutLength) = 0; */
virtual void* encryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength,
const unsigned char* iv=0, unsigned int ivLength=0) = 0;
/** /**
* @brief Finalize encryption (pad and encrypt last block if needed) * @brief Encrypt data
* Destroy handler at the end *
* * @param handler Crypto handler
* @param handler Crypto handler * @param dataIn Data to encrypt
* @param dataOut Last block of encrypted data * @param dataInLength Data length
* @param dataOutLength Length of encrypted data * @param dataOut Encrypted data
*/ * @param dataOutLength Length of encrypted data
virtual void encryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength) = 0; */
virtual void encryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength) = 0;
/** /**
* @brief Do decryption. If length of data is not multiple of block size, PKCS#5 padding is done * @brief Finalize encryption (pad and encrypt last block if needed)
* * Destroy handler at the end
* @param algo Algorithm to use *
* @param chaining Chaining mode * @param handler Crypto handler
* @param key AES key * @param dataOut Last block of encrypted data
* @param keyLength AES key length * @param dataOutLength Length of encrypted data
* @param iv IV key */
* @param ivLength IV key length virtual void encryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength) = 0;
* @param dataIn Data to encrypt
* @param dataInLength Data length
* @param dataOut Encrypted data
* @param dataOutLength Length of encrypted data
*/
virtual void decrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength,
const unsigned char* iv, unsigned int ivLength,
const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength) = 0;
/** /**
* @brief Init decryption * @brief Do decryption. If length of data is not multiple of block size, PKCS#5 padding is done
* *
* @param chaining Chaining mode * @param algo Algorithm to use
* @param key Key * @param chaining Chaining mode
* @param keyLength Key length * @param key AES key
* @param iv IV key * @param keyLength AES key length
* @param ivLength IV key length * @param iv IV key
* * @param ivLength IV key length
* @return AES handler * @param dataIn Data to encrypt
*/ * @param dataInLength Data length
virtual void* decryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, * @param dataOut Encrypted data
const unsigned char* key, unsigned int keyLength, * @param dataOutLength Length of encrypted data
const unsigned char* iv=0, unsigned int ivLength=0) = 0; */
virtual void decrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength,
const unsigned char* iv, unsigned int ivLength,
const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength) = 0;
/** /**
* @brief Decrypt data * @brief Init decryption
* *
* @param handler Crypto handler * @param chaining Chaining mode
* @param dataIn Data to decrypt * @param key Key
* @param dataInLength Data length * @param keyLength Key length
* @param dataOut Decrypted data * @param iv IV key
* @param dataOutLength Length of decrypted data * @param ivLength IV key length
*/ *
virtual void decryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, * @return AES handler
unsigned char* dataOut, unsigned int* dataOutLength) = 0; */
/** virtual void* decryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining,
* @brief Finalize decryption (decrypt last block and remove padding if it is set). const unsigned char* key, unsigned int keyLength,
* Destroy handler at the end const unsigned char* iv=0, unsigned int ivLength=0) = 0;
*
* @param handler Crypto handler /**
* @param dataOut Last block decrypted data * @brief Decrypt data
* @param dataOutLength Length of decrypted data *
*/ * @param handler Crypto handler
virtual void decryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength) = 0; * @param dataIn Data to decrypt
* @param dataInLength Data length
* @param dataOut Decrypted data
* @param dataOutLength Length of decrypted data
*/
virtual void decryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength) = 0;
/**
* @brief Finalize decryption (decrypt last block and remove padding if it is set).
* Destroy handler at the end
*
* @param handler Crypto handler
* @param dataOut Last block decrypted data
* @param dataOutLength Length of decrypted data
*/
virtual void decryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength) = 0;
}; };
class ZIPInterface class ZIPInterface
{ {
public: public:
/** /**
* @brief Open a zip file and return an handler * @brief Open a zip file and return an handler
* *
* @param path Path of zip file * @param path Path of zip file
* *
* @return ZIP file handler * @return ZIP file handler
*/ */
virtual void* zipOpen(const std::string& path) = 0; virtual void* zipOpen(const std::string& path) = 0;
/**
* @brief Read zip internal file
*
* @param handler ZIP file handler
* @param path Internal path inside zip file
* @param result Result buffer
* @param decompress If false, don't decompress read data
*/
virtual void zipReadFile(void* handler, const std::string& path, ByteArray& result, bool decompress=true) = 0;
/**
* @brief Write zip internal file
*
* @param handler ZIP file handler
* @param path Internal path inside zip file
* @param content File content
*/
virtual void zipWriteFile(void* handler, const std::string& path, ByteArray& content) = 0;
/** /**
* @brief Delete zip internal file * @brief Read zip internal file
* *
* @param handler ZIP file handler * @param handler ZIP file handler
* @param path Internal path inside zip file * @param path Internal path inside zip file
*/ * @param result Result buffer
virtual void zipDeleteFile(void* handler, const std::string& path) = 0; * @param decompress If false, don't decompress read data
*/
virtual void zipReadFile(void* handler, const std::string& path, ByteArray& result, bool decompress=true) = 0;
/** /**
* @brief Close ZIP file handler * @brief Write zip internal file
* *
* @param handler ZIP file handler * @param handler ZIP file handler
*/ * @param path Internal path inside zip file
virtual void zipClose(void* handler) = 0; * @param content File content
*/
virtual void zipWriteFile(void* handler, const std::string& path, ByteArray& content) = 0;
/** /**
* @brief Inflate algorithm * @brief Delete zip internal file
* *
* @param data Data to inflate * @param handler ZIP file handler
* @param result Zipped data * @param path Internal path inside zip file
* @param wbits Window bits value for libz */
*/ virtual void zipDeleteFile(void* handler, const std::string& path) = 0;
virtual void inflate(gourou::ByteArray& data, gourou::ByteArray& result,
int wbits=-15) = 0; /**
* @brief Close ZIP file handler
/** *
* @brief Deflate algorithm * @param handler ZIP file handler
* */
* @param data Data to deflate virtual void zipClose(void* handler) = 0;
* @param result Unzipped data
* @param wbits Window bits value for libz /**
* @param compressionLevel Compression level for libz * @brief Inflate algorithm
*/ *
virtual void deflate(gourou::ByteArray& data, gourou::ByteArray& result, * @param data Data to inflate
int wbits=-15, int compressionLevel=8) = 0; * @param result Zipped data
* @param wbits Window bits value for libz
*/
virtual void inflate(gourou::ByteArray& data, gourou::ByteArray& result,
int wbits=-15) = 0;
/**
* @brief Deflate algorithm
*
* @param data Data to deflate
* @param result Unzipped data
* @param wbits Window bits value for libz
* @param compressionLevel Compression level for libz
*/
virtual void deflate(gourou::ByteArray& data, gourou::ByteArray& result,
int wbits=-15, int compressionLevel=8) = 0;
}; };
class DRMProcessorClient: public DigestInterface, public RandomInterface, public HTTPInterface, \ class DRMProcessorClient: public DigestInterface, public RandomInterface, public HTTPInterface, \
public RSAInterface, public CryptoInterface, public ZIPInterface public RSAInterface, public CryptoInterface, public ZIPInterface
{}; {};
} }
#endif #endif

View file

@ -34,52 +34,52 @@ namespace gourou
class FulfillmentItem class FulfillmentItem
{ {
public: public:
/** /**
* @brief Main constructor. Not to be called by user * @brief Main constructor. Not to be called by user
* *
* @param doc Fulfill reply * @param doc Fulfill reply
* @param user User pointer * @param user User pointer
*/ */
FulfillmentItem(pugi::xml_document& doc, User* user); FulfillmentItem(pugi::xml_document& doc, User* user);
~FulfillmentItem(); ~FulfillmentItem();
/** /**
* @brief Return metadata value from ACSM metadata section * @brief Return metadata value from ACSM metadata section
* *
* @param name Name of key to return * @param name Name of key to return
*/ */
std::string getMetadata(std::string name); std::string getMetadata(std::string name);
/** /**
* @brief Return rights generated by ACS server (XML format) * @brief Return rights generated by ACS server (XML format)
*/ */
std::string getRights(); std::string getRights();
/** /**
* @brief Return epub download URL * @brief Return epub download URL
*/ */
std::string getDownloadURL(); std::string getDownloadURL();
/** /**
* @brief Return resource value * @brief Return resource value
*/ */
std::string getResource(); std::string getResource();
/** /**
* @brief Return loan token if there is one * @brief Return loan token if there is one
*/ */
LoanToken* getLoanToken(); LoanToken* getLoanToken();
private: private:
pugi::xml_document fulfillDoc; pugi::xml_document fulfillDoc;
pugi::xml_node metadatas; pugi::xml_node metadatas;
pugi::xml_document rights; pugi::xml_document rights;
std::string downloadURL; std::string downloadURL;
std::string resource; std::string resource;
LoanToken* loanToken; LoanToken* loanToken;
void buildRights(const pugi::xml_node& licenseToken, User* user); void buildRights(const pugi::xml_node& licenseToken, User* user);
}; };
} }

View file

@ -27,20 +27,17 @@
#include "drmprocessorclient.h" #include "drmprocessorclient.h"
#include <pugixml.hpp> #include <pugixml.hpp>
#include <stdint.h>
#ifndef HOBBES_DEFAULT_VERSION #ifndef HOBBES_DEFAULT_VERSION
#define HOBBES_DEFAULT_VERSION "10.0.4" #define HOBBES_DEFAULT_VERSION "10.0.4"
#endif #endif
#ifndef DEFAULT_ADEPT_DIR
#define DEFAULT_ADEPT_DIR "./.adept"
#endif
#ifndef ACS_SERVER #ifndef ACS_SERVER
#define ACS_SERVER "http://adeactivate.adobe.com/adept" #define ACS_SERVER "http://adeactivate.adobe.com/adept"
#endif #endif
#define LIBGOUROU_VERSION "0.8" #define LIBGOUROU_VERSION "0.8.8"
namespace gourou namespace gourou
{ {
@ -51,194 +48,204 @@ namespace gourou
{ {
public: public:
static const std::string VERSION; static const std::string VERSION;
enum ITEM_TYPE { EPUB=0, PDF }; enum ITEM_TYPE { EPUB=0, PDF };
/** /**
* @brief Main constructor. To be used once all is configured (user has signedIn, device is activated) * @brief Main constructor. To be used once all is configured (user has signedIn, device is activated)
* *
* @param client Client processor * @param client Client processor
* @param deviceFile Path of device.xml * @param deviceFile Path of device.xml
* @param activationFile Path of activation.xml * @param activationFile Path of activation.xml
* @param deviceKeyFile Path of devicesalt * @param deviceKeyFile Path of devicesalt
*/ */
DRMProcessor(DRMProcessorClient* client, const std::string& deviceFile, const std::string& activationFile, const std::string& deviceKeyFile); DRMProcessor(DRMProcessorClient* client, const std::string& deviceFile, const std::string& activationFile, const std::string& deviceKeyFile);
~DRMProcessor();
/** ~DRMProcessor();
* @brief Fulfill ACSM file to server in order to retrieve ePub fulfillment item
*
* @param ACSMFile Path of ACSMFile
*
* @return a FulfillmentItem if all is OK
*/
FulfillmentItem* fulfill(const std::string& ACSMFile);
/** /**
* @brief Once fulfilled, ePub file needs to be downloaded. * @brief Fulfill ACSM file to server in order to retrieve ePub fulfillment item
* During this operation, DRM information is added into downloaded file *
* * @param ACSMFile Path of ACSMFile
* @param item Item from fulfill() method * @param notify Notify server if requested by response
* @param path Output file path *
* @param resume false if target file should be truncated, true to try resume download * @return a FulfillmentItem if all is OK
* */
* @return Type of downloaded item FulfillmentItem* fulfill(const std::string& ACSMFile, bool notify=true);
*/
ITEM_TYPE download(FulfillmentItem* item, std::string path, bool resume=false);
/** /**
* @brief SignIn into ACS Server (required to activate device) * @brief Once fulfilled, ePub file needs to be downloaded.
* * During this operation, DRM information is added into downloaded file
* @param adobeID AdobeID username *
* @param adobePassword Adobe password * @param item Item from fulfill() method
*/ * @param path Output file path
void signIn(const std::string& adobeID, const std::string& adobePassword); * @param resume false if target file should be truncated, true to try resume download
*
* @return Type of downloaded item
*/
ITEM_TYPE download(FulfillmentItem* item, std::string path, bool resume=false);
/** /**
* @brief Activate newly created device (user must have successfuly signedIn before) * @brief SignIn into ACS Server (required to activate device)
*/ *
void activateDevice(); * @param adobeID AdobeID username
* @param adobePassword Adobe password
*/
void signIn(const std::string& adobeID, const std::string& adobePassword);
/** /**
* @brief Return loaned book to server * @brief Activate newly created device (user must have successfuly signedIn before)
* */
* @param loanID Loan ID received during fulfill void activateDevice();
* @param operatorURL URL of operator that loans this book
*/ /**
void returnLoan(const std::string& loanID, const std::string& operatorURL); * @brief Return loaned book to server
*
/** * @param loanID Loan ID received during fulfill
* @brief Create a new ADEPT environment (device.xml, devicesalt and activation.xml). * @param operatorURL URL of operator that loans this book
* * @param notify Notify server if requested by response
* @param client Client processor */
* @param randomSerial Always generate a new device (or not) void returnLoan(const std::string& loanID, const std::string& operatorURL, bool notify=true);
* @param dirName Directory where to put generated files (.adept)
* @param hobbes Override hobbes default version /**
* @param ACSServer Override main ACS server (default adeactivate.adobe.com) * @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).
*
* @param client Client processor
* @param randomSerial Always generate a new device (or not)
* @param dirName Directory where to put generated files (.adept)
* @param hobbes Override hobbes default version
* @param ACSServer Override main ACS server (default adeactivate.adobe.com)
*/
static DRMProcessor* createDRMProcessor(DRMProcessorClient* client, 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& hobbes=std::string(HOBBES_DEFAULT_VERSION),
const std::string& ACSServer=ACS_SERVER); const std::string& ACSServer=ACS_SERVER);
/** /**
* @brief Get current log level * @brief Get current log level
*/ */
static int getLogLevel(); static int getLogLevel();
/** /**
* @brief Set log level (higher number for verbose output) * @brief Set log level (higher number for verbose output)
*/ */
static void setLogLevel(int logLevel); static void setLogLevel(int logLevel);
/** /**
* Functions used internally, should not be called by user * Functions used internally, should not be called by user
*/ */
/** /**
* @brief Send HTTP (GET or POST) request * @brief Send HTTP (GET or POST) request
* *
* @param URL HTTP URL * @param URL HTTP URL
* @param POSTData POST data if needed, if not set, a GET request is done * @param POSTData POST data if needed, if not set, a GET request is done
* @param contentType Optional content type of POST Data * @param contentType Optional content type of POST Data
* @param responseHeaders Optional Response headers of HTTP request * @param responseHeaders Optional Response headers of HTTP request
* @param fd Optional File descriptor to write received data * @param fd Optional File descriptor to write received data
* @param resume false if target file should be truncated, true to try resume download (works only in combination of a valid fd) * @param resume false if target file should be truncated, true to try resume download (works only in combination of a valid fd)
* *
* @return data of HTTP response * @return data of HTTP response
*/ */
ByteArray sendRequest(const std::string& URL, const std::string& POSTData=std::string(), const char* contentType=0, std::map<std::string, std::string>* responseHeaders=0, int fd=0, bool resume=false); ByteArray sendRequest(const std::string& URL, const std::string& POSTData=std::string(), const char* contentType=0, std::map<std::string, std::string>* responseHeaders=0, int fd=0, bool resume=false);
/** /**
* @brief Send HTTP POST request to URL with document as POSTData * @brief Send HTTP POST request to URL with document as POSTData
*/ */
ByteArray sendRequest(const pugi::xml_document& document, const std::string& url); ByteArray sendRequest(const pugi::xml_document& document, const std::string& url);
/** /**
* @brief In place encrypt data with private device key * @brief In place encrypt data with private device key
*/ */
ByteArray encryptWithDeviceKey(const unsigned char* data, unsigned int len); ByteArray encryptWithDeviceKey(const unsigned char* data, unsigned int len);
/** /**
* @brief In place decrypt data with private device key * @brief In place decrypt data with private device key
*/ */
ByteArray decryptWithDeviceKey(const unsigned char* data, unsigned int len); ByteArray decryptWithDeviceKey(const unsigned char* data, unsigned int len);
/** /**
* @brief Return base64 encoded value of RSA public key * @brief Return base64 encoded value of RSA public key
*/ */
std::string serializeRSAPublicKey(void* rsa); std::string serializeRSAPublicKey(void* rsa);
/** /**
* @brief Return base64 encoded value of RSA private key encrypted with private device key * @brief Return base64 encoded value of RSA private key encrypted with private device key
*/ */
std::string serializeRSAPrivateKey(void* rsa); std::string serializeRSAPrivateKey(void* rsa);
/** /**
* @brief Export clear private license key into path * @brief Export clear private license key into path
*/ */
void exportPrivateLicenseKey(std::string path); void exportPrivateLicenseKey(std::string path);
/**
* @brief Get current user
*/
User* getUser() { return user; }
/** /**
* @brief Get current device * @brief Get current user
*/ */
Device* getDevice() { return device; } User* getUser() { return user; }
/**
* @brief Get current device
*/
Device* getDevice() { return device; }
/**
* @brief Get current client
*/
DRMProcessorClient* getClient() { return client; }
/**
* @brief Remove ADEPT DRM
* Warning: for PDF format, filenameIn must be different than filenameOut
*
* @param filenameIn Input file (with ADEPT DRM)
* @param filenameOut Output file (without ADEPT DRM)
* @param type Type of file (ePub or PDF)
* @param encryptionKey Optional encryption key, do not try to decrypt the one inside input file
* @param encryptionKeySize Size of encryption key (if provided)
*/
void removeDRM(const std::string& filenameIn, const std::string& filenameOut, ITEM_TYPE type, const unsigned char* encryptionKey=0, unsigned encryptionKeySize=0);
/**
* @brief Get current client
*/
DRMProcessorClient* getClient() { return client; }
/**
* @brief Remove ADEPT DRM
* Warning: for PDF format, filenameIn must be different than filenameOut
*
* @param filenameIn Input file (with ADEPT DRM)
* @param filenameOut Output file (without ADEPT DRM)
* @param type Type of file (ePub or PDF)
* @param encryptionKey Optional encryption key, do not try to decrypt the one inside input file
* @param encryptionKeySize Size of encryption key (if provided)
*/
void removeDRM(const std::string& filenameIn, const std::string& filenameOut, ITEM_TYPE type, const unsigned char* encryptionKey=0, unsigned encryptionKeySize=0);
private: private:
gourou::DRMProcessorClient* client; gourou::DRMProcessorClient* client;
gourou::Device* device; gourou::Device* device;
gourou::User* user; gourou::User* user;
DRMProcessor(DRMProcessorClient* client); DRMProcessor(DRMProcessorClient* client);
void pushString(void* sha_ctx, const std::string& string); void pushString(void* sha_ctx, const std::string& string);
void pushTag(void* sha_ctx, uint8_t tag); void pushTag(void* sha_ctx, uint8_t tag);
void hashNode(const pugi::xml_node& root, void *sha_ctx, std::map<std::string,std::string> nsHash); void hashNode(const pugi::xml_node& root, void *sha_ctx, std::map<std::string,std::string> nsHash);
void hashNode(const pugi::xml_node& root, unsigned char* sha_out); void hashNode(const pugi::xml_node& root, unsigned char* sha_out);
void signNode(pugi::xml_node& rootNode); void signNode(pugi::xml_node& rootNode);
void addNonce(pugi::xml_node& root); void addNonce(pugi::xml_node& root);
void buildAuthRequest(pugi::xml_document& authReq); void buildAuthRequest(pugi::xml_document& authReq);
void buildInitLicenseServiceRequest(pugi::xml_document& initLicReq, std::string operatorURL); void buildInitLicenseServiceRequest(pugi::xml_document& initLicReq, std::string operatorURL);
void doOperatorAuth(std::string operatorURL); void doOperatorAuth(std::string operatorURL);
void operatorAuth(std::string operatorURL); void operatorAuth(std::string operatorURL);
void buildFulfillRequest(pugi::xml_document& acsmDoc, pugi::xml_document& fulfillReq); void buildFulfillRequest(pugi::xml_document& acsmDoc, pugi::xml_document& fulfillReq);
void buildActivateReq(pugi::xml_document& activateReq); void buildActivateReq(pugi::xml_document& activateReq);
void buildReturnReq(pugi::xml_document& returnReq, const std::string& loanID, const std::string& operatorURL); void buildReturnReq(pugi::xml_document& returnReq, const std::string& loanID, const std::string& operatorURL);
ByteArray sendFulfillRequest(const pugi::xml_document& document, const std::string& url); ByteArray sendFulfillRequest(const pugi::xml_document& document, const std::string& url);
void buildSignInRequest(pugi::xml_document& signInRequest, const std::string& adobeID, const std::string& adobePassword, const std::string& authenticationCertificate); void buildSignInRequest(pugi::xml_document& signInRequest, const std::string& adobeID, const std::string& adobePassword, const std::string& authenticationCertificate);
void fetchLicenseServiceCertificate(const std::string& licenseURL, void fetchLicenseServiceCertificate(const std::string& licenseURL,
const std::string& operatorURL); const std::string& operatorURL);
std::string encryptedKeyFirstPass(pugi::xml_document& rightsDoc, const std::string& encryptedKey, const std::string& keyType, ITEM_TYPE type); void buildNotifyReq(pugi::xml_document& returnReq, pugi::xml_node& body);
void decryptADEPTKey(const std::string& encryptedKey, unsigned char* decryptedKey); void notifyServer(pugi::xml_node& notifyRoot);
void removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut, const unsigned char* encryptionKey, unsigned encryptionKeySize); void notifyServer(pugi::xml_document& fulfillReply);
void generatePDFObjectKey(int version, std::string encryptedKeyFirstPass(pugi::xml_document& rightsDoc, const std::string& encryptedKey, const std::string& keyType);
const unsigned char* masterKey, unsigned int masterKeyLength, void decryptADEPTKey(pugi::xml_document& rightsDoc, unsigned char* decryptedKey, const unsigned char* encryptionKey=0, unsigned encryptionKeySize=0);
int objectId, int objectGenerationNumber, void removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut, const unsigned char* encryptionKey, unsigned encryptionKeySize);
unsigned char* keyOut); void generatePDFObjectKey(int version,
void removePDFDRM(const std::string& filenameIn, const std::string& filenameOut, const unsigned char* encryptionKey, unsigned encryptionKeySize); const unsigned char* masterKey, unsigned int masterKeyLength,
int objectId, int objectGenerationNumber,
unsigned char* keyOut);
void removePDFDRM(const std::string& filenameIn, const std::string& filenameOut, const unsigned char* encryptionKey, unsigned encryptionKeySize);
}; };
} }

View file

@ -43,139 +43,152 @@ namespace gourou
/** /**
* Some common utilities * Some common utilities
*/ */
#define ADOBE_ADEPT_NS "http://ns.adobe.com/adept" #define ADOBE_ADEPT_NS "http://ns.adobe.com/adept"
static const int SHA1_LEN = 20; static const int SHA1_LEN = 20;
static const int RSA_KEY_SIZE = 128; static const int RSA_KEY_SIZE = 128;
static const int RSA_KEY_SIZE_BITS = (RSA_KEY_SIZE*8); static const int RSA_KEY_SIZE_BITS = (RSA_KEY_SIZE*8);
enum GOUROU_ERROR { enum GOUROU_ERROR {
GOUROU_DEVICE_DOES_NOT_MATCH = 0x1000, GOUROU_DEVICE_DOES_NOT_MATCH = 0x1000,
GOUROU_INVALID_CLIENT, GOUROU_INVALID_CLIENT,
GOUROU_TAG_NOT_FOUND, GOUROU_TAG_NOT_FOUND,
GOUROU_ADEPT_ERROR, GOUROU_ADEPT_ERROR,
GOUROU_FILE_ERROR, GOUROU_FILE_ERROR,
GOUROU_INVALID_PROPERTY GOUROU_INVALID_PROPERTY
}; };
enum FULFILL_ERROR { enum FULFILL_ERROR {
FF_ACSM_FILE_NOT_EXISTS = 0x1100, FF_ACSM_FILE_NOT_EXISTS = 0x1100,
FF_INVALID_ACSM_FILE, FF_INVALID_ACSM_FILE,
FF_NO_HMAC_IN_ACSM_FILE, FF_NO_HMAC_IN_ACSM_FILE,
FF_NOT_ACTIVATED, FF_NOT_ACTIVATED,
FF_NO_OPERATOR_URL FF_NO_OPERATOR_URL,
FF_SERVER_INTERNAL_ERROR
}; };
enum DOWNLOAD_ERROR { enum DOWNLOAD_ERROR {
DW_NO_ITEM = 0x1200, DW_NO_ITEM = 0x1200,
DW_NO_EBX_HANDLER, DW_NO_EBX_HANDLER,
}; };
enum SIGNIN_ERROR { enum SIGNIN_ERROR {
SIGN_INVALID_CREDENTIALS = 0x1300, SIGN_INVALID_CREDENTIALS = 0x1300,
}; };
enum ACTIVATE_ERROR { enum ACTIVATE_ERROR {
ACTIVATE_NOT_SIGNEDIN = 0x1400 ACTIVATE_NOT_SIGNEDIN = 0x1400
}; };
enum DEV_ERROR { enum DEV_ERROR {
DEV_MKPATH = 0x2000, DEV_MKPATH = 0x2000,
DEV_MAC_ERROR, DEV_MAC_ERROR,
DEV_INVALID_DEVICE_FILE, DEV_INVALID_DEVICE_FILE,
DEV_INVALID_DEVICE_KEY_FILE, DEV_INVALID_DEVICE_KEY_FILE,
DEV_INVALID_DEV_PROPERTY, DEV_INVALID_DEV_PROPERTY,
}; };
enum USER_ERROR { enum USER_ERROR {
USER_MKPATH = 0x3000, USER_MKPATH = 0x3000,
USER_INVALID_ACTIVATION_FILE, USER_INVALID_ACTIVATION_FILE,
USER_NO_AUTHENTICATION_URL, USER_NO_AUTHENTICATION_URL,
USER_NO_PROPERTY, USER_NO_PROPERTY,
USER_INVALID_INPUT, USER_INVALID_INPUT,
}; };
enum FULFILL_ITEM_ERROR { enum FULFILL_ITEM_ERROR {
FFI_INVALID_FULFILLMENT_DATA = 0x4000, FFI_INVALID_FULFILLMENT_DATA = 0x4000,
FFI_INVALID_LOAN_TOKEN FFI_INVALID_LOAN_TOKEN
}; };
enum CLIENT_ERROR { enum CLIENT_ERROR {
CLIENT_BAD_PARAM = 0x5000, CLIENT_BAD_PARAM = 0x5000,
CLIENT_INVALID_PKCS12, CLIENT_INVALID_PKCS12,
CLIENT_INVALID_CERTIFICATE, CLIENT_INVALID_CERTIFICATE,
CLIENT_NO_PRIV_KEY, CLIENT_NO_PRIV_KEY,
CLIENT_NO_PUB_KEY, CLIENT_NO_PUB_KEY,
CLIENT_RSA_ERROR, CLIENT_RSA_ERROR,
CLIENT_BAD_CHAINING, CLIENT_BAD_CHAINING,
CLIENT_BAD_KEY_SIZE, CLIENT_BAD_KEY_SIZE,
CLIENT_BAD_ZIP_FILE, CLIENT_BAD_ZIP_FILE,
CLIENT_ZIP_ERROR, CLIENT_ZIP_ERROR,
CLIENT_GENERIC_EXCEPTION, CLIENT_GENERIC_EXCEPTION,
CLIENT_NETWORK_ERROR, CLIENT_NETWORK_ERROR,
CLIENT_INVALID_PKCS8, CLIENT_INVALID_PKCS8,
CLIENT_FILE_ERROR, CLIENT_FILE_ERROR,
CLIENT_OSSL_ERROR, CLIENT_OSSL_ERROR,
CLIENT_CRYPT_ERROR, CLIENT_CRYPT_ERROR,
CLIENT_DIGEST_ERROR, CLIENT_DIGEST_ERROR,
CLIENT_HTTP_ERROR
}; };
enum DRM_REMOVAL_ERROR { enum DRM_REMOVAL_ERROR {
DRM_ERR_ENCRYPTION_KEY = 0x6000, DRM_ERR_ENCRYPTION_KEY = 0x6000,
DRM_VERSION_NOT_SUPPORTED, DRM_VERSION_NOT_SUPPORTED,
DRM_FILE_ERROR, DRM_FILE_ERROR,
DRM_FORMAT_NOT_SUPPORTED, DRM_FORMAT_NOT_SUPPORTED,
DRM_IN_OUT_EQUALS, DRM_IN_OUT_EQUALS,
DRM_MISSING_PARAMETER, DRM_MISSING_PARAMETER,
DRM_INVALID_KEY_SIZE, 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 * Generic exception class
*/ */
class Exception : public std::exception class Exception : public std::exception
{ {
public: public:
Exception(int code, const char* message, const char* file, int line): Exception(int code, const char* message, const char* file, int line):
code(code), line(line), file(file) code(code), line(line), file(file)
{ {
std::stringstream msg; std::stringstream msg;
msg << "Exception code : 0x" << std::setbase(16) << code << std::endl; msg << "Exception code : 0x" << std::setbase(16) << code << std::endl;
msg << "Message : " << message << std::endl; msg << "Message : " << message << std::endl;
if (logLevel >= LG_LOG_DEBUG) if (logLevel >= LG_LOG_DEBUG)
msg << "File : " << file << ":" << std::setbase(10) << line << std::endl; msg << "File : " << file << ":" << std::setbase(10) << line << std::endl;
fullmessage = strdup(msg.str().c_str()); fullmessage = strdup(msg.str().c_str());
} }
Exception(const Exception& other) Exception(const Exception& other)
{ {
this->code = other.code; this->code = other.code;
this->line = line; this->line = other.line;
this->file = file; this->file = other.file;
this->fullmessage = strdup(other.fullmessage); this->fullmessage = strdup(other.fullmessage);
} }
~Exception() ~Exception() _NOEXCEPT
{ {
free(fullmessage); free(fullmessage);
} }
const char * what () const throw () { return fullmessage; } const char * what () const throw () { return fullmessage; }
int getErrorCode() {return code;} int getErrorCode() {return code;}
private: private:
int code, line; int code, line;
const char* message, *file; const char* file;
char* fullmessage; char* fullmessage;
}; };
/** /**
* @brief Throw an exception * @brief Throw an exception
*/ */
#define EXCEPTION(code, message) \ #define EXCEPTION(code, message) \
{std::stringstream __msg;__msg << message; throw gourou::Exception(code, __msg.str().c_str(), __FILE__, __LINE__);} {std::stringstream __msg;__msg << message; throw gourou::Exception(code, __msg.str().c_str(), __FILE__, __LINE__);}
/** /**
@ -184,15 +197,15 @@ namespace gourou
class StringXMLWriter : public pugi::xml_writer class StringXMLWriter : public pugi::xml_writer
{ {
public: public:
virtual void write(const void* data, size_t size) virtual void write(const void* data, size_t size)
{ {
result.append(static_cast<const char*>(data), size); result.append(static_cast<const char*>(data), size);
} }
const std::string& getResult() {return result;} const std::string& getResult() {return result;}
private: private:
std::string result; std::string result;
}; };
static const char* ws = " \t\n\r\f\v"; static const char* ws = " \t\n\r\f\v";
@ -202,8 +215,8 @@ namespace gourou
*/ */
inline std::string& rtrim(std::string& s, const char* t = ws) inline std::string& rtrim(std::string& s, const char* t = ws)
{ {
s.erase(s.find_last_not_of(t) + 1); s.erase(s.find_last_not_of(t) + 1);
return s; return s;
} }
/** /**
@ -211,8 +224,8 @@ namespace gourou
*/ */
inline std::string& ltrim(std::string& s, const char* t = ws) inline std::string& ltrim(std::string& s, const char* t = ws)
{ {
s.erase(0, s.find_first_not_of(t)); s.erase(0, s.find_first_not_of(t));
return s; return s;
} }
/** /**
@ -220,7 +233,23 @@ namespace gourou
*/ */
inline std::string& trim(std::string& s, const char* t = ws) inline std::string& trim(std::string& s, const char* t = ws)
{ {
return ltrim(rtrim(s, t), t); return ltrim(rtrim(s, t), t);
}
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);
if (!xpath_node)
{
if (throwOnNull)
EXCEPTION(GOUROU_TAG_NOT_FOUND, "Tag " << tagName << " not found");
return pugi::xml_node();
}
return xpath_node.node();
} }
/** /**
@ -230,30 +259,46 @@ namespace gourou
*/ */
static inline std::string extractTextElem(const pugi::xml_node& root, const char* tagName, bool throwOnNull=true) static inline std::string extractTextElem(const pugi::xml_node& root, const char* tagName, bool throwOnNull=true)
{ {
pugi::xpath_node xpath_node = root.select_node(tagName); pugi::xml_node node = getNode(root, tagName, throwOnNull);
node = node.first_child();
if (!node)
{
if (throwOnNull)
EXCEPTION(GOUROU_TAG_NOT_FOUND, "Text element for tag " << tagName << " not found");
if (!xpath_node)
{
if (throwOnNull)
EXCEPTION(GOUROU_TAG_NOT_FOUND, "Tag " << tagName << " not found");
return ""; return "";
} }
pugi::xml_node node = xpath_node.node().first_child(); std::string res = node.value();
if (!node)
{
if (throwOnNull)
EXCEPTION(GOUROU_TAG_NOT_FOUND, "Text element for tag " << tagName << " not found");
return "";
}
std::string res = node.value();
return trim(res); 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 * @brief Extract text attribute from tag in document
* It can throw an exception if attribute does not exists * It can throw an exception if attribute does not exists
@ -261,28 +306,20 @@ namespace gourou
*/ */
static inline std::string extractTextAttribute(const pugi::xml_node& root, const char* tagName, const char* attributeName, bool throwOnNull=true) 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);
pugi::xml_attribute attr = node.attribute(attributeName);
if (!attr)
{
if (throwOnNull)
EXCEPTION(GOUROU_TAG_NOT_FOUND, "Attribute element " << attributeName << " for tag " << tagName << " not found");
if (!xpath_node)
{
if (throwOnNull)
EXCEPTION(GOUROU_TAG_NOT_FOUND, "Tag " << tagName << " not found");
return ""; return "";
} }
pugi::xml_attribute attr = xpath_node.node().attribute(attributeName); std::string res = attr.value();
return trim(res);
if (!attr)
{
if (throwOnNull)
EXCEPTION(GOUROU_TAG_NOT_FOUND, "Attribute element " << attributeName << " for tag " << tagName << " not found");
return "";
}
std::string res = attr.value();
return trim(res);
} }
/** /**
@ -294,8 +331,8 @@ namespace gourou
*/ */
static inline void appendTextElem(pugi::xml_node& root, const std::string& name, const std::string& value) static inline void appendTextElem(pugi::xml_node& root, const std::string& name, const std::string& value)
{ {
pugi::xml_node node = root.append_child(name.c_str()); pugi::xml_node node = root.append_child(name.c_str());
node.append_child(pugi::node_pcdata).set_value(value.c_str()); node.append_child(pugi::node_pcdata).set_value(value.c_str());
} }
/** /**
@ -306,21 +343,21 @@ namespace gourou
*/ */
static inline std::string extractIdFromUUID(const std::string& uuid) static inline std::string extractIdFromUUID(const std::string& uuid)
{ {
unsigned int i = 0; unsigned int i = 0;
std::string res; std::string res;
if (uuid.find("urn:uuid:") == 0)
i = 9;
for(; i<uuid.size(); i++) if (uuid.find("urn:uuid:") == 0)
{ i = 9;
if (uuid[i] != '-')
res += uuid[i];
}
return res; for(; i<uuid.size(); i++)
{
if (uuid[i] != '-')
res += uuid[i];
}
return res;
} }
/** /**
* @brief Open a file descriptor on path. If it already exists and truncate == true, it's truncated * @brief Open a file descriptor on path. If it already exists and truncate == true, it's truncated
* *
@ -328,31 +365,31 @@ namespace gourou
*/ */
static inline int createNewFile(std::string path, bool truncate=true) static inline int createNewFile(std::string path, bool truncate=true)
{ {
int options = O_CREAT|O_WRONLY; int options = O_CREAT|O_WRONLY;
if (truncate) if (truncate)
options |= O_TRUNC; options |= O_TRUNC;
else else
options |= O_APPEND; options |= O_APPEND;
int fd = open(path.c_str(), options, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH); int fd = open(path.c_str(), options, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH);
if (fd <= 0) if (fd <= 0)
EXCEPTION(GOUROU_FILE_ERROR, "Unable to create " << path); EXCEPTION(GOUROU_FILE_ERROR, "Unable to create " << path);
return fd; return fd;
} }
/** /**
* @brief Write data in a file. If it already exists, it's truncated * @brief Write data in a file. If it already exists, it's truncated
*/ */
static inline void writeFile(std::string path, const unsigned char* data, unsigned int length) static inline void writeFile(std::string path, const unsigned char* data, unsigned int length)
{ {
int fd = createNewFile(path); int fd = createNewFile(path);
if (write(fd, data, length) != length)
EXCEPTION(GOUROU_FILE_ERROR, "Write error for file " << path);
close (fd); if (write(fd, data, length) != length)
EXCEPTION(GOUROU_FILE_ERROR, "Write error for file " << path);
close (fd);
} }
/** /**
@ -360,7 +397,7 @@ namespace gourou
*/ */
static inline void writeFile(std::string path, ByteArray& data) static inline void writeFile(std::string path, ByteArray& data)
{ {
writeFile(path, data.data(), data.length()); writeFile(path, data.data(), data.length());
} }
/** /**
@ -368,7 +405,7 @@ namespace gourou
*/ */
static inline void writeFile(std::string path, const std::string& data) static inline void writeFile(std::string path, const std::string& data)
{ {
writeFile(path, (const unsigned char*)data.c_str(), data.length()); writeFile(path, (const unsigned char*)data.c_str(), data.length());
} }
/** /**
@ -376,15 +413,15 @@ namespace gourou
*/ */
static inline void readFile(std::string path, const unsigned char* data, unsigned int length) static inline void readFile(std::string path, const unsigned char* data, unsigned int length)
{ {
int fd = open(path.c_str(), O_RDONLY); int fd = open(path.c_str(), O_RDONLY);
if (fd <= 0) if (fd <= 0)
EXCEPTION(GOUROU_FILE_ERROR, "Unable to open " << path); EXCEPTION(GOUROU_FILE_ERROR, "Unable to open " << path);
if (read(fd, (void*)data, length) != length) if (read(fd, (void*)data, length) != length)
EXCEPTION(GOUROU_FILE_ERROR, "Read error for file " << path); EXCEPTION(GOUROU_FILE_ERROR, "Read error for file " << path);
close (fd); close (fd);
} }
#define PATH_MAX_STRING_SIZE 256 #define PATH_MAX_STRING_SIZE 256
@ -392,73 +429,73 @@ namespace gourou
// https://gist.github.com/ChisholmKyle/0cbedcd3e64132243a39 // https://gist.github.com/ChisholmKyle/0cbedcd3e64132243a39
/* recursive mkdir */ /* recursive mkdir */
static inline int mkdir_p(const char *dir, const mode_t mode) { static inline int mkdir_p(const char *dir, const mode_t mode) {
char tmp[PATH_MAX_STRING_SIZE]; char tmp[PATH_MAX_STRING_SIZE];
char *p = NULL; char *p = NULL;
struct stat sb; struct stat sb;
size_t len; size_t len;
/* copy path */
len = strnlen (dir, PATH_MAX_STRING_SIZE);
if (len == 0 || len == PATH_MAX_STRING_SIZE) {
return -1;
}
memcpy (tmp, dir, len);
tmp[len] = '\0';
/* remove trailing slash */ /* copy path */
if(tmp[len - 1] == '/') { len = strnlen (dir, PATH_MAX_STRING_SIZE);
tmp[len - 1] = '\0'; if (len == 0 || len == PATH_MAX_STRING_SIZE) {
} return -1;
}
memcpy (tmp, dir, len);
tmp[len] = '\0';
/* check if path exists and is a directory */ /* remove trailing slash */
if (stat (tmp, &sb) == 0) { if(tmp[len - 1] == '/') {
if (S_ISDIR (sb.st_mode)) { tmp[len - 1] = '\0';
return 0; }
}
} /* check if path exists and is a directory */
if (stat (tmp, &sb) == 0) {
/* recursive mkdir */ if (S_ISDIR (sb.st_mode)) {
for(p = tmp + 1; *p; p++) { return 0;
if(*p == '/') { }
*p = 0; }
/* test path */
if (stat(tmp, &sb) != 0) { /* recursive mkdir */
/* path does not exist - create directory */ for(p = tmp + 1; *p; p++) {
if (mkdir(tmp, mode) < 0) { if(*p == '/') {
return -1; *p = 0;
} /* test path */
} else if (!S_ISDIR(sb.st_mode)) { if (stat(tmp, &sb) != 0) {
/* not a directory */ /* path does not exist - create directory */
return -1; if (mkdir(tmp, mode) < 0) {
} return -1;
*p = '/'; }
} } else if (!S_ISDIR(sb.st_mode)) {
} /* not a directory */
/* test path */ return -1;
if (stat(tmp, &sb) != 0) { }
/* path does not exist - create directory */ *p = '/';
if (mkdir(tmp, mode) < 0) { }
return -1; }
} /* test path */
} else if (!S_ISDIR(sb.st_mode)) { if (stat(tmp, &sb) != 0) {
/* not a directory */ /* path does not exist - create directory */
return -1; if (mkdir(tmp, mode) < 0) {
} return -1;
return 0; }
} else if (!S_ISDIR(sb.st_mode)) {
/* not a directory */
return -1;
}
return 0;
} }
static inline void dumpBuffer(GOUROU_LOG_LEVEL level, const char* title, const unsigned char* data, unsigned int len) static inline void dumpBuffer(GOUROU_LOG_LEVEL level, const char* title, const unsigned char* data, unsigned int len)
{ {
if (gourou::logLevel < level) if (gourou::logLevel < level)
return; return;
printf("%s", title); printf("%s", title);
for(unsigned int i=0; i<len; i++) for(unsigned int i=0; i<len; i++)
{ {
if (i && !(i%16)) printf("\n"); if (i && !(i%16)) printf("\n");
printf("%02x ", data[i]); printf("%02x ", data[i]);
} }
printf("\n"); printf("\n");
} }
} }

View file

@ -24,11 +24,11 @@
namespace gourou { namespace gourou {
enum GOUROU_LOG_LEVEL { enum GOUROU_LOG_LEVEL {
LG_LOG_ERROR, LG_LOG_ERROR,
LG_LOG_WARN, LG_LOG_WARN,
LG_LOG_INFO, LG_LOG_INFO,
LG_LOG_DEBUG, LG_LOG_DEBUG,
LG_LOG_TRACE LG_LOG_TRACE
}; };
extern GOUROU_LOG_LEVEL logLevel; extern GOUROU_LOG_LEVEL logLevel;

View file

@ -32,21 +32,21 @@ namespace gourou
class LoanToken class LoanToken
{ {
public: public:
/** /**
* @brief Main constructor. Not to be called by user * @brief Main constructor. Not to be called by user
* *
* @param doc Fulfill reply * @param doc Fulfill reply
*/ */
LoanToken(pugi::xml_document& doc); LoanToken(pugi::xml_document& doc);
/** /**
* @brief Get a property (id, operatorURL, validity) * @brief Get a property (id, operatorURL, validity)
*/ */
std::string getProperty(const std::string& property, const std::string& _default=std::string("")); std::string getProperty(const std::string& property, const std::string& _default=std::string(""));
std::string operator[](const std::string& property); std::string operator[](const std::string& property);
private: private:
std::map<std::string, std::string> properties; std::map<std::string, std::string> properties;
}; };
} }

View file

@ -30,7 +30,7 @@
namespace gourou namespace gourou
{ {
class DRMProcessor; class DRMProcessor;
/** /**
* @brief This class is a container for activation.xml (activation info). It should not be used by user. * @brief This class is a container for activation.xml (activation info). It should not be used by user.
*/ */
@ -39,73 +39,73 @@ namespace gourou
public: public:
User(DRMProcessor* processor, const std::string& activationFile); User(DRMProcessor* processor, const std::string& activationFile);
/** /**
* @brief Retrieve some values from activation.xml * @brief Retrieve some values from activation.xml
*/ */
std::string& getUUID(); std::string& getUUID();
std::string& getPKCS12(); std::string& getPKCS12();
std::string& getDeviceUUID(); std::string& getDeviceUUID();
std::string& getDeviceFingerprint(); std::string& getDeviceFingerprint();
std::string& getUsername(); std::string& getUsername();
std::string& getLoginMethod(); std::string& getLoginMethod();
std::string getLicenseServiceCertificate(std::string url); std::string getLicenseServiceCertificate(std::string url);
std::string& getAuthenticationCertificate(); std::string& getAuthenticationCertificate();
std::string& getPrivateLicenseKey(); std::string& getPrivateLicenseKey();
/** /**
* @brief Read activation.xml and put result into doc * @brief Read activation.xml and put result into doc
*/ */
void readActivation(pugi::xml_document& doc); void readActivation(pugi::xml_document& doc);
/** /**
* @brief Update activation.xml with new data * @brief Update activation.xml with new data
*/ */
void updateActivationFile(const char* data); void updateActivationFile(const char* data);
/** /**
* @brief Update activation.xml with doc data * @brief Update activation.xml with doc data
*/ */
void updateActivationFile(const pugi::xml_document& doc); void updateActivationFile(const pugi::xml_document& doc);
/** /**
* @brief Get one value of activation.xml * @brief Get one value of activation.xml
*/ */
std::string getProperty(const std::string property); std::string getProperty(const std::string property);
/** /**
* @brief Get all nodes with property name * @brief Get all nodes with property name
*/ */
pugi::xpath_node_set getProperties(const std::string property); pugi::xpath_node_set getProperties(const std::string property);
/** /**
* @brief Create activation.xml and devicesalt files if they did not exists * @brief Create activation.xml and devicesalt files if they did not exists
* *
* @param processor Instance of DRMProcessor * @param processor Instance of DRMProcessor
* @param dirName Directory where to put files (.adept) * @param dirName Directory where to put files (.adept)
* @param ACSServer Server used for signIn * @param ACSServer Server used for signIn
*/ */
static User* createUser(DRMProcessor* processor, const std::string& dirName, const std::string& ACSServer); static User* createUser(DRMProcessor* processor, const std::string& dirName, const std::string& ACSServer);
private: private:
DRMProcessor* processor; DRMProcessor* processor;
pugi::xml_document activationDoc; pugi::xml_document activationDoc;
std::string activationFile;
std::string pkcs12;
std::string uuid;
std::string deviceUUID;
std::string deviceFingerprint;
std::string username;
std::string loginMethod;
std::map<std::string,std::string> licenseServiceCertificates;
std::string authenticationCertificate;
std::string privateLicenseKey;
User(DRMProcessor* processor); std::string activationFile;
std::string pkcs12;
void parseActivationFile(bool throwOnNull=true); std::string uuid;
ByteArray signIn(const std::string& adobeID, const std::string& adobePassword, std::string deviceUUID;
ByteArray authenticationCertificate); std::string deviceFingerprint;
std::string username;
std::string loginMethod;
std::map<std::string,std::string> licenseServiceCertificates;
std::string authenticationCertificate;
std::string privateLicenseKey;
User(DRMProcessor* processor);
void parseActivationFile(bool throwOnNull=true);
ByteArray signIn(const std::string& adobeID, const std::string& adobePassword,
ByteArray authenticationCertificate);
}; };
} }

View file

@ -1,16 +1,8 @@
#!/bin/bash #!/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 # uPDFParser
if [ ! -d lib/updfparser ] ; then 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 pushd lib/updfparser
make BUILD_STATIC=1 BUILD_SHARED=0 make BUILD_STATIC=1 BUILD_SHARED=0
popd popd

View file

@ -1,17 +1,12 @@
#!/bin/bash #!/bin/bash
if [ ! -d lib/pugixml -o ! -d lib/updfparser ] ; then if [ ! -d lib/updfparser ] ; then
echo "Some libraries are missing" echo "Some libraries are missing"
echo "You must run this script at the top of libgourou working direcotry." 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 exit 1
fi fi
# Pugixml
pushd lib/pugixml
git pull origin latest
popd
# uPDFParser # uPDFParser
pushd lib/updfparser pushd lib/updfparser
git pull origin master git pull origin master

View file

@ -26,232 +26,232 @@
namespace gourou namespace gourou
{ {
std::map<unsigned char*, int> ByteArray::refCounter; std::map<unsigned char*, int> ByteArray::refCounter;
ByteArray::ByteArray(bool useMalloc):_useMalloc(useMalloc), _data(0), _length(0) ByteArray::ByteArray(bool useMalloc):_useMalloc(useMalloc), _data(0), _length(0)
{} {}
ByteArray::ByteArray(unsigned int length, bool useMalloc): ByteArray::ByteArray(unsigned int length, bool useMalloc):
_useMalloc(useMalloc) _useMalloc(useMalloc)
{ {
initData(0, length); initData(0, length);
} }
ByteArray::ByteArray(const unsigned char* data, unsigned int length):
_useMalloc(false)
{
initData(data, length);
}
ByteArray::ByteArray(const char* data, int length):
_useMalloc(false)
{
if (length == -1)
length = strlen(data);
initData((unsigned char*)data, (unsigned int) length); ByteArray::ByteArray(const unsigned char* data, unsigned int length):
} _useMalloc(false)
ByteArray::ByteArray(const std::string& str):
_useMalloc(false)
{ {
initData((unsigned char*)str.c_str(), (unsigned int)str.length()); initData(data, length);
}
ByteArray::ByteArray(const char* data, int length):
_useMalloc(false)
{
if (length == -1)
length = strlen(data);
initData((unsigned char*)data, (unsigned int) length);
}
ByteArray::ByteArray(const std::string& str):
_useMalloc(false)
{
initData((unsigned char*)str.c_str(), (unsigned int)str.length());
} }
void ByteArray::initData(const unsigned char* data, unsigned int length) void ByteArray::initData(const unsigned char* data, unsigned int length)
{ {
if (_useMalloc) if (_useMalloc)
_data = (unsigned char*)malloc(length); _data = (unsigned char*)malloc(length);
else else
_data = new unsigned char[length]; _data = new unsigned char[length];
if (data) if (data)
memcpy((void*)_data, data, length); memcpy((void*)_data, data, length);
_length = length; _length = length;
addRef(); addRef();
} }
ByteArray::ByteArray(const ByteArray& other) ByteArray::ByteArray(const ByteArray& other)
{ {
this->_useMalloc = other._useMalloc; this->_useMalloc = other._useMalloc;
this->_data = other._data; this->_data = other._data;
this->_length = other._length; this->_length = other._length;
addRef(); addRef();
} }
ByteArray& ByteArray::operator=(const ByteArray& other) ByteArray& ByteArray::operator=(const ByteArray& other)
{ {
delRef(); delRef();
this->_useMalloc = other._useMalloc;
this->_data = other._data;
this->_length = other._length;
addRef(); this->_useMalloc = other._useMalloc;
this->_data = other._data;
return *this; this->_length = other._length;
addRef();
return *this;
} }
ByteArray::~ByteArray() ByteArray::~ByteArray()
{ {
delRef(); delRef();
} }
void ByteArray::addRef() void ByteArray::addRef()
{ {
if (!_data) return; if (!_data) return;
if (refCounter.count(_data) == 0) if (refCounter.count(_data) == 0)
refCounter[_data] = 1; refCounter[_data] = 1;
else else
refCounter[_data]++; refCounter[_data]++;
} }
void ByteArray::delRef() void ByteArray::delRef()
{ {
if (!_data) return; if (!_data) return;
if (refCounter[_data] == 1) if (refCounter[_data] == 1)
{ {
if (_useMalloc) if (_useMalloc)
free(_data); free(_data);
else else
delete[] _data; delete[] _data;
refCounter.erase(_data); refCounter.erase(_data);
} }
else else
refCounter[_data]--; refCounter[_data]--;
} }
ByteArray ByteArray::fromBase64(const ByteArray& other) ByteArray ByteArray::fromBase64(const ByteArray& other)
{ {
std::string b64; std::string b64;
macaron::Base64::Decode(std::string((char*)other._data, other._length), b64); macaron::Base64::Decode(std::string((char*)other._data, other._length), b64);
return ByteArray(b64); return ByteArray(b64);
} }
ByteArray ByteArray::fromBase64(const char* data, int length) ByteArray ByteArray::fromBase64(const char* data, int length)
{ {
std::string b64; std::string b64;
if (length == -1) if (length == -1)
length = strlen(data); length = strlen(data);
macaron::Base64::Decode(std::string(data, length), b64);
return ByteArray(b64); macaron::Base64::Decode(std::string(data, length), b64);
return ByteArray(b64);
} }
ByteArray ByteArray::fromBase64(const std::string& str) ByteArray ByteArray::fromBase64(const std::string& str)
{ {
return ByteArray::fromBase64(str.c_str(), str.length()); return ByteArray::fromBase64(str.c_str(), str.length());
} }
std::string ByteArray::toBase64() std::string ByteArray::toBase64()
{ {
return macaron::Base64::Encode(std::string((char*)_data, _length)); return macaron::Base64::Encode(std::string((char*)_data, _length));
} }
ByteArray ByteArray::fromHex(const std::string& str) ByteArray ByteArray::fromHex(const std::string& str)
{ {
if (str.size() % 2) if (str.size() % 2)
throw std::invalid_argument("Size of hex string not multiple of 2"); throw std::invalid_argument("Size of hex string not multiple of 2");
ByteArray res((unsigned int)(str.size()/2)); ByteArray res((unsigned int)(str.size()/2));
unsigned int i; unsigned int i;
unsigned char* data = res.data(); unsigned char* data = res.data();
unsigned char cur, tmp; unsigned char cur, tmp;
for (i=0; i<str.size(); i+=2)
{
cur = 0;
tmp = str[i]; for (i=0; i<str.size(); i+=2)
if (tmp >= 'a' && tmp <= 'f') {
cur = (tmp - 'a' + 10) << 4; cur = 0;
else if (tmp >= 'A' && tmp <= 'F')
cur = (tmp - 'A' + 10) << 4;
else if (tmp >= '0' && tmp <= '9')
cur = (tmp - '0') << 4;
else
throw std::invalid_argument("Invalid character in hex string");
tmp = str[i+1]; tmp = str[i];
if (tmp >= 'a' && tmp <= 'f') if (tmp >= 'a' && tmp <= 'f')
cur += tmp - 'a' + 10; cur = (tmp - 'a' + 10) << 4;
else if (tmp >= 'A' && tmp <= 'F') else if (tmp >= 'A' && tmp <= 'F')
cur += tmp - 'A' + 10; cur = (tmp - 'A' + 10) << 4;
else if (tmp >= '0' && tmp <= '9') else if (tmp >= '0' && tmp <= '9')
cur += tmp - '0'; cur = (tmp - '0') << 4;
else else
throw std::invalid_argument("Invalid character in hex string"); throw std::invalid_argument("Invalid character in hex string");
data[i/2] = cur; tmp = str[i+1];
} if (tmp >= 'a' && tmp <= 'f')
cur += tmp - 'a' + 10;
return res; else if (tmp >= 'A' && tmp <= 'F')
cur += tmp - 'A' + 10;
else if (tmp >= '0' && tmp <= '9')
cur += tmp - '0';
else
throw std::invalid_argument("Invalid character in hex string");
data[i/2] = cur;
}
return res;
} }
std::string ByteArray::toHex() std::string ByteArray::toHex()
{ {
char* tmp = new char[_length*2+1]; char* tmp = new char[_length*2+1];
for(int i=0; i<(int)_length; i++) 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; tmp[_length*2] = 0;
std::string res = tmp; std::string res = tmp;
delete[] tmp; delete[] tmp;
return res; return res;
} }
void ByteArray::append(const unsigned char* data, unsigned int length) void ByteArray::append(const unsigned char* data, unsigned int length)
{ {
if (!length) if (!length)
return; return;
unsigned int oldLength = _length;
resize(_length+length, true); unsigned int oldLength = _length;
memcpy(&_data[oldLength], data, length); resize(_length+length, true);
memcpy(&_data[oldLength], data, length);
} }
void ByteArray::append(unsigned char c) { append(&c, 1);} void ByteArray::append(unsigned char c) { append(&c, 1);}
void ByteArray::append(const char* str) { append((const unsigned char*)str, strlen(str));} void ByteArray::append(const char* str) { append((const unsigned char*)str, strlen(str));}
void ByteArray::append(const std::string& str) { append((const unsigned char*)str.c_str(), str.length()); } void ByteArray::append(const std::string& str) { append((const unsigned char*)str.c_str(), str.length()); }
void ByteArray::resize(unsigned length, bool keepData) void ByteArray::resize(unsigned length, bool keepData)
{ {
if (length == _length) if (length == _length)
return; return;
else if (length < _length) else if (length < _length)
_length = length ; // Don't touch data _length = length ; // Don't touch data
else // New size > else // New size >
{ {
unsigned char* newData; unsigned char* newData;
if (_useMalloc) if (_useMalloc)
newData = (unsigned char*)malloc(_length+length); newData = (unsigned char*)malloc(_length+length);
else else
newData = new unsigned char[_length+length]; newData = new unsigned char[_length+length];
if (keepData) if (keepData)
memcpy(newData, _data, _length); memcpy(newData, _data, _length);
delRef();
_length = length; delRef();
_data = newData;
_length = length;
addRef(); _data = newData;
}
addRef();
}
} }
} }

View file

@ -29,13 +29,23 @@
#include <libgourou_log.h> #include <libgourou_log.h>
#include <device.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 <sys/ioctl.h>
#include <net/if.h> #include <net/if.h>
#include <unistd.h> #include <unistd.h>
#include <netinet/in.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) int get_mac_address(unsigned char* mac_address)
{ {
struct ifreq ifr; struct ifreq ifr;
@ -68,238 +78,275 @@ int get_mac_address(unsigned char* mac_address)
if (success) if (success)
{ {
memcpy(mac_address, ifr.ifr_hwaddr.sa_data, 6); memcpy(mac_address, ifr.ifr_hwaddr.sa_data, 6);
return 0; return 0;
} }
return 1; 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 namespace gourou
{ {
Device::Device(DRMProcessor* processor): Device::Device(DRMProcessor* processor):
processor(processor) processor(processor)
{} {}
Device::Device(DRMProcessor* processor, const std::string& deviceFile, const std::string& deviceKeyFile): Device::Device(DRMProcessor* processor, const std::string& deviceFile, const std::string& deviceKeyFile):
processor(processor), deviceFile(deviceFile), deviceKeyFile(deviceKeyFile) processor(processor), deviceFile(deviceFile), deviceKeyFile(deviceKeyFile)
{ {
parseDeviceKeyFile(); parseDeviceKeyFile();
parseDeviceFile(); parseDeviceFile();
} }
/* SHA1(uid ":" username ":" macaddress ":" */ /* SHA1(uid ":" username ":" macaddress ":" */
std::string Device::makeSerial(bool random) std::string Device::makeSerial(bool random)
{ {
unsigned char sha_out[SHA1_LEN]; unsigned char sha_out[SHA1_LEN];
DRMProcessorClient* client = processor->getClient(); DRMProcessorClient* client = processor->getClient();
if (!random)
{
uid_t uid = getuid();
struct passwd * passwd = getpwuid(uid);
// Default mac address in case of failure
unsigned char mac_address[6] = {0x01, 0x02, 0x03, 0x04, 0x05};
get_mac_address(mac_address); if (!random)
{
uid_t uid = getuid();
struct passwd * passwd = getpwuid(uid);
// Default mac address in case of failure
unsigned char mac_address[6] = {0x01, 0x02, 0x03, 0x04, 0x05};
int dataToHashLen = 10 /* UID */ + strlen(passwd->pw_name) + sizeof(mac_address)*2 /*mac address*/ + 1 /* \0 */; get_mac_address(mac_address);
dataToHashLen += 8; /* Separators */
unsigned char* dataToHash = new unsigned char[dataToHashLen];
dataToHashLen = snprintf((char*)dataToHash, dataToHashLen, "%d:%s:%02x:%02x:%02x:%02x:%02x:%02x:",
uid, passwd->pw_name,
mac_address[0], mac_address[1], mac_address[2],
mac_address[3], mac_address[4], mac_address[5]);
client->digest("SHA1", dataToHash, dataToHashLen+1, sha_out);
delete[] dataToHash; int dataToHashLen = 10 /* UID */ + strlen(passwd->pw_name) + sizeof(mac_address)*2 /*mac address*/ + 1 /* \0 */;
} dataToHashLen += 8; /* Separators */
else unsigned char* dataToHash = new unsigned char[dataToHashLen];
{ dataToHashLen = snprintf((char*)dataToHash, dataToHashLen, "%d:%s:%02x:%02x:%02x:%02x:%02x:%02x:",
client->randBytes(sha_out, sizeof(sha_out)); uid, passwd->pw_name,
} mac_address[0], mac_address[1], mac_address[2],
mac_address[3], mac_address[4], mac_address[5]);
std::string res = ByteArray((const char*)sha_out, DEVICE_SERIAL_LEN).toHex(); client->digest("SHA1", dataToHash, dataToHashLen+1, sha_out);
GOUROU_LOG(DEBUG, "Serial : " << res);
return res; delete[] dataToHash;
}
else
{
client->randBytes(sha_out, sizeof(sha_out));
}
std::string res = ByteArray((const char*)sha_out, DEVICE_SERIAL_LEN).toHex();
GOUROU_LOG(DEBUG, "Serial : " << res);
return res;
} }
/* base64(SHA1 (serial + privateKey)) */ /* base64(SHA1 (serial + privateKey)) */
std::string Device::makeFingerprint(const std::string& serial) std::string Device::makeFingerprint(const std::string& serial)
{ {
DRMProcessorClient* client = processor->getClient(); DRMProcessorClient* client = processor->getClient();
unsigned char sha_out[SHA1_LEN]; unsigned char sha_out[SHA1_LEN];
void* handler = client->createDigest("SHA1"); void* handler = client->createDigest("SHA1");
client->digestUpdate(handler, (unsigned char*) serial.c_str(), serial.length()); client->digestUpdate(handler, (unsigned char*) serial.c_str(), serial.length());
client->digestUpdate(handler, deviceKey, sizeof(deviceKey)); client->digestUpdate(handler, deviceKey, sizeof(deviceKey));
client->digestFinalize(handler, sha_out); client->digestFinalize(handler, sha_out);
std::string res = ByteArray(sha_out, sizeof(sha_out)).toBase64(); std::string res = ByteArray(sha_out, sizeof(sha_out)).toBase64();
GOUROU_LOG(DEBUG, "Fingerprint : " << res); GOUROU_LOG(DEBUG, "Fingerprint : " << res);
return res; return res;
} }
void Device::createDeviceFile(const std::string& hobbes, bool randomSerial) void Device::createDeviceFile(const std::string& hobbes, bool randomSerial)
{ {
struct utsname sysname; struct utsname sysname;
uname(&sysname); uname(&sysname);
std::string serial = makeSerial(randomSerial); std::string serial = makeSerial(randomSerial);
std::string fingerprint = makeFingerprint(serial); std::string fingerprint = makeFingerprint(serial);
pugi::xml_document deviceDoc;
pugi::xml_node decl = deviceDoc.append_child(pugi::node_declaration);
decl.append_attribute("version") = "1.0";
pugi::xml_node root = deviceDoc.append_child("adept:deviceInfo");
root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS;
appendTextElem(root, "adept:deviceClass", "Desktop"); pugi::xml_document deviceDoc;
appendTextElem(root, "adept:deviceSerial", serial); pugi::xml_node decl = deviceDoc.append_child(pugi::node_declaration);
appendTextElem(root, "adept:deviceName", sysname.nodename); decl.append_attribute("version") = "1.0";
appendTextElem(root, "adept:deviceType", "standalone");
pugi::xml_node version = root.append_child("adept:version"); pugi::xml_node root = deviceDoc.append_child("adept:deviceInfo");
version.append_attribute("name") = "hobbes"; root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS;
version.append_attribute("value") = hobbes.c_str();
version = root.append_child("adept:version");
version.append_attribute("name") = "clientOS";
std::string os = std::string(sysname.sysname) + " " + std::string(sysname.release);
version.append_attribute("value") = os.c_str();
version = root.append_child("adept:version");
version.append_attribute("name") = "clientLocale";
version.append_attribute("value") = setlocale(LC_ALL, NULL);
appendTextElem(root, "adept:fingerprint", fingerprint); appendTextElem(root, "adept:deviceClass", "Desktop");
appendTextElem(root, "adept:deviceSerial", serial);
appendTextElem(root, "adept:deviceName", sysname.nodename);
appendTextElem(root, "adept:deviceType", "standalone");
StringXMLWriter xmlWriter; pugi::xml_node version = root.append_child("adept:version");
deviceDoc.save(xmlWriter, " "); version.append_attribute("name") = "hobbes";
version.append_attribute("value") = hobbes.c_str();
GOUROU_LOG(DEBUG, "Create device file " << deviceFile); version = root.append_child("adept:version");
version.append_attribute("name") = "clientOS";
std::string os = std::string(sysname.sysname) + " " + std::string(sysname.release);
version.append_attribute("value") = os.c_str();
writeFile(deviceFile, xmlWriter.getResult()); version = root.append_child("adept:version");
version.append_attribute("name") = "clientLocale";
version.append_attribute("value") = setlocale(LC_ALL, NULL);
appendTextElem(root, "adept:fingerprint", fingerprint);
StringXMLWriter xmlWriter;
deviceDoc.save(xmlWriter, " ");
GOUROU_LOG(DEBUG, "Create device file " << deviceFile);
writeFile(deviceFile, xmlWriter.getResult());
} }
void Device::createDeviceKeyFile() void Device::createDeviceKeyFile()
{ {
unsigned char key[DEVICE_KEY_SIZE]; unsigned char key[DEVICE_KEY_SIZE];
GOUROU_LOG(DEBUG, "Create device key file " << deviceKeyFile); GOUROU_LOG(DEBUG, "Create device key file " << deviceKeyFile);
processor->getClient()->randBytes(key, sizeof(key)); processor->getClient()->randBytes(key, sizeof(key));
writeFile(deviceKeyFile, key, sizeof(key)); writeFile(deviceKeyFile, key, sizeof(key));
} }
Device* Device::createDevice(DRMProcessor* processor, const std::string& dirName, const std::string& hobbes, bool randomSerial) Device* Device::createDevice(DRMProcessor* processor, const std::string& dirName, const std::string& hobbes, bool randomSerial)
{ {
struct stat _stat; struct stat _stat;
if (stat(dirName.c_str(), &_stat) != 0) if (stat(dirName.c_str(), &_stat) != 0)
{ {
if (mkdir_p(dirName.c_str(), S_IRWXU)) if (mkdir_p(dirName.c_str(), S_IRWXU))
EXCEPTION(DEV_MKPATH, "Unable to create " << dirName) EXCEPTION(DEV_MKPATH, "Unable to create " << dirName)
} }
Device* device = new Device(processor); Device* device = new Device(processor);
device->deviceFile = dirName + "/device.xml"; device->deviceFile = dirName + "/device.xml";
device->deviceKeyFile = dirName + "/devicesalt"; device->deviceKeyFile = dirName + "/devicesalt";
try try
{ {
device->parseDeviceKeyFile(); device->parseDeviceKeyFile();
} }
catch (...) catch (...)
{ {
device->createDeviceKeyFile(); device->createDeviceKeyFile();
device->parseDeviceKeyFile(); device->parseDeviceKeyFile();
} }
try try
{ {
device->parseDeviceFile(); device->parseDeviceFile();
} }
catch (...) catch (...)
{ {
device->createDeviceFile(hobbes, randomSerial); device->createDeviceFile(hobbes, randomSerial);
device->parseDeviceFile(); device->parseDeviceFile();
} }
return device; return device;
} }
const unsigned char* Device::getDeviceKey() const unsigned char* Device::getDeviceKey()
{ {
return deviceKey; return deviceKey;
} }
void Device::parseDeviceFile() void Device::parseDeviceFile()
{ {
pugi::xml_document doc; pugi::xml_document doc;
if (!doc.load_file(deviceFile.c_str())) if (!doc.load_file(deviceFile.c_str()))
EXCEPTION(DEV_INVALID_DEVICE_FILE, "Invalid device file"); EXCEPTION(DEV_INVALID_DEVICE_FILE, "Invalid device file");
try try
{ {
properties["deviceClass"] = gourou::extractTextElem(doc, "/adept:deviceInfo/adept:deviceClass"); properties["deviceClass"] = gourou::extractTextElem(doc, "/adept:deviceInfo/adept:deviceClass");
properties["deviceSerial"] = gourou::extractTextElem(doc, "/adept:deviceInfo/adept:deviceSerial"); properties["deviceSerial"] = gourou::extractTextElem(doc, "/adept:deviceInfo/adept:deviceSerial");
properties["deviceName"] = gourou::extractTextElem(doc, "/adept:deviceInfo/adept:deviceName"); properties["deviceName"] = gourou::extractTextElem(doc, "/adept:deviceInfo/adept:deviceName");
properties["deviceType"] = gourou::extractTextElem(doc, "/adept:deviceInfo/adept:deviceType"); properties["deviceType"] = gourou::extractTextElem(doc, "/adept:deviceInfo/adept:deviceType");
properties["fingerprint"] = gourou::extractTextElem(doc, "/adept:deviceInfo/adept:fingerprint"); properties["fingerprint"] = gourou::extractTextElem(doc, "/adept:deviceInfo/adept:fingerprint");
pugi::xpath_node_set nodeSet = doc.select_nodes("/adept:deviceInfo/adept:version"); pugi::xpath_node_set nodeSet = doc.select_nodes("/adept:deviceInfo/adept:version");
for (pugi::xpath_node_set::const_iterator it = nodeSet.begin(); for (pugi::xpath_node_set::const_iterator it = nodeSet.begin();
it != nodeSet.end(); ++it) it != nodeSet.end(); ++it)
{ {
pugi::xml_node node = it->node(); pugi::xml_node node = it->node();
pugi::xml_attribute name = node.attribute("name"); pugi::xml_attribute name = node.attribute("name");
pugi::xml_attribute value = node.attribute("value"); pugi::xml_attribute value = node.attribute("value");
properties[name.value()] = value.value(); properties[name.value()] = value.value();
} }
} }
catch (gourou::Exception& e) catch (gourou::Exception& e)
{ {
EXCEPTION(DEV_INVALID_DEVICE_FILE, "Invalid device file"); EXCEPTION(DEV_INVALID_DEVICE_FILE, "Invalid device file");
} }
} }
void Device::parseDeviceKeyFile() void Device::parseDeviceKeyFile()
{ {
struct stat _stat; struct stat _stat;
if (stat(deviceKeyFile.c_str(), &_stat) == 0 && if (stat(deviceKeyFile.c_str(), &_stat) == 0 &&
_stat.st_size == DEVICE_KEY_SIZE) _stat.st_size == DEVICE_KEY_SIZE)
{ {
readFile(deviceKeyFile, deviceKey, sizeof(deviceKey)); readFile(deviceKeyFile, deviceKey, sizeof(deviceKey));
} }
else else
EXCEPTION(DEV_INVALID_DEVICE_KEY_FILE, "Invalid device key file"); EXCEPTION(DEV_INVALID_DEVICE_KEY_FILE, "Invalid device key file");
} }
std::string Device::getProperty(const std::string& property, const std::string& _default) std::string Device::getProperty(const std::string& property, const std::string& _default)
{ {
if (properties.find(property) == properties.end()) if (properties.find(property) == properties.end())
{ {
if (_default == "") if (_default == "")
EXCEPTION(DEV_INVALID_DEV_PROPERTY, "Invalid property " << property); EXCEPTION(DEV_INVALID_DEV_PROPERTY, "Invalid property " << property);
return _default; return _default;
} }
return properties[property]; return properties[property];
} }
std::string Device::operator[](const std::string& property) std::string Device::operator[](const std::string& property)
{ {
return getProperty(property); return getProperty(property);
} }
} }

View file

@ -17,6 +17,7 @@
along with libgourou. If not, see <http://www.gnu.org/licenses/>. along with libgourou. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <cctype>
#include <fulfillment_item.h> #include <fulfillment_item.h>
#include <libgourou_common.h> #include <libgourou_common.h>
#include "user.h" #include "user.h"
@ -24,105 +25,109 @@
namespace gourou namespace gourou
{ {
FulfillmentItem::FulfillmentItem(pugi::xml_document& doc, User* user) FulfillmentItem::FulfillmentItem(pugi::xml_document& doc, User* user)
: fulfillDoc(), loanToken(0) : fulfillDoc(), loanToken(0)
{ {
fulfillDoc.reset(doc); /* We must keep a copy */ fulfillDoc.reset(doc); /* We must keep a copy */
metadatas = fulfillDoc.select_node("//metadata").node(); metadatas = fulfillDoc.select_node("//metadata").node();
if (!metadatas)
EXCEPTION(FFI_INVALID_FULFILLMENT_DATA, "No metadata tag in document");
pugi::xml_node node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/src").node();
downloadURL = node.first_child().value();
if (downloadURL == "") if (!metadatas)
EXCEPTION(FFI_INVALID_FULFILLMENT_DATA, "No download URL in document"); EXCEPTION(FFI_INVALID_FULFILLMENT_DATA, "No metadata tag in document");
node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/resource").node();
resource = node.first_child().value();
if (resource == "") pugi::xml_node node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/src").node();
EXCEPTION(FFI_INVALID_FULFILLMENT_DATA, "No resource in document"); downloadURL = node.first_child().value();
pugi::xml_node licenseToken = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken").node(); if (downloadURL == "")
EXCEPTION(FFI_INVALID_FULFILLMENT_DATA, "No download URL in document");
if (!licenseToken) node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/resource").node();
EXCEPTION(FFI_INVALID_FULFILLMENT_DATA, "Any license token in document"); resource = node.first_child().value();
buildRights(licenseToken, user);
node = doc.select_node("/envelope/fulfillmentResult/returnable").node(); if (resource == "")
try EXCEPTION(FFI_INVALID_FULFILLMENT_DATA, "No resource in document");
{
if (node && node.first_child().value() == std::string("true")) pugi::xml_node licenseToken = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken").node();
loanToken = new LoanToken(doc);
} if (!licenseToken)
catch(std::exception& e) EXCEPTION(FFI_INVALID_FULFILLMENT_DATA, "Any license token in document");
{
GOUROU_LOG(ERROR, "Book is returnable, but contains invalid loan token"); buildRights(licenseToken, user);
GOUROU_LOG(ERROR, e.what());
} node = doc.select_node("/envelope/fulfillmentResult/returnable").node();
try
{
if (node && node.first_child().value() == std::string("true"))
loanToken = new LoanToken(doc);
}
catch(std::exception& e)
{
GOUROU_LOG(ERROR, "Book is returnable, but contains invalid loan token");
GOUROU_LOG(ERROR, e.what());
}
} }
FulfillmentItem::~FulfillmentItem() FulfillmentItem::~FulfillmentItem()
{ {
if (loanToken) delete loanToken; if (loanToken) delete loanToken;
} }
void FulfillmentItem::buildRights(const pugi::xml_node& licenseToken, User* user) void FulfillmentItem::buildRights(const pugi::xml_node& licenseToken, User* user)
{ {
pugi::xml_node decl = rights.append_child(pugi::node_declaration); pugi::xml_node decl = rights.append_child(pugi::node_declaration);
decl.append_attribute("version") = "1.0"; decl.append_attribute("version") = "1.0";
pugi::xml_node root = rights.append_child("adept:rights");
root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS;
pugi::xml_node newLicenseToken = root.append_copy(licenseToken);
if (!newLicenseToken.attribute("xmlns"))
newLicenseToken.append_attribute("xmlns") = ADOBE_ADEPT_NS;
pugi::xml_node licenseServiceInfo = root.append_child("adept:licenseServiceInfo"); pugi::xml_node root = rights.append_child("adept:rights");
pugi::xml_node licenseURL = licenseToken.select_node("licenseURL").node(); root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS;
licenseURL.set_name("adept:licenseURL");
licenseServiceInfo.append_copy(licenseURL); pugi::xml_node newLicenseToken = root.append_copy(licenseToken);
pugi::xml_node certificate = licenseServiceInfo.append_child("adept:certificate"); if (!newLicenseToken.attribute("xmlns"))
std::string certificateValue = user->getLicenseServiceCertificate(licenseURL.first_child().value()); newLicenseToken.append_attribute("xmlns") = ADOBE_ADEPT_NS;
certificate.append_child(pugi::node_pcdata).set_value(certificateValue.c_str());
pugi::xml_node licenseServiceInfo = root.append_child("adept:licenseServiceInfo");
pugi::xml_node licenseURL = licenseToken.select_node("licenseURL").node();
licenseURL.set_name("adept:licenseURL");
licenseServiceInfo.append_copy(licenseURL);
pugi::xml_node certificate = licenseServiceInfo.append_child("adept:certificate");
std::string certificateValue = user->getLicenseServiceCertificate(licenseURL.first_child().value());
certificate.append_child(pugi::node_pcdata).set_value(certificateValue.c_str());
} }
std::string FulfillmentItem::getMetadata(std::string name) std::string FulfillmentItem::getMetadata(std::string name)
{ {
// https://stackoverflow.com/questions/313970/how-to-convert-an-instance-of-stdstring-to-lower-case // https://stackoverflow.com/questions/313970/how-to-convert-an-instance-of-stdstring-to-lower-case
std::transform(name.begin(), name.end(), name.begin(), #if __STDC_VERSION__ >= 201112L
[](unsigned char c){ return std::tolower(c); }); std::transform(name.begin(), name.end(), name.begin(),
name = std::string("dc:") + name; [](unsigned char c){ return std::tolower(c); });
pugi::xpath_node path = metadatas.select_node(name.c_str()); #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());
if (!path) if (!path)
return ""; return "";
return path.node().first_child().value(); return path.node().first_child().value();
} }
std::string FulfillmentItem::getRights() std::string FulfillmentItem::getRights()
{ {
StringXMLWriter xmlWriter; StringXMLWriter xmlWriter;
rights.save(xmlWriter, " "); rights.save(xmlWriter, " ");
return xmlWriter.getResult(); return xmlWriter.getResult();
} }
std::string FulfillmentItem::getDownloadURL() std::string FulfillmentItem::getDownloadURL()
{ {
return downloadURL; return downloadURL;
} }
std::string FulfillmentItem::getResource() std::string FulfillmentItem::getResource()
{ {
return resource; return resource;
} }
LoanToken* FulfillmentItem::getLoanToken() LoanToken* FulfillmentItem::getLoanToken()
{ {
return loanToken; return loanToken;
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -24,68 +24,57 @@ namespace gourou
{ {
LoanToken::LoanToken(pugi::xml_document& doc) LoanToken::LoanToken(pugi::xml_document& doc)
{ {
pugi::xml_node node = doc.select_node("/envelope/loanToken").node(); pugi::xml_node node = doc.select_node("/envelope/loanToken").node();
if (!node) if (!node)
EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken element in document"); 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) if (node)
properties["id"] = node.first_child().value(); properties["id"] = node.first_child().value();
else else
{ {
node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken/permissions/display/loan").node(); EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No fulfillment element in document");
}
if (node) node = doc.select_node("/envelope/loanToken/operatorURL").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");
}
}
node = doc.select_node("/envelope/loanToken/operatorURL").node(); if (!node)
EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken/operatorURL element in document");
if (!node) properties["operatorURL"] = node.first_child().value();
EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken/operatorURL element in document");
properties["operatorURL"] = node.first_child().value(); node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken/permissions/display/until").node();
node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken/permissions/display/until").node(); if (node)
properties["validity"] = node.first_child().value();
else
{
node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken/permissions/play/until").node();
if (node) if (node)
properties["validity"] = node.first_child().value(); properties["validity"] = node.first_child().value();
else else
{ EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken/operatorURL element in document");
node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken/permissions/play/until").node(); }
if (node)
properties["validity"] = node.first_child().value();
else
EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken/operatorURL element in document");
}
} }
std::string LoanToken::getProperty(const std::string& property, const std::string& _default) std::string LoanToken::getProperty(const std::string& property, const std::string& _default)
{ {
if (properties.find(property) == properties.end()) if (properties.find(property) == properties.end())
{ {
if (_default == "") if (_default == "")
EXCEPTION(GOUROU_INVALID_PROPERTY, "Invalid property " << property); EXCEPTION(GOUROU_INVALID_PROPERTY, "Invalid property " << property);
return _default; return _default;
} }
return properties[property]; return properties[property];
} }
std::string LoanToken::operator[](const std::string& property) std::string LoanToken::operator[](const std::string& property)
{ {
return getProperty(property); return getProperty(property);
} }
} }

View file

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

View file

@ -24,60 +24,60 @@
namespace gourou { namespace gourou {
User::User(DRMProcessor* processor):processor(processor) {} User::User(DRMProcessor* processor):processor(processor) {}
User::User(DRMProcessor* processor, const std::string& activationFile): User::User(DRMProcessor* processor, const std::string& activationFile):
processor(processor), activationFile(activationFile) processor(processor), activationFile(activationFile)
{ {
parseActivationFile(); parseActivationFile();
} }
void User::parseActivationFile(bool throwOnNull) void User::parseActivationFile(bool throwOnNull)
{ {
GOUROU_LOG(DEBUG, "Parse activation file " << activationFile); GOUROU_LOG(DEBUG, "Parse activation file " << activationFile);
if (!activationDoc.load_file(activationFile.c_str()))
{
if (throwOnNull)
EXCEPTION(USER_INVALID_ACTIVATION_FILE, "Invalid activation file");
return;
}
try if (!activationDoc.load_file(activationFile.c_str()))
{ {
pkcs12 = gourou::extractTextElem(activationDoc, "//adept:pkcs12", throwOnNull); if (throwOnNull)
uuid = gourou::extractTextElem(activationDoc, "//adept:user", throwOnNull); EXCEPTION(USER_INVALID_ACTIVATION_FILE, "Invalid activation file");
deviceUUID = gourou::extractTextElem(activationDoc, "//device", throwOnNull); return;
deviceFingerprint = gourou::extractTextElem(activationDoc, "//fingerprint", throwOnNull); }
authenticationCertificate = gourou::extractTextElem(activationDoc, "//adept:authenticationCertificate", throwOnNull);
privateLicenseKey = gourou::extractTextElem(activationDoc, "//adept:privateLicenseKey", throwOnNull);
pugi::xpath_node xpath_node = activationDoc.select_node("//adept:username"); try
if (xpath_node) {
loginMethod = xpath_node.node().attribute("method").value(); pkcs12 = gourou::extractTextElem(activationDoc, "//adept:pkcs12", throwOnNull);
else uuid = gourou::extractTextElem(activationDoc, "//adept:user", throwOnNull);
{ deviceUUID = gourou::extractTextElem(activationDoc, "//device", throwOnNull);
if (throwOnNull) deviceFingerprint = gourou::extractTextElem(activationDoc, "//fingerprint", throwOnNull);
EXCEPTION(USER_INVALID_ACTIVATION_FILE, "Invalid activation file"); authenticationCertificate = gourou::extractTextElem(activationDoc, "//adept:authenticationCertificate", throwOnNull);
} privateLicenseKey = gourou::extractTextElem(activationDoc, "//adept:privateLicenseKey", throwOnNull);
if (loginMethod == "anonymous") pugi::xpath_node xpath_node = activationDoc.select_node("//adept:username");
username = "anonymous"; if (xpath_node)
else loginMethod = xpath_node.node().attribute("method").value();
username = gourou::extractTextElem(activationDoc, "//adept:username", throwOnNull); else
{
pugi::xpath_node_set nodeSet = activationDoc.select_nodes("//adept:licenseServices/adept:licenseServiceInfo"); if (throwOnNull)
for (pugi::xpath_node_set::const_iterator it = nodeSet.begin(); EXCEPTION(USER_INVALID_ACTIVATION_FILE, "Invalid activation file");
it != nodeSet.end(); ++it) }
{
std::string url = gourou::extractTextElem(it->node(), "adept:licenseURL"); if (loginMethod == "anonymous")
std::string certificate = gourou::extractTextElem(it->node(), "adept:certificate"); username = "anonymous";
licenseServiceCertificates[url] = certificate; else
} username = gourou::extractTextElem(activationDoc, "//adept:username", throwOnNull);
}
catch(gourou::Exception& e) pugi::xpath_node_set nodeSet = activationDoc.select_nodes("//adept:licenseServices/adept:licenseServiceInfo");
{ for (pugi::xpath_node_set::const_iterator it = nodeSet.begin();
EXCEPTION(USER_INVALID_ACTIVATION_FILE, "Invalid activation file"); it != nodeSet.end(); ++it)
} {
std::string url = gourou::extractTextElem(it->node(), "adept:licenseURL");
std::string certificate = gourou::extractTextElem(it->node(), "adept:certificate");
licenseServiceCertificates[url] = certificate;
}
}
catch(gourou::Exception& e)
{
EXCEPTION(USER_INVALID_ACTIVATION_FILE, "Invalid activation file");
}
} }
std::string& User::getUUID() { return uuid; } std::string& User::getUUID() { return uuid; }
@ -88,136 +88,136 @@ namespace gourou {
std::string& User::getLoginMethod() { return loginMethod; } std::string& User::getLoginMethod() { return loginMethod; }
std::string& User::getAuthenticationCertificate() { return authenticationCertificate; } std::string& User::getAuthenticationCertificate() { return authenticationCertificate; }
std::string& User::getPrivateLicenseKey() { return privateLicenseKey; } std::string& User::getPrivateLicenseKey() { return privateLicenseKey; }
void User::readActivation(pugi::xml_document& doc) void User::readActivation(pugi::xml_document& doc)
{ {
if (!doc.load_file(activationFile.c_str())) if (!doc.load_file(activationFile.c_str()))
EXCEPTION(USER_INVALID_ACTIVATION_FILE, "Invalid activation file"); EXCEPTION(USER_INVALID_ACTIVATION_FILE, "Invalid activation file");
} }
void User::updateActivationFile(const char* data) void User::updateActivationFile(const char* data)
{ {
GOUROU_LOG(INFO, "Update Activation file : " << std::endl << data); GOUROU_LOG(INFO, "Update Activation file : " << std::endl << data);
writeFile(activationFile, (unsigned char*)data, strlen(data)); writeFile(activationFile, (unsigned char*)data, strlen(data));
parseActivationFile(false); parseActivationFile(false);
} }
void User::updateActivationFile(const pugi::xml_document& doc) void User::updateActivationFile(const pugi::xml_document& doc)
{ {
StringXMLWriter xmlWriter; StringXMLWriter xmlWriter;
doc.save(xmlWriter, " "); doc.save(xmlWriter, " ");
updateActivationFile(xmlWriter.getResult().c_str()); updateActivationFile(xmlWriter.getResult().c_str());
} }
std::string User::getProperty(const std::string property) std::string User::getProperty(const std::string property)
{ {
pugi::xpath_node xpathRes = activationDoc.select_node(property.c_str()); pugi::xpath_node xpathRes = activationDoc.select_node(property.c_str());
if (!xpathRes) if (!xpathRes)
EXCEPTION(USER_NO_PROPERTY, "Property " << property << " not found in activation.xml"); EXCEPTION(USER_NO_PROPERTY, "Property " << property << " not found in activation.xml");
std::string res = xpathRes.node().first_child().value(); std::string res = xpathRes.node().first_child().value();
return trim(res); return trim(res);
} }
pugi::xpath_node_set User::getProperties(const std::string property) pugi::xpath_node_set User::getProperties(const std::string property)
{ {
return activationDoc.select_nodes(property.c_str()); return activationDoc.select_nodes(property.c_str());
} }
User* User::createUser(DRMProcessor* processor, const std::string& dirName, const std::string& ACSServer) User* User::createUser(DRMProcessor* processor, const std::string& dirName, const std::string& ACSServer)
{ {
struct stat _stat; struct stat _stat;
if (stat(dirName.c_str(), &_stat) != 0) if (stat(dirName.c_str(), &_stat) != 0)
{ {
if (mkdir_p(dirName.c_str(), S_IRWXU)) if (mkdir_p(dirName.c_str(), S_IRWXU))
EXCEPTION(USER_MKPATH, "Unable to create " << dirName) EXCEPTION(USER_MKPATH, "Unable to create " << dirName)
} }
User* user = new User(processor); User* user = new User(processor);
bool doUpdate = false; bool doUpdate = false;
user->activationFile = dirName + "/activation.xml";
user->parseActivationFile(false);
pugi::xpath_node nodeActivationInfo = user->activationDoc.select_node("activation_info"); user->activationFile = dirName + "/activation.xml";
pugi::xpath_node nodeActivationServiceInfo = nodeActivationInfo.node().select_node("adept:activationServiceInfo"); user->parseActivationFile(false);
pugi::xml_node activationInfo;
pugi::xml_node activationServiceInfo;
if (nodeActivationInfo && nodeActivationServiceInfo)
{
GOUROU_LOG(DEBUG, "Read previous activation configuration");
activationInfo = nodeActivationInfo.node();
activationServiceInfo = nodeActivationServiceInfo.node();
}
else
{
GOUROU_LOG(DEBUG, "Create new activation");
user->activationDoc.reset(); pugi::xpath_node nodeActivationInfo = user->activationDoc.select_node("activation_info");
pugi::xpath_node nodeActivationServiceInfo = nodeActivationInfo.node().select_node("adept:activationServiceInfo");
pugi::xml_node decl = user->activationDoc.append_child(pugi::node_declaration); pugi::xml_node activationInfo;
decl.append_attribute("version") = "1.0"; pugi::xml_node activationServiceInfo;
activationInfo = user->activationDoc.append_child("activationInfo");
activationInfo.append_attribute("xmlns") = ADOBE_ADEPT_NS;
activationServiceInfo = activationInfo.append_child("adept:activationServiceInfo");
activationServiceInfo.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS;
// Go to activation Service Info
std::string activationURL = ACSServer + "/ActivationServiceInfo";
ByteArray activationServiceInfoReply = processor->sendRequest(activationURL);
pugi::xml_document docActivationServiceInfo;
docActivationServiceInfo.load_buffer(activationServiceInfoReply.data(),
activationServiceInfoReply.length());
pugi::xpath_node path = docActivationServiceInfo.select_node("//authURL"); if (nodeActivationInfo && nodeActivationServiceInfo)
appendTextElem(activationServiceInfo, "adept:authURL", path.node().first_child().value()); {
path = docActivationServiceInfo.select_node("//userInfoURL"); GOUROU_LOG(DEBUG, "Read previous activation configuration");
appendTextElem(activationServiceInfo, "adept:userInfoURL", path.node().first_child().value()); activationInfo = nodeActivationInfo.node();
appendTextElem(activationServiceInfo, "adept:activationURL", ACSServer); activationServiceInfo = nodeActivationServiceInfo.node();
path = docActivationServiceInfo.select_node("//certificate"); }
appendTextElem(activationServiceInfo, "adept:certificate", path.node().first_child().value()); else
doUpdate = true; {
} GOUROU_LOG(DEBUG, "Create new activation");
pugi::xpath_node nodeAuthenticationCertificate = activationServiceInfo.select_node("adept:authenticationCertificate");
if (!nodeAuthenticationCertificate) user->activationDoc.reset();
{
GOUROU_LOG(DEBUG, "Create new activation, authentication part");
pugi::xpath_node xpathRes = activationServiceInfo.select_node("adept:authURL"); pugi::xml_node decl = user->activationDoc.append_child(pugi::node_declaration);
if (!xpathRes) decl.append_attribute("version") = "1.0";
EXCEPTION(USER_NO_AUTHENTICATION_URL, "No authentication URL"); activationInfo = user->activationDoc.append_child("activationInfo");
activationInfo.append_attribute("xmlns") = ADOBE_ADEPT_NS;
std::string authenticationURL = xpathRes.node().first_child().value(); activationServiceInfo = activationInfo.append_child("adept:activationServiceInfo");
authenticationURL = trim(authenticationURL) + "/AuthenticationServiceInfo"; activationServiceInfo.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS;
// Go to authentication Service Info
ByteArray authenticationServiceInfo = processor->sendRequest(authenticationURL);
pugi::xml_document docAuthenticationServiceInfo;
docAuthenticationServiceInfo.load_buffer(authenticationServiceInfo.data(), authenticationServiceInfo.length());
pugi::xpath_node path = docAuthenticationServiceInfo.select_node("//certificate");
appendTextElem(activationServiceInfo, "adept:authenticationCertificate", path.node().first_child().value());
doUpdate = true;
}
if (doUpdate) // Go to activation Service Info
user->updateActivationFile(user->activationDoc); std::string activationURL = ACSServer + "/ActivationServiceInfo";
ByteArray activationServiceInfoReply = processor->sendRequest(activationURL);
pugi::xml_document docActivationServiceInfo;
return user; docActivationServiceInfo.load_buffer(activationServiceInfoReply.data(),
activationServiceInfoReply.length());
pugi::xpath_node path = docActivationServiceInfo.select_node("//authURL");
appendTextElem(activationServiceInfo, "adept:authURL", path.node().first_child().value());
path = docActivationServiceInfo.select_node("//userInfoURL");
appendTextElem(activationServiceInfo, "adept:userInfoURL", path.node().first_child().value());
appendTextElem(activationServiceInfo, "adept:activationURL", ACSServer);
path = docActivationServiceInfo.select_node("//certificate");
appendTextElem(activationServiceInfo, "adept:certificate", path.node().first_child().value());
doUpdate = true;
}
pugi::xpath_node nodeAuthenticationCertificate = activationServiceInfo.select_node("adept:authenticationCertificate");
if (!nodeAuthenticationCertificate)
{
GOUROU_LOG(DEBUG, "Create new activation, authentication part");
pugi::xpath_node xpathRes = activationServiceInfo.select_node("adept:authURL");
if (!xpathRes)
EXCEPTION(USER_NO_AUTHENTICATION_URL, "No authentication URL");
std::string authenticationURL = xpathRes.node().first_child().value();
authenticationURL = trim(authenticationURL) + "/AuthenticationServiceInfo";
// Go to authentication Service Info
ByteArray authenticationServiceInfo = processor->sendRequest(authenticationURL);
pugi::xml_document docAuthenticationServiceInfo;
docAuthenticationServiceInfo.load_buffer(authenticationServiceInfo.data(), authenticationServiceInfo.length());
pugi::xpath_node path = docAuthenticationServiceInfo.select_node("//certificate");
appendTextElem(activationServiceInfo, "adept:authenticationCertificate", path.node().first_child().value());
doUpdate = true;
}
if (doUpdate)
user->updateActivationFile(user->activationDoc);
return user;
} }
std::string User::getLicenseServiceCertificate(std::string url) std::string User::getLicenseServiceCertificate(std::string url)
{ {
if (licenseServiceCertificates.count(trim(url))) if (licenseServiceCertificates.count(trim(url)))
return licenseServiceCertificates[trim(url)]; return licenseServiceCertificates[trim(url)];
return ""; return "";
} }
} }

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= STATIC_DEP=
LDFLAGS += -L$(ROOT) -lcrypto -lzip -lz -lcurl # LDFLAGS += -Wl,--gc-sections
LDFLAGS += -L$(ROOT) -lcrypto -lzip -lz -lcurl -lpugixml
ifneq ($(STATIC_UTILS),) ifneq ($(STATIC_UTILS),)
STATIC_DEP = $(ROOT)/libgourou.a STATIC_DEP = $(ROOT)/libgourou.a
@ -25,12 +31,25 @@ COMMON_LIB = utils.a
all: $(TARGETS) all: $(TARGETS)
${COMMON_LIB}: ${COMMON_DEPS} ${STATIC_DEP} ${COMMON_LIB}: $(COMMON_DEPS)
$(CXX) $(CXXFLAGS) ${COMMON_DEPS} $(LDFLAGS) -c $(CXX) $(CXXFLAGS) $(COMMON_DEPS) -c
$(AR) crs $@ ${COMMON_OBJECTS} $(STATIC_DEP) $(AR) crs $@ $(COMMON_OBJECTS)
%: %.cpp ${COMMON_LIB} %: %.cpp $(COMMON_LIB) $(STATIC_DEP)
$(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@ $(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: clean:
rm -f $(TARGETS) $(COMMON_LIB) rm -f $(TARGETS) $(COMMON_LIB)

View file

@ -4,16 +4,16 @@
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright * Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer. notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright * Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution. documentation and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the * Neither the name of the copyright holder nor the
names of its contributors may be used to endorse or promote products names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission. derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
@ -46,160 +46,164 @@ static bool exportPrivateKey = false;
static const char* outputFile = 0; static const char* outputFile = 0;
static const char* outputDir = 0; static const char* outputDir = 0;
static bool resume = false; static bool resume = false;
static bool notify = true;
class ACSMDownloader class ACSMDownloader
{ {
public: public:
int run() int run()
{ {
int ret = 0; int ret = 0;
try try
{ {
gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile); gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile);
gourou::User* user = processor.getUser(); gourou::User* user = processor.getUser();
if (exportPrivateKey) if (exportPrivateKey)
{ {
std::string filename; std::string filename;
if (!outputFile) if (outputFile)
filename = std::string("Adobe_PrivateLicenseKey--") + user->getUsername() + ".der"; filename = outputFile;
else else
filename = outputFile; {
filename = std::string("Adobe_PrivateLicenseKey--") + user->getUsername() + ".der";
if (outputDir)
{
if (!fileExists(outputDir))
mkpath(outputDir);
filename = std::string(outputDir) + "/" + filename;
}
processor.exportPrivateLicenseKey(filename); if (outputDir)
filename = std::string(outputDir) + "/" + filename;
}
std::cout << "Private license key exported to " << filename << std::endl; createPath(filename.c_str());
}
else processor.exportPrivateLicenseKey(filename);
{
gourou::FulfillmentItem* item = processor.fulfill(acsmFile);
std::string filename; std::cout << "Private license key exported to " << filename << std::endl;
if (!outputFile) }
{ else
filename = item->getMetadata("title"); {
if (filename == "") gourou::FulfillmentItem* item = processor.fulfill(acsmFile, notify);
filename = "output";
else
{
// Remove invalid characters
std::replace(filename.begin(), filename.end(), '/', '_');
}
}
else
filename = outputFile;
if (outputDir)
{
if (!fileExists(outputDir))
mkpath(outputDir);
filename = std::string(outputDir) + "/" + filename; std::string filename;
} if (outputFile)
filename = outputFile;
gourou::DRMProcessor::ITEM_TYPE type = processor.download(item, filename, resume); else
{
filename = item->getMetadata("title");
if (filename == "")
filename = "output";
else
{
// Remove invalid characters
std::replace(filename.begin(), filename.end(), '/', '_');
}
if (!outputFile) if (outputDir)
{ filename = std::string(outputDir) + "/" + filename;
std::string finalName = filename; }
if (type == gourou::DRMProcessor::ITEM_TYPE::PDF)
finalName += ".pdf"; createPath(filename.c_str());
else
finalName += ".epub";
rename(filename.c_str(), finalName.c_str());
filename = finalName;
}
std::cout << "Created " << filename << std::endl;
serializeLoanToken(item); gourou::DRMProcessor::ITEM_TYPE type = processor.download(item, filename, resume);
}
} catch(std::exception& e)
{
std::cout << e.what() << std::endl;
ret = 1;
}
return ret; if (!outputFile)
{
std::string finalName = filename;
if (type == gourou::DRMProcessor::ITEM_TYPE::PDF)
finalName += ".pdf";
else
finalName += ".epub";
rename(filename.c_str(), finalName.c_str());
filename = finalName;
}
std::cout << "Created " << filename << std::endl;
serializeLoanToken(item);
}
} catch(std::exception& e)
{
std::cout << e.what() << std::endl;
ret = 1;
}
return ret;
} }
void serializeLoanToken(gourou::FulfillmentItem* item) void serializeLoanToken(gourou::FulfillmentItem* item)
{ {
gourou::LoanToken* token = item->getLoanToken(); gourou::LoanToken* token = item->getLoanToken();
// No loan token available // No loan token available
if (!token) if (!token)
return; return;
pugi::xml_document doc; pugi::xml_document doc;
pugi::xml_node decl = doc.append_child(pugi::node_declaration); pugi::xml_node decl = doc.append_child(pugi::node_declaration);
decl.append_attribute("version") = "1.0"; decl.append_attribute("version") = "1.0";
pugi::xml_node root = doc.append_child("loanToken"); pugi::xml_node root = doc.append_child("loanToken");
gourou::appendTextElem(root, "id", (*token)["id"]); gourou::appendTextElem(root, "id", (*token)["id"]);
gourou::appendTextElem(root, "operatorURL", (*token)["operatorURL"]); gourou::appendTextElem(root, "operatorURL", (*token)["operatorURL"]);
gourou::appendTextElem(root, "validity", (*token)["validity"]); gourou::appendTextElem(root, "validity", (*token)["validity"]);
gourou::appendTextElem(root, "name", item->getMetadata("title")); gourou::appendTextElem(root, "name", item->getMetadata("title"));
char * activationDir = strdup(deviceFile); char * activationDir = strdup(deviceFile);
activationDir = dirname(activationDir); activationDir = dirname(activationDir);
gourou::StringXMLWriter xmlWriter; gourou::StringXMLWriter xmlWriter;
doc.save(xmlWriter, " "); doc.save(xmlWriter, " ");
std::string xmlStr = xmlWriter.getResult(); std::string xmlStr = xmlWriter.getResult();
// Use first bytes of SHA1(id) as filename // Use first bytes of SHA1(id) as filename
unsigned char sha1[gourou::SHA1_LEN]; unsigned char sha1[gourou::SHA1_LEN];
client.digest("SHA1", (unsigned char*)(*token)["id"].c_str(), (*token)["id"].size(), sha1); client.digest("SHA1", (unsigned char*)(*token)["id"].c_str(), (*token)["id"].size(), sha1);
gourou::ByteArray tmp(sha1, sizeof(sha1)); gourou::ByteArray tmp(sha1, sizeof(sha1));
std::string filenameHex = tmp.toHex(); std::string filenameHex = tmp.toHex();
std::string filename(filenameHex.c_str(), ID_HASH_SIZE); std::string filename(filenameHex.c_str(), ID_HASH_SIZE);
std::string fullPath = std::string(activationDir); std::string fullPath = std::string(activationDir);
fullPath += std::string ("/") + std::string(LOANS_DIR); fullPath += std::string ("/") + std::string(LOANS_DIR);
mkpath(fullPath.c_str()); mkpath(fullPath.c_str());
fullPath += filename + std::string(".xml"); fullPath += filename + std::string(".xml");
gourou::writeFile(fullPath, xmlStr); gourou::writeFile(fullPath, xmlStr);
std::cout << "Loan token serialized into " << fullPath << std::endl; std::cout << "Loan token serialized into " << fullPath << std::endl;
free(activationDir); free(activationDir);
} }
private: private:
DRMProcessorClientImpl client; DRMProcessorClientImpl client;
}; };
static void usage(const char* cmd) static void usage(const char* cmd)
{ {
std::cout << "Download EPUB file from ACSM request file" << 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 << "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 << "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 << " " << "-d|--device-file" << "\t" << "device.xml file from eReader" << std::endl; std::cout << " " << "-o|--output-file" << "\t" << "Optional output filename (default <title.(epub|pdf|der)>) (not compatible with -O)" << std::endl;
std::cout << " " << "-a|--activation-file" << "\t" << "activation.xml file from eReader" << std::endl; std::cout << " " << "-f|--acsm-file" << "\t" << "Backward compatibility: ACSM request file for epub download" << 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 << " " << "-e|--export-private-key"<< "\t" << "Export private key in DER format" << 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 << " " << "-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|--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 << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl;
std::cout << " " << "-h|--help" << "\t\t" << "This help" << 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 << 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 << " * Current directory" << std::endl;
std::cout << " * .adept" << std::endl; std::cout << " * .adept" << std::endl;
std::cout << " * adobe-digital-editions directory" << std::endl; std::cout << " * adobe-digital-editions directory" << std::endl;
@ -209,79 +213,102 @@ static void usage(const char* cmd)
int main(int argc, char** argv) int main(int argc, char** argv)
{ {
int c, ret = -1; int c, ret = -1;
std::string _deviceFile, _activationFile, _devicekeyFile;
const char** files[] = {&devicekeyFile, &deviceFile, &activationFile}; const char** files[] = {&devicekeyFile, &deviceFile, &activationFile};
int verbose = gourou::DRMProcessor::getLogLevel(); int verbose = gourou::DRMProcessor::getLogLevel();
while (1) { while (1) {
int option_index = 0; int option_index = 0;
static struct option long_options[] = { static struct option long_options[] = {
{"device-file", required_argument, 0, 'd' }, {"adept-directory", required_argument, 0, 'D' },
{"activation-file", required_argument, 0, 'a' }, {"device-file", required_argument, 0, 'd' },
{"device-key-file", required_argument, 0, 'k' }, {"activation-file", required_argument, 0, 'a' },
{"output-dir", required_argument, 0, 'O' }, {"device-key-file", required_argument, 0, 'k' },
{"output-file", required_argument, 0, 'o' }, {"output-dir", required_argument, 0, 'O' },
{"acsm-file", required_argument, 0, 'f' }, {"output-file", required_argument, 0, 'o' },
{"export-private-key",no_argument, 0, 'e' }, {"acsm-file", required_argument, 0, 'f' },
{"resume", no_argument, 0, 'r' }, {"export-private-key",no_argument, 0, 'e' },
{"verbose", no_argument, 0, 'v' }, {"resume", no_argument, 0, 'r' },
{"version", no_argument, 0, 'V' }, {"no-notify", no_argument, 0, 'N' },
{"help", no_argument, 0, 'h' }, {"verbose", no_argument, 0, 'v' },
{0, 0, 0, 0 } {"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); long_options, &option_index);
if (c == -1) if (c == -1)
break; break;
switch (c) { switch (c) {
case 'd': case 'D':
deviceFile = optarg; _deviceFile = std::string(optarg) + "/device.xml";
break; _activationFile = std::string(optarg) + "/activation.xml";
case 'a': _devicekeyFile = std::string(optarg) + "/devicesalt";
activationFile = optarg; deviceFile = _deviceFile.c_str();
break; activationFile = _activationFile.c_str();
case 'k': devicekeyFile = _devicekeyFile.c_str();
devicekeyFile = optarg; break;
break; case 'd':
case 'f': deviceFile = optarg;
acsmFile = optarg; break;
break; case 'a':
case 'O': activationFile = optarg;
outputDir = optarg; break;
break; case 'k':
case 'o': devicekeyFile = optarg;
outputFile = optarg; break;
break; case 'f':
case 'e': acsmFile = optarg;
exportPrivateKey = true; break;
break; case 'O':
case 'r': outputDir = optarg;
resume = true; break;
break; case 'o':
case 'v': outputFile = optarg;
verbose++; break;
break; case 'e':
case 'V': exportPrivateKey = true;
version(); break;
return 0; case 'r':
case 'h': resume = true;
usage(argv[0]); break;
return 0; case 'N':
default: notify = false;
usage(argv[0]); break;
return -1; 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); gourou::DRMProcessor::setLogLevel(verbose);
if (optind == argc-1)
acsmFile = argv[optind];
if ((!acsmFile && !exportPrivateKey) || (outputDir && !outputDir[0]) || if ((!acsmFile && !exportPrivateKey) || (outputDir && !outputDir[0]) ||
(outputFile && !outputFile[0])) (outputFile && !outputFile[0]))
{ {
usage(argv[0]); usage(argv[0]);
return -1; return -1;
}
if (outputDir && outputFile)
{
std::cout << "Error : you cannot use both -o and -O" << std::endl;
return -1;
} }
ACSMDownloader downloader; ACSMDownloader downloader;
@ -291,44 +318,44 @@ int main(int argc, char** argv)
const char* orig; const char* orig;
for (i=0; i<(int)ARRAY_SIZE(files); i++) for (i=0; i<(int)ARRAY_SIZE(files); i++)
{ {
orig = *files[i]; orig = *files[i];
*files[i] = findFile(*files[i]); *files[i] = findFile(*files[i]);
if (!*files[i]) if (!*files[i])
{ {
std::cout << "Error : " << orig << " doesn't exists, did you activate your device ?" << std::endl; std::cout << "Error : " << orig << " doesn't exists, did you activate your device ?" << std::endl;
ret = -1; ret = -1;
hasErrors = true; hasErrors = true;
} }
} }
if (hasErrors) if (hasErrors)
goto end; goto end;
if (exportPrivateKey) if (exportPrivateKey)
{ {
if (acsmFile) if (acsmFile)
{ {
usage(argv[0]); usage(argv[0]);
return -1; return -1;
} }
} }
else else
{ {
if (!fileExists(acsmFile)) if (!pathExists(acsmFile))
{ {
std::cout << "Error : " << acsmFile << " doesn't exists" << std::endl; std::cout << "Error : " << acsmFile << " doesn't exists" << std::endl;
ret = -1; ret = -1;
goto end; goto end;
} }
} }
ret = downloader.run(); ret = downloader.run();
end: end:
for (i=0; i<(int)ARRAY_SIZE(files); i++) for (i=0; i<(int)ARRAY_SIZE(files); i++)
{ {
if (*files[i]) if (*files[i])
free((void*)*files[i]); free((void*)*files[i]);
} }
return ret; return ret;

View file

@ -4,16 +4,16 @@
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright * Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer. notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright * Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution. documentation and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the * Neither the name of the copyright holder nor the
names of its contributors may be used to endorse or promote products names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission. derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
@ -31,6 +31,7 @@
#include <termios.h> #include <termios.h>
#include <string.h> #include <string.h>
#include <limits.h> #include <limits.h>
#include <libgen.h>
#include <iostream> #include <iostream>
#include <ostream> #include <ostream>
@ -63,71 +64,72 @@ static int getch() {
static std::string getpass(const char *prompt, bool show_asterisk=false) static std::string getpass(const char *prompt, bool show_asterisk=false)
{ {
const char BACKSPACE=127; const char BACKSPACE=127;
const char RETURN=10; const char RETURN=10;
std::string password; std::string password;
unsigned char ch=0; unsigned char ch=0;
std::cout <<prompt; std::cout <<prompt;
while((ch=getch())!= RETURN) while((ch=getch())!= RETURN)
{ {
if(ch==BACKSPACE) if(ch==BACKSPACE)
{ {
if(password.length()!=0) if(password.length()!=0)
{ {
if(show_asterisk) if(show_asterisk)
std::cout <<"\b \b"; std::cout <<"\b \b";
password.resize(password.length()-1); password.resize(password.length()-1);
} }
} }
else else
{ {
password+=ch; password+=ch;
if(show_asterisk) if(show_asterisk)
std::cout <<'*'; std::cout <<'*';
} }
} }
std::cout <<std::endl; std::cout <<std::endl;
return password; return password;
} }
class ADEPTActivate class ADEPTActivate
{ {
public: public:
int run() int run()
{ {
int ret = 0; int ret = 0;
try try
{ {
DRMProcessorClientImpl client; DRMProcessorClientImpl client;
gourou::DRMProcessor* processor = gourou::DRMProcessor::createDRMProcessor( gourou::DRMProcessor* processor = gourou::DRMProcessor::createDRMProcessor(
&client, randomSerial, outputDir, hobbesVersion); &client, randomSerial, outputDir, hobbesVersion);
processor->signIn(username, password); processor->signIn(username, password);
processor->activateDevice(); processor->activateDevice();
std::cout << username << " fully signed and device activated in " << outputDir << std::endl; std::cout << username << " fully signed and device activated in " << outputDir << std::endl;
} catch(std::exception& e) } catch(std::exception& e)
{ {
std::cout << e.what() << std::endl; std::cout << e.what() << std::endl;
ret = 1; ret = 1;
} }
return ret; return ret;
} }
}; };
static void usage(const char* cmd) 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 << " " << "-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 << " " << "-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; std::cout << " " << "-p|--password" << "\t\t" << "AdobeID password (asked if not set via command line) " << std::endl;
@ -158,129 +160,129 @@ int main(int argc, char** argv)
const char* _outputDir = outputDir; const char* _outputDir = outputDir;
int verbose = gourou::DRMProcessor::getLogLevel(); int verbose = gourou::DRMProcessor::getLogLevel();
bool anonymous = false; bool anonymous = false;
while (1) { while (1) {
int option_index = 0; int option_index = 0;
static struct option long_options[] = { static struct option long_options[] = {
{"anonymous", no_argument , 0, 'a' }, {"anonymous", no_argument , 0, 'a' },
{"username", required_argument, 0, 'u' }, {"username", required_argument, 0, 'u' },
{"password", required_argument, 0, 'p' }, {"password", required_argument, 0, 'p' },
{"output-dir", required_argument, 0, 'O' }, {"output-dir", required_argument, 0, 'O' },
{"hobbes-version",required_argument, 0, 'H' }, {"hobbes-version",required_argument, 0, 'H' },
{"random-serial", no_argument, 0, 'r' }, {"random-serial", no_argument, 0, 'r' },
{"verbose", no_argument, 0, 'v' }, {"verbose", no_argument, 0, 'v' },
{"version", no_argument, 0, 'V' }, {"version", no_argument, 0, 'V' },
{"help", no_argument, 0, 'h' }, {"help", no_argument, 0, 'h' },
{0, 0, 0, 0 } {0, 0, 0, 0 }
}; };
c = getopt_long(argc, argv, "au:p:O:H:rvVh", c = getopt_long(argc, argv, "au:p:O:H:rvVh",
long_options, &option_index); long_options, &option_index);
if (c == -1) if (c == -1)
break; break;
switch (c) { switch (c) {
case 'a': case 'a':
anonymous = true; anonymous = true;
break; break;
case 'u': case 'u':
username = optarg; username = optarg;
break; break;
case 'p': case 'p':
password = optarg; password = optarg;
break; break;
case 'O': case 'O':
_outputDir = optarg; _outputDir = optarg;
break; break;
case 'H': case 'H':
hobbesVersion = optarg; hobbesVersion = optarg;
break; break;
case 'v': case 'v':
verbose++; verbose++;
break; break;
case 'V': case 'V':
version(); version();
return 0; return 0;
case 'h': case 'h':
usage(argv[0]); usage(argv[0]);
return 0; return 0;
case 'r': case 'r':
randomSerial = true; randomSerial = true;
break; break;
default: default:
usage(argv[0]); usage(argv[0]);
return -1; return -1;
} }
} }
gourou::DRMProcessor::setLogLevel(verbose); gourou::DRMProcessor::setLogLevel(verbose);
if ((!username && !anonymous) || if ((!username && !anonymous) ||
(username && anonymous)) (username && anonymous))
{ {
usage(argv[0]); usage(argv[0]);
return -1; return -1;
} }
if (anonymous) if (anonymous)
{ {
username = "anonymous"; username = "anonymous";
password = ""; password = "";
} }
if (!_outputDir || _outputDir[0] == 0) if (!_outputDir || _outputDir[0] == 0)
{ {
outputDir = strdup(abspath(DEFAULT_ADEPT_DIR)); outputDir = strdup(gourou::DRMProcessor::getDefaultAdeptDir().c_str());
} }
else else
{ {
// Relative path // Relative path
if (_outputDir[0] == '.' || _outputDir[0] != '/') if (_outputDir[0] == '.' || _outputDir[0] != '/')
{ {
// realpath doesn't works if file/dir doesn't exists // realpath doesn't works if file/dir doesn't exists
if (fileExists(_outputDir)) if (pathExists(_outputDir))
outputDir = strdup(realpath(_outputDir, 0)); outputDir = strdup(realpath(_outputDir, 0));
else else
outputDir = strdup(abspath(_outputDir)); outputDir = strdup(abspath(_outputDir));
} }
else else
outputDir = strdup(_outputDir); outputDir = strdup(_outputDir);
} }
std::string pass; std::string pass;
if (fileExists(outputDir)) if (pathExists(outputDir))
{ {
int key; int key;
while (true) while (true)
{ {
std::cout << "!! Warning !! : " << outputDir << " already exists." << std::endl; std::cout << "!! Warning !! : " << outputDir << " already exists." << std::endl;
std::cout << "All your data will be overwrite. Would you like to continue ? [y/N] " << std::flush ; std::cout << "All your data will be overwrite. Would you like to continue ? [y/N] " << std::flush ;
key = getchar(); key = getchar();
if (key == 'n' || key == 'N' || key == '\n' || key == '\r') if (key == 'n' || key == 'N' || key == '\n' || key == '\r')
goto end; goto end;
if (key == 'y' || key == 'Y') if (key == 'y' || key == 'Y')
break; break;
} }
// Clean STDIN buf // Clean STDIN buf
while ((key = getchar()) != '\n') while ((key = getchar()) != '\n')
; ;
} }
if (!password) if (!password)
{ {
char prompt[128]; char prompt[128];
std::snprintf(prompt, sizeof(prompt), "Enter password for <%s> : ", username); std::snprintf(prompt, sizeof(prompt), "Enter password for <%s> : ", username);
pass = getpass((const char*)prompt, false); pass = getpass((const char*)prompt, false);
password = pass.c_str(); password = pass.c_str();
} }
ADEPTActivate activate; ADEPTActivate activate;
ret = activate.run(); ret = activate.run();
end: end:
free((void*)outputDir); free((void*)outputDir);
return ret; return ret;
} }

View file

@ -4,16 +4,16 @@
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright * Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer. notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright * Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution. documentation and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the * Neither the name of the copyright holder nor the
names of its contributors may be used to endorse or promote products names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission. derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
@ -45,13 +45,14 @@
#define MAX_SIZE_BOOK_NAME 30 #define MAX_SIZE_BOOK_NAME 30
static char* activationDir = 0; static char* adeptDir = 0;
static const char* deviceFile = "device.xml"; static const char* deviceFile = "device.xml";
static const char* activationFile = "activation.xml"; static const char* activationFile = "activation.xml";
static const char* devicekeyFile = "devicesalt"; static const char* devicekeyFile = "devicesalt";
static bool list = false; static bool list = false;
static const char* returnID = 0; static const char* returnID = 0;
static const char* deleteID = 0; static const char* deleteID = 0;
static bool notify = true;
struct Loan struct Loan
{ {
@ -59,295 +60,314 @@ struct Loan
std::string operatorURL; std::string operatorURL;
std::string validity; std::string validity;
std::string bookName; std::string bookName;
std::string path; std::string path;
}; };
class LoanMGT class LoanMGT
{ {
public: public:
~LoanMGT() ~LoanMGT()
{ {
for (const auto& kv : loanedBooks) for (const auto& kv : loanedBooks)
delete kv.second; delete kv.second;
} }
int run() int run()
{ {
int ret = 0; int ret = 0;
try try
{ {
DRMProcessorClientImpl client; DRMProcessorClientImpl client;
gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile); gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile);
loadLoanedBooks(); loadLoanedBooks();
if (list) if (list)
displayLoanList(); displayLoanList();
else if (returnID) else if (returnID)
returnBook(processor); returnBook(processor);
else if (deleteID) else if (deleteID)
deleteLoan(); deleteLoan();
} catch(std::exception& e) } catch(std::exception& e)
{ {
std::cout << e.what() << std::endl; std::cout << e.what() << std::endl;
ret = 1; ret = 1;
} }
return ret; return ret;
} }
private: private:
void loadLoanedBooks() void loadLoanedBooks()
{ {
DIR *dp; DIR *dp;
struct dirent *ep; struct dirent *ep;
int entryLen; int entryLen;
struct Loan* loan; struct Loan* loan;
char * res; 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; return;
dp = opendir (loanDir.c_str()); dp = opendir (loanDir.c_str());
if(!dp) if(!dp)
EXCEPTION(gourou::USER_INVALID_INPUT, "Cannot read directory " << loanDir); EXCEPTION(gourou::USER_INVALID_INPUT, "Cannot read directory " << loanDir);
while ((ep = readdir (dp))) while ((ep = readdir (dp)))
{ {
if (ep->d_type != DT_LNK && if (ep->d_type != DT_LNK &&
ep->d_type != DT_REG) ep->d_type != DT_REG)
continue; continue;
entryLen = strlen(ep->d_name); entryLen = strlen(ep->d_name);
if (entryLen <= 4 || if (entryLen <= 4 ||
ep->d_name[entryLen-4] != '.' || ep->d_name[entryLen-4] != '.' ||
ep->d_name[entryLen-3] != 'x' || ep->d_name[entryLen-3] != 'x' ||
ep->d_name[entryLen-2] != 'm' || ep->d_name[entryLen-2] != 'm' ||
ep->d_name[entryLen-1] != 'l') ep->d_name[entryLen-1] != 'l')
continue; continue;
std::string id = std::string(ep->d_name, entryLen-4); std::string id = std::string(ep->d_name, entryLen-4);
loan = new Loan; loan = new Loan;
loan->path = loanDir + std::string("/") + ep->d_name; loan->path = loanDir + std::string("/") + ep->d_name;
pugi::xml_document xmlDoc; pugi::xml_document xmlDoc;
pugi::xml_node node; pugi::xml_node node;
if (!xmlDoc.load_file(loan->path.c_str(), pugi::parse_ws_pcdata_single|pugi::parse_escapes, pugi::encoding_utf8)) if (!xmlDoc.load_file(loan->path.c_str(), pugi::parse_ws_pcdata_single|pugi::parse_escapes, pugi::encoding_utf8))
{ {
std::cout << "Invalid loan entry " << loan->path << std::endl; std::cout << "Invalid loan entry " << loan->path << std::endl;
goto error; goto error;
} }
// id // id
node = xmlDoc.select_node("//id").node(); node = xmlDoc.select_node("//id").node();
if (!node) if (!node)
{ {
std::cout << "Invalid loan entry " << ep->d_name << ", no id element" << std::endl; std::cout << "Invalid loan entry " << ep->d_name << ", no id element" << std::endl;
goto error; goto error;
} }
loan->id = node.first_child().value(); loan->id = node.first_child().value();
// operatorURL // operatorURL
node = xmlDoc.select_node("//operatorURL").node(); node = xmlDoc.select_node("//operatorURL").node();
if (!node) if (!node)
{ {
std::cout << "Invalid loan entry " << ep->d_name << ", no operatorURL element" << std::endl; std::cout << "Invalid loan entry " << ep->d_name << ", no operatorURL element" << std::endl;
goto error; goto error;
} }
loan->operatorURL = node.first_child().value(); loan->operatorURL = node.first_child().value();
// validity // validity
node = xmlDoc.select_node("//validity").node(); node = xmlDoc.select_node("//validity").node();
if (!node) if (!node)
{ {
std::cout << "Invalid loan entry " << ep->d_name << ", no validity element" << std::endl; std::cout << "Invalid loan entry " << ep->d_name << ", no validity element" << std::endl;
goto error; goto error;
} }
loan->validity = node.first_child().value(); loan->validity = node.first_child().value();
// bookName // bookName
node = xmlDoc.select_node("//name").node(); node = xmlDoc.select_node("//name").node();
if (!node) if (!node)
{ {
std::cout << "Invalid loan entry " << ep->d_name << ", no name element" << std::endl; std::cout << "Invalid loan entry " << ep->d_name << ", no name element" << std::endl;
goto error; goto error;
} }
loan->bookName = node.first_child().value(); loan->bookName = node.first_child().value();
struct tm tm; struct tm tm;
res = strptime(loan->validity.c_str(), "%Y-%m-%dT%H:%M:%S%Z", &tm); #ifdef __ANDROID__
if (*res == 0) res = strptime(loan->validity.c_str(), "%Y-%m-%dT%H:%M:%S%z", &tm);
{ #else
if (mktime(&tm) <= time(NULL)) res = strptime(loan->validity.c_str(), "%Y-%m-%dT%H:%M:%S%Z", &tm);
loan->validity = " (Expired)"; #endif
}
else if (res != NULL && *res == 0)
{ {
std::cout << "Unable to parse validity timestamp :" << loan->validity << std::endl; if (mktime(&tm) <= time(NULL))
loan->validity = " (Unknown)"; loan->validity = " (Expired)";
} }
else
loanedBooks[id] = loan; {
continue; std::cout << "Unable to parse validity timestamp :" << loan->validity << std::endl;
loan->validity = " (Unknown)";
}
loanedBooks[id] = loan;
continue;
error: error:
if (loan) if (loan)
delete loan; delete loan;
} }
closedir (dp); closedir (dp);
} }
void displayLoanList() void displayLoanList()
{ {
if (!loanedBooks.size()) if (!loanedBooks.size())
{ {
std::cout << "Any book loaned" << std::endl; std::cout << "No books loaned" << std::endl;
return; return;
} }
struct Loan* loan; struct Loan* loan;
unsigned int maxSizeBookName=0; unsigned int maxSizeBookName=0;
// Compute max size // Compute max size
for (const auto& kv : loanedBooks) for (const auto& kv : loanedBooks)
{ {
loan = kv.second; loan = kv.second;
if (loan->bookName.size() > maxSizeBookName) if (loan->bookName.size() > maxSizeBookName)
maxSizeBookName = loan->bookName.size(); maxSizeBookName = loan->bookName.size();
} }
if (maxSizeBookName > MAX_SIZE_BOOK_NAME) /* Manage empty names */
maxSizeBookName = MAX_SIZE_BOOK_NAME; if (maxSizeBookName == 0)
else if ((maxSizeBookName % 2)) maxSizeBookName = sizeof("No name ")-1;
maxSizeBookName++; else if (maxSizeBookName < 4)
maxSizeBookName = 4;
else if (maxSizeBookName > MAX_SIZE_BOOK_NAME)
maxSizeBookName = MAX_SIZE_BOOK_NAME;
else if ((maxSizeBookName % 2))
maxSizeBookName++;
// std::cout << " ID Book Expiration" << std::endl; // std::cout << " ID Book Expiration" << std::endl;
// std::cout << "------------------------------" << std::endl; // std::cout << "------------------------------" << std::endl;
int fillID, fillBookName, fillExpiration=(20 - 10)/2; int fillID, fillBookName, fillExpiration=(20 - 10)/2;
fillID = (ID_HASH_SIZE - 2) / 2; fillID = (ID_HASH_SIZE - 2) / 2;
fillBookName = (maxSizeBookName - 4) / 2; fillBookName = (maxSizeBookName - 4) / 2;
std::cout.width (fillID); std::cout.width (fillID);
std::cout << ""; std::cout << "";
std::cout << "ID" ; std::cout << "ID" ;
std::cout.width (fillID); std::cout.width (fillID);
std::cout << ""; std::cout << "";
std::cout << " " ; std::cout << " " ;
std::cout.width (fillBookName); std::cout.width (fillBookName);
std::cout << ""; std::cout << "";
std::cout << "Book" ; std::cout << "Book" ;
std::cout.width (fillBookName); std::cout.width (fillBookName);
std::cout << ""; std::cout << "";
std::cout << " " ; std::cout << " " ;
std::cout.width (fillExpiration); std::cout.width (fillExpiration);
std::cout << ""; std::cout << "";
std::cout << "Exipration"; std::cout << "Expiration";
std::cout.width (fillExpiration); std::cout.width (fillExpiration);
std::cout << "" << std::endl; std::cout << "" << std::endl;
std::cout.fill ('-'); std::cout.fill ('-');
std::cout.width (ID_HASH_SIZE + 4 + maxSizeBookName + 4 + 20); std::cout.width (ID_HASH_SIZE + 4 + maxSizeBookName + 4 + 20);
std::cout << "" << std::endl; std::cout << "" << std::endl;
std::cout.fill (' '); std::cout.fill (' ');
std::string bookName; std::string bookName;
for (const auto& kv : loanedBooks) for (const auto& kv : loanedBooks)
{ {
loan = kv.second; loan = kv.second;
std::cout << kv.first; std::cout << kv.first;
std::cout << " "; std::cout << " ";
if (loan->bookName.size() > MAX_SIZE_BOOK_NAME) if (loan->bookName.size() == 0)
bookName = std::string(loan->bookName.c_str(), MAX_SIZE_BOOK_NAME); bookName = std::string("No name ");
else else if (loan->bookName.size() > MAX_SIZE_BOOK_NAME)
bookName = loan->bookName; bookName = std::string(loan->bookName.c_str(), MAX_SIZE_BOOK_NAME);
else
bookName = loan->bookName;
std::cout << bookName; std::cout << bookName;
std::cout.width (maxSizeBookName - bookName.size()); std::cout.width (maxSizeBookName - bookName.size());
std::cout << ""; std::cout << "";
std::cout << " "; std::cout << " ";
std::cout << loan->validity << std::endl; std::cout << loan->validity << std::endl;
} }
std::cout << std::endl; std::cout << std::endl;
} }
void returnBook(gourou::DRMProcessor& processor) void returnBook(gourou::DRMProcessor& processor)
{ {
struct Loan* loan = loanedBooks[std::string(returnID)]; struct Loan* loan = loanedBooks[std::string(returnID)];
if (!loan) if (!loan)
{ {
std::cout << "Error : Loan " << returnID << " doesn't exists" << std::endl; std::cout << "Error : Loan " << returnID << " doesn't exists" << std::endl;
return; return;
} }
processor.returnLoan(loan->id, loan->operatorURL); processor.returnLoan(loan->id, loan->operatorURL, notify);
deleteID = returnID;
if (deleteLoan(false))
{
std::cout << "Loan " << returnID << " successfully returned" << std::endl;
}
}
deleteID = returnID;
if (deleteLoan(false))
{
std::cout << "Loan " << returnID << " successfully returned" << std::endl;
}
}
bool deleteLoan(bool displayResult=true) bool deleteLoan(bool displayResult=true)
{ {
struct Loan* loan = loanedBooks[std::string(deleteID)]; struct Loan* loan = loanedBooks[std::string(deleteID)];
if (!loan) if (!loan)
{ {
std::cout << "Error : Loan " << deleteID << " doesn't exists" << std::endl; std::cout << "Error : Loan " << deleteID << " doesn't exists" << std::endl;
return false; return false;
} }
if (unlink(loan->path.c_str())) if (unlink(loan->path.c_str()))
{ {
std::cout << "Error : Cannot delete " << loan->path << std::endl; std::cout << "Error : Cannot delete " << loan->path << std::endl;
return false; return false;
} }
else if (displayResult) else if (displayResult)
{ {
std::cout << "Loan " << deleteID << " deleted" << std::endl; std::cout << "Loan " << deleteID << " deleted" << std::endl;
} }
return true; return true;
} }
std::map<std::string, struct Loan*> loanedBooks; std::map<std::string, struct Loan*> loanedBooks;
}; };
static void usage(const char* cmd) 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 << " " << "-l|--list" << "\t\t" << "List all loaned books" << std::endl;
std::cout << " " << "-r|--return" << "\t\t" << "Return a loaned book" << 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|--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 << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl;
std::cout << " " << "-h|--help" << "\t\t" << "This help" << 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 << 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 << " * Current directory" << std::endl;
std::cout << " * .adept" << std::endl; std::cout << " * .adept" << std::endl;
std::cout << " * adobe-digital-editions directory" << std::endl; std::cout << " * adobe-digital-editions directory" << std::endl;
@ -361,65 +381,69 @@ int main(int argc, char** argv)
const char** files[] = {&devicekeyFile, &deviceFile, &activationFile}; const char** files[] = {&devicekeyFile, &deviceFile, &activationFile};
int verbose = gourou::DRMProcessor::getLogLevel(); int verbose = gourou::DRMProcessor::getLogLevel();
int actions = 0; int actions = 0;
while (1) { while (1) {
int option_index = 0; int option_index = 0;
static struct option long_options[] = { static struct option long_options[] = {
{"activation-dir", required_argument, 0, 'd' }, {"adept-directory", required_argument, 0, 'D' },
{"list", no_argument, 0, 'l' }, {"list", no_argument, 0, 'l' },
{"return", no_argument, 0, 'r' }, {"return", no_argument, 0, 'r' },
{"delete", no_argument, 0, 'D' }, {"delete", no_argument, 0, 'd' },
{"verbose", no_argument, 0, 'v' }, {"no-notify", no_argument, 0, 'N' },
{"version", no_argument, 0, 'V' }, {"verbose", no_argument, 0, 'v' },
{"help", no_argument, 0, 'h' }, {"version", no_argument, 0, 'V' },
{0, 0, 0, 0 } {"help", no_argument, 0, 'h' },
}; {0, 0, 0, 0 }
};
c = getopt_long(argc, argv, "d:lr:D:vVh", c = getopt_long(argc, argv, "d:lr:D:vVh",
long_options, &option_index); long_options, &option_index);
if (c == -1) if (c == -1)
break; break;
switch (c) { switch (c) {
case 'd': case 'D':
activationDir = optarg; adeptDir = optarg;
break; break;
case 'l': case 'l':
list = true; list = true;
actions++; actions++;
break; break;
case 'r': case 'r':
returnID = optarg; returnID = optarg;
actions++; actions++;
break; break;
case 'D': case 'd':
deleteID = optarg; deleteID = optarg;
actions++; actions++;
break; break;
case 'v': case 'N':
verbose++; notify = false;
break; break;
case 'V': case 'v':
version(); verbose++;
return 0; break;
case 'h': case 'V':
usage(argv[0]); version();
return 0; return 0;
default: case 'h':
usage(argv[0]); usage(argv[0]);
return -1; return 0;
} default:
usage(argv[0]);
return -1;
}
} }
gourou::DRMProcessor::setLogLevel(verbose); gourou::DRMProcessor::setLogLevel(verbose);
// By default, simply list books loaned // By default, simply list books loaned
if (actions == 0) if (actions == 0)
list = true; list = true;
else if (actions != 1) else if (actions != 1)
{ {
usage(argv[0]); usage(argv[0]);
return -1; return -1;
} }
LoanMGT loanMGT; LoanMGT loanMGT;
@ -430,50 +454,50 @@ int main(int argc, char** argv)
char *filename; char *filename;
for (i=0; i<(int)ARRAY_SIZE(files); i++) for (i=0; i<(int)ARRAY_SIZE(files); i++)
{ {
orig = *files[i]; 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()); filename = strdup(path.c_str());
} }
else else
filename = strdup(orig); filename = strdup(orig);
*files[i] = findFile(filename); *files[i] = findFile(filename);
free(filename); free(filename);
if (!*files[i]) if (!*files[i])
{ {
std::cout << "Error : " << orig << " doesn't exists, did you activate your device ?" << std::endl; std::cout << "Error : " << orig << " doesn't exists, did you activate your device ?" << std::endl;
hasErrors = true; hasErrors = true;
} }
} }
if (hasErrors) if (hasErrors)
{ {
// In case of activation dir was provided by user // In case of adept dir was provided by user
activationDir = 0; adeptDir = 0;
goto end; goto end;
} }
if (activationDir) if (adeptDir)
activationDir = strdup(activationDir); // For below free adeptDir = strdup(adeptDir); // For below free
else else
{ {
activationDir = strdup(deviceFile); adeptDir = strdup(deviceFile);
activationDir = dirname(activationDir); adeptDir = dirname(adeptDir);
} }
ret = loanMGT.run(); ret = loanMGT.run();
end: end:
for (i=0; i<(int)ARRAY_SIZE(files); i++) for (i=0; i<(int)ARRAY_SIZE(files); i++)
{ {
if (*files[i]) if (*files[i])
free((void*)*files[i]); free((void*)*files[i]);
} }
if (activationDir) if (adeptDir)
free(activationDir); free(adeptDir);
return ret; return ret;
} }

View file

@ -4,16 +4,16 @@
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright * Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer. notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright * Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution. documentation and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the * Neither the name of the copyright holder nor the
names of its contributors may be used to endorse or promote products names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission. derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
@ -27,6 +27,7 @@
*/ */
#include <getopt.h> #include <getopt.h>
#include <libgen.h>
#include <iostream> #include <iostream>
@ -50,13 +51,13 @@ static unsigned encryptionKeySize = 0;
static inline unsigned char htoi(unsigned char c) static inline unsigned char htoi(unsigned char c)
{ {
if (c >= '0' && c <= '9') if (c >= '0' && c <= '9')
c -= '0'; c -= '0';
else if (c >= 'a' && c <= 'f') else if (c >= 'a' && c <= 'f')
c -= 'a' - 10; c -= 'a' - 10;
else if (c >= 'A' && c <= 'F') else if (c >= 'A' && c <= 'F')
c -= 'A' - 10; c -= 'A' - 10;
else else
EXCEPTION(gourou::USER_INVALID_INPUT, "Invalid character " << c << " in encryption key"); EXCEPTION(gourou::USER_INVALID_INPUT, "Invalid character " << c << " in encryption key");
return c; return c;
} }
@ -69,94 +70,101 @@ static inline bool endsWith(const std::string& s, const std::string& suffix)
class ADEPTRemove class ADEPTRemove
{ {
public: public:
int run() int run()
{ {
int ret = 0; int ret = 0;
try try
{ {
gourou::DRMProcessor::ITEM_TYPE type; gourou::DRMProcessor::ITEM_TYPE type;
DRMProcessorClientImpl client; DRMProcessorClientImpl client;
gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile); gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile);
std::string filename; std::string filename;
if (!outputFile) if (outputFile)
filename = std::string(inputFile); filename = outputFile;
else else
filename = outputFile; {
filename = std::string(inputFile);
if (outputDir)
{
if (!fileExists(outputDir))
mkpath(outputDir);
filename = std::string(outputDir) + "/" + filename; if (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 (endsWith(filename, ".epub")) createPath(filename.c_str());
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)
{
unlink(filename.c_str());
fileCopy(inputFile, filename.c_str());
processor.removeDRM(inputFile, filename, type, encryptionKey, encryptionKeySize);
std::cout << "DRM removed into new file " << filename << std::endl;
}
else
{
// Use temp file for PDF
if (type == gourou::DRMProcessor::ITEM_TYPE::PDF)
{
std::string tempFile = filename + ".tmp";
/* Be sure there is not already a temp file */
unlink(tempFile.c_str());
processor.removeDRM(filename, tempFile, type, encryptionKey, encryptionKeySize);
/* Original file must be removed before doing a copy... */
unlink(filename.c_str());
if (rename(tempFile.c_str(), filename.c_str()))
{
EXCEPTION(gourou::DRM_FILE_ERROR, "Unable to copy " << tempFile << " into " << filename);
}
}
else
processor.removeDRM(inputFile, filename, type, encryptionKey, encryptionKeySize);
std::cout << "DRM removed from " << filename << std::endl;
}
} catch(std::exception& e)
{
std::cout << e.what() << std::endl;
ret = 1;
}
return ret; if (inputFile != filename)
{
unlink(filename.c_str());
fileCopy(inputFile, filename.c_str());
processor.removeDRM(inputFile, filename, type, encryptionKey, encryptionKeySize);
std::cout << "DRM removed into new file " << filename << std::endl;
}
else
{
// Use temp file for PDF
if (type == gourou::DRMProcessor::ITEM_TYPE::PDF)
{
std::string tempFile = filename + ".tmp";
/* Be sure there is not already a temp file */
unlink(tempFile.c_str());
processor.removeDRM(filename, tempFile, type, encryptionKey, encryptionKeySize);
/* Original file must be removed before doing a copy... */
unlink(filename.c_str());
if (rename(tempFile.c_str(), filename.c_str()))
{
EXCEPTION(gourou::DRM_FILE_ERROR, "Unable to copy " << tempFile << " into " << filename);
}
}
else
processor.removeDRM(inputFile, filename, type, encryptionKey, encryptionKeySize);
std::cout << "DRM removed from " << filename << std::endl;
}
} catch(std::exception& e)
{
std::cout << e.what() << std::endl;
ret = 1;
}
return ret;
} }
}; };
static void usage(const char* cmd) 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 << "Global Options:" << std::endl;
std::cout << " " << "-a|--activation-file" << "\t" << "activation.xml file from eReader" << std::endl; std::cout << " " << "-O|--output-dir" << "\t" << "Optional output directory were to put result (default ./) (not compatible with -o)" << 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-file" << "\t" << "Optional output filename (default inplace DRM removal>) (not compatible with -O)" << std::endl;
std::cout << " " << "-O|--output-dir" << "\t" << "Optional output directory were to put result (default ./)" << std::endl; std::cout << " " << "-f|--input-file" << "\t" << "Backward compatibility: EPUB/PDF file to process" << 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|--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 << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl;
std::cout << " " << "-h|--help" << "\t\t" << "This help" << 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 << 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 << "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 << " * Current directory" << std::endl;
std::cout << " * .adept" << std::endl; std::cout << " * .adept" << std::endl;
std::cout << " * adobe-digital-editions directory" << std::endl; std::cout << " * adobe-digital-editions directory" << std::endl;
@ -169,74 +177,93 @@ int main(int argc, char** argv)
const char** files[] = {&devicekeyFile, &deviceFile, &activationFile}; const char** files[] = {&devicekeyFile, &deviceFile, &activationFile};
int verbose = gourou::DRMProcessor::getLogLevel(); int verbose = gourou::DRMProcessor::getLogLevel();
std::string _deviceFile, _activationFile, _devicekeyFile;
while (1) { while (1) {
int option_index = 0; int option_index = 0;
static struct option long_options[] = { static struct option long_options[] = {
{"device-file", required_argument, 0, 'd' }, {"adept-directory", required_argument, 0, 'D' },
{"activation-file", required_argument, 0, 'a' }, {"device-file", required_argument, 0, 'd' },
{"device-key-file", required_argument, 0, 'k' }, {"activation-file", required_argument, 0, 'a' },
{"output-dir", required_argument, 0, 'O' }, {"device-key-file", required_argument, 0, 'k' },
{"output-file", required_argument, 0, 'o' }, {"output-dir", required_argument, 0, 'O' },
{"input-file", required_argument, 0, 'f' }, {"output-file", required_argument, 0, 'o' },
{"encryption-key", required_argument, 0, 'K' }, // Private option {"input-file", required_argument, 0, 'f' },
{"verbose", no_argument, 0, 'v' }, {"encryption-key", required_argument, 0, 'K' }, // Private option
{"version", no_argument, 0, 'V' }, {"verbose", no_argument, 0, 'v' },
{"help", no_argument, 0, 'h' }, {"version", no_argument, 0, 'V' },
{0, 0, 0, 0 } {"help", no_argument, 0, 'h' },
}; {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); long_options, &option_index);
if (c == -1) if (c == -1)
break; break;
switch (c) { switch (c) {
case 'd': case 'D':
deviceFile = optarg; _deviceFile = std::string(optarg) + "/device.xml";
break; _activationFile = std::string(optarg) + "/activation.xml";
case 'a': _devicekeyFile = std::string(optarg) + "/devicesalt";
activationFile = optarg; deviceFile = _deviceFile.c_str();
break; activationFile = _activationFile.c_str();
case 'k': devicekeyFile = _devicekeyFile.c_str();
devicekeyFile = optarg; break;
break; case 'd':
case 'f': deviceFile = optarg;
inputFile = optarg; break;
break; case 'a':
case 'O': activationFile = optarg;
outputDir = optarg; break;
break; case 'k':
case 'o': devicekeyFile = optarg;
outputFile = optarg; break;
break; case 'f':
case 'K': inputFile = optarg;
encryptionKeyUser = optarg; break;
break; case 'O':
case 'v': outputDir = optarg;
verbose++; break;
break; case 'o':
case 'V': outputFile = optarg;
version(); break;
return 0; case 'K':
case 'h': encryptionKeyUser = optarg;
usage(argv[0]); break;
return 0; case 'v':
default: verbose++;
usage(argv[0]); break;
return -1; case 'V':
} version();
return 0;
case 'h':
usage(argv[0]);
return 0;
default:
usage(argv[0]);
return -1;
}
} }
gourou::DRMProcessor::setLogLevel(verbose); gourou::DRMProcessor::setLogLevel(verbose);
if (optind == argc-1)
inputFile = argv[optind];
if (!inputFile || (outputDir && !outputDir[0]) || if (!inputFile || (outputDir && !outputDir[0]) ||
(outputFile && !outputFile[0])) (outputFile && !outputFile[0]))
{ {
usage(argv[0]); usage(argv[0]);
return -1; return -1;
} }
if (outputDir && outputFile)
{
std::cout << "Error : you cannot use both -o and -O" << std::endl;
return -1;
}
ADEPTRemove remover; ADEPTRemove remover;
int i; int i;
@ -244,56 +271,56 @@ int main(int argc, char** argv)
const char* orig; const char* orig;
for (i=0; i<(int)ARRAY_SIZE(files); i++) for (i=0; i<(int)ARRAY_SIZE(files); i++)
{ {
orig = *files[i]; orig = *files[i];
*files[i] = findFile(*files[i]); *files[i] = findFile(*files[i]);
if (!*files[i]) if (!*files[i])
{ {
std::cout << "Error : " << orig << " doesn't exists, did you activate your device ?" << std::endl; std::cout << "Error : " << orig << " doesn't exists, did you activate your device ?" << std::endl;
ret = -1; ret = -1;
hasErrors = true; hasErrors = true;
} }
} }
if (encryptionKeyUser) if (encryptionKeyUser)
{ {
int size = std::string(encryptionKeyUser).size(); int size = std::string(encryptionKeyUser).size();
if ((size % 2)) if ((size % 2))
{ {
std::cout << "Error : Encryption key must be odd length" << std::endl; std::cout << "Error : Encryption key must be odd length" << std::endl;
goto end; goto end;
} }
if (encryptionKeyUser[0] == '0' && encryptionKeyUser[1] == 'x') if (encryptionKeyUser[0] == '0' && encryptionKeyUser[1] == 'x')
{ {
encryptionKeyUser += 2; encryptionKeyUser += 2;
size -= 2; size -= 2;
} }
encryptionKey = new unsigned char[size/2]; encryptionKey = new unsigned char[size/2];
for(i=0; i<size; i+=2) for(i=0; i<size; i+=2)
{ {
encryptionKey[i/2] = htoi(encryptionKeyUser[i]) << 4; encryptionKey[i/2] = htoi(encryptionKeyUser[i]) << 4;
encryptionKey[i/2] |= htoi(encryptionKeyUser[i+1]); encryptionKey[i/2] |= htoi(encryptionKeyUser[i+1]);
} }
encryptionKeySize = size/2; encryptionKeySize = size/2;
} }
if (hasErrors) if (hasErrors)
goto end; goto end;
ret = remover.run(); ret = remover.run();
end: end:
for (i=0; i<(int)ARRAY_SIZE(files); i++) for (i=0; i<(int)ARRAY_SIZE(files); i++)
{ {
if (*files[i]) if (*files[i])
free((void*)*files[i]); free((void*)*files[i]);
} }
if (encryptionKey) if (encryptionKey)
free(encryptionKey); free(encryptionKey);
return ret; return ret;
} }

File diff suppressed because it is too large Load diff

View file

@ -4,16 +4,16 @@
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright * Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer. notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright * Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution. documentation and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the * Neither the name of the copyright holder nor the
names of its contributors may be used to endorse or promote products names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission. derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
@ -42,7 +42,7 @@ class DRMProcessorClientImpl : public gourou::DRMProcessorClient
public: public:
DRMProcessorClientImpl(); DRMProcessorClientImpl();
~DRMProcessorClientImpl(); ~DRMProcessorClientImpl();
/* Digest interface */ /* Digest interface */
virtual void* createDigest(const std::string& digestName); virtual void* createDigest(const std::string& digestName);
virtual void digestUpdate(void* handler, unsigned char* data, unsigned int length); virtual void digestUpdate(void* handler, unsigned char* data, unsigned int length);
@ -56,86 +56,90 @@ public:
virtual std::string sendHTTPRequest(const std::string& URL, const std::string& POSTData=std::string(""), const std::string& contentType=std::string(""), std::map<std::string, std::string>* responseHeaders=0, int fd=0, bool resume=false); virtual std::string sendHTTPRequest(const std::string& URL, const std::string& POSTData=std::string(""), const std::string& contentType=std::string(""), std::map<std::string, std::string>* responseHeaders=0, int fd=0, bool resume=false);
virtual void RSAPrivateEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength, virtual void RSAPrivateEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength,
const RSA_KEY_TYPE keyType, const std::string& password, const RSA_KEY_TYPE keyType, const std::string& password,
const unsigned char* data, unsigned dataLength, const unsigned char* data, unsigned dataLength,
unsigned char* res); unsigned char* res);
virtual void RSAPrivateDecrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength, virtual void RSAPrivateDecrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength,
const RSA_KEY_TYPE keyType, const std::string& password, const RSA_KEY_TYPE keyType, const std::string& password,
const unsigned char* data, unsigned dataLength, const unsigned char* data, unsigned dataLength,
unsigned char* res); unsigned char* res);
virtual void RSAPublicEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength, virtual void RSAPublicEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength,
const RSA_KEY_TYPE keyType, const RSA_KEY_TYPE keyType,
const unsigned char* data, unsigned dataLength, const unsigned char* data, unsigned dataLength,
unsigned char* res); unsigned char* res);
virtual void* generateRSAKey(int keyLengthBits); virtual void* generateRSAKey(int keyLengthBits);
virtual void destroyRSAHandler(void* handler); virtual void destroyRSAHandler(void* handler);
virtual void extractRSAPublicKey(void* RSAKeyHandler, unsigned char** keyOut, unsigned int* keyOutLength); virtual void extractRSAPublicKey(void* RSAKeyHandler, unsigned char** keyOut, unsigned int* keyOutLength);
virtual void extractRSAPrivateKey(void* RSAKeyHandler, unsigned char** keyOut, unsigned int* keyOutLength); virtual void extractRSAPrivateKey(void* RSAKeyHandler, unsigned char** keyOut, unsigned int* keyOutLength);
virtual void extractCertificate(const unsigned char* RSAKey, unsigned int RSAKeyLength, virtual void extractCertificate(const unsigned char* RSAKey, unsigned int RSAKeyLength,
const RSA_KEY_TYPE keyType, const std::string& password, const RSA_KEY_TYPE keyType, const std::string& password,
unsigned char** certOut, unsigned int* certOutLength); unsigned char** certOut, unsigned int* certOutLength);
/* Crypto interface */ /* Crypto interface */
virtual void encrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining, virtual void encrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength, const unsigned char* key, unsigned int keyLength,
const unsigned char* iv, unsigned int ivLength, const unsigned char* iv, unsigned int ivLength,
const unsigned char* dataIn, unsigned int dataInLength, const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength); unsigned char* dataOut, unsigned int* dataOutLength);
virtual void* encryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, virtual void* encryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength, const unsigned char* key, unsigned int keyLength,
const unsigned char* iv=0, unsigned int ivLength=0); const unsigned char* iv=0, unsigned int ivLength=0);
virtual void encryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, virtual void encryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength); unsigned char* dataOut, unsigned int* dataOutLength);
virtual void encryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength); virtual void encryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength);
virtual void decrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining, virtual void decrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength, const unsigned char* key, unsigned int keyLength,
const unsigned char* iv, unsigned int ivLength, const unsigned char* iv, unsigned int ivLength,
const unsigned char* dataIn, unsigned int dataInLength, const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength); unsigned char* dataOut, unsigned int* dataOutLength);
virtual void* decryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining, virtual void* decryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength, const unsigned char* key, unsigned int keyLength,
const unsigned char* iv=0, unsigned int ivLength=0); const unsigned char* iv=0, unsigned int ivLength=0);
virtual void decryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength, virtual void decryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength); unsigned char* dataOut, unsigned int* dataOutLength);
virtual void decryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength); virtual void decryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength);
/* ZIP Interface */ /* ZIP Interface */
virtual void* zipOpen(const std::string& path); virtual void* zipOpen(const std::string& path);
virtual void zipReadFile(void* handler, const std::string& path, gourou::ByteArray& result, bool decompress=true); virtual void zipReadFile(void* handler, const std::string& path, gourou::ByteArray& result, bool decompress=true);
virtual void zipWriteFile(void* handler, const std::string& path, gourou::ByteArray& content); virtual void zipWriteFile(void* handler, const std::string& path, gourou::ByteArray& content);
virtual void zipDeleteFile(void* handler, const std::string& path); virtual void zipDeleteFile(void* handler, const std::string& path);
virtual void zipClose(void* handler); virtual void zipClose(void* handler);
virtual void inflate(gourou::ByteArray& data, gourou::ByteArray& result, virtual void inflate(gourou::ByteArray& data, gourou::ByteArray& result,
int wbits=-15); int wbits=-15);
virtual void deflate(gourou::ByteArray& data, gourou::ByteArray& result, virtual void deflate(gourou::ByteArray& data, gourou::ByteArray& result,
int wbits=-15, int compressionLevel=8); int wbits=-15, int compressionLevel=8);
private: private:
void padWithPKCS1(unsigned char* out, unsigned int outLength, void padWithPKCS1(unsigned char* out, unsigned int outLength,
const unsigned char* in, unsigned int inLength); 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 #if OPENSSL_VERSION_MAJOR >= 3
OSSL_PROVIDER *legacy, *deflt; OSSL_PROVIDER *legacy, *deflt;
#else #else
void *legacy, *deflt; void *legacy, *deflt;
#endif #endif
char cookiejar[64];
}; };
#endif #endif

View file

@ -23,18 +23,18 @@ int main(int argc, char** argv)
fullPath = std::string(mountPoint) + util; 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; fullPath = std::string(mountPoint) + DEFAULT_UTIL;
free(argv0); free(argv0);
argv[0] = strdup(fullPath.c_str()); argv[0] = strdup(fullPath.c_str());
if (execvp(argv[0], argv)) if (execvp(argv[0], argv))
std::cout << "Unable to launch '" << argv[0] << "'" << std::endl; std::cout << "Unable to launch '" << argv[0] << "'" << std::endl;
/* Should not happens */ /* Should not happens */
free(argv[0]); free(argv[0]);
return 0; return 0;
} }

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

@ -4,16 +4,16 @@
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright * Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer. notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright * Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution. documentation and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the * Neither the name of the copyright holder nor the
names of its contributors may be used to endorse or promote products names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission. derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
@ -33,6 +33,7 @@
#include <stdio.h> #include <stdio.h>
#include <fcntl.h> #include <fcntl.h>
#include <limits.h> #include <limits.h>
#include <libgen.h>
#include <iostream> #include <iostream>
@ -51,28 +52,42 @@ void version(void)
std::cout << "Current libgourou version : " << gourou::DRMProcessor::VERSION << std::endl ; std::cout << "Current libgourou version : " << gourou::DRMProcessor::VERSION << std::endl ;
} }
bool fileExists(const char* filename) bool pathExists(const char* path)
{ {
struct stat _stat; struct stat _stat;
int ret = stat(filename, &_stat); int ret = stat(path, &_stat);
return (ret == 0); return (ret == 0);
} }
const char* findFile(const char* filename, bool inDefaultDirs) const char* findFile(const char* filename, bool inDefaultDirs)
{ {
if (fileExists(filename)) std::string path;
return strdup(filename);
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; if (!inDefaultDirs) return 0;
for (int i=0; i<(int)ARRAY_SIZE(defaultDirs); i++) for (int i=0; i<(int)ARRAY_SIZE(defaultDirs); i++)
{ {
std::string path = std::string(defaultDirs[i]) + filename; path = std::string(defaultDirs[i]) + filename;
if (fileExists(path.c_str())) if (pathExists(path.c_str()))
return strdup(path.c_str()); return strdup(path.c_str());
} }
return 0; return 0;
} }
@ -98,30 +113,52 @@ void mkpath(const char *dir)
void fileCopy(const char* in, const char* out) void fileCopy(const char* in, const char* out)
{ {
char buffer[4096]; char buffer[4096], *_buffer;
int ret, fdIn, fdOut; int ret, ret2, fdIn, fdOut;
fdIn = open(in, O_RDONLY); fdIn = open(in, O_RDONLY);
if (!fdIn) if (!fdIn)
EXCEPTION(gourou::CLIENT_FILE_ERROR, "Unable to open " << in); EXCEPTION(gourou::CLIENT_FILE_ERROR, "Unable to open " << in);
fdOut = gourou::createNewFile(out); fdOut = gourou::createNewFile(out);
if (!fdOut) if (!fdOut)
{ {
close (fdIn); close (fdIn);
EXCEPTION(gourou::CLIENT_FILE_ERROR, "Unable to open " << out); EXCEPTION(gourou::CLIENT_FILE_ERROR, "Unable to open " << out);
} }
while (true) while (true)
{ {
ret = ::read(fdIn, buffer, sizeof(buffer)); ret = ::read(fdIn, buffer, sizeof(buffer));
if (ret <= 0) if (ret <= 0)
break; 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 (fdIn);
close (fdOut); 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

@ -4,16 +4,16 @@
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright * Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer. notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright * Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution. documentation and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the * Neither the name of the copyright holder nor the
names of its contributors may be used to endorse or promote products names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission. derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
@ -50,9 +50,9 @@ void version(void);
const char* findFile(const char* filename, bool inDefaultDirs=true); 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 * @brief Recursively created dir
@ -64,4 +64,9 @@ void mkpath(const char *dir);
*/ */
void fileCopy(const char* in, const char* out); void fileCopy(const char* in, const char* out);
/**
* @brief Create intermediate directories if it does not exists
*/
void createPath(const char* filename);
#endif #endif