1 | /*␊ |
2 | Copyright (c) 2021, Grégory Soutadé␊ |
3 | ␊ |
4 | All rights reserved.␊ |
5 | Redistribution and use in source and binary forms, with or without␊ |
6 | modification, are permitted provided that the following conditions are met:␊ |
7 | ␊ |
8 | * Redistributions of source code must retain the above copyright␊ |
9 | notice, this list of conditions and the following disclaimer.␊ |
10 | * Redistributions in binary form must reproduce the above copyright␊ |
11 | notice, this list of conditions and the following disclaimer in the␊ |
12 | documentation and/or other materials provided with the distribution.␊ |
13 | * Neither the name of the copyright holder nor the␊ |
14 | names of its contributors may be used to endorse or promote products␊ |
15 | derived from this software without specific prior written permission.␊ |
16 | ␊ |
17 | THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY␊ |
18 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED␊ |
19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE␊ |
20 | DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY␊ |
21 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES␊ |
22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;␊ |
23 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND␊ |
24 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT␊ |
25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS␊ |
26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.␊ |
27 | */␊ |
28 | ␊ |
29 | #include <getopt.h>␊ |
30 | #include <libgen.h>␊ |
31 | ␊ |
32 | #include <iostream>␊ |
33 | ␊ |
34 | #include <libgourou.h>␊ |
35 | #include <libgourou_common.h>␊ |
36 | ␊ |
37 | #include "drmprocessorclientimpl.h"␊ |
38 | #include "utils_common.h"␊ |
39 | ␊ |
40 | static const char* deviceFile = "device.xml";␊ |
41 | static const char* activationFile = "activation.xml";␊ |
42 | static const char* devicekeyFile = "devicesalt";␊ |
43 | static const char* inputFile = 0;␊ |
44 | static const char* outputFile = 0;␊ |
45 | static const char* outputDir = 0;␊ |
46 | ␊ |
47 | static char* encryptionKeyUser = 0;␊ |
48 | static unsigned char* encryptionKey = 0;␊ |
49 | static unsigned encryptionKeySize = 0;␊ |
50 | ␊ |
51 | static inline unsigned char htoi(unsigned char c)␊ |
52 | {␊ |
53 | if (c >= '0' && c <= '9')␊ |
54 | ␉c -= '0';␊ |
55 | else if (c >= 'a' && c <= 'f')␊ |
56 | ␉c -= 'a' - 10;␊ |
57 | else if (c >= 'A' && c <= 'F')␊ |
58 | ␉c -= 'A' - 10;␊ |
59 | else␊ |
60 | ␉EXCEPTION(gourou::USER_INVALID_INPUT, "Invalid character " << c << " in encryption key");␊ |
61 | ␊ |
62 | return c;␊ |
63 | }␊ |
64 | ␊ |
65 | static inline bool endsWith(const std::string& s, const std::string& suffix)␊ |
66 | {␊ |
67 | return s.rfind(suffix) == std::abs((int)(s.size()-suffix.size()));␊ |
68 | }␊ |
69 | ␊ |
70 | class ADEPTRemove␊ |
71 | {␊ |
72 | public:␊ |
73 | ␊ |
74 | int run()␊ |
75 | {␊ |
76 | ␉int ret = 0;␊ |
77 | ␉try␊ |
78 | ␉{␊ |
79 | ␉ gourou::DRMProcessor::ITEM_TYPE type;␊ |
80 | ␉ DRMProcessorClientImpl client;␊ |
81 | ␉ gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile);␊ |
82 | ␉ ␊ |
83 | ␉ std::string filename;␊ |
84 | ␉ if (!outputFile)␊ |
85 | ␉␉filename = std::string(inputFile);␊ |
86 | ␉ else␊ |
87 | ␉␉filename = outputFile;␊ |
88 | ␉ ␊ |
89 | ␉ if (outputDir)␊ |
90 | ␉ {␊ |
91 | ␉␉if (!fileExists(outputDir))␊ |
92 | ␉␉ mkpath(outputDir);␊ |
93 | ␊ |
94 | ␉␉filename = std::string(outputDir) + "/" + filename;␊ |
95 | ␉ }␊ |
96 | ␊ |
97 | ␉ if (endsWith(filename, ".epub"))␊ |
98 | ␉␉type = gourou::DRMProcessor::ITEM_TYPE::EPUB;␊ |
99 | ␉ else if (endsWith(filename, ".pdf"))␊ |
100 | ␉␉type = gourou::DRMProcessor::ITEM_TYPE::PDF;␊ |
101 | ␉ else␊ |
102 | ␉ {␊ |
103 | ␉␉EXCEPTION(gourou::DRM_FORMAT_NOT_SUPPORTED, "Unsupported file format of " << filename);␊ |
104 | ␉ }␊ |
105 | ␉ ␊ |
106 | ␉ if (inputFile != filename)␊ |
107 | ␉ {␊ |
108 | ␉␉unlink(filename.c_str());␊ |
109 | ␉␉fileCopy(inputFile, filename.c_str());␊ |
110 | ␉␉processor.removeDRM(inputFile, filename, type, encryptionKey, encryptionKeySize);␊ |
111 | ␉␉std::cout << "DRM removed into new file " << filename << std::endl;␊ |
112 | ␉ }␊ |
113 | ␉ else␊ |
114 | ␉ {␊ |
115 | ␉␉// Use temp file for PDF␊ |
116 | ␉␉if (type == gourou::DRMProcessor::ITEM_TYPE::PDF)␊ |
117 | ␉␉{␊ |
118 | ␉␉ std::string tempFile = filename + ".tmp";␊ |
119 | ␉␉ /* Be sure there is not already a temp file */␊ |
120 | ␉␉ unlink(tempFile.c_str());␊ |
121 | ␉␉ processor.removeDRM(filename, tempFile, type, encryptionKey, encryptionKeySize);␊ |
122 | ␉␉ /* Original file must be removed before doing a copy... */␊ |
123 | ␉␉ unlink(filename.c_str());␊ |
124 | ␉␉ if (rename(tempFile.c_str(), filename.c_str()))␊ |
125 | ␉␉ {␊ |
126 | ␉␉␉EXCEPTION(gourou::DRM_FILE_ERROR, "Unable to copy " << tempFile << " into " << filename);␊ |
127 | ␉␉ }␊ |
128 | ␉␉}␊ |
129 | ␉␉else␊ |
130 | ␉␉ processor.removeDRM(inputFile, filename, type, encryptionKey, encryptionKeySize);␊ |
131 | ␉␉std::cout << "DRM removed from " << filename << std::endl;␊ |
132 | ␉ }␊ |
133 | ␉} catch(std::exception& e)␊ |
134 | ␉{␊ |
135 | ␉ std::cout << e.what() << std::endl;␊ |
136 | ␉ ret = 1;␊ |
137 | ␉}␊ |
138 | ␊ |
139 | ␉return ret;␊ |
140 | }␊ |
141 | };␉ ␊ |
142 | ␊ |
143 | static void usage(const char* cmd)␊ |
144 | {␊ |
145 | std::cout << basename((char*)cmd) << " remove ADEPT DRM (from Adobe) of EPUB/PDF file" << std::endl << std::endl;␊ |
146 | ␊ |
147 | std::cout << "Usage: " << basename((char*)cmd) << " [OPTIONS] file(.epub|pdf)" << std::endl << std::endl;␊ |
148 | ␊ |
149 | std::cout << "Global Options:" << std::endl;␊ |
150 | std::cout << " " << "-O|--output-dir" << "\t" << "Optional output directory were to put result (default ./)" << std::endl;␊ |
151 | std::cout << " " << "-o|--output-file" << "\t" << "Optional output filename (default inplace DRM removal>)" << std::endl;␊ |
152 | std::cout << " " << "-f|--input-file" << "\t" << "Backward compatibility: EPUB/PDF file to process" << std::endl;␊ |
153 | std::cout << " " << "-v|--verbose" << "\t\t" << "Increase verbosity, can be set multiple times" << std::endl;␊ |
154 | std::cout << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl;␊ |
155 | std::cout << " " << "-h|--help" << "\t\t" << "This help" << std::endl;␊ |
156 | ␊ |
157 | std::cout << "ADEPT Options:" << std::endl;␊ |
158 | std::cout << " " << "-D|--adept-directory" << "\t" << ".adept directory that must contains device.xml, activation.xml and devicesalt" << std::endl;␊ |
159 | std::cout << " " << "-d|--device-file" << "\t" << "device.xml file from eReader" << std::endl;␊ |
160 | std::cout << " " << "-a|--activation-file" << "\t" << "activation.xml file from eReader" << std::endl;␊ |
161 | std::cout << " " << "-k|--device-key-file" << "\t" << "private device key file (eg devicesalt/devkey.bin) from eReader" << std::endl;␊ |
162 | ␊ |
163 | std::cout << std::endl;␊ |
164 | ␊ |
165 | std::cout << "Environment:" << std::endl;␊ |
166 | std::cout << "Device file, activation file and device key file are optionals. If not set, they are looked into :" << std::endl;␊ |
167 | std::cout << " * $ADEPT_DIR environment variable" << std::endl;␊ |
168 | std::cout << " * /home/<user>/.config/adept" << std::endl;␊ |
169 | std::cout << " * Current directory" << std::endl;␊ |
170 | std::cout << " * .adept" << std::endl;␊ |
171 | std::cout << " * adobe-digital-editions directory" << std::endl;␊ |
172 | std::cout << " * .adobe-digital-editions directory" << std::endl;␊ |
173 | }␊ |
174 | ␊ |
175 | int main(int argc, char** argv)␊ |
176 | {␊ |
177 | int c, ret = -1;␊ |
178 | ␊ |
179 | const char** files[] = {&devicekeyFile, &deviceFile, &activationFile};␊ |
180 | int verbose = gourou::DRMProcessor::getLogLevel();␊ |
181 | std::string _deviceFile, _activationFile, _devicekeyFile;␊ |
182 | ␊ |
183 | while (1) {␊ |
184 | ␉int option_index = 0;␊ |
185 | ␉static struct option long_options[] = {␊ |
186 | ␉ {"adept-directory", required_argument, 0, 'D' },␊ |
187 | ␉ {"device-file", required_argument, 0, 'd' },␊ |
188 | ␉ {"activation-file", required_argument, 0, 'a' },␊ |
189 | ␉ {"device-key-file", required_argument, 0, 'k' },␊ |
190 | ␉ {"output-dir", required_argument, 0, 'O' },␊ |
191 | ␉ {"output-file", required_argument, 0, 'o' },␊ |
192 | ␉ {"input-file", required_argument, 0, 'f' },␊ |
193 | ␉ {"encryption-key", required_argument, 0, 'K' }, // Private option␊ |
194 | ␉ {"verbose", no_argument, 0, 'v' },␊ |
195 | ␉ {"version", no_argument, 0, 'V' },␊ |
196 | ␉ {"help", no_argument, 0, 'h' },␊ |
197 | ␉ {0, 0, 0, 0 }␊ |
198 | ␉};␊ |
199 | ␊ |
200 | ␉c = getopt_long(argc, argv, "D:d:a:k:O:o:f:K:vVh",␊ |
201 | long_options, &option_index);␊ |
202 | ␉if (c == -1)␊ |
203 | ␉ break;␊ |
204 | ␊ |
205 | ␉switch (c) {␊ |
206 | ␉case 'D':␊ |
207 | ␉ _deviceFile = std::string(optarg) + "/device.xml";␊ |
208 | ␉ _activationFile = std::string(optarg) + "/activation.xml";␊ |
209 | ␉ _devicekeyFile = std::string(optarg) + "/devicesalt";␊ |
210 | ␉ deviceFile = _deviceFile.c_str();␊ |
211 | ␉ activationFile = _activationFile.c_str();␊ |
212 | ␉ devicekeyFile = _devicekeyFile.c_str();␊ |
213 | ␉ break;␊ |
214 | ␉case 'd':␊ |
215 | ␉ deviceFile = optarg;␊ |
216 | ␉ break;␊ |
217 | ␉case 'a':␊ |
218 | ␉ activationFile = optarg;␊ |
219 | ␉ break;␊ |
220 | ␉case 'k':␊ |
221 | ␉ devicekeyFile = optarg;␊ |
222 | ␉ break;␊ |
223 | ␉case 'f':␊ |
224 | ␉ inputFile = optarg;␊ |
225 | ␉ break;␊ |
226 | ␉case 'O':␊ |
227 | ␉ outputDir = optarg;␊ |
228 | ␉ break;␊ |
229 | ␉case 'o':␊ |
230 | ␉ outputFile = optarg;␊ |
231 | ␉ break;␊ |
232 | ␉case 'K':␊ |
233 | ␉ encryptionKeyUser = optarg;␊ |
234 | ␉ break;␊ |
235 | ␉case 'v':␊ |
236 | ␉ verbose++;␊ |
237 | ␉ break;␊ |
238 | ␉case 'V':␊ |
239 | ␉ version();␊ |
240 | ␉ return 0;␊ |
241 | ␉case 'h':␊ |
242 | ␉ usage(argv[0]);␊ |
243 | ␉ return 0;␊ |
244 | ␉default:␊ |
245 | ␉ usage(argv[0]);␊ |
246 | ␉ return -1;␊ |
247 | ␉}␊ |
248 | }␊ |
249 | ␊ |
250 | gourou::DRMProcessor::setLogLevel(verbose);␊ |
251 | ␊ |
252 | if (optind == argc-1)␊ |
253 | ␉inputFile = argv[optind];␊ |
254 | ␊ |
255 | if (!inputFile || (outputDir && !outputDir[0]) ||␊ |
256 | ␉(outputFile && !outputFile[0]))␊ |
257 | {␊ |
258 | ␉usage(argv[0]);␊ |
259 | ␉return -1;␊ |
260 | }␊ |
261 | ␊ |
262 | ADEPTRemove remover;␊ |
263 | ␊ |
264 | int i;␊ |
265 | bool hasErrors = false;␊ |
266 | const char* orig;␊ |
267 | for (i=0; i<(int)ARRAY_SIZE(files); i++)␊ |
268 | {␊ |
269 | ␉orig = *files[i];␊ |
270 | ␉*files[i] = findFile(*files[i]);␊ |
271 | ␉if (!*files[i])␊ |
272 | ␉{␊ |
273 | ␉ std::cout << "Error : " << orig << " doesn't exists, did you activate your device ?" << std::endl;␊ |
274 | ␉ ret = -1;␊ |
275 | ␉ hasErrors = true;␊ |
276 | ␉}␊ |
277 | }␊ |
278 | ␊ |
279 | if (encryptionKeyUser)␊ |
280 | {␊ |
281 | ␉int size = std::string(encryptionKeyUser).size();␊ |
282 | ␉if ((size % 2))␊ |
283 | ␉{␊ |
284 | ␉ std::cout << "Error : Encryption key must be odd length" << std::endl;␊ |
285 | ␉ goto end;␊ |
286 | ␉}␊ |
287 | ␊ |
288 | ␉if (encryptionKeyUser[0] == '0' && encryptionKeyUser[1] == 'x')␊ |
289 | ␉{␊ |
290 | ␉ encryptionKeyUser += 2;␊ |
291 | ␉ size -= 2;␊ |
292 | ␉}␊ |
293 | ␊ |
294 | ␉encryptionKey = new unsigned char[size/2];␊ |
295 | ␊ |
296 | ␉for(i=0; i<size; i+=2)␊ |
297 | ␉{␊ |
298 | ␉ encryptionKey[i/2] = htoi(encryptionKeyUser[i]) << 4;␊ |
299 | ␉ encryptionKey[i/2] |= htoi(encryptionKeyUser[i+1]);␊ |
300 | ␉}␊ |
301 | ␊ |
302 | ␉encryptionKeySize = size/2;␊ |
303 | }␊ |
304 | ␊ |
305 | if (hasErrors)␊ |
306 | ␉goto end;␊ |
307 | ␊ |
308 | ret = remover.run();␊ |
309 | ␊ |
310 | end:␊ |
311 | for (i=0; i<(int)ARRAY_SIZE(files); i++)␊ |
312 | {␊ |
313 | ␉if (*files[i])␊ |
314 | ␉ free((void*)*files[i]);␊ |
315 | }␊ |
316 | ␊ |
317 | if (encryptionKey)␊ |
318 | ␉free(encryptionKey);␊ |
319 | ␊ |
320 | return ret;␊ |
321 | }␊ |