Compare commits

..

No commits in common. "master" and "v0.3.2" have entirely different histories.

37 changed files with 2489 additions and 6097 deletions

7
.gitignore vendored
View file

@ -1,11 +1,8 @@
obj
lib
*.o
*.a
*.so
*~
utils/acsmdownloader
utils/adept_activate
utils/adept_remove
utils/adept_loan_mgt
.adept*
utils/activate
.adept

106
Makefile
View file

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

119
README.md
View file

@ -1,62 +1,45 @@
Introduction
------------
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.
libgourou is a free implementation of Adobe's ADEPT protocol used to add DRM on ePub files. It overcome the lacks of Adobe support for Linux platforms.
Architecture
------------
Like RMSDK, libgourou has a client/server scheme. All platform specific functions (crypto, network...) have to be implemented in a client class (that derives from DRMProcessorClient) while server implements ADEPT protocol.
A reference implementation using cURL, OpenSSL and libzip is provided (in _utils_ directory).
Like RMSDK, libgourou has a client/server scheme. All platform specific functions (crypto, network...) has to be implemented in a client class (that derives from DRMProcessorClient) while server implements ADEPT protocol.
A reference implementation using Qt, OpenSSL and libzip is provided (in _utils_ directory).
Main functions to use from gourou::DRMProcessor are:
Main fucntions to use from gourou::DRMProcessor are :
* Get an ePub from an ACSM file : _fulfill()_ and _download()_
* Create a new device : _createDRMProcessor()_
* Register a new device : _signIn()_ and _activateDevice()_
* Remove DRM : _removeDRM()_
* Return loaned book : _returnLoan()_
You can import configuration from (at least) :
* Kobo device : .adept/device.xml, .adept/devicesalt and .adept/activation.xml
* Bookeen device : .adobe-digital-editions/device.xml, root/devkey.bin and .adobe-digital-editions/activation.xml
Or create a new one. Be careful: there is a limited number of devices that can be created by one account.
Or create a new one. Be careful : there is a limited number of devices that can be created bye one account.
ePub are encrypted using a shared key : one account / multiple devices, so you can create and register a device into your computer and read downloaded (and encrypted) ePub file with your eReader configured using the same AdobeID account.
For those who 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
------------
For libgourou :
_externals_ :
* libpugixml
_internals_:
* uPDFParser
* None
For utils :
* libcurl
* openssl
* QT5Core
* QT5Network
* OpenSSL
* libzip
* libpugixml
External & utils dependencies has to be installed by your package manager (_apt_ for example).
Use _-dev_ flavours to get needed headers.
Internal libraries are automatically fetched and statically compiled during the first compilation.
When you update libgourou's repository, **don't forget to update internal libraries** with:
make update_lib
Compilation
@ -64,78 +47,29 @@ Compilation
Use _make_ command
make [CROSS=XXX] [DEBUG=(0*|1)] [STATIC_UTILS=(0*|1)] [BUILD_UTILS=(0|1*)] [BUILD_STATIC=(0*|1)] [BUILD_SHARED=(0|1*)] [all*|clean|ultraclean|build_utils|install|uninstall]
make [CROSS=XXX] [DEBUG=1] [STATIC_UTILS=1]
CROSS can define a cross compiler prefix (ie arm-linux-gnueabihf-)
DEBUG can be set to compile in DEBUG mode
BUILD_UTILS to build utils or not
STATIC_UTILS to build utils with static library (libgourou.a) instead of default dynamic one (libgourou.so)
BUILD_STATIC build libgourou.a if 1, nothing if 0, can be combined with BUILD_SHARED
BUILD_SHARED build libgourou.so if 1, nothing if 0, can be combined with BUILD_STATIC
other variables are DESTDIR and PREFIX to handle destination install directory
* Default value
Utils
-----
First, add libgourou.so to your LD_LIBRARY_PATH
You can import configuration from your eReader or create a new one with utils/activate :
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
./utils/activate -u <AdobeID USERNAME>
You can optionaly specify your .adept directory
Then a _./.adept_ directory is created with all configuration file
export ADEPT_DIR=/home/XXX
To download an ePub :
Then, use utils as following:
You can import configuration from your eReader or create a new one with _utils/adept\_activate_:
./utils/adept_activate -u <AdobeID USERNAME>
Then a _/home/<user>/.config/adept_ directory is created with all configuration file
To download an ePub/PDF:
./utils/acsmdownloader <ACSM_FILE>
To export your private key (for DeDRM software):
./utils/acsmdownloader --export-private-key [-o adobekey_1.der]
To remove ADEPT DRM:
./utils/adept_remove <encryptedFile>
To list loaned books:
./utils/adept_loan_mgt [-l]
To return a loaned book:
./utils/adept_loan_mgt -r <id>
You can get utils full options description with -h or --help switch
Binary packages
---------------
Compiled version (and AppImage) of libgourou and utils are available in [Release page](https://forge.soutade.fr/soutade/libgourou/releases)
Docker
------
A docker image (by bcliang) is available at [https://github.com/bcliang/docker-libgourou/](https://github.com/bcliang/docker-libgourou/)
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
./utils/acsmdownloader -f <ACSM_FILE>
Copyright
@ -144,6 +78,7 @@ Copyright
Grégory Soutadé
License
-------
@ -152,24 +87,8 @@ libgourou : LGPL v3 or later
utils : BSD
Special thanks
--------------
* _Jens_ for all test samples and utils testing
* _Milian_ for debug & code
* _Berwyn H_ for all test samples, feedbacks, patches and kind donation
Donation
--------
https://www.paypal.com/donate/?hosted_button_id=JD3U6XMZCPHKN
Donators
--------
* _Berwyn H_
* _bwitt_
* _Ismail_
* _Radon_

68
README_package.md Normal file
View file

@ -0,0 +1,68 @@
Introduction
------------
libgourou is a free implementation of Adobe's ADEPT protocol used to add DRM on ePub files. It overcome the lacks of Adobe support for Linux platforms.
Dependencies
------------
For libgourou :
* None
For utils :
* QT5Core
* QT5Network
* OpenSSL
* libzip
Utils
-----
You can import configuration from your eReader or create a new one with activate :
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
./activate -u <AdobeID USERNAME>
Then a _./.adept_ directory is created with all configuration file
To download an ePub :
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
./acsmdownloader -f <ACSM_FILE>
Sources
-------
http://indefero.soutade.fr/p/libgourou
Copyright
---------
Grégory Soutadé
License
-------
libgourou : LGPL v3 or later
utils : BSD
Special thanks
--------------
* _Jens_ for all test samples and utils testing

View file

@ -1,133 +0,0 @@
#ifndef _MACARON_BASE64_H_
#define _MACARON_BASE64_H_
/**
* The MIT License (MIT)
* Copyright (c) 2016 tomykaira
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <string>
#include <stdint.h>
namespace macaron {
class Base64 {
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 "";
}
};
}
#endif /* _MACARON_BASE64_H_ */

View file

@ -32,7 +32,6 @@ namespace gourou
*
* Data handled is first copied in a newly allocated buffer
* and then shared between all copies until last object is destroyed
* (internal reference counter == 0)
*/
class ByteArray
{
@ -40,18 +39,8 @@ namespace gourou
/**
* @brief Create an empty byte array
*
* @param useMalloc If true, use malloc() instead of new[] for allocation
*/
ByteArray(bool useMalloc=false);
/**
* @brief Create an empty byte array of length bytes
*
* @param length Length of data
* @param useMalloc If true, use malloc() instead of new[] for allocation
*/
ByteArray(unsigned int length, bool useMalloc=false);
ByteArray();
/**
* @brief Initialize ByteArray with a copy of data
@ -104,13 +93,6 @@ namespace gourou
*/
std::string toBase64();
/**
* @brief Convert hex string into bytes
*
* @param str Hex string
*/
static ByteArray fromHex(const std::string& str);
/**
* @brief Return a string with human readable hex encoded internal data
*/
@ -137,38 +119,14 @@ namespace gourou
void append(const std::string& str);
/**
* @brief Get internal data. Must not be freed
* @brief Get internal data. Must bot be modified nor freed
*/
unsigned char* data() {return _data;}
/**
* @brief Get internal data and increment internal reference counter.
* Must bot be freed
*/
unsigned char* takeShadowData() {addRef() ; return _data;}
/**
* @brief Release shadow data. It can now be freed by ByteArray
*/
void releaseShadowData() {delRef();}
const unsigned char* data() {return _data;}
/**
* @brief Get internal data length
*/
unsigned int length() const {return _length;}
/**
* @brief Get internal data length
*/
unsigned int size() const {return length();}
/**
* @brief Increase or decrease internal buffer
* @param length New length of internal buffer
* @param keepData If true copy old data on new buffer, if false,
* create a new buffer with random data
*/
void resize(unsigned int length, bool keepData=true);
unsigned int length() {return _length;}
ByteArray& operator=(const ByteArray& other);
@ -177,10 +135,9 @@ namespace gourou
void addRef();
void delRef();
bool _useMalloc;
unsigned char* _data;
const unsigned char* _data;
unsigned int _length;
static std::map<unsigned char*, int> refCounter;
static std::map<const unsigned char*, int> refCounter;
};
}
#endif

View file

@ -21,7 +21,6 @@
#define _DRMPROCESSORCLIENT_H_
#include <string>
#include <bytearray.h>
namespace gourou
{
@ -35,7 +34,7 @@ namespace gourou
{
public:
/**
* @brief Create a digest handler
* @brief Create a digest handler (for now only SHA1 is used)
*
* @param digestName Digest name to instanciate
*/
@ -47,16 +46,20 @@ namespace gourou
* @param handler Digest handler
* @param data Data to digest
* @param length Length of data
*
* @return OK/KO
*/
virtual void digestUpdate(void* handler, unsigned char* data, unsigned int length) = 0;
virtual int digestUpdate(void* handler, unsigned char* data, unsigned int length) = 0;
/**
* @brief Finalize digest with remained buffered data and destroy handler
*
* @param handler Digest handler
* @param digestOut Digest result (buffer must be pre allocated with right size)
*
* @return OK/KO
*/
virtual void digestFinalize(void* handler, unsigned char* digestOut) = 0;
virtual int digestFinalize(void* handler, unsigned char* digestOut) = 0;
/**
* @brief Global digest function
@ -65,8 +68,10 @@ namespace gourou
* @param data Data to digest
* @param length Length of data
* @param digestOut Digest result (buffer must be pre allocated with right size)
*
* @return OK/KO
*/
virtual void digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut) = 0;
virtual int digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut) = 0;
};
class RandomInterface
@ -91,13 +96,8 @@ namespace gourou
* @param URL HTTP URL
* @param POSTData POST data if needed, if not set, a GET request is done
* @param contentType Optional content type of POST Data
* @param responseHeaders Optional Response headers of HTTP request
* @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)
*
* @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("")) = 0;
};
class RSAInterface
@ -105,7 +105,6 @@ namespace gourou
public:
enum RSA_KEY_TYPE {
RSA_KEY_PKCS12 = 0,
RSA_KEY_PKCS8,
RSA_KEY_X509
};
@ -125,22 +124,6 @@ namespace gourou
const unsigned char* data, unsigned dataLength,
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
*
@ -209,20 +192,14 @@ namespace gourou
class CryptoInterface
{
public:
enum CRYPTO_ALGO {
ALGO_AES=0,
ALGO_RC4
};
enum CHAINING_MODE {
CHAIN_ECB=0,
CHAIN_CBC
};
/**
* @brief Do encryption. If length of data is not multiple of block size, PKCS#5 padding is done
* @brief Do AES encryption. If length of data is not multiple of 16, PKCS#5 padding is done
*
* @param algo Algorithm to use
* @param chaining Chaining mode
* @param key AES key
* @param keyLength AES key length
@ -233,53 +210,52 @@ namespace gourou
* @param dataOut Encrypted data
* @param dataOutLength Length of encrypted data
*/
virtual void encrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining,
virtual void AESEncrypt(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 Init AES CBC encryption
*
* @param chaining Chaining mode
* @param key Key
* @param keyLength Key length
* @param iv Optional IV key
* @param ivLength Optional IV key length
* @param key AES key
* @param keyLength AES key length
* @param iv IV key
* @param ivLength IV key length
*
* @return AES handler
*/
virtual void* encryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining,
virtual void* AESEncryptInit(CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength,
const unsigned char* iv=0, unsigned int ivLength=0) = 0;
/**
* @brief Encrypt data
*
* @param handler Crypto handler
* @param handler AES handler
* @param dataIn Data to encrypt
* @param dataInLength Data length
* @param dataOut Encrypted data
* @param dataOutLength Length of encrypted data
*/
virtual void encryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength,
virtual void AESEncryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength) = 0;
/**
* @brief Finalize encryption (pad and encrypt last block if needed)
* @brief Finalize AES encryption (pad and encrypt last block if needed)
* Destroy handler at the end
*
* @param handler Crypto handler
* @param handler AES handler
* @param dataOut Last block of encrypted data
* @param dataOutLength Length of encrypted data
*/
virtual void encryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength) = 0;
virtual void AESEncryptFinalize(void* handler, 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 Do AES decryption. If length of data is not multiple of 16, PKCS#5 padding is done
*
* @param algo Algorithm to use
* @param chaining Chaining mode
* @param key AES key
* @param keyLength AES key length
@ -290,47 +266,47 @@ namespace gourou
* @param dataOut Encrypted data
* @param dataOutLength Length of encrypted data
*/
virtual void decrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining,
virtual void AESDecrypt(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 Init AES decryption
*
* @param chaining Chaining mode
* @param key Key
* @param keyLength Key length
* @param key AES key
* @param keyLength AES key length
* @param iv IV key
* @param ivLength IV key length
*
* @return AES handler
*/
virtual void* decryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining,
virtual void* AESDecryptInit(CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength,
const unsigned char* iv=0, unsigned int ivLength=0) = 0;
/**
* @brief Decrypt data
*
* @param handler Crypto handler
* @param handler AES handler
* @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,
virtual void AESDecryptUpdate(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).
* @brief Finalize AES decryption (decrypt last block and remove padding if it is set).
* Destroy handler at the end
*
* @param handler Crypto handler
* @param handler AES 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;
virtual void AESDecryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength) = 0;
};
@ -351,19 +327,19 @@ namespace gourou
*
* @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
*
* @return File content
*/
virtual void zipReadFile(void* handler, const std::string& path, ByteArray& result, bool decompress=true) = 0;
virtual std::string zipReadFile(void* handler, const std::string& path) = 0;
/**
* @brief Write zip internal file
*
* @param handler ZIP file handler
* @param path Internal path inside zip file
* @param content File content
* @param content Internal file content
*/
virtual void zipWriteFile(void* handler, const std::string& path, ByteArray& content) = 0;
virtual void zipWriteFile(void* handler, const std::string& path, const std::string& content) = 0;
/**
* @brief Delete zip internal file
@ -379,27 +355,6 @@ namespace gourou
* @param handler ZIP file handler
*/
virtual void zipClose(void* handler) = 0;
/**
* @brief Inflate algorithm
*
* @param data Data to inflate
* @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, \

View file

@ -20,7 +20,7 @@
#ifndef _FULFILLMENT_ITEM_H_
#define _FULFILLMENT_ITEM_H_
#include "loan_token.h"
#include "bytearray.h"
#include <pugixml.hpp>
@ -34,16 +34,8 @@ namespace gourou
class FulfillmentItem
{
public:
/**
* @brief Main constructor. Not to be called by user
*
* @param doc Fulfill reply
* @param user User pointer
*/
FulfillmentItem(pugi::xml_document& doc, User* user);
~FulfillmentItem();
/**
* @brief Return metadata value from ACSM metadata section
*
@ -61,23 +53,10 @@ namespace gourou
*/
std::string getDownloadURL();
/**
* @brief Return resource value
*/
std::string getResource();
/**
* @brief Return loan token if there is one
*/
LoanToken* getLoanToken();
private:
pugi::xml_document fulfillDoc;
pugi::xml_node metadatas;
pugi::xml_document rights;
std::string downloadURL;
std::string resource;
LoanToken* loanToken;
void buildRights(const pugi::xml_node& licenseToken, User* user);
};

View file

@ -27,17 +27,20 @@
#include "drmprocessorclient.h"
#include <pugixml.hpp>
#include <stdint.h>
#ifndef HOBBES_DEFAULT_VERSION
#define HOBBES_DEFAULT_VERSION "10.0.4"
#endif
#ifndef DEFAULT_ADEPT_DIR
#define DEFAULT_ADEPT_DIR "./.adept"
#endif
#ifndef ACS_SERVER
#define ACS_SERVER "http://adeactivate.adobe.com/adept"
#endif
#define LIBGOUROU_VERSION "0.8.8"
#define LIBGOUROU_VERSION "0.3.2"
namespace gourou
{
@ -50,7 +53,6 @@ namespace gourou
static const std::string VERSION;
enum ITEM_TYPE { EPUB=0, PDF };
/**
* @brief Main constructor. To be used once all is configured (user has signedIn, device is activated)
*
@ -67,11 +69,10 @@ namespace gourou
* @brief Fulfill ACSM file to server in order to retrieve ePub fulfillment item
*
* @param ACSMFile Path of ACSMFile
* @param notify Notify server if requested by response
*
* @return a FulfillmentItem if all is OK
*/
FulfillmentItem* fulfill(const std::string& ACSMFile, bool notify=true);
FulfillmentItem* fulfill(const std::string& ACSMFile);
/**
* @brief Once fulfilled, ePub file needs to be downloaded.
@ -79,11 +80,8 @@ namespace gourou
*
* @param item Item from fulfill() method
* @param path Output file path
* @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);
void download(FulfillmentItem* item, std::string path);
/**
* @brief SignIn into ACS Server (required to activate device)
@ -98,20 +96,6 @@ namespace gourou
*/
void activateDevice();
/**
* @brief Return loaned book to server
*
* @param loanID Loan ID received during fulfill
* @param operatorURL URL of operator that loans this book
* @param notify Notify server if requested by response
*/
void returnLoan(const std::string& loanID, const std::string& operatorURL, bool notify=true);
/**
* @brief Return default ADEPT directory (ie /home/<user>/.config/adept)
*/
static std::string getDefaultAdeptDir(void);
/**
* @brief Create a new ADEPT environment (device.xml, devicesalt and activation.xml).
*
@ -122,7 +106,7 @@ namespace gourou
* @param ACSServer Override main ACS server (default adeactivate.adobe.com)
*/
static DRMProcessor* createDRMProcessor(DRMProcessorClient* client,
bool randomSerial=false, std::string dirName=std::string(""),
bool randomSerial=false, const std::string& dirName=std::string(DEFAULT_ADEPT_DIR),
const std::string& hobbes=std::string(HOBBES_DEFAULT_VERSION),
const std::string& ACSServer=ACS_SERVER);
@ -146,13 +130,8 @@ namespace gourou
* @param URL HTTP URL
* @param POSTData POST data if needed, if not set, a GET request is done
* @param contentType Optional content type of POST Data
* @param responseHeaders Optional Response headers of HTTP request
* @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)
*
* @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);
/**
* @brief Send HTTP POST request to URL with document as POSTData
@ -179,11 +158,6 @@ namespace gourou
*/
std::string serializeRSAPrivateKey(void* rsa);
/**
* @brief Export clear private license key into path
*/
void exportPrivateLicenseKey(std::string path);
/**
* @brief Get current user
*/
@ -199,18 +173,6 @@ namespace gourou
*/
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:
gourou::DRMProcessorClient* client;
gourou::Device* device;
@ -222,7 +184,7 @@ namespace gourou
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, unsigned char* sha_out);
void signNode(pugi::xml_node& rootNode);
std::string signNode(const pugi::xml_node& rootNode);
void addNonce(pugi::xml_node& root);
void buildAuthRequest(pugi::xml_document& authReq);
void buildInitLicenseServiceRequest(pugi::xml_document& initLicReq, std::string operatorURL);
@ -230,22 +192,8 @@ namespace gourou
void operatorAuth(std::string operatorURL);
void buildFulfillRequest(pugi::xml_document& acsmDoc, pugi::xml_document& fulfillReq);
void buildActivateReq(pugi::xml_document& activateReq);
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);
void buildSignInRequest(pugi::xml_document& signInRequest, const std::string& adobeID, const std::string& adobePassword, const std::string& authenticationCertificate);
void fetchLicenseServiceCertificate(const std::string& licenseURL,
const std::string& operatorURL);
void buildNotifyReq(pugi::xml_document& returnReq, pugi::xml_node& body);
void notifyServer(pugi::xml_node& notifyRoot);
void notifyServer(pugi::xml_document& fulfillReply);
std::string encryptedKeyFirstPass(pugi::xml_document& rightsDoc, const std::string& encryptedKey, const std::string& keyType);
void decryptADEPTKey(pugi::xml_document& rightsDoc, unsigned char* decryptedKey, const unsigned char* encryptionKey=0, unsigned encryptionKeySize=0);
void removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut, const unsigned char* encryptionKey, unsigned encryptionKeySize);
void generatePDFObjectKey(int version,
const unsigned char* masterKey, unsigned int masterKeyLength,
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

@ -55,8 +55,7 @@ namespace gourou
GOUROU_INVALID_CLIENT,
GOUROU_TAG_NOT_FOUND,
GOUROU_ADEPT_ERROR,
GOUROU_FILE_ERROR,
GOUROU_INVALID_PROPERTY
GOUROU_FILE_ERROR
};
enum FULFILL_ERROR {
@ -64,13 +63,11 @@ namespace gourou
FF_INVALID_ACSM_FILE,
FF_NO_HMAC_IN_ACSM_FILE,
FF_NOT_ACTIVATED,
FF_NO_OPERATOR_URL,
FF_SERVER_INTERNAL_ERROR
FF_NO_OPERATOR_URL
};
enum DOWNLOAD_ERROR {
DW_NO_ITEM = 0x1200,
DW_NO_EBX_HANDLER,
};
enum SIGNIN_ERROR {
@ -94,12 +91,10 @@ namespace gourou
USER_INVALID_ACTIVATION_FILE,
USER_NO_AUTHENTICATION_URL,
USER_NO_PROPERTY,
USER_INVALID_INPUT,
};
enum FULFILL_ITEM_ERROR {
FFI_INVALID_FULFILLMENT_DATA = 0x4000,
FFI_INVALID_LOAN_TOKEN
FFI_INVALID_FULFILLMENT_DATA = 0x4000
};
enum CLIENT_ERROR {
@ -107,7 +102,6 @@ namespace gourou
CLIENT_INVALID_PKCS12,
CLIENT_INVALID_CERTIFICATE,
CLIENT_NO_PRIV_KEY,
CLIENT_NO_PUB_KEY,
CLIENT_RSA_ERROR,
CLIENT_BAD_CHAINING,
CLIENT_BAD_KEY_SIZE,
@ -115,36 +109,8 @@ namespace gourou
CLIENT_ZIP_ERROR,
CLIENT_GENERIC_EXCEPTION,
CLIENT_NETWORK_ERROR,
CLIENT_INVALID_PKCS8,
CLIENT_FILE_ERROR,
CLIENT_OSSL_ERROR,
CLIENT_CRYPT_ERROR,
CLIENT_DIGEST_ERROR,
CLIENT_HTTP_ERROR
};
enum DRM_REMOVAL_ERROR {
DRM_ERR_ENCRYPTION_KEY = 0x6000,
DRM_VERSION_NOT_SUPPORTED,
DRM_FILE_ERROR,
DRM_FORMAT_NOT_SUPPORTED,
DRM_IN_OUT_EQUALS,
DRM_MISSING_PARAMETER,
DRM_INVALID_KEY_SIZE,
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
*/
@ -157,7 +123,7 @@ namespace gourou
std::stringstream msg;
msg << "Exception code : 0x" << std::setbase(16) << code << std::endl;
msg << "Message : " << message << std::endl;
if (logLevel >= LG_LOG_DEBUG)
if (logLevel >= DEBUG)
msg << "File : " << file << ":" << std::setbase(10) << line << std::endl;
fullmessage = strdup(msg.str().c_str());
}
@ -165,12 +131,12 @@ namespace gourou
Exception(const Exception& other)
{
this->code = other.code;
this->line = other.line;
this->file = other.file;
this->line = line;
this->file = file;
this->fullmessage = strdup(other.fullmessage);
}
~Exception() _NOEXCEPT
~Exception()
{
free(fullmessage);
}
@ -181,7 +147,7 @@ namespace gourou
private:
int code, line;
const char* file;
const char* message, *file;
char* fullmessage;
};
@ -236,32 +202,24 @@ namespace gourou
return ltrim(rtrim(s, t), t);
}
static inline pugi::xml_node getNode(const pugi::xml_node& root, const char* tagName, bool throwOnNull=true)
/**
* @brief Extract text node from tag in document
* It can throw an exception if tag does not exists
* or just return an empty value
*/
static inline std::string extractTextElem(const pugi::xml_document& doc, const char* tagName, bool throwOnNull=true)
{
pugi::xpath_node xpath_node = root.select_node(tagName);
pugi::xpath_node xpath_node = doc.select_node(tagName);
if (!xpath_node)
{
if (throwOnNull)
EXCEPTION(GOUROU_TAG_NOT_FOUND, "Tag " << tagName << " not found");
return pugi::xml_node();
return "";
}
return xpath_node.node();
}
/**
* @brief Extract text node from tag in document
* It can throw an exception if tag does not exists
* or just return an empty value
*/
static inline std::string extractTextElem(const pugi::xml_node& root, const char* tagName, bool throwOnNull=true)
{
pugi::xml_node node = getNode(root, tagName, throwOnNull);
node = node.first_child();
pugi::xml_node node = xpath_node.node().first_child();
if (!node)
{
@ -275,53 +233,6 @@ namespace gourou
return trim(res);
}
/**
* @brief Set text node of a tag in document
* It can throw an exception if tag does not exists
*/
static inline void setTextElem(const pugi::xml_node& root, const char* tagName,
const std::string& value, bool throwOnNull=true)
{
pugi::xml_node node = getNode(root, tagName, throwOnNull);
if (!node)
{
if (throwOnNull)
EXCEPTION(GOUROU_TAG_NOT_FOUND, "Text element for tag " << tagName << " not found");
return;
}
node = node.first_child();
if (!node)
node.append_child(pugi::node_pcdata).set_value(value.c_str());
else
node.set_value(value.c_str());
}
/**
* @brief Extract text attribute from tag in document
* It can throw an exception if attribute does not exists
* or just return an empty value
*/
static inline std::string extractTextAttribute(const pugi::xml_node& root, const char* tagName, const char* attributeName, bool throwOnNull=true)
{
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");
return "";
}
std::string res = attr.value();
return trim(res);
}
/**
* @brief Append an element to root with a sub text element
*
@ -335,56 +246,15 @@ namespace gourou
node.append_child(pugi::node_pcdata).set_value(value.c_str());
}
/**
* Remove "urn:uuid:" prefix and all '-' from uuid
* urn:uuid:9cb786e8-586a-4950-8901-fff8d2ee6025
* ->
* 9cb786e8586a49508901fff8d2ee6025
*/
static inline std::string extractIdFromUUID(const std::string& uuid)
{
unsigned int i = 0;
std::string res;
if (uuid.find("urn:uuid:") == 0)
i = 9;
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
*
* @return Created fd, must be closed
*/
static inline int createNewFile(std::string path, bool truncate=true)
{
int options = O_CREAT|O_WRONLY;
if (truncate)
options |= O_TRUNC;
else
options |= O_APPEND;
int fd = open(path.c_str(), options, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH);
if (fd <= 0)
EXCEPTION(GOUROU_FILE_ERROR, "Unable to create " << path);
return fd;
}
/**
* @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)
{
int fd = createNewFile(path);
int fd = open(path.c_str(), O_CREAT|O_WRONLY|O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH);
if (fd <= 0)
EXCEPTION(GOUROU_FILE_ERROR, "Unable to create " << path);
if (write(fd, data, length) != length)
EXCEPTION(GOUROU_FILE_ERROR, "Write error for file " << path);
@ -483,20 +353,6 @@ namespace gourou
}
return 0;
}
static inline void dumpBuffer(GOUROU_LOG_LEVEL level, const char* title, const unsigned char* data, unsigned int len)
{
if (gourou::logLevel < level)
return;
printf("%s", title);
for(unsigned int i=0; i<len; i++)
{
if (i && !(i%16)) printf("\n");
printf("%02x ", data[i]);
}
printf("\n");
}
}
#endif

View file

@ -24,16 +24,16 @@
namespace gourou {
enum GOUROU_LOG_LEVEL {
LG_LOG_ERROR,
LG_LOG_WARN,
LG_LOG_INFO,
LG_LOG_DEBUG,
LG_LOG_TRACE
ERROR,
WARN,
INFO,
DEBUG,
TRACE
};
extern GOUROU_LOG_LEVEL logLevel;
#define GOUROU_LOG(__lvl, __msg) if (gourou::LG_LOG_##__lvl <= gourou::logLevel) {std::cout << __msg << std::endl << std::flush;}
#define GOUROU_LOG(__lvl, __msg) if (__lvl <= gourou::logLevel) {std::cout << __msg << std::endl << std::flush;}
#define GOUROU_LOG_FUNC() GOUROU_LOG(TRACE, __FUNCTION__ << "() @ " << __FILE__ << ":" << __LINE__)
/**

View file

@ -1,54 +0,0 @@
/*
Copyright 2022 Grégory Soutadé
This file is part of libgourou.
libgourou is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
libgourou is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with libgourou. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _LOAN_TOKEN_H_
#define _LOAN_TOKEN_H_
#include <map>
#include <pugixml.hpp>
namespace gourou
{
/**
* @brief This class is a container for a fulfillment object
*/
class LoanToken
{
public:
/**
* @brief Main constructor. Not to be called by user
*
* @param doc Fulfill reply
*/
LoanToken(pugi::xml_document& doc);
/**
* @brief Get a property (id, operatorURL, validity)
*/
std::string getProperty(const std::string& property, const std::string& _default=std::string(""));
std::string operator[](const std::string& property);
private:
std::map<std::string, std::string> properties;
};
}
#endif

View file

@ -21,8 +21,6 @@
#define _USER_H_
#include <string>
#include <map>
#include "bytearray.h"
#include <pugixml.hpp>
@ -48,7 +46,7 @@ namespace gourou
std::string& getDeviceFingerprint();
std::string& getUsername();
std::string& getLoginMethod();
std::string getLicenseServiceCertificate(std::string url);
std::string& getCertificate();
std::string& getAuthenticationCertificate();
std::string& getPrivateLicenseKey();
@ -97,7 +95,7 @@ namespace gourou
std::string deviceFingerprint;
std::string username;
std::string loginMethod;
std::map<std::string,std::string> licenseServiceCertificates;
std::string certificate;
std::string authenticationCertificate;
std::string privateLicenseKey;

View file

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

View file

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

View file

@ -17,56 +17,40 @@
along with libgourou. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include <stdexcept>
#include <Base64.h>
#include <base64/Base64.h>
#include <bytearray.h>
namespace gourou
{
std::map<unsigned char*, int> ByteArray::refCounter;
std::map<const unsigned char*, int> ByteArray::refCounter;
ByteArray::ByteArray(bool useMalloc):_useMalloc(useMalloc), _data(0), _length(0)
ByteArray::ByteArray():_data(0), _length(0)
{}
ByteArray::ByteArray(unsigned int length, bool useMalloc):
_useMalloc(useMalloc)
{
initData(0, length);
}
ByteArray::ByteArray(const unsigned char* data, unsigned int length):
_useMalloc(false)
ByteArray::ByteArray(const unsigned char* data, unsigned int length)
{
initData(data, length);
}
ByteArray::ByteArray(const char* data, int length):
_useMalloc(false)
ByteArray::ByteArray(const char* data, int length)
{
if (length == -1)
length = strlen(data);
length = strlen(data) + 1;
initData((unsigned char*)data, (unsigned int) length);
initData((const unsigned char*)data, (unsigned int) length);
}
ByteArray::ByteArray(const std::string& str):
_useMalloc(false)
ByteArray::ByteArray(const std::string& str)
{
initData((unsigned char*)str.c_str(), (unsigned int)str.length());
initData((unsigned char*)str.c_str(), (unsigned int)str.length() + 1);
}
void ByteArray::initData(const unsigned char* data, unsigned int length)
{
if (_useMalloc)
_data = (unsigned char*)malloc(length);
else
_data = new unsigned char[length];
if (data)
memcpy((void*)_data, data, length);
_length = length;
addRef();
@ -74,7 +58,6 @@ namespace gourou
ByteArray::ByteArray(const ByteArray& other)
{
this->_useMalloc = other._useMalloc;
this->_data = other._data;
this->_length = other._length;
@ -85,7 +68,6 @@ namespace gourou
{
delRef();
this->_useMalloc = other._useMalloc;
this->_data = other._data;
this->_length = other._length;
@ -115,9 +97,6 @@ namespace gourou
if (refCounter[_data] == 1)
{
if (_useMalloc)
free(_data);
else
delete[] _data;
refCounter.erase(_data);
}
@ -156,53 +135,12 @@ namespace gourou
return macaron::Base64::Encode(std::string((char*)_data, _length));
}
ByteArray ByteArray::fromHex(const std::string& str)
{
if (str.size() % 2)
throw std::invalid_argument("Size of hex string not multiple of 2");
ByteArray res((unsigned int)(str.size()/2));
unsigned int i;
unsigned char* data = res.data();
unsigned char cur, tmp;
for (i=0; i<str.size(); i+=2)
{
cur = 0;
tmp = str[i];
if (tmp >= 'a' && tmp <= 'f')
cur = (tmp - 'a' + 10) << 4;
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];
if (tmp >= 'a' && tmp <= 'f')
cur += tmp - 'a' + 10;
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()
{
char* tmp = new char[_length*2+1];
for(int i=0; i<(int)_length; i++)
snprintf(&tmp[i*2], (_length-i)*2+1, "%02x", _data[i]);
sprintf(&tmp[i*2], "%02x", _data[i]);
tmp[_length*2] = 0;
@ -214,44 +152,22 @@ namespace gourou
void ByteArray::append(const unsigned char* data, unsigned int length)
{
if (!length)
return;
const unsigned char* oldData = _data;
unsigned char* newData = new unsigned char[_length+length];
unsigned int oldLength = _length;
memcpy(newData, oldData, _length);
resize(_length+length, true);
delRef();
memcpy(&_data[oldLength], data, length);
memcpy(&newData[_length], data, length);
_length += length;
_data = newData;
addRef();
}
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 std::string& str) { append((const unsigned char*)str.c_str(), str.length()); }
void ByteArray::resize(unsigned length, bool keepData)
{
if (length == _length)
return;
else if (length < _length)
_length = length ; // Don't touch data
else // New size >
{
unsigned char* newData;
if (_useMalloc)
newData = (unsigned char*)malloc(_length+length);
else
newData = new unsigned char[_length+length];
if (keepData)
memcpy(newData, _data, _length);
delRef();
_length = length;
_data = newData;
addRef();
}
}
}

View file

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

View file

@ -17,7 +17,6 @@
along with libgourou. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cctype>
#include <fulfillment_item.h>
#include <libgourou_common.h>
#include "user.h"
@ -25,10 +24,8 @@
namespace gourou
{
FulfillmentItem::FulfillmentItem(pugi::xml_document& doc, User* user)
: fulfillDoc(), loanToken(0)
{
fulfillDoc.reset(doc); /* We must keep a copy */
metadatas = fulfillDoc.select_node("//metadata").node();
metadatas = doc.select_node("//metadata").node();
if (!metadatas)
EXCEPTION(FFI_INVALID_FULFILLMENT_DATA, "No metadata tag in document");
@ -39,35 +36,12 @@ namespace gourou
if (downloadURL == "")
EXCEPTION(FFI_INVALID_FULFILLMENT_DATA, "No download URL in document");
node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/resource").node();
resource = node.first_child().value();
if (resource == "")
EXCEPTION(FFI_INVALID_FULFILLMENT_DATA, "No resource in document");
pugi::xml_node licenseToken = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken").node();
if (!licenseToken)
EXCEPTION(FFI_INVALID_FULFILLMENT_DATA, "Any license token in document");
buildRights(licenseToken, user);
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()
{
if (loanToken) delete loanToken;
}
void FulfillmentItem::buildRights(const pugi::xml_node& licenseToken, User* user)
@ -82,24 +56,18 @@ namespace gourou
if (!newLicenseToken.attribute("xmlns"))
newLicenseToken.append_attribute("xmlns") = ADOBE_ADEPT_NS;
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());
pugi::xml_node licenseServiceInfo = root.append_child("licenseServiceInfo");
licenseServiceInfo.append_attribute("xmlns") = ADOBE_ADEPT_NS;
licenseServiceInfo.append_copy(licenseToken.select_node("licenseURL").node());
pugi::xml_node certificate = licenseServiceInfo.append_child("certificate");
certificate.append_child(pugi::node_pcdata).set_value(user->getCertificate().c_str());
}
std::string FulfillmentItem::getMetadata(std::string name)
{
// https://stackoverflow.com/questions/313970/how-to-convert-an-instance-of-stdstring-to-lower-case
#if __STDC_VERSION__ >= 201112L
std::transform(name.begin(), name.end(), name.begin(),
[](unsigned char c){ return std::tolower(c); });
#else
std::transform(name.begin(), name.end(), name.begin(), tolower);
#endif
name = std::string("dc:") + name;
pugi::xpath_node path = metadatas.select_node(name.c_str());
@ -120,14 +88,4 @@ namespace gourou
{
return downloadURL;
}
std::string FulfillmentItem::getResource()
{
return resource;
}
LoanToken* FulfillmentItem::getLoanToken()
{
return loanToken;
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,80 +0,0 @@
/*
Copyright 2022 Grégory Soutadé
This file is part of libgourou.
libgourou is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
libgourou is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with libgourou. If not, see <http://www.gnu.org/licenses/>.
*/
#include "libgourou_common.h"
#include "loan_token.h"
namespace gourou
{
LoanToken::LoanToken(pugi::xml_document& doc)
{
pugi::xml_node node = doc.select_node("/envelope/loanToken").node();
if (!node)
EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken element in document");
node = doc.select_node("/envelope/fulfillmentResult/fulfillment").node();
if (node)
properties["id"] = node.first_child().value();
else
{
EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No fulfillment element in document");
}
node = doc.select_node("/envelope/loanToken/operatorURL").node();
if (!node)
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();
if (node)
properties["validity"] = node.first_child().value();
else
{
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)
{
if (properties.find(property) == properties.end())
{
if (_default == "")
EXCEPTION(GOUROU_INVALID_PROPERTY, "Invalid property " << property);
return _default;
}
return properties[property];
}
std::string LoanToken::operator[](const std::string& property)
{
return getProperty(property);
}
}

1
src/pugixml.cpp Symbolic link
View file

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

View file

@ -48,8 +48,10 @@ namespace gourou {
uuid = gourou::extractTextElem(activationDoc, "//adept:user", throwOnNull);
deviceUUID = gourou::extractTextElem(activationDoc, "//device", throwOnNull);
deviceFingerprint = gourou::extractTextElem(activationDoc, "//fingerprint", throwOnNull);
certificate = gourou::extractTextElem(activationDoc, "//adept:certificate", throwOnNull);
authenticationCertificate = gourou::extractTextElem(activationDoc, "//adept:authenticationCertificate", throwOnNull);
privateLicenseKey = gourou::extractTextElem(activationDoc, "//adept:privateLicenseKey", throwOnNull);
username = gourou::extractTextElem(activationDoc, "//adept:username", throwOnNull);
pugi::xpath_node xpath_node = activationDoc.select_node("//adept:username");
if (xpath_node)
@ -59,20 +61,6 @@ namespace gourou {
if (throwOnNull)
EXCEPTION(USER_INVALID_ACTIVATION_FILE, "Invalid activation file");
}
if (loginMethod == "anonymous")
username = "anonymous";
else
username = gourou::extractTextElem(activationDoc, "//adept:username", throwOnNull);
pugi::xpath_node_set nodeSet = activationDoc.select_nodes("//adept:licenseServices/adept:licenseServiceInfo");
for (pugi::xpath_node_set::const_iterator it = nodeSet.begin();
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)
{
@ -86,6 +74,7 @@ namespace gourou {
std::string& User::getDeviceFingerprint() { return deviceFingerprint; }
std::string& User::getUsername() { return username; }
std::string& User::getLoginMethod() { return loginMethod; }
std::string& User::getCertificate() { return certificate; }
std::string& User::getAuthenticationCertificate() { return authenticationCertificate; }
std::string& User::getPrivateLicenseKey() { return privateLicenseKey; }
@ -211,13 +200,4 @@ namespace gourou {
return user;
}
std::string User::getLicenseServiceCertificate(std::string url)
{
if (licenseServiceCertificates.count(trim(url)))
return licenseServiceCertificates[trim(url)];
return "";
}
}

View file

@ -1,57 +1,28 @@
BINDIR ?= /bin
MANDIR ?= /share/man
TARGET_BINARIES=acsmdownloader adept_activate adept_remove adept_loan_mgt
TARGETS=$(TARGET_BINARIES) launcher
MAN_PAGES=acsmdownloader adept_activate adept_remove adept_loan_mgt
CXXFLAGS=-Wall -fPIC -I$(ROOT)/include -fmacro-prefix-map=$(ROOT)/= -fdata-sections -ffunction-sections
STATIC_DEP=
# LDFLAGS += -Wl,--gc-sections
LDFLAGS += -L$(ROOT) -lcrypto -lzip -lz -lcurl -lpugixml
TARGETS=acsmdownloader adept_activate
CXXFLAGS=-Wall `pkg-config --cflags Qt5Core Qt5Network` -fPIC -I$(ROOT)/include -I$(ROOT)/lib/pugixml/src/
ifneq ($(STATIC_UTILS),)
STATIC_DEP = $(ROOT)/libgourou.a
LDFLAGS=`pkg-config --libs Qt5Core Qt5Network` -L$(ROOT) $(ROOT)/libgourou.a -lcrypto -lzip
else
LDFLAGS += -lgourou
LDFLAGS=`pkg-config --libs Qt5Core Qt5Network` -L$(ROOT) -lgourou -lcrypto -lzip
endif
ifneq ($(DEBUG),)
CXXFLAGS += -ggdb -O0 -DDEBUG
CXXFLAGS += -ggdb -O0
else
CXXFLAGS += -O2
endif
COMMON_DEPS = drmprocessorclientimpl.cpp utils_common.cpp
COMMON_OBJECTS = $(COMMON_DEPS:.cpp=.o)
COMMON_LIB = utils.a
all: $(TARGETS)
${COMMON_LIB}: $(COMMON_DEPS)
$(CXX) $(CXXFLAGS) $(COMMON_DEPS) -c
$(AR) crs $@ $(COMMON_OBJECTS)
acsmdownloader: drmprocessorclientimpl.cpp acsmdownloader.cpp
$(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@
%: %.cpp $(COMMON_LIB) $(STATIC_DEP)
$(CXX) $(CXXFLAGS) $^ $(STATIC_DEP) $(LDFLAGS) -o $@
install: $(TARGET_BINARIES)
install -d $(DESTDIR)$(PREFIX)/$(BINDIR)
install -m 755 $(TARGET_BINARIES) $(DESTDIR)$(PREFIX)/$(BINDIR)
install -d $(DESTDIR)$(PREFIX)/$(MANDIR)/man1
install -m 644 man/*.1 $(DESTDIR)$(PREFIX)/$(MANDIR)/man1
uninstall:
cd $(DESTDIR)$(PREFIX)/$(BINDIR)
rm -f $(TARGET_BINARIES)
cd -
cd $(DESTDIR)$(PREFIX)/$(MANDIR)/man1
rm -f $(addsuffix .1,$(TARGET_BINARIES)
adept_activate: drmprocessorclientimpl.cpp adept_activate.cpp
$(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@
clean:
rm -f $(TARGETS) $(COMMON_LIB)
rm -f $(TARGETS)
ultraclean: clean

View file

@ -26,184 +26,133 @@
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <unistd.h>
#include <getopt.h>
#include <libgen.h>
#include <iostream>
#include <algorithm>
#include <QFile>
#include <QDir>
#include <QCoreApplication>
#include <QRunnable>
#include <QThreadPool>
#include <libgourou.h>
#include <libgourou_common.h>
#include "drmprocessorclientimpl.h"
#include "utils_common.h"
#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
static const char* deviceFile = "device.xml";
static const char* activationFile = "activation.xml";
static const char* devicekeyFile = "devicesalt";
static const char* acsmFile = 0;
static bool exportPrivateKey = false;
static const char* outputFile = 0;
static const char* outputDir = 0;
static bool resume = false;
static bool notify = true;
static const char* defaultDirs[] = {
".adept/",
"./adobe-digital-editions/",
"./.adobe-digital-editions/"
};
class ACSMDownloader
class ACSMDownloader: public QRunnable
{
public:
ACSMDownloader(QCoreApplication* app):
app(app)
{
setAutoDelete(false);
}
int run()
void run()
{
int ret = 0;
try
{
DRMProcessorClientImpl client;
gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile);
gourou::User* user = processor.getUser();
if (exportPrivateKey)
{
std::string filename;
if (outputFile)
filename = outputFile;
else
{
filename = std::string("Adobe_PrivateLicenseKey--") + user->getUsername() + ".der";
if (outputDir)
filename = std::string(outputDir) + "/" + filename;
}
createPath(filename.c_str());
processor.exportPrivateLicenseKey(filename);
std::cout << "Private license key exported to " << filename << std::endl;
}
else
{
gourou::FulfillmentItem* item = processor.fulfill(acsmFile, notify);
gourou::FulfillmentItem* item = processor.fulfill(acsmFile);
std::string filename;
if (outputFile)
filename = outputFile;
else
if (!outputFile)
{
filename = item->getMetadata("title");
if (filename == "")
filename = "output";
filename = "output.epub";
else
{
// Remove invalid characters
std::replace(filename.begin(), filename.end(), '/', '_');
filename += ".epub";
}
else
filename = outputFile;
if (outputDir)
{
QDir dir(outputDir);
if (!dir.exists(outputDir))
dir.mkpath(outputDir);
filename = std::string(outputDir) + "/" + filename;
}
createPath(filename.c_str());
gourou::DRMProcessor::ITEM_TYPE type = processor.download(item, filename, resume);
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;
}
processor.download(item, filename);
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)
{
gourou::LoanToken* token = item->getLoanToken();
// No loan token available
if (!token)
return;
pugi::xml_document doc;
pugi::xml_node decl = doc.append_child(pugi::node_declaration);
decl.append_attribute("version") = "1.0";
pugi::xml_node root = doc.append_child("loanToken");
gourou::appendTextElem(root, "id", (*token)["id"]);
gourou::appendTextElem(root, "operatorURL", (*token)["operatorURL"]);
gourou::appendTextElem(root, "validity", (*token)["validity"]);
gourou::appendTextElem(root, "name", item->getMetadata("title"));
char * activationDir = strdup(deviceFile);
activationDir = dirname(activationDir);
gourou::StringXMLWriter xmlWriter;
doc.save(xmlWriter, " ");
std::string xmlStr = xmlWriter.getResult();
// Use first bytes of SHA1(id) as filename
unsigned char sha1[gourou::SHA1_LEN];
client.digest("SHA1", (unsigned char*)(*token)["id"].c_str(), (*token)["id"].size(), sha1);
gourou::ByteArray tmp(sha1, sizeof(sha1));
std::string filenameHex = tmp.toHex();
std::string filename(filenameHex.c_str(), ID_HASH_SIZE);
std::string fullPath = std::string(activationDir);
fullPath += std::string ("/") + std::string(LOANS_DIR);
mkpath(fullPath.c_str());
fullPath += filename + std::string(".xml");
gourou::writeFile(fullPath, xmlStr);
std::cout << "Loan token serialized into " << fullPath << std::endl;
free(activationDir);
this->app->exit(ret);
}
private:
DRMProcessorClientImpl client;
QCoreApplication* app;
};
static const char* findFile(const char* filename, bool inDefaultDirs=true)
{
QFile file(filename);
if (file.exists())
return strdup(filename);
if (!inDefaultDirs) return 0;
for (int i=0; i<(int)ARRAY_SIZE(defaultDirs); i++)
{
QString path = QString(defaultDirs[i]) + QString(filename);
file.setFileName(path);
if (file.exists())
return strdup(path.toStdString().c_str());
}
return 0;
}
static void version(void)
{
std::cout << "Current libgourou version : " << gourou::DRMProcessor::VERSION << std::endl ;
}
static void usage(const char* cmd)
{
std::cout << basename((char*)cmd) << " download EPUB file from ACSM request file" << std::endl << std::endl;
std::cout << "Usage: " << basename((char*)cmd) << " [OPTIONS] file.acsm" << std::endl << std::endl;
std::cout << "Global Options:" << std::endl;
std::cout << " " << "-O|--output-dir" << "\t" << "Optional output directory were to put result (default ./) (not compatible with -o)" << std::endl;
std::cout << " " << "-o|--output-file" << "\t" << "Optional output filename (default <title.(epub|pdf|der)>) (not compatible with -O)" << std::endl;
std::cout << " " << "-f|--acsm-file" << "\t" << "Backward compatibility: ACSM request file for epub download" << std::endl;
std::cout << " " << "-e|--export-private-key"<< "\t" << "Export private key in DER format" << std::endl;
std::cout << " " << "-r|--resume" << "\t\t" << "Try to resume download (in case of previous failure)" << std::endl;
std::cout << " " << "-N|--no-notify" << "\t\t" << "Don't notify server, even if requested" << std::endl;
std::cout << "Download EPUB file from ACSM request file" << std::endl;
std::cout << "Usage: " << cmd << " [(-d|--device-file) device.xml] [(-a|--activation-file) activation.xml] [(-s|--device-key-file) devicesalt] [(-O|--output-dir) dir] [(-o|--output-file) output.epub] [(-v|--verbose)] [(-h|--help)] (-f|--acsm-file) file.acsm" << std::endl << std::endl;
std::cout << " " << "-d|--device-file" << "\t" << "device.xml file from eReader" << std::endl;
std::cout << " " << "-a|--activation-file" << "\t" << "activation.xml file from eReader" << std::endl;
std::cout << " " << "-k|--device-key-file" << "\t" << "private device key file (eg devicesalt/devkey.bin) from eReader" << std::endl;
std::cout << " " << "-O|--output-dir" << "\t" << "Optional output directory were to put result (default ./)" << std::endl;
std::cout << " " << "-o|--output-file" << "\t" << "Optional output epub filename (default <title.epub>)" << std::endl;
std::cout << " " << "-f|--acsm-file" << "\t" << "ACSM request file for epub download" << std::endl;
std::cout << " " << "-v|--verbose" << "\t\t" << "Increase verbosity, can be set multiple times" << std::endl;
std::cout << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl;
std::cout << " " << "-h|--help" << "\t\t" << "This help" << std::endl;
std::cout << "ADEPT Options:" << std::endl;
std::cout << " " << "-D|--adept-directory" << "\t" << ".adept directory that must contains device.xml, activation.xml and devicesalt" << std::endl;
std::cout << " " << "-d|--device-file" << "\t" << "device.xml file from eReader" << std::endl;
std::cout << " " << "-a|--activation-file" << "\t" << "activation.xml file from eReader" << std::endl;
std::cout << " " << "-k|--device-key-file" << "\t" << "private device key file (eg devicesalt/devkey.bin) from eReader" << std::endl;
std::cout << std::endl;
std::cout << "Environment:" << std::endl;
std::cout << "Device file, activation file and device key file are optionals. If not set, they are looked into :" << std::endl << std::endl;
std::cout << " * $ADEPT_DIR environment variable" << std::endl;
std::cout << " * /home/<user>/.config/adept" << std::endl;
std::cout << "Device file, activation file and device key file are optionals. If not set, they are looked into :" << std::endl;
std::cout << " * Current directory" << std::endl;
std::cout << " * .adept" << std::endl;
std::cout << " * adobe-digital-editions directory" << std::endl;
@ -213,7 +162,6 @@ static void usage(const char* cmd)
int main(int argc, char** argv)
{
int c, ret = -1;
std::string _deviceFile, _activationFile, _devicekeyFile;
const char** files[] = {&devicekeyFile, &deviceFile, &activationFile};
int verbose = gourou::DRMProcessor::getLogLevel();
@ -221,36 +169,24 @@ int main(int argc, char** argv)
while (1) {
int option_index = 0;
static struct option long_options[] = {
{"adept-directory", required_argument, 0, 'D' },
{"device-file", required_argument, 0, 'd' },
{"activation-file", required_argument, 0, 'a' },
{"device-key-file", required_argument, 0, 'k' },
{"output-dir", required_argument, 0, 'O' },
{"output-file", required_argument, 0, 'o' },
{"acsm-file", required_argument, 0, 'f' },
{"export-private-key",no_argument, 0, 'e' },
{"resume", no_argument, 0, 'r' },
{"no-notify", no_argument, 0, 'N' },
{"verbose", no_argument, 0, 'v' },
{"version", no_argument, 0, 'V' },
{"help", no_argument, 0, 'h' },
{0, 0, 0, 0 }
};
c = getopt_long(argc, argv, "D:d:a:k:O:o:f:erNvVh",
c = getopt_long(argc, argv, "d:a:k:O:o:f:vVh",
long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 'D':
_deviceFile = std::string(optarg) + "/device.xml";
_activationFile = std::string(optarg) + "/activation.xml";
_devicekeyFile = std::string(optarg) + "/devicesalt";
deviceFile = _deviceFile.c_str();
activationFile = _activationFile.c_str();
devicekeyFile = _devicekeyFile.c_str();
break;
case 'd':
deviceFile = optarg;
break;
@ -269,15 +205,6 @@ int main(int argc, char** argv)
case 'o':
outputFile = optarg;
break;
case 'e':
exportPrivateKey = true;
break;
case 'r':
resume = true;
break;
case 'N':
notify = false;
break;
case 'v':
verbose++;
break;
@ -295,61 +222,39 @@ int main(int argc, char** argv)
gourou::DRMProcessor::setLogLevel(verbose);
if (optind == argc-1)
acsmFile = argv[optind];
if ((!acsmFile && !exportPrivateKey) || (outputDir && !outputDir[0]) ||
if (!acsmFile || (outputDir && !outputDir[0]) ||
(outputFile && !outputFile[0]))
{
usage(argv[0]);
return -1;
}
if (outputDir && outputFile)
{
std::cout << "Error : you cannot use both -o and -O" << std::endl;
return -1;
}
ACSMDownloader downloader;
QCoreApplication app(argc, argv);
ACSMDownloader downloader(&app);
int i;
bool hasErrors = false;
const char* orig;
for (i=0; i<(int)ARRAY_SIZE(files); i++)
{
orig = *files[i];
*files[i] = findFile(*files[i]);
if (!*files[i])
{
std::cout << "Error : " << orig << " doesn't exists, did you activate your device ?" << std::endl;
std::cout << "Error : " << *files[i] << " doesn't exists" << std::endl;
ret = -1;
hasErrors = true;
}
}
if (hasErrors)
goto end;
}
}
if (exportPrivateKey)
{
if (acsmFile)
{
usage(argv[0]);
return -1;
}
}
else
{
if (!pathExists(acsmFile))
QFile file(acsmFile);
if (!file.exists())
{
std::cout << "Error : " << acsmFile << " doesn't exists" << std::endl;
ret = -1;
goto end;
}
}
ret = downloader.run();
QThreadPool::globalInstance()->start(&downloader);
ret = app.exec();
end:
for (i=0; i<(int)ARRAY_SIZE(files); i++)

View file

@ -28,17 +28,22 @@
#include <unistd.h>
#include <getopt.h>
#include <stdlib.h>
#include <termios.h>
#include <string.h>
#include <limits.h>
#include <libgen.h>
#include <iostream>
#include <ostream>
#include <QFile>
#include <QDir>
#include <QCoreApplication>
#include <QRunnable>
#include <QThreadPool>
#include <libgourou.h>
#include "drmprocessorclientimpl.h"
#include "utils_common.h"
#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
static const char* username = 0;
static const char* password = 0;
@ -95,11 +100,16 @@ static std::string getpass(const char *prompt, bool show_asterisk=false)
}
class ADEPTActivate
class ADEPTActivate: public QRunnable
{
public:
ADEPTActivate(QCoreApplication* app):
app(app)
{
setAutoDelete(false);
}
int run()
void run()
{
int ret = 0;
try
@ -118,19 +128,24 @@ public:
ret = 1;
}
return ret;
this->app->exit(ret);
}
private:
QCoreApplication* app;
};
static void version(void)
{
std::cout << "Current libgourou version : " << gourou::DRMProcessor::VERSION << std::endl ;
}
static void usage(const char* cmd)
{
std::cout << basename((char*)cmd) << " create new device files used by ADEPT DRM" << std::endl << std::endl;
std::cout << "Create new device files used by ADEPT DRM" << std::endl;
std::cout << "Usage: " << basename((char*)cmd) << " OPTIONS" << std::endl << std::endl;
std::cout << "Usage: " << cmd << " (-u|--username) username [(-p|--password) password] [(-O|--output-dir) dir] [(-r|--random-serial)] [(-v|--verbose)] [(-h|--help)]" << std::endl << std::endl;
std::cout << "Global Options:" << std::endl;
std::cout << " " << "-a|--anonymous" << "\t" << "Anonymous account, no need for username/password (Use it only with a DRM removal software)" << std::endl;
std::cout << " " << "-u|--username" << "\t\t" << "AdobeID username (ie adobe.com email account)" << std::endl;
std::cout << " " << "-p|--password" << "\t\t" << "AdobeID password (asked if not set via command line) " << std::endl;
std::cout << " " << "-O|--output-dir" << "\t" << "Optional output directory were to put result (default ./.adept). This directory must not already exists" << std::endl;
@ -146,8 +161,8 @@ static void usage(const char* cmd)
static const char* abspath(const char* filename)
{
const char* root = getcwd(0, PATH_MAX);
std::string fullPath = std::string(root) + std::string("/") + filename;
const char* res = strdup(fullPath.c_str());
QString fullPath = QString(root) + QString("/") + QString(filename);
const char* res = strdup(fullPath.toStdString().c_str());
free((void*)root);
@ -159,12 +174,10 @@ int main(int argc, char** argv)
int c, ret = -1;
const char* _outputDir = outputDir;
int verbose = gourou::DRMProcessor::getLogLevel();
bool anonymous = false;
while (1) {
int option_index = 0;
static struct option long_options[] = {
{"anonymous", no_argument , 0, 'a' },
{"username", required_argument, 0, 'u' },
{"password", required_argument, 0, 'p' },
{"output-dir", required_argument, 0, 'O' },
@ -176,15 +189,12 @@ int main(int argc, char** argv)
{0, 0, 0, 0 }
};
c = getopt_long(argc, argv, "au:p:O:H:rvVh",
c = getopt_long(argc, argv, "u:p:O:H:rvVh",
long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 'a':
anonymous = true;
break;
case 'u':
username = optarg;
break;
@ -217,72 +227,47 @@ int main(int argc, char** argv)
gourou::DRMProcessor::setLogLevel(verbose);
if ((!username && !anonymous) ||
(username && anonymous))
if (!username)
{
usage(argv[0]);
return -1;
}
if (anonymous)
{
username = "anonymous";
password = "";
}
if (!_outputDir || _outputDir[0] == 0)
{
outputDir = strdup(gourou::DRMProcessor::getDefaultAdeptDir().c_str());
outputDir = abspath(DEFAULT_ADEPT_DIR);
}
else
{
// Relative path
if (_outputDir[0] == '.' || _outputDir[0] != '/')
{
QFile file(_outputDir);
// realpath doesn't works if file/dir doesn't exists
if (pathExists(_outputDir))
outputDir = strdup(realpath(_outputDir, 0));
if (file.exists())
outputDir = realpath(_outputDir, 0);
else
outputDir = strdup(abspath(_outputDir));
outputDir = abspath(_outputDir);
}
else
outputDir = strdup(_outputDir);
}
std::string pass;
if (pathExists(outputDir))
{
int key;
while (true)
{
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 ;
key = getchar();
if (key == 'n' || key == 'N' || key == '\n' || key == '\r')
goto end;
if (key == 'y' || key == 'Y')
break;
}
// Clean STDIN buf
while ((key = getchar()) != '\n')
;
}
if (!password)
{
char prompt[128];
std::snprintf(prompt, sizeof(prompt), "Enter password for <%s> : ", username);
pass = getpass((const char*)prompt, false);
std::string pass = getpass((const char*)prompt, false);
password = pass.c_str();
}
ADEPTActivate activate;
QCoreApplication app(argc, argv);
ret = activate.run();
ADEPTActivate activate(&app);
QThreadPool::globalInstance()->start(&activate);
ret = app.exec();
end:
free((void*)outputDir);
return ret;
}

View file

@ -1,503 +0,0 @@
/*
Copyright (c) 2022, Grégory Soutadé
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <getopt.h>
#include <iostream>
#include <algorithm>
#define _XOPEN_SOURCE 700
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
#include <libgen.h>
#include <time.h>
#include <libgourou.h>
#include <libgourou_common.h>
#include "drmprocessorclientimpl.h"
#include "utils_common.h"
#define MAX_SIZE_BOOK_NAME 30
static char* adeptDir = 0;
static const char* deviceFile = "device.xml";
static const char* activationFile = "activation.xml";
static const char* devicekeyFile = "devicesalt";
static bool list = false;
static const char* returnID = 0;
static const char* deleteID = 0;
static bool notify = true;
struct Loan
{
std::string id;
std::string operatorURL;
std::string validity;
std::string bookName;
std::string path;
};
class LoanMGT
{
public:
~LoanMGT()
{
for (const auto& kv : loanedBooks)
delete kv.second;
}
int run()
{
int ret = 0;
try
{
DRMProcessorClientImpl client;
gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile);
loadLoanedBooks();
if (list)
displayLoanList();
else if (returnID)
returnBook(processor);
else if (deleteID)
deleteLoan();
} catch(std::exception& e)
{
std::cout << e.what() << std::endl;
ret = 1;
}
return ret;
}
private:
void loadLoanedBooks()
{
DIR *dp;
struct dirent *ep;
int entryLen;
struct Loan* loan;
char * res;
std::string loanDir = std::string(adeptDir) + std::string("/") + LOANS_DIR;
if (!pathExists(loanDir.c_str()))
return;
dp = opendir (loanDir.c_str());
if(!dp)
EXCEPTION(gourou::USER_INVALID_INPUT, "Cannot read directory " << loanDir);
while ((ep = readdir (dp)))
{
if (ep->d_type != DT_LNK &&
ep->d_type != DT_REG)
continue;
entryLen = strlen(ep->d_name);
if (entryLen <= 4 ||
ep->d_name[entryLen-4] != '.' ||
ep->d_name[entryLen-3] != 'x' ||
ep->d_name[entryLen-2] != 'm' ||
ep->d_name[entryLen-1] != 'l')
continue;
std::string id = std::string(ep->d_name, entryLen-4);
loan = new Loan;
loan->path = loanDir + std::string("/") + ep->d_name;
pugi::xml_document xmlDoc;
pugi::xml_node node;
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;
goto error;
}
// id
node = xmlDoc.select_node("//id").node();
if (!node)
{
std::cout << "Invalid loan entry " << ep->d_name << ", no id element" << std::endl;
goto error;
}
loan->id = node.first_child().value();
// operatorURL
node = xmlDoc.select_node("//operatorURL").node();
if (!node)
{
std::cout << "Invalid loan entry " << ep->d_name << ", no operatorURL element" << std::endl;
goto error;
}
loan->operatorURL = node.first_child().value();
// validity
node = xmlDoc.select_node("//validity").node();
if (!node)
{
std::cout << "Invalid loan entry " << ep->d_name << ", no validity element" << std::endl;
goto error;
}
loan->validity = node.first_child().value();
// bookName
node = xmlDoc.select_node("//name").node();
if (!node)
{
std::cout << "Invalid loan entry " << ep->d_name << ", no name element" << std::endl;
goto error;
}
loan->bookName = node.first_child().value();
struct tm tm;
#ifdef __ANDROID__
res = strptime(loan->validity.c_str(), "%Y-%m-%dT%H:%M:%S%z", &tm);
#else
res = strptime(loan->validity.c_str(), "%Y-%m-%dT%H:%M:%S%Z", &tm);
#endif
if (res != NULL && *res == 0)
{
if (mktime(&tm) <= time(NULL))
loan->validity = " (Expired)";
}
else
{
std::cout << "Unable to parse validity timestamp :" << loan->validity << std::endl;
loan->validity = " (Unknown)";
}
loanedBooks[id] = loan;
continue;
error:
if (loan)
delete loan;
}
closedir (dp);
}
void displayLoanList()
{
if (!loanedBooks.size())
{
std::cout << "No books loaned" << std::endl;
return;
}
struct Loan* loan;
unsigned int maxSizeBookName=0;
// Compute max size
for (const auto& kv : loanedBooks)
{
loan = kv.second;
if (loan->bookName.size() > maxSizeBookName)
maxSizeBookName = loan->bookName.size();
}
/* Manage empty names */
if (maxSizeBookName == 0)
maxSizeBookName = sizeof("No name ")-1;
else if (maxSizeBookName < 4)
maxSizeBookName = 4;
else if (maxSizeBookName > MAX_SIZE_BOOK_NAME)
maxSizeBookName = MAX_SIZE_BOOK_NAME;
else if ((maxSizeBookName % 2))
maxSizeBookName++;
// std::cout << " ID Book Expiration" << std::endl;
// std::cout << "------------------------------" << std::endl;
int fillID, fillBookName, fillExpiration=(20 - 10)/2;
fillID = (ID_HASH_SIZE - 2) / 2;
fillBookName = (maxSizeBookName - 4) / 2;
std::cout.width (fillID);
std::cout << "";
std::cout << "ID" ;
std::cout.width (fillID);
std::cout << "";
std::cout << " " ;
std::cout.width (fillBookName);
std::cout << "";
std::cout << "Book" ;
std::cout.width (fillBookName);
std::cout << "";
std::cout << " " ;
std::cout.width (fillExpiration);
std::cout << "";
std::cout << "Expiration";
std::cout.width (fillExpiration);
std::cout << "" << std::endl;
std::cout.fill ('-');
std::cout.width (ID_HASH_SIZE + 4 + maxSizeBookName + 4 + 20);
std::cout << "" << std::endl;
std::cout.fill (' ');
std::string bookName;
for (const auto& kv : loanedBooks)
{
loan = kv.second;
std::cout << kv.first;
std::cout << " ";
if (loan->bookName.size() == 0)
bookName = std::string("No name ");
else if (loan->bookName.size() > MAX_SIZE_BOOK_NAME)
bookName = std::string(loan->bookName.c_str(), MAX_SIZE_BOOK_NAME);
else
bookName = loan->bookName;
std::cout << bookName;
std::cout.width (maxSizeBookName - bookName.size());
std::cout << "";
std::cout << " ";
std::cout << loan->validity << std::endl;
}
std::cout << std::endl;
}
void returnBook(gourou::DRMProcessor& processor)
{
struct Loan* loan = loanedBooks[std::string(returnID)];
if (!loan)
{
std::cout << "Error : Loan " << returnID << " doesn't exists" << std::endl;
return;
}
processor.returnLoan(loan->id, loan->operatorURL, notify);
deleteID = returnID;
if (deleteLoan(false))
{
std::cout << "Loan " << returnID << " successfully returned" << std::endl;
}
}
bool deleteLoan(bool displayResult=true)
{
struct Loan* loan = loanedBooks[std::string(deleteID)];
if (!loan)
{
std::cout << "Error : Loan " << deleteID << " doesn't exists" << std::endl;
return false;
}
if (unlink(loan->path.c_str()))
{
std::cout << "Error : Cannot delete " << loan->path << std::endl;
return false;
}
else if (displayResult)
{
std::cout << "Loan " << deleteID << " deleted" << std::endl;
}
return true;
}
std::map<std::string, struct Loan*> loanedBooks;
};
static void usage(const char* cmd)
{
std::cout << basename((char*)cmd) << " manage loaned books" << std::endl << std::endl;
std::cout << "Usage: " << basename((char*)cmd) << " [OPTIONS]" << std::endl << std::endl;
std::cout << "Global Options:" << std::endl;
std::cout << " " << "-l|--list" << "\t\t" << "List all loaned books" << std::endl;
std::cout << " " << "-r|--return" << "\t\t" << "Return a loaned book" << std::endl;
std::cout << " " << "-d|--delete" << "\t\t" << "Delete a loan entry without returning it" << std::endl;
std::cout << " " << "-N|--no-notify" << "\t\t" << "Don't notify server, even if requested" << std::endl;
std::cout << " " << "-v|--verbose" << "\t\t" << "Increase verbosity, can be set multiple times" << std::endl;
std::cout << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl;
std::cout << " " << "-h|--help" << "\t\t" << "This help" << std::endl;
std::cout << "ADEPT Options:" << std::endl;
std::cout << " " << "-D|--adept-directory" << "\t" << ".adept directory that must contains device.xml, activation.xml and devicesalt" << std::endl;
std::cout << std::endl;
std::cout << "Environment:" << std::endl;
std::cout << "ADEPT directory is optional. If not set, it's looked into :" << std::endl;
std::cout << " * $ADEPT_DIR environment variable" << std::endl;
std::cout << " * /home/<user>/.config/adept" << std::endl;
std::cout << " * Current directory" << std::endl;
std::cout << " * .adept" << std::endl;
std::cout << " * adobe-digital-editions directory" << std::endl;
std::cout << " * .adobe-digital-editions directory" << std::endl;
}
int main(int argc, char** argv)
{
int c, ret = -1;
const char** files[] = {&devicekeyFile, &deviceFile, &activationFile};
int verbose = gourou::DRMProcessor::getLogLevel();
int actions = 0;
while (1) {
int option_index = 0;
static struct option long_options[] = {
{"adept-directory", required_argument, 0, 'D' },
{"list", no_argument, 0, 'l' },
{"return", no_argument, 0, 'r' },
{"delete", no_argument, 0, 'd' },
{"no-notify", no_argument, 0, 'N' },
{"verbose", no_argument, 0, 'v' },
{"version", no_argument, 0, 'V' },
{"help", no_argument, 0, 'h' },
{0, 0, 0, 0 }
};
c = getopt_long(argc, argv, "d:lr:D:vVh",
long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 'D':
adeptDir = optarg;
break;
case 'l':
list = true;
actions++;
break;
case 'r':
returnID = optarg;
actions++;
break;
case 'd':
deleteID = optarg;
actions++;
break;
case 'N':
notify = false;
break;
case 'v':
verbose++;
break;
case 'V':
version();
return 0;
case 'h':
usage(argv[0]);
return 0;
default:
usage(argv[0]);
return -1;
}
}
gourou::DRMProcessor::setLogLevel(verbose);
// By default, simply list books loaned
if (actions == 0)
list = true;
else if (actions != 1)
{
usage(argv[0]);
return -1;
}
LoanMGT loanMGT;
int i;
bool hasErrors = false;
const char* orig;
char *filename;
for (i=0; i<(int)ARRAY_SIZE(files); i++)
{
orig = *files[i];
if (adeptDir)
{
std::string path = std::string(adeptDir) + std::string("/") + orig;
filename = strdup(path.c_str());
}
else
filename = strdup(orig);
*files[i] = findFile(filename);
free(filename);
if (!*files[i])
{
std::cout << "Error : " << orig << " doesn't exists, did you activate your device ?" << std::endl;
hasErrors = true;
}
}
if (hasErrors)
{
// In case of adept dir was provided by user
adeptDir = 0;
goto end;
}
if (adeptDir)
adeptDir = strdup(adeptDir); // For below free
else
{
adeptDir = strdup(deviceFile);
adeptDir = dirname(adeptDir);
}
ret = loanMGT.run();
end:
for (i=0; i<(int)ARRAY_SIZE(files); i++)
{
if (*files[i])
free((void*)*files[i]);
}
if (adeptDir)
free(adeptDir);
return ret;
}

View file

@ -1,326 +0,0 @@
/*
Copyright (c) 2021, Grégory Soutadé
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <getopt.h>
#include <libgen.h>
#include <iostream>
#include <libgourou.h>
#include <libgourou_common.h>
#include "drmprocessorclientimpl.h"
#include "utils_common.h"
static const char* deviceFile = "device.xml";
static const char* activationFile = "activation.xml";
static const char* devicekeyFile = "devicesalt";
static const char* inputFile = 0;
static const char* outputFile = 0;
static const char* outputDir = 0;
static char* encryptionKeyUser = 0;
static unsigned char* encryptionKey = 0;
static unsigned encryptionKeySize = 0;
static inline unsigned char htoi(unsigned char c)
{
if (c >= '0' && c <= '9')
c -= '0';
else if (c >= 'a' && c <= 'f')
c -= 'a' - 10;
else if (c >= 'A' && c <= 'F')
c -= 'A' - 10;
else
EXCEPTION(gourou::USER_INVALID_INPUT, "Invalid character " << c << " in encryption key");
return c;
}
static inline bool endsWith(const std::string& s, const std::string& suffix)
{
return s.rfind(suffix) == std::abs((int)(s.size()-suffix.size()));
}
class ADEPTRemove
{
public:
int run()
{
int ret = 0;
try
{
gourou::DRMProcessor::ITEM_TYPE type;
DRMProcessorClientImpl client;
gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile);
std::string filename;
if (outputFile)
filename = outputFile;
else
{
filename = std::string(inputFile);
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);
}
createPath(filename.c_str());
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)
{
std::cout << basename((char*)cmd) << " remove ADEPT DRM (from Adobe) of EPUB/PDF file" << std::endl << std::endl;
std::cout << "Usage: " << basename((char*)cmd) << " [OPTIONS] file(.epub|pdf)" << std::endl << std::endl;
std::cout << "Global Options:" << std::endl;
std::cout << " " << "-O|--output-dir" << "\t" << "Optional output directory were to put result (default ./) (not compatible with -o)" << std::endl;
std::cout << " " << "-o|--output-file" << "\t" << "Optional output filename (default inplace DRM removal>) (not compatible with -O)" << std::endl;
std::cout << " " << "-f|--input-file" << "\t" << "Backward compatibility: EPUB/PDF file to process" << std::endl;
std::cout << " " << "-v|--verbose" << "\t\t" << "Increase verbosity, can be set multiple times" << std::endl;
std::cout << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl;
std::cout << " " << "-h|--help" << "\t\t" << "This help" << std::endl;
std::cout << "ADEPT Options:" << std::endl;
std::cout << " " << "-D|--adept-directory" << "\t" << ".adept directory that must contains device.xml, activation.xml and devicesalt" << std::endl;
std::cout << " " << "-d|--device-file" << "\t" << "device.xml file from eReader" << std::endl;
std::cout << " " << "-a|--activation-file" << "\t" << "activation.xml file from eReader" << std::endl;
std::cout << " " << "-k|--device-key-file" << "\t" << "private device key file (eg devicesalt/devkey.bin) from eReader" << std::endl;
std::cout << std::endl;
std::cout << "Environment:" << std::endl;
std::cout << "Device file, activation file and device key file are optionals. If not set, they are looked into :" << std::endl;
std::cout << " * $ADEPT_DIR environment variable" << std::endl;
std::cout << " * /home/<user>/.config/adept" << std::endl;
std::cout << " * Current directory" << std::endl;
std::cout << " * .adept" << std::endl;
std::cout << " * adobe-digital-editions directory" << std::endl;
std::cout << " * .adobe-digital-editions directory" << std::endl;
}
int main(int argc, char** argv)
{
int c, ret = -1;
const char** files[] = {&devicekeyFile, &deviceFile, &activationFile};
int verbose = gourou::DRMProcessor::getLogLevel();
std::string _deviceFile, _activationFile, _devicekeyFile;
while (1) {
int option_index = 0;
static struct option long_options[] = {
{"adept-directory", required_argument, 0, 'D' },
{"device-file", required_argument, 0, 'd' },
{"activation-file", required_argument, 0, 'a' },
{"device-key-file", required_argument, 0, 'k' },
{"output-dir", required_argument, 0, 'O' },
{"output-file", required_argument, 0, 'o' },
{"input-file", required_argument, 0, 'f' },
{"encryption-key", required_argument, 0, 'K' }, // Private option
{"verbose", no_argument, 0, 'v' },
{"version", no_argument, 0, 'V' },
{"help", no_argument, 0, 'h' },
{0, 0, 0, 0 }
};
c = getopt_long(argc, argv, "D:d:a:k:O:o:f:K:vVh",
long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 'D':
_deviceFile = std::string(optarg) + "/device.xml";
_activationFile = std::string(optarg) + "/activation.xml";
_devicekeyFile = std::string(optarg) + "/devicesalt";
deviceFile = _deviceFile.c_str();
activationFile = _activationFile.c_str();
devicekeyFile = _devicekeyFile.c_str();
break;
case 'd':
deviceFile = optarg;
break;
case 'a':
activationFile = optarg;
break;
case 'k':
devicekeyFile = optarg;
break;
case 'f':
inputFile = optarg;
break;
case 'O':
outputDir = optarg;
break;
case 'o':
outputFile = optarg;
break;
case 'K':
encryptionKeyUser = optarg;
break;
case 'v':
verbose++;
break;
case 'V':
version();
return 0;
case 'h':
usage(argv[0]);
return 0;
default:
usage(argv[0]);
return -1;
}
}
gourou::DRMProcessor::setLogLevel(verbose);
if (optind == argc-1)
inputFile = argv[optind];
if (!inputFile || (outputDir && !outputDir[0]) ||
(outputFile && !outputFile[0]))
{
usage(argv[0]);
return -1;
}
if (outputDir && outputFile)
{
std::cout << "Error : you cannot use both -o and -O" << std::endl;
return -1;
}
ADEPTRemove remover;
int i;
bool hasErrors = false;
const char* orig;
for (i=0; i<(int)ARRAY_SIZE(files); i++)
{
orig = *files[i];
*files[i] = findFile(*files[i]);
if (!*files[i])
{
std::cout << "Error : " << orig << " doesn't exists, did you activate your device ?" << std::endl;
ret = -1;
hasErrors = true;
}
}
if (encryptionKeyUser)
{
int size = std::string(encryptionKeyUser).size();
if ((size % 2))
{
std::cout << "Error : Encryption key must be odd length" << std::endl;
goto end;
}
if (encryptionKeyUser[0] == '0' && encryptionKeyUser[1] == 'x')
{
encryptionKeyUser += 2;
size -= 2;
}
encryptionKey = new unsigned char[size/2];
for(i=0; i<size; i+=2)
{
encryptionKey[i/2] = htoi(encryptionKeyUser[i]) << 4;
encryptionKey[i/2] |= htoi(encryptionKeyUser[i+1]);
}
encryptionKeySize = size/2;
}
if (hasErrors)
goto end;
ret = remover.run();
end:
for (i=0; i<(int)ARRAY_SIZE(files); i++)
{
if (*files[i])
free((void*)*files[i]);
}
if (encryptionKey)
free(encryptionKey);
return ret;
}

View file

@ -25,116 +25,54 @@
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <bytearray.h>
#include <algorithm>
#include <cctype>
#include <locale>
#include <stdlib.h>
#define OPENSSL_NO_DEPRECATED 1
#include <openssl/rand.h>
#include <openssl/pkcs12.h>
#include <openssl/evp.h>
#include <openssl/err.h>
#include <openssl/rsa.h>
#include <openssl/bn.h>
#include <curl/curl.h>
#include <QCoreApplication>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QNetworkAccessManager>
#include <QFile>
#include <zlib.h>
#include <zip.h>
#include <libgourou_common.h>
#include <libgourou_log.h>
#include "drmprocessorclientimpl.h"
static int error_cb(const char *str, size_t len, void *u)
{
std::cout << str << std::endl;
return 0;
}
DRMProcessorClientImpl::DRMProcessorClientImpl():
legacy(0), deflt(0)
{
#if OPENSSL_VERSION_MAJOR >= 3
legacy = OSSL_PROVIDER_load(NULL, "legacy");
if (!legacy)
{
ERR_print_errors_cb(error_cb, NULL);
EXCEPTION(gourou::CLIENT_OSSL_ERROR, "Error, OpenSSL legacy provider not available");
}
deflt = OSSL_PROVIDER_load(NULL, "default");
if (!deflt)
EXCEPTION(gourou::CLIENT_OSSL_ERROR, "Error, OpenSSL default provider not available");
OSSL_PROVIDER_load(NULL, "base");
#endif
#ifdef WIN32
strcpy(cookiejar, "C:\\temp\\libgourou_cookie_jar_XXXXXX");
#else
strcpy(cookiejar, "/tmp/libgourou_cookie_jar_XXXXXX");
#endif
int fd = mkstemp(cookiejar);
if (fd >= 0)
close(fd);
else
{
EXCEPTION(gourou::CLIENT_FILE_ERROR, "mkstemp error");
}
}
DRMProcessorClientImpl::~DRMProcessorClientImpl()
{
#if OPENSSL_VERSION_MAJOR >= 3
if (legacy)
OSSL_PROVIDER_unload(legacy);
if (deflt)
OSSL_PROVIDER_unload(deflt);
#endif
unlink(cookiejar);
}
/* Digest interface */
void* DRMProcessorClientImpl::createDigest(const std::string& digestName)
{
EVP_MD_CTX *md_ctx = EVP_MD_CTX_new();
EVP_MD_CTX *sha_ctx = EVP_MD_CTX_new();
const EVP_MD* md = EVP_get_digestbyname(digestName.c_str());
EVP_DigestInit(sha_ctx, md);
if (EVP_DigestInit(md_ctx, md) != 1)
return sha_ctx;
}
int DRMProcessorClientImpl::digestUpdate(void* handler, unsigned char* data, unsigned int length)
{
EVP_MD_CTX_free(md_ctx);
EXCEPTION(gourou::CLIENT_DIGEST_ERROR, ERR_error_string(ERR_get_error(), NULL));
return EVP_DigestUpdate((EVP_MD_CTX *)handler, data, length);
}
return md_ctx;
}
void DRMProcessorClientImpl::digestUpdate(void* handler, unsigned char* data, unsigned int length)
{
if (EVP_DigestUpdate((EVP_MD_CTX *)handler, data, length) != 1)
EXCEPTION(gourou::CLIENT_DIGEST_ERROR, ERR_error_string(ERR_get_error(), NULL));
}
void DRMProcessorClientImpl::digestFinalize(void* handler, unsigned char* digestOut)
int DRMProcessorClientImpl::digestFinalize(void* handler, unsigned char* digestOut)
{
int res = EVP_DigestFinal((EVP_MD_CTX *)handler, digestOut, NULL);
EVP_MD_CTX_free((EVP_MD_CTX *)handler);
if (res <= 0)
EXCEPTION(gourou::CLIENT_DIGEST_ERROR, ERR_error_string(ERR_get_error(), NULL));
return res;
}
void DRMProcessorClientImpl::digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut)
int DRMProcessorClientImpl::digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut)
{
void* handler = createDigest(digestName);
digestUpdate(handler, data, length);
digestFinalize(handler, digestOut);
if (!handler)
return -1;
if (digestUpdate(handler, data, length))
return -1;
return digestFinalize(handler, digestOut);
}
/* Random interface */
@ -144,333 +82,91 @@ void DRMProcessorClientImpl::randBytes(unsigned char* bytesOut, unsigned int len
}
/* HTTP interface */
#define HTTP_REQ_MAX_RETRY 5
#define DISPLAY_THRESHOLD 10*1024 // Threshold to display download progression
static unsigned downloadedBytes;
static int lastPercent = -1;
static int downloadProgress(void *clientp, curl_off_t dltotal, curl_off_t dlnow,
curl_off_t ultotal, curl_off_t ulnow)
std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, const std::string& POSTData, const std::string& contentType)
{
// For "big" files only
if (dltotal >= DISPLAY_THRESHOLD && gourou::logLevel >= gourou::LG_LOG_WARN)
{
int percent = 0;
if (dltotal)
percent = (dlnow * 100) / dltotal;
QNetworkRequest request(QUrl(URL.c_str()));
QNetworkAccessManager networkManager;
QByteArray replyData;
if (lastPercent != percent)
{
std::cout << "\rDownload " << percent << "%" << std::flush;
lastPercent = percent;
}
}
return 0;
}
static size_t curlRead(void *data, size_t size, size_t nmemb, void *userp)
{
gourou::ByteArray* replyData = (gourou::ByteArray*) userp;
replyData->append((unsigned char*)data, size*nmemb);
return size*nmemb;
}
static size_t curlReadFd(void *data, size_t size, size_t nmemb, void *userp)
{
int fd = *(int*) userp;
size_t res = write(fd, data, size*nmemb);
downloadedBytes += res;
return res;
}
static size_t curlHeaders(char *buffer, size_t size, size_t nitems, void *userdata)
{
std::map<std::string, std::string>* responseHeaders = (std::map<std::string, std::string>*)userdata;
std::string::size_type pos = 0;
std::string buf(buffer, size*nitems);
pos = buf.find(":", pos);
if (pos != std::string::npos)
{
std::string key = std::string(buffer, pos);
std::string value = std::string(&buffer[pos+1], (size*nitems)-(pos+1));
key = gourou::trim(key);
value = gourou::trim(value);
(*responseHeaders)[key] = value;
if (gourou::logLevel >= gourou::LG_LOG_DEBUG)
std::cout << key << " : " << value << std::endl;
}
return size*nitems;
}
std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, const std::string& POSTData, const std::string& contentType, std::map<std::string, std::string>* responseHeaders, int fd, bool resume)
{
gourou::ByteArray replyData;
std::map<std::string, std::string> localHeaders;
if (!responseHeaders)
responseHeaders = &localHeaders;
GOUROU_LOG(INFO, "Send request to " << URL);
GOUROU_LOG(gourou::INFO, "Send request to " << URL);
if (POSTData.size())
{
GOUROU_LOG(DEBUG, "<<< " << std::endl << POSTData);
GOUROU_LOG(gourou::DEBUG, "<<< " << std::endl << POSTData);
}
unsigned prevDownloadedBytes;
downloadedBytes = 0;
if (fd && resume)
{
struct stat _stat;
if (!fstat(fd, &_stat))
{
GOUROU_LOG(WARN, "Resume download @ " << _stat.st_size << " bytes");
downloadedBytes = _stat.st_size;
}
else
GOUROU_LOG(WARN, "Want to resume, but fstat failed");
}
CURL *curl = curl_easy_init();
CURLcode res;
curl_easy_setopt(curl, CURLOPT_URL, URL.c_str());
curl_easy_setopt(curl, CURLOPT_USERAGENT, "book2png");
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
struct curl_slist *list = NULL;
list = curl_slist_append(list, "Accept: */*");
std::string _contentType;
request.setRawHeader("Accept", "*/*");
request.setRawHeader("User-Agent", "book2png");
if (contentType.size())
{
_contentType = "Content-Type: " + contentType;
list = curl_slist_append(list, _contentType.c_str());
}
request.setRawHeader("Content-Type", contentType.c_str());
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
curl_easy_setopt(curl, CURLOPT_COOKIEJAR, cookiejar);
QNetworkReply* reply;
if (POSTData.size())
{
curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, POSTData.size());
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, POSTData.data());
}
if (fd)
{
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlReadFd);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&fd);
}
reply = networkManager.post(request, POSTData.c_str());
else
reply = networkManager.get(request);
QCoreApplication* app = QCoreApplication::instance();
networkManager.moveToThread(app->thread());
while (!reply->isFinished())
app->processEvents();
QByteArray location = reply->rawHeader("Location");
if (location.size() != 0)
{
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlRead);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&replyData);
GOUROU_LOG(gourou::DEBUG, "New location");
return sendHTTPRequest(location.constData(), POSTData, contentType);
}
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curlHeaders);
curl_easy_setopt(curl, CURLOPT_HEADERDATA, (void*)responseHeaders);
if (reply->error() != QNetworkReply::NoError)
EXCEPTION(gourou::CLIENT_NETWORK_ERROR, "Error " << reply->errorString().toStdString());
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, downloadProgress);
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
lastPercent = -1;
for (int i=0; i<HTTP_REQ_MAX_RETRY; i++)
if (gourou::logLevel >= gourou::DEBUG)
{
prevDownloadedBytes = downloadedBytes;
if (downloadedBytes)
curl_easy_setopt(curl, CURLOPT_RESUME_FROM, downloadedBytes);
QList<QByteArray> headers = reply->rawHeaderList();
for (int i = 0; i < headers.size(); ++i) {
std::cout << headers[i].constData() << " : " << reply->rawHeader(headers[i]).constData() << std::endl;
}
}
res = curl_easy_perform(curl);
// Connexion failed, wait & retry
if (res == CURLE_COULDNT_CONNECT)
replyData = reply->readAll();
if (reply->rawHeader("Content-Type") == "application/vnd.adobe.adept+xml")
{
GOUROU_LOG(WARN, "\nConnection failed, attempt " << (i+1) << "/" << HTTP_REQ_MAX_RETRY);
}
// Transfer failed but some data has been received
// --> try again without incrementing tries
else if (res == CURLE_RECV_ERROR)
{
if (prevDownloadedBytes != downloadedBytes)
{
GOUROU_LOG(WARN, "\nConnection broken, but data received, try again");
i--;
}
else
GOUROU_LOG(WARN, "\nConnection broken and no data received, attempt " << (i+1) << "/" << HTTP_REQ_MAX_RETRY);
}
// Other error --> fail
else
break;
// Wait a little bit (250ms * i)
usleep((250 * 1000) * (i+1));
GOUROU_LOG(gourou::DEBUG, ">>> " << std::endl << replyData.data());
}
curl_slist_free_all(list);
long http_code = 400;
curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &http_code);
curl_easy_cleanup(curl);
if (res != CURLE_OK)
EXCEPTION(gourou::CLIENT_NETWORK_ERROR, "Error " << curl_easy_strerror(res));
if (http_code >= 400)
EXCEPTION(gourou::CLIENT_HTTP_ERROR, "HTTP Error code " << http_code);
if ((downloadedBytes >= DISPLAY_THRESHOLD || replyData.size() >= DISPLAY_THRESHOLD) &&
gourou::logLevel >= gourou::LG_LOG_WARN)
std::cout << std::endl;
if ((*responseHeaders)["Content-Type"] == "application/vnd.adobe.adept+xml")
{
GOUROU_LOG(DEBUG, ">>> " << std::endl << replyData.data());
return std::string(replyData.data(), replyData.length());
}
return std::string((char*)replyData.data(), replyData.length());
}
void DRMProcessorClientImpl::padWithPKCS1(unsigned char* out, unsigned int outLength,
const unsigned char* in, unsigned int inLength)
{
if (outLength < (inLength + 3))
EXCEPTION(gourou::CLIENT_RSA_ERROR, "Not enough space for PKCS1 padding");
/*
PKCS1v5 Padding is :
0x00 0x01 0xff * n 0x00 dataIn
*/
memset(out, 0xFF, outLength - inLength - 1);
out[0] = 0x0;
out[1] = 0x1;
out[outLength - inLength - 1] = 0x00;
memcpy(&out[outLength - inLength], in, inLength);
}
void DRMProcessorClientImpl::padWithPKCS1Type2(unsigned char* out, unsigned int outLength,
const unsigned char* in, unsigned int inLength)
{
if (outLength < (inLength + 3))
EXCEPTION(gourou::CLIENT_RSA_ERROR, "Not enough space for PKCS1 padding");
/*
PKCS1v5 type 2 Padding is :
0x00 0x02 0xXX * n 0x00 dataIn
XX is random non zero data
*/
RAND_bytes(&out[2], outLength - inLength - 1);
for(unsigned int i=2; i<outLength - inLength - 1; i++)
{
while (out[i] == 0)
RAND_bytes(&out[i], 1);
}
out[0] = 0x0;
out[1] = 0x2;
out[outLength - inLength - 1] = 0x00;
memcpy(&out[outLength - inLength], in, inLength);
}
void DRMProcessorClientImpl::RSAPrivateEncrypt(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)
{
PKCS12 * pkcs12;
EVP_PKEY_CTX *ctx;
EVP_PKEY* pkey = NULL;
size_t outlen;
unsigned char* tmp;
int ret;
EVP_PKEY* pkey;
X509* cert;
STACK_OF(X509)* ca;
RSA * rsa;
pkcs12 = d2i_PKCS12(NULL, &RSAKey, RSAKeyLength);
if (!pkcs12)
EXCEPTION(gourou::CLIENT_INVALID_PKCS12, ERR_error_string(ERR_get_error(), NULL));
PKCS12_parse(pkcs12, password.c_str(), &pkey, &cert, &ca);
rsa = EVP_PKEY_get1_RSA(pkey);
if (PKCS12_parse(pkcs12, password.c_str(), &pkey, NULL, NULL) <= 0)
EXCEPTION(gourou::CLIENT_INVALID_PKCS12, ERR_error_string(ERR_get_error(), NULL));
int ret = RSA_private_encrypt(dataLength, data, res, rsa, RSA_PKCS1_PADDING);
outlen = EVP_PKEY_get_size(pkey);
ctx = EVP_PKEY_CTX_new(pkey, NULL);
/* Use RSA private key */
if (EVP_PKEY_decrypt_init(ctx) <= 0)
if (ret < 0)
EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL));
if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_NO_PADDING) <= 0)
EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL));
tmp = (unsigned char*)malloc(outlen);
/* PKCS1 functions are no more exported */
padWithPKCS1(tmp, outlen, data, dataLength);
ret = EVP_PKEY_decrypt(ctx, res, &outlen, tmp, outlen);
EVP_PKEY_CTX_free(ctx);
free(tmp);
if (ret <= 0)
EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL));
}
void DRMProcessorClientImpl::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)
if (gourou::logLevel >= gourou::DEBUG)
{
BIO* mem = BIO_new_mem_buf(RSAKey, RSAKeyLength);
PKCS8_PRIV_KEY_INFO* p8inf = d2i_PKCS8_PRIV_KEY_INFO_bio(mem, NULL);
if (!p8inf)
EXCEPTION(gourou::CLIENT_INVALID_PKCS8, ERR_error_string(ERR_get_error(), NULL));
EVP_PKEY_CTX *ctx;
EVP_PKEY* pkey = EVP_PKCS82PKEY(p8inf);
size_t outlen = dataLength;
int ret;
if (!pkey)
EXCEPTION(gourou::CLIENT_INVALID_PKCS8, ERR_error_string(ERR_get_error(), NULL));
ctx = EVP_PKEY_CTX_new(pkey, NULL);
if (EVP_PKEY_decrypt_init(ctx) <= 0)
EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL));
if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_NO_PADDING) <= 0)
EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL));
ret = EVP_PKEY_decrypt(ctx, res, &outlen, data, dataLength);
PKCS8_PRIV_KEY_INFO_free(p8inf);
EVP_PKEY_CTX_free(ctx);
BIO_free(mem);
if (ret <= 0)
EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL));
printf("Sig : ");
for(int i=0; i<(int)sizeof(res); i++)
printf("%02x ", res[i]);
printf("\n");
}
}
void DRMProcessorClientImpl::RSAPublicEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength,
@ -478,44 +174,18 @@ void DRMProcessorClientImpl::RSAPublicEncrypt(const unsigned char* RSAKey, unsig
const unsigned char* data, unsigned dataLength,
unsigned char* res)
{
size_t outlen;
unsigned char* tmp;
X509 * x509 = d2i_X509(0, &RSAKey, RSAKeyLength);
if (!x509)
EXCEPTION(gourou::CLIENT_INVALID_CERTIFICATE, "Invalid certificate");
EVP_PKEY_CTX *ctx;
EVP_PKEY * pkey = X509_get_pubkey(x509);
EVP_PKEY * evpKey = X509_get_pubkey(x509);
RSA* rsa = EVP_PKEY_get1_RSA(evpKey);
EVP_PKEY_free(evpKey);
if (!pkey)
EXCEPTION(gourou::CLIENT_NO_PUB_KEY, "No public key in certificate");
ctx = EVP_PKEY_CTX_new(pkey, NULL);
if (EVP_PKEY_encrypt_init(ctx) <= 0)
EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL));
if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_NO_PADDING) <= 0)
EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL));
outlen = EVP_PKEY_get_size(pkey);
tmp = (unsigned char*)malloc(outlen);
/*
PKCS1 functions are no more exported.
Some OpenSSL libraries still use type 1
*/
padWithPKCS1Type2(tmp, outlen, data, dataLength);
int ret = EVP_PKEY_encrypt(ctx, res, &outlen, tmp, outlen);
EVP_PKEY_CTX_free(ctx);
free(tmp);
EVP_PKEY_free(pkey);
if (!rsa)
EXCEPTION(gourou::CLIENT_NO_PRIV_KEY, "No private key in certificate");
int ret = RSA_public_encrypt(dataLength, data, res, rsa, RSA_PKCS1_PADDING);
if (ret < 0)
EXCEPTION(gourou::CLIENT_RSA_ERROR, ERR_error_string(ERR_get_error(), NULL));
}
@ -523,45 +193,42 @@ void DRMProcessorClientImpl::RSAPublicEncrypt(const unsigned char* RSAKey, unsig
void* DRMProcessorClientImpl::generateRSAKey(int keyLengthBits)
{
BIGNUM * bn = BN_new();
EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
EVP_PKEY *key = NULL;
RSA * rsa = RSA_new();
BN_set_word(bn, 0x10001);
EVP_PKEY_keygen_init(ctx);
EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, keyLengthBits);
EVP_PKEY_CTX_set1_rsa_keygen_pubexp(ctx, bn);
EVP_PKEY_keygen(ctx, &key);
EVP_PKEY_CTX_free(ctx);
RSA_generate_key_ex(rsa, keyLengthBits, bn, 0);
BN_free(bn);
return key;
return rsa;
}
void DRMProcessorClientImpl::destroyRSAHandler(void* handler)
{
free(handler);
RSA_free((RSA*)handler);
}
void DRMProcessorClientImpl::extractRSAPublicKey(void* handler, unsigned char** keyOut, unsigned int* keyOutLength)
{
EVP_PKEY * evpKey = EVP_PKEY_new();
EVP_PKEY_set1_RSA(evpKey, (RSA*)handler);
X509_PUBKEY *x509_pubkey = 0;
X509_PUBKEY_set(&x509_pubkey, (EVP_PKEY*)handler);
X509_PUBKEY_set(&x509_pubkey, evpKey);
*keyOutLength = i2d_X509_PUBKEY(x509_pubkey, keyOut);
X509_PUBKEY_free(x509_pubkey);
EVP_PKEY_free(evpKey);
}
void DRMProcessorClientImpl::extractRSAPrivateKey(void* handler, unsigned char** keyOut, unsigned int* keyOutLength)
{
PKCS8_PRIV_KEY_INFO * privKey = EVP_PKEY2PKCS8((EVP_PKEY*)handler);
EVP_PKEY * evpKey = EVP_PKEY_new();
EVP_PKEY_set1_RSA(evpKey, (RSA*)handler);
PKCS8_PRIV_KEY_INFO * privKey = EVP_PKEY2PKCS8(evpKey);
*keyOutLength = i2d_PKCS8_PRIV_KEY_INFO(privKey, keyOut);
PKCS8_PRIV_KEY_INFO_free(privKey);
EVP_PKEY_free(evpKey);
}
void DRMProcessorClientImpl::extractCertificate(const unsigned char* RSAKey, unsigned int RSAKeyLength,
@ -571,14 +238,12 @@ void DRMProcessorClientImpl::extractCertificate(const unsigned char* RSAKey, uns
PKCS12 * pkcs12;
EVP_PKEY* pkey = 0;
X509* cert = 0;
STACK_OF(X509)* ca;
pkcs12 = d2i_PKCS12(NULL, &RSAKey, RSAKeyLength);
if (!pkcs12)
EXCEPTION(gourou::CLIENT_INVALID_PKCS12, ERR_error_string(ERR_get_error(), NULL));
PKCS12_parse(pkcs12, password.c_str(), &pkey, &cert, NULL);
if (!cert)
EXCEPTION(gourou::CLIENT_INVALID_PKCS12, ERR_error_string(ERR_get_error(), NULL));
PKCS12_parse(pkcs12, password.c_str(), &pkey, &cert, &ca);
*certOutLength = i2d_X509(cert, certOut);
@ -586,38 +251,33 @@ void DRMProcessorClientImpl::extractCertificate(const unsigned char* RSAKey, uns
}
/* Crypto interface */
void DRMProcessorClientImpl::encrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining,
void DRMProcessorClientImpl::AESEncrypt(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)
{
void* handler = encryptInit(algo, chaining, key, keyLength, iv, ivLength);
encryptUpdate(handler, dataIn, dataInLength, dataOut, dataOutLength);
encryptFinalize(handler, dataOut+*dataOutLength, dataOutLength);
void* handler = AESEncryptInit(chaining, key, keyLength, iv, ivLength);
AESEncryptUpdate(handler, dataIn, dataInLength, dataOut, dataOutLength);
AESEncryptFinalize(handler, dataOut+*dataOutLength, dataOutLength);
}
void* DRMProcessorClientImpl::encryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining,
void* DRMProcessorClientImpl::AESEncryptInit(CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength,
const unsigned char* iv, unsigned int ivLength)
{
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
int ret = 0;
switch (algo)
{
case ALGO_AES:
{
switch(keyLength)
{
case 16:
switch(chaining)
{
case CHAIN_ECB:
ret = EVP_EncryptInit(ctx, EVP_aes_128_ecb(), key, iv);
EVP_EncryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, key, iv);
break;
case CHAIN_CBC:
ret = EVP_EncryptInit(ctx, EVP_aes_128_cbc(), key, iv);
EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv);
break;
default:
EXCEPTION(gourou::CLIENT_BAD_CHAINING, "Unknown chaining mode " << chaining);
@ -627,50 +287,26 @@ void* DRMProcessorClientImpl::encryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaini
EVP_CIPHER_CTX_free(ctx);
EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength);
}
break;
}
case ALGO_RC4:
{
if (keyLength != 16)
{
EVP_CIPHER_CTX_free(ctx);
EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength);
}
ret = EVP_DecryptInit(ctx, EVP_rc4(), key, iv);
break;
}
}
if (ret <= 0)
{
EVP_CIPHER_CTX_free(ctx);
EXCEPTION(gourou::CLIENT_CRYPT_ERROR, ERR_error_string(ERR_get_error(), NULL));
}
return ctx;
}
void* DRMProcessorClientImpl::decryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining,
void* DRMProcessorClientImpl::AESDecryptInit(CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength,
const unsigned char* iv, unsigned int ivLength)
{
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
int ret = 0;
switch(algo)
{
case ALGO_AES:
{
switch(keyLength)
{
case 16:
switch(chaining)
{
case CHAIN_ECB:
ret = EVP_DecryptInit(ctx, EVP_aes_128_ecb(), key, iv);
EVP_DecryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, key, iv);
break;
case CHAIN_CBC:
ret = EVP_DecryptInit(ctx, EVP_aes_128_cbc(), key, iv);
EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv);
break;
default:
EXCEPTION(gourou::CLIENT_BAD_CHAINING, "Unknown chaining mode " << chaining);
@ -680,81 +316,48 @@ void* DRMProcessorClientImpl::decryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaini
EVP_CIPHER_CTX_free(ctx);
EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength);
}
break;
}
case ALGO_RC4:
{
if (keyLength != 16)
{
EVP_CIPHER_CTX_free(ctx);
EXCEPTION(gourou::CLIENT_BAD_KEY_SIZE, "Invalid key size " << keyLength);
}
ret = EVP_DecryptInit(ctx, EVP_rc4(), key, iv);
break;
}
}
if (ret <= 0)
{
EVP_CIPHER_CTX_free(ctx);
EXCEPTION(gourou::CLIENT_CRYPT_ERROR, ERR_error_string(ERR_get_error(), NULL));
}
return ctx;
}
void DRMProcessorClientImpl::encryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength,
void DRMProcessorClientImpl::AESEncryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength)
{
int ret = EVP_EncryptUpdate((EVP_CIPHER_CTX*)handler, dataOut, (int*)dataOutLength, dataIn, dataInLength);
if (ret <= 0)
EXCEPTION(gourou::CLIENT_CRYPT_ERROR, ERR_error_string(ERR_get_error(), NULL));
EVP_EncryptUpdate((EVP_CIPHER_CTX*)handler, dataOut, (int*)dataOutLength, dataIn, dataInLength);
}
void DRMProcessorClientImpl::encryptFinalize(void* handler,
void DRMProcessorClientImpl::AESEncryptFinalize(void* handler,
unsigned char* dataOut, unsigned int* dataOutLength)
{
int len, ret;
ret = EVP_EncryptFinal_ex((EVP_CIPHER_CTX*)handler, dataOut, &len);
int len;
EVP_EncryptFinal_ex((EVP_CIPHER_CTX*)handler, dataOut, &len);
*dataOutLength += len;
EVP_CIPHER_CTX_free((EVP_CIPHER_CTX*)handler);
if (ret <= 0)
EXCEPTION(gourou::CLIENT_CRYPT_ERROR, ERR_error_string(ERR_get_error(), NULL));
}
void DRMProcessorClientImpl::decrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining,
void DRMProcessorClientImpl::AESDecrypt(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)
{
void* handler = decryptInit(algo, chaining, key, keyLength, iv, ivLength);
decryptUpdate(handler, dataIn, dataInLength, dataOut, dataOutLength);
decryptFinalize(handler, dataOut+*dataOutLength, dataOutLength);
void* handler = AESDecryptInit(chaining, key, keyLength, iv, ivLength);
AESDecryptUpdate(handler, dataIn, dataInLength, dataOut, dataOutLength);
AESDecryptFinalize(handler, dataOut+*dataOutLength, dataOutLength);
}
void DRMProcessorClientImpl::decryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength,
void DRMProcessorClientImpl::AESDecryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength)
{
int ret = EVP_DecryptUpdate((EVP_CIPHER_CTX*)handler, dataOut, (int*)dataOutLength, dataIn, dataInLength);
if (ret <= 0)
EXCEPTION(gourou::CLIENT_CRYPT_ERROR, ERR_error_string(ERR_get_error(), NULL));
EVP_DecryptUpdate((EVP_CIPHER_CTX*)handler, dataOut, (int*)dataOutLength, dataIn, dataInLength);
}
void DRMProcessorClientImpl::decryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength)
void DRMProcessorClientImpl::AESDecryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength)
{
int len, ret;
ret = EVP_DecryptFinal_ex((EVP_CIPHER_CTX*)handler, dataOut, &len);
int len;
EVP_DecryptFinal_ex((EVP_CIPHER_CTX*)handler, dataOut, &len);
*dataOutLength += len;
EVP_CIPHER_CTX_free((EVP_CIPHER_CTX*)handler);
if (ret <= 0)
EXCEPTION(gourou::CLIENT_CRYPT_ERROR, ERR_error_string(ERR_get_error(), NULL));
}
void* DRMProcessorClientImpl::zipOpen(const std::string& path)
@ -767,39 +370,35 @@ void* DRMProcessorClientImpl::zipOpen(const std::string& path)
return handler;
}
void DRMProcessorClientImpl::zipReadFile(void* handler, const std::string& path, gourou::ByteArray& result, bool decompress)
std::string DRMProcessorClientImpl::zipReadFile(void* handler, const std::string& path)
{
std::string res;
unsigned char* buffer;
zip_stat_t sb;
if (zip_stat((zip_t *)handler, path.c_str(), 0, &sb) < 0)
EXCEPTION(gourou::CLIENT_ZIP_ERROR, "Zip error, no file " << path << ", " << zip_strerror((zip_t *)handler));
EXCEPTION(gourou::CLIENT_ZIP_ERROR, "Zip error " << zip_strerror((zip_t *)handler));
if (!(sb.valid & (ZIP_STAT_INDEX|ZIP_STAT_SIZE)))
EXCEPTION(gourou::CLIENT_ZIP_ERROR, "Required fields missing");
result.resize(sb.size);
buffer = new unsigned char[sb.size];
zip_file_t *f = zip_fopen_index((zip_t *)handler, sb.index, (decompress)?0:ZIP_FL_COMPRESSED);
zip_fread(f, result.data(), sb.size);
zip_file_t *f = zip_fopen_index((zip_t *)handler, sb.index, ZIP_FL_COMPRESSED);
zip_fread(f, buffer, sb.size);
zip_fclose(f);
res = std::string((char*)buffer, sb.size);
delete[] buffer;
return res;
}
void DRMProcessorClientImpl::zipWriteFile(void* handler, const std::string& path, gourou::ByteArray& content)
void DRMProcessorClientImpl::zipWriteFile(void* handler, const std::string& path, const std::string& content)
{
zip_int64_t ret;
zip_source_t* s = zip_source_buffer((zip_t*)handler, content.takeShadowData(), content.length(), 1);
zip_int64_t idx = zip_name_locate((zip_t*)handler, path.c_str(), 0);
// File doesn't exists
if (idx == -1)
ret = zip_file_add((zip_t*)handler, path.c_str(), s, 0);
else
ret = zip_file_replace((zip_t*)handler, idx, s, ZIP_FL_OVERWRITE);
if (ret < 0)
zip_source_t* s = zip_source_buffer((zip_t*)handler, content.c_str(), content.length(), 0);
if (zip_file_add((zip_t*)handler, path.c_str(), s, ZIP_FL_OVERWRITE|ZIP_FL_ENC_UTF_8) < 0)
{
zip_source_free(s);
EXCEPTION(gourou::CLIENT_ZIP_ERROR, "Zip error " << zip_strerror((zip_t *)handler));
@ -821,93 +420,3 @@ void DRMProcessorClientImpl::zipClose(void* handler)
{
zip_close((zip_t*)handler);
}
void DRMProcessorClientImpl::inflate(gourou::ByteArray& data, gourou::ByteArray& result,
int wbits)
{
unsigned int dataSize = data.size()*2;
unsigned char* buffer = new unsigned char[dataSize];
z_stream infstream;
infstream.zalloc = Z_NULL;
infstream.zfree = Z_NULL;
infstream.opaque = Z_NULL;
infstream.avail_in = (uInt)data.size();
infstream.next_in = (Bytef *)data.data(); // input char array
infstream.avail_out = (uInt)dataSize; // size of output
infstream.next_out = (Bytef *)buffer; // output char array
int ret = inflateInit2(&infstream, wbits);
if (ret != Z_OK)
EXCEPTION(gourou::CLIENT_ZIP_ERROR, "Inflate error, code " << zError(ret) << ", msg " << infstream.msg);
ret = ::inflate(&infstream, Z_FINISH);
while (ret == Z_OK || ret == Z_STREAM_END || ret == Z_BUF_ERROR)
{
// Real error
if (ret == Z_BUF_ERROR && infstream.avail_out == (uInt)dataSize)
break;
result.append(buffer, dataSize-infstream.avail_out);
if ((ret == Z_OK && infstream.avail_out != 0) || ret == Z_STREAM_END)
break;
infstream.avail_out = (uInt)dataSize; // size of output
infstream.next_out = (Bytef *)buffer; // output char array
ret = ::inflate(&infstream, Z_FINISH);
}
if (ret == Z_STREAM_END)
ret = inflateEnd(&infstream);
delete[] buffer;
if (ret != Z_OK && ret != Z_STREAM_END)
EXCEPTION(gourou::CLIENT_ZIP_ERROR, "Inflate error, code " << zError(ret) << ", msg " << infstream.msg);
}
void DRMProcessorClientImpl::deflate(gourou::ByteArray& data, gourou::ByteArray& result,
int wbits, int compressionLevel)
{
unsigned int dataSize = data.size();
unsigned char* buffer = new unsigned char[dataSize];
z_stream defstream;
defstream.zalloc = Z_NULL;
defstream.zfree = Z_NULL;
defstream.opaque = Z_NULL;
defstream.avail_in = (uInt)dataSize;
defstream.next_in = (Bytef *)data.data(); // input char array
defstream.avail_out = (uInt)dataSize; // size of output
defstream.next_out = (Bytef *)buffer; // output char array
int ret = deflateInit2(&defstream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, wbits,
compressionLevel, Z_DEFAULT_STRATEGY);
if (ret != Z_OK)
EXCEPTION(gourou::CLIENT_ZIP_ERROR, "Deflate error, code " << zError(ret) << ", msg " << defstream.msg);
ret = ::deflate(&defstream, Z_FINISH);
while (ret == Z_OK || ret == Z_STREAM_END)
{
result.append(buffer, dataSize-defstream.avail_out);
if ((ret == Z_OK && defstream.avail_out != 0) || ret == Z_STREAM_END)
break;
defstream.avail_out = (uInt)dataSize; // size of output
defstream.next_out = (Bytef *)buffer; // output char array
ret = ::deflate(&defstream, Z_FINISH);
}
if (ret == Z_STREAM_END)
ret = deflateEnd(&defstream);
delete[] buffer;
if (ret != Z_OK && ret != Z_STREAM_END)
EXCEPTION(gourou::CLIENT_ZIP_ERROR, "Deflate error, code " << zError(ret) << ", msg " << defstream.msg);
}

View file

@ -31,40 +31,28 @@
#include <string>
#if OPENSSL_VERSION_MAJOR >= 3
#include <openssl/provider.h>
#endif
#include <drmprocessorclient.h>
class DRMProcessorClientImpl : public gourou::DRMProcessorClient
{
public:
DRMProcessorClientImpl();
~DRMProcessorClientImpl();
/* Digest interface */
virtual void* createDigest(const std::string& digestName);
virtual void digestUpdate(void* handler, unsigned char* data, unsigned int length);
virtual void digestFinalize(void* handler,unsigned char* digestOut);
virtual void digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut);
virtual int digestUpdate(void* handler, unsigned char* data, unsigned int length);
virtual int digestFinalize(void* handler,unsigned char* digestOut);
virtual int digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut);
/* Random interface */
virtual void randBytes(unsigned char* bytesOut, unsigned int length);
/* HTTP interface */
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(""));
virtual void RSAPrivateEncrypt(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);
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);
virtual void RSAPublicEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength,
const RSA_KEY_TYPE keyType,
const unsigned char* data, unsigned dataLength,
@ -80,66 +68,46 @@ public:
unsigned char** certOut, unsigned int* certOutLength);
/* Crypto interface */
virtual void encrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining,
virtual void AESEncrypt(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);
virtual void* encryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining,
virtual void* AESEncryptInit(CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength,
const unsigned char* iv=0, unsigned int ivLength=0);
virtual void encryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength,
virtual void AESEncryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength);
virtual void encryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength);
virtual void AESEncryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength);
virtual void decrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining,
virtual void AESDecrypt(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);
virtual void* decryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining,
virtual void* AESDecryptInit(CHAINING_MODE chaining,
const unsigned char* key, unsigned int keyLength,
const unsigned char* iv=0, unsigned int ivLength=0);
virtual void decryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength,
virtual void AESDecryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength,
unsigned char* dataOut, unsigned int* dataOutLength);
virtual void decryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength);
virtual void AESDecryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength);
/* ZIP Interface */
virtual void* zipOpen(const std::string& path);
virtual void zipReadFile(void* handler, const std::string& path, gourou::ByteArray& result, bool decompress=true);
virtual std::string zipReadFile(void* handler, const std::string& path);
virtual void zipWriteFile(void* handler, const std::string& path, gourou::ByteArray& content);
virtual void zipWriteFile(void* handler, const std::string& path, const std::string& content);
virtual void zipDeleteFile(void* handler, const std::string& path);
virtual void zipClose(void* handler);
virtual void inflate(gourou::ByteArray& data, gourou::ByteArray& result,
int wbits=-15);
virtual void deflate(gourou::ByteArray& data, gourou::ByteArray& result,
int wbits=-15, int compressionLevel=8);
private:
void padWithPKCS1(unsigned char* out, unsigned int outLength,
const unsigned char* in, unsigned int inLength);
void padWithPKCS1Type2(unsigned char* out, unsigned int outLength,
const unsigned char* in, unsigned int inLength);
#if OPENSSL_VERSION_MAJOR >= 3
OSSL_PROVIDER *legacy, *deflt;
#else
void *legacy, *deflt;
#endif
char cookiejar[64];
};
#endif

View file

@ -1,40 +0,0 @@
#include <iostream>
#include <unistd.h>
#include <libgen.h>
#include <string.h>
#include "utils_common.h"
#ifndef DEFAULT_UTIL
#define DEFAULT_UTIL "acsmdownloader"
#endif
/* Inspired from https://discourse.appimage.org/t/call-alternative-binary-from-appimage/93/10*/
int main(int argc, char** argv)
{
char* util, *argv0;
char* mountPoint = getenv("APPDIR");
std::string fullPath;
/* Original command is in ARGV0 env variable*/
argv0 = strdup(getenv("ARGV0"));
util = basename(argv0);
fullPath = std::string(mountPoint) + util;
if (std::string(util) == "launcher" || !pathExists(fullPath.c_str()))
fullPath = std::string(mountPoint) + DEFAULT_UTIL;
free(argv0);
argv[0] = strdup(fullPath.c_str());
if (execvp(argv[0], argv))
std::cout << "Unable to launch '" << argv[0] << "'" << std::endl;
/* Should not happens */
free(argv[0]);
return 0;
}

View file

@ -1,61 +0,0 @@
.\" 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

@ -1,67 +0,0 @@
.\" 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

@ -1,46 +0,0 @@
.\" 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

View file

@ -1,55 +0,0 @@
.\" 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

@ -1,164 +0,0 @@
/*
Copyright (c) 2022, Grégory Soutadé
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <limits.h>
#include <libgen.h>
#include <iostream>
#include <libgourou.h>
#include <libgourou_common.h>
#include "utils_common.h"
static const char* defaultDirs[] = {
".adept/",
"./adobe-digital-editions/",
"./.adobe-digital-editions/"
};
void version(void)
{
std::cout << "Current libgourou version : " << gourou::DRMProcessor::VERSION << std::endl ;
}
bool pathExists(const char* path)
{
struct stat _stat;
int ret = stat(path, &_stat);
return (ret == 0);
}
const char* findFile(const char* filename, bool inDefaultDirs)
{
std::string path;
const char* adeptDir = getenv("ADEPT_DIR");
if (adeptDir && adeptDir[0])
{
path = adeptDir + std::string("/") + filename;
if (pathExists(path.c_str()))
return strdup(path.c_str());
}
path = gourou::DRMProcessor::getDefaultAdeptDir() + filename;
if (pathExists(path.c_str()))
return strdup(path.c_str());
if (pathExists(filename))
return strdup(filename);
if (!inDefaultDirs) return 0;
for (int i=0; i<(int)ARRAY_SIZE(defaultDirs); i++)
{
path = std::string(defaultDirs[i]) + filename;
if (pathExists(path.c_str()))
return strdup(path.c_str());
}
return 0;
}
// https://stackoverflow.com/questions/2336242/recursive-mkdir-system-call-on-unix
void mkpath(const char *dir)
{
char tmp[PATH_MAX];
char *p = NULL;
size_t len;
snprintf(tmp, sizeof(tmp),"%s",dir);
len = strlen(tmp);
if (tmp[len - 1] == '/')
tmp[len - 1] = 0;
for (p = tmp + 1; *p; p++)
if (*p == '/') {
*p = 0;
mkdir(tmp, S_IRWXU);
*p = '/';
}
mkdir(tmp, S_IRWXU);
}
void fileCopy(const char* in, const char* out)
{
char buffer[4096], *_buffer;
int ret, ret2, fdIn, fdOut;
fdIn = open(in, O_RDONLY);
if (!fdIn)
EXCEPTION(gourou::CLIENT_FILE_ERROR, "Unable to open " << in);
fdOut = gourou::createNewFile(out);
if (!fdOut)
{
close (fdIn);
EXCEPTION(gourou::CLIENT_FILE_ERROR, "Unable to open " << out);
}
while (true)
{
ret = ::read(fdIn, buffer, sizeof(buffer));
if (ret <= 0)
break;
do
{
_buffer = buffer;
ret2 = ::write(fdOut, _buffer, ret);
if (ret2 >= 0)
{
ret -= ret2;
_buffer += ret2;
}
else
{
EXCEPTION(gourou::CLIENT_FILE_ERROR, "Error writing " << out);
}
} while (ret);
}
close (fdIn);
close (fdOut);
}
void createPath(const char* filename)
{
char* basepath = strdup(filename);
char* outputDir = dirname(basepath);
if (outputDir && !pathExists(outputDir))
mkpath(outputDir);
free(basepath);
}

View file

@ -1,72 +0,0 @@
/*
Copyright (c) 2022, Grégory Soutadé
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef _UTILS_COMMON_H_
#define _UTILS_COMMON_H_
#define LOANS_DIR "loans/"
#define ID_HASH_SIZE 16
#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
/**
* @brief Display libgourou version
*/
void version(void);
/**
* @brief Find a given filename in current directory and/or in default directories
*
* @param filename Filename to search
* @param inDefaultDirs Search is default directories or not
*
* @return A copy of full path
*/
const char* findFile(const char* filename, bool inDefaultDirs=true);
/**
* @brief Does the file (or directory) exists
*/
bool pathExists(const char* path);
/**
* @brief Recursively created dir
*/
void mkpath(const char *dir);
/**
* @brief Copy file in into file out
*/
void fileCopy(const char* in, const char* out);
/**
* @brief Create intermediate directories if it does not exists
*/
void createPath(const char* filename);
#endif