1 | /*␊ |
2 | Copyright 2021 Grégory Soutadé␊ |
3 | ␊ |
4 | This file is part of libgourou.␊ |
5 | ␊ |
6 | libgourou is free software: you can redistribute it and/or modify␊ |
7 | it under the terms of the GNU Lesser General Public License as published by␊ |
8 | the Free Software Foundation, either version 3 of the License, or␊ |
9 | (at your option) any later version.␊ |
10 | ␊ |
11 | libgourou is distributed in the hope that it will be useful,␊ |
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of␊ |
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the␊ |
14 | GNU Lesser General Public License for more details.␊ |
15 | ␊ |
16 | You should have received a copy of the GNU Lesser General Public License␊ |
17 | along with libgourou. If not, see <http://www.gnu.org/licenses/>.␊ |
18 | */␊ |
19 | ␊ |
20 | #ifndef _LIBGOUROU_COMMON_H_␊ |
21 | #define _LIBGOUROU_COMMON_H_␊ |
22 | ␊ |
23 | #include <sys/types.h>␊ |
24 | #include <sys/stat.h>␊ |
25 | #include <fcntl.h>␊ |
26 | #include <unistd.h>␊ |
27 | ␊ |
28 | #include <pugixml.hpp>␊ |
29 | ␊ |
30 | #include <exception>␊ |
31 | #include <iostream>␊ |
32 | #include <sstream>␊ |
33 | #include <iomanip>␊ |
34 | #include <algorithm>␊ |
35 | ␊ |
36 | #include <string.h>␊ |
37 | ␊ |
38 | #include <libgourou_log.h>␊ |
39 | #include "bytearray.h"␊ |
40 | ␊ |
41 | namespace gourou␊ |
42 | {␊ |
43 | /**␊ |
44 | * Some common utilities␊ |
45 | */␊ |
46 | ␊ |
47 | #define ADOBE_ADEPT_NS "http://ns.adobe.com/adept"␊ |
48 | ␊ |
49 | static const int SHA1_LEN = 20;␊ |
50 | static const int RSA_KEY_SIZE = 128;␊ |
51 | static const int RSA_KEY_SIZE_BITS = (RSA_KEY_SIZE*8);␊ |
52 | ␊ |
53 | enum GOUROU_ERROR {␊ |
54 | ␉GOUROU_DEVICE_DOES_NOT_MATCH = 0x1000,␊ |
55 | ␉GOUROU_INVALID_CLIENT,␊ |
56 | ␉GOUROU_TAG_NOT_FOUND,␊ |
57 | ␉GOUROU_ADEPT_ERROR,␊ |
58 | ␉GOUROU_FILE_ERROR,␊ |
59 | ␉GOUROU_INVALID_PROPERTY␊ |
60 | };␊ |
61 | ␊ |
62 | enum FULFILL_ERROR {␊ |
63 | ␉FF_ACSM_FILE_NOT_EXISTS = 0x1100,␊ |
64 | ␉FF_INVALID_ACSM_FILE,␊ |
65 | ␉FF_NO_HMAC_IN_ACSM_FILE,␊ |
66 | ␉FF_NOT_ACTIVATED,␊ |
67 | ␉FF_NO_OPERATOR_URL,␊ |
68 | ␉FF_SERVER_INTERNAL_ERROR␊ |
69 | };␊ |
70 | ␊ |
71 | enum DOWNLOAD_ERROR {␊ |
72 | ␉DW_NO_ITEM = 0x1200,␊ |
73 | ␉DW_NO_EBX_HANDLER,␊ |
74 | };␊ |
75 | ␊ |
76 | enum SIGNIN_ERROR {␊ |
77 | ␉SIGN_INVALID_CREDENTIALS = 0x1300,␊ |
78 | };␊ |
79 | ␊ |
80 | enum ACTIVATE_ERROR {␊ |
81 | ␉ACTIVATE_NOT_SIGNEDIN = 0x1400␊ |
82 | };␊ |
83 | ␊ |
84 | enum DEV_ERROR {␊ |
85 | ␉DEV_MKPATH = 0x2000,␊ |
86 | ␉DEV_MAC_ERROR,␊ |
87 | ␉DEV_INVALID_DEVICE_FILE,␊ |
88 | ␉DEV_INVALID_DEVICE_KEY_FILE,␊ |
89 | ␉DEV_INVALID_DEV_PROPERTY,␊ |
90 | };␊ |
91 | ␊ |
92 | enum USER_ERROR {␊ |
93 | ␉USER_MKPATH = 0x3000,␊ |
94 | ␉USER_INVALID_ACTIVATION_FILE,␊ |
95 | ␉USER_NO_AUTHENTICATION_URL,␊ |
96 | ␉USER_NO_PROPERTY,␊ |
97 | ␉USER_INVALID_INPUT,␊ |
98 | };␊ |
99 | ␊ |
100 | enum FULFILL_ITEM_ERROR {␊ |
101 | ␉FFI_INVALID_FULFILLMENT_DATA = 0x4000,␊ |
102 | ␉FFI_INVALID_LOAN_TOKEN␊ |
103 | };␊ |
104 | ␊ |
105 | enum CLIENT_ERROR {␊ |
106 | ␉CLIENT_BAD_PARAM = 0x5000,␊ |
107 | ␉CLIENT_INVALID_PKCS12,␊ |
108 | ␉CLIENT_INVALID_CERTIFICATE,␊ |
109 | ␉CLIENT_NO_PRIV_KEY,␊ |
110 | ␉CLIENT_NO_PUB_KEY,␊ |
111 | ␉CLIENT_RSA_ERROR,␊ |
112 | ␉CLIENT_BAD_CHAINING,␊ |
113 | ␉CLIENT_BAD_KEY_SIZE,␊ |
114 | ␉CLIENT_BAD_ZIP_FILE,␊ |
115 | ␉CLIENT_ZIP_ERROR,␊ |
116 | ␉CLIENT_GENERIC_EXCEPTION,␊ |
117 | ␉CLIENT_NETWORK_ERROR,␊ |
118 | ␉CLIENT_INVALID_PKCS8,␊ |
119 | ␉CLIENT_FILE_ERROR,␊ |
120 | ␉CLIENT_OSSL_ERROR,␊ |
121 | ␉CLIENT_CRYPT_ERROR,␊ |
122 | ␉CLIENT_DIGEST_ERROR,␊ |
123 | };␊ |
124 | ␊ |
125 | enum DRM_REMOVAL_ERROR {␊ |
126 | ␉DRM_ERR_ENCRYPTION_KEY = 0x6000,␊ |
127 | ␉DRM_VERSION_NOT_SUPPORTED,␊ |
128 | ␉DRM_FILE_ERROR,␊ |
129 | ␉DRM_FORMAT_NOT_SUPPORTED,␊ |
130 | ␉DRM_IN_OUT_EQUALS,␊ |
131 | ␉DRM_MISSING_PARAMETER,␊ |
132 | ␉DRM_INVALID_KEY_SIZE,␊ |
133 | ␉DRM_ERR_ENCRYPTION_KEY_FP,␊ |
134 | ␉DRM_INVALID_USER␊ |
135 | };␊ |
136 | ␊ |
137 | #ifndef _NOEXCEPT␊ |
138 | #if __STDC_VERSION__ >= 201112L␊ |
139 | # define _NOEXCEPT noexcept␊ |
140 | # define _NOEXCEPT_(x) noexcept(x)␊ |
141 | #else␊ |
142 | # define _NOEXCEPT throw()␊ |
143 | # define _NOEXCEPT_(x)␊ |
144 | #endif␊ |
145 | #endif /* !_NOEXCEPT */␊ |
146 | ␊ |
147 | /**␊ |
148 | * Generic exception class␊ |
149 | */␊ |
150 | class Exception : public std::exception␊ |
151 | {␊ |
152 | public:␊ |
153 | ␉Exception(int code, const char* message, const char* file, int line):␊ |
154 | ␉ code(code), line(line), file(file)␊ |
155 | ␉{␊ |
156 | ␉ std::stringstream msg;␊ |
157 | ␉ msg << "Exception code : 0x" << std::setbase(16) << code << std::endl;␊ |
158 | ␉ msg << "Message : " << message << std::endl;␊ |
159 | ␉ if (logLevel >= LG_LOG_DEBUG)␊ |
160 | ␉␉msg << "File : " << file << ":" << std::setbase(10) << line << std::endl;␊ |
161 | ␉ fullmessage = strdup(msg.str().c_str());␊ |
162 | ␉}␊ |
163 | ␊ |
164 | ␉Exception(const Exception& other)␊ |
165 | ␉{␊ |
166 | ␉ this->code = other.code;␊ |
167 | ␉ this->line = line;␊ |
168 | ␉ this->file = file;␊ |
169 | ␉ this->fullmessage = strdup(other.fullmessage);␊ |
170 | ␉}␊ |
171 | ␊ |
172 | ␉~Exception() _NOEXCEPT␊ |
173 | ␉{␊ |
174 | ␉ free(fullmessage);␊ |
175 | ␉}␊ |
176 | ␊ |
177 | ␉const char * what () const throw () { return fullmessage; }␊ |
178 | ␉␊ |
179 | ␉int getErrorCode() {return code;}␊ |
180 | ␉␊ |
181 | ␉private:␊ |
182 | ␉int code, line;␊ |
183 | ␉const char* message, *file;␊ |
184 | ␉char* fullmessage;␊ |
185 | };␊ |
186 | ␊ |
187 | /**␊ |
188 | * @brief Throw an exception␊ |
189 | */␊ |
190 | #define EXCEPTION(code, message)␉␉␉␉␉\␊ |
191 | {std::stringstream __msg;__msg << message; throw gourou::Exception(code, __msg.str().c_str(), __FILE__, __LINE__);}␊ |
192 | ␊ |
193 | /**␊ |
194 | * Stream writer for pugi::xml␊ |
195 | */␊ |
196 | class StringXMLWriter : public pugi::xml_writer␊ |
197 | {␊ |
198 | public:␊ |
199 | ␉virtual void write(const void* data, size_t size)␊ |
200 | ␉{␊ |
201 | ␉ result.append(static_cast<const char*>(data), size);␊ |
202 | ␉}␊ |
203 | ␊ |
204 | ␉const std::string& getResult() {return result;}␊ |
205 | ␊ |
206 | private:␊ |
207 | ␉std::string result;␊ |
208 | };␊ |
209 | ␊ |
210 | static const char* ws = " \t\n\r\f\v";␊ |
211 | ␊ |
212 | /**␊ |
213 | * @brief trim from end of string (right)␊ |
214 | */␊ |
215 | inline std::string& rtrim(std::string& s, const char* t = ws)␊ |
216 | {␊ |
217 | ␉s.erase(s.find_last_not_of(t) + 1);␊ |
218 | ␉return s;␊ |
219 | }␊ |
220 | ␊ |
221 | /**␊ |
222 | * @brief trim from beginning of string (left)␊ |
223 | */␊ |
224 | inline std::string& ltrim(std::string& s, const char* t = ws)␊ |
225 | {␊ |
226 | ␉s.erase(0, s.find_first_not_of(t));␊ |
227 | ␉return s;␊ |
228 | }␊ |
229 | ␊ |
230 | /**␊ |
231 | * @brief trim from both ends of string (right then left)␊ |
232 | */␊ |
233 | inline std::string& trim(std::string& s, const char* t = ws)␊ |
234 | {␊ |
235 | ␉return ltrim(rtrim(s, t), t);␊ |
236 | }␊ |
237 | ␊ |
238 | /**␊ |
239 | * @brief Extract text node from tag in document␊ |
240 | * It can throw an exception if tag does not exists␊ |
241 | * or just return an empty value␊ |
242 | */␊ |
243 | static inline std::string extractTextElem(const pugi::xml_node& root, const char* tagName, bool throwOnNull=true)␊ |
244 | {␊ |
245 | pugi::xpath_node xpath_node = root.select_node(tagName);␊ |
246 | ␊ |
247 | if (!xpath_node)␊ |
248 | ␉{␊ |
249 | ␉ if (throwOnNull)␊ |
250 | ␉␉EXCEPTION(GOUROU_TAG_NOT_FOUND, "Tag " << tagName << " not found");␊ |
251 | ␉ ␊ |
252 | return "";␊ |
253 | ␉}␊ |
254 | ␊ |
255 | ␉pugi::xml_node node = xpath_node.node().first_child();␊ |
256 | ␊ |
257 | ␉if (!node)␊ |
258 | ␉{␊ |
259 | ␉ if (throwOnNull)␊ |
260 | ␉␉EXCEPTION(GOUROU_TAG_NOT_FOUND, "Text element for tag " << tagName << " not found");␊ |
261 | ␉ ␊ |
262 | return "";␊ |
263 | ␉}␊ |
264 | ␊ |
265 | ␉std::string res = node.value();␊ |
266 | return trim(res);␊ |
267 | }␊ |
268 | ␊ |
269 | /**␊ |
270 | * @brief Extract text attribute from tag in document␊ |
271 | * It can throw an exception if attribute does not exists␊ |
272 | * or just return an empty value␊ |
273 | */␊ |
274 | static inline std::string extractTextAttribute(const pugi::xml_node& root, const char* tagName, const char* attributeName, bool throwOnNull=true)␊ |
275 | {␊ |
276 | pugi::xpath_node xpath_node = root.select_node(tagName);␊ |
277 | ␊ |
278 | if (!xpath_node)␊ |
279 | ␉{␊ |
280 | ␉ if (throwOnNull)␊ |
281 | ␉␉EXCEPTION(GOUROU_TAG_NOT_FOUND, "Tag " << tagName << " not found");␊ |
282 | ␉ ␊ |
283 | return "";␊ |
284 | ␉}␊ |
285 | ␊ |
286 | ␉pugi::xml_attribute attr = xpath_node.node().attribute(attributeName);␊ |
287 | ␊ |
288 | ␉if (!attr)␊ |
289 | ␉{␊ |
290 | ␉ if (throwOnNull)␊ |
291 | ␉␉EXCEPTION(GOUROU_TAG_NOT_FOUND, "Attribute element " << attributeName << " for tag " << tagName << " not found");␊ |
292 | ␉ ␊ |
293 | return "";␊ |
294 | ␉}␊ |
295 | ␊ |
296 | ␉std::string res = attr.value();␊ |
297 | ␉return trim(res);␊ |
298 | }␊ |
299 | ␊ |
300 | /**␊ |
301 | * @brief Append an element to root with a sub text element␊ |
302 | *␊ |
303 | * @param root Root node where to put child␊ |
304 | * @param name Tag name for child␊ |
305 | * @param value Text child value of tag element␊ |
306 | */␊ |
307 | static inline void appendTextElem(pugi::xml_node& root, const std::string& name, const std::string& value)␊ |
308 | {␊ |
309 | ␉pugi::xml_node node = root.append_child(name.c_str());␊ |
310 | ␉node.append_child(pugi::node_pcdata).set_value(value.c_str());␊ |
311 | }␊ |
312 | ␊ |
313 | /**␊ |
314 | * Remove "urn:uuid:" prefix and all '-' from uuid␊ |
315 | * urn:uuid:9cb786e8-586a-4950-8901-fff8d2ee6025␊ |
316 | * ->␊ |
317 | * 9cb786e8586a49508901fff8d2ee6025␊ |
318 | */␊ |
319 | static inline std::string extractIdFromUUID(const std::string& uuid)␊ |
320 | {␊ |
321 | ␉unsigned int i = 0;␊ |
322 | ␉std::string res;␊ |
323 | ␉␊ |
324 | ␉if (uuid.find("urn:uuid:") == 0)␊ |
325 | ␉ i = 9;␊ |
326 | ␊ |
327 | ␉for(; i<uuid.size(); i++)␊ |
328 | ␉{␊ |
329 | ␉ if (uuid[i] != '-')␊ |
330 | ␉␉res += uuid[i];␊ |
331 | ␉}␊ |
332 | ␊ |
333 | ␉return res;␊ |
334 | }␊ |
335 | ␊ |
336 | /**␊ |
337 | * @brief Open a file descriptor on path. If it already exists and truncate == true, it's truncated␊ |
338 | *␊ |
339 | * @return Created fd, must be closed␊ |
340 | */␊ |
341 | static inline int createNewFile(std::string path, bool truncate=true)␊ |
342 | {␊ |
343 | ␉int options = O_CREAT|O_WRONLY;␊ |
344 | ␉if (truncate)␊ |
345 | ␉ options |= O_TRUNC;␊ |
346 | ␉else␊ |
347 | ␉ options |= O_APPEND;␊ |
348 | ␊ |
349 | ␉int fd = open(path.c_str(), options, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH);␊ |
350 | ␊ |
351 | ␉if (fd <= 0)␊ |
352 | ␉ EXCEPTION(GOUROU_FILE_ERROR, "Unable to create " << path);␊ |
353 | ␊ |
354 | ␉return fd;␊ |
355 | }␊ |
356 | ␊ |
357 | /**␊ |
358 | * @brief Write data in a file. If it already exists, it's truncated␊ |
359 | */␊ |
360 | static inline void writeFile(std::string path, const unsigned char* data, unsigned int length)␊ |
361 | {␊ |
362 | ␉int fd = createNewFile(path);␊ |
363 | ␉␊ |
364 | ␉if (write(fd, data, length) != length)␊ |
365 | ␉ EXCEPTION(GOUROU_FILE_ERROR, "Write error for file " << path);␊ |
366 | ␊ |
367 | ␉close (fd);␊ |
368 | }␊ |
369 | ␊ |
370 | /**␊ |
371 | * @brief Write data in a file. If it already exists, it's truncated␊ |
372 | */␊ |
373 | static inline void writeFile(std::string path, ByteArray& data)␊ |
374 | {␊ |
375 | ␉writeFile(path, data.data(), data.length());␊ |
376 | }␊ |
377 | ␊ |
378 | /**␊ |
379 | * @brief Write data in a file. If it already exists, it's truncated␊ |
380 | */␊ |
381 | static inline void writeFile(std::string path, const std::string& data)␊ |
382 | {␊ |
383 | ␉writeFile(path, (const unsigned char*)data.c_str(), data.length());␊ |
384 | }␊ |
385 | ␊ |
386 | /**␊ |
387 | * Read data from file␊ |
388 | */␊ |
389 | static inline void readFile(std::string path, const unsigned char* data, unsigned int length)␊ |
390 | {␊ |
391 | ␉int fd = open(path.c_str(), O_RDONLY);␊ |
392 | ␊ |
393 | ␉if (fd <= 0)␊ |
394 | ␉ EXCEPTION(GOUROU_FILE_ERROR, "Unable to open " << path);␊ |
395 | ␊ |
396 | ␉if (read(fd, (void*)data, length) != length)␊ |
397 | ␉ EXCEPTION(GOUROU_FILE_ERROR, "Read error for file " << path);␊ |
398 | ␊ |
399 | ␉close (fd);␊ |
400 | }␊ |
401 | ␊ |
402 | #define PATH_MAX_STRING_SIZE 256␊ |
403 | ␊ |
404 | // https://gist.github.com/ChisholmKyle/0cbedcd3e64132243a39␊ |
405 | /* recursive mkdir */␊ |
406 | static inline int mkdir_p(const char *dir, const mode_t mode) {␊ |
407 | ␉char tmp[PATH_MAX_STRING_SIZE];␊ |
408 | ␉char *p = NULL;␊ |
409 | ␉struct stat sb;␊ |
410 | ␉size_t len;␊ |
411 | ␊ |
412 | ␉/* copy path */␊ |
413 | ␉len = strnlen (dir, PATH_MAX_STRING_SIZE);␊ |
414 | ␉if (len == 0 || len == PATH_MAX_STRING_SIZE) {␊ |
415 | ␉ return -1;␊ |
416 | ␉}␊ |
417 | ␉memcpy (tmp, dir, len);␊ |
418 | ␉tmp[len] = '\0';␊ |
419 | ␊ |
420 | ␉/* remove trailing slash */␊ |
421 | ␉if(tmp[len - 1] == '/') {␊ |
422 | ␉ tmp[len - 1] = '\0';␊ |
423 | ␉}␊ |
424 | ␊ |
425 | ␉/* check if path exists and is a directory */␊ |
426 | ␉if (stat (tmp, &sb) == 0) {␊ |
427 | ␉ if (S_ISDIR (sb.st_mode)) {␊ |
428 | ␉␉return 0;␊ |
429 | ␉ }␊ |
430 | ␉}␊ |
431 | ␊ |
432 | ␉/* recursive mkdir */␊ |
433 | ␉for(p = tmp + 1; *p; p++) {␊ |
434 | ␉ if(*p == '/') {␊ |
435 | ␉␉*p = 0;␊ |
436 | ␉␉/* test path */␊ |
437 | ␉␉if (stat(tmp, &sb) != 0) {␊ |
438 | ␉␉ /* path does not exist - create directory */␊ |
439 | ␉␉ if (mkdir(tmp, mode) < 0) {␊ |
440 | ␉␉␉return -1;␊ |
441 | ␉␉ }␊ |
442 | ␉␉} else if (!S_ISDIR(sb.st_mode)) {␊ |
443 | ␉␉ /* not a directory */␊ |
444 | ␉␉ return -1;␊ |
445 | ␉␉}␊ |
446 | ␉␉*p = '/';␊ |
447 | ␉ }␊ |
448 | ␉}␊ |
449 | ␉/* test path */␊ |
450 | ␉if (stat(tmp, &sb) != 0) {␊ |
451 | ␉ /* path does not exist - create directory */␊ |
452 | ␉ if (mkdir(tmp, mode) < 0) {␊ |
453 | ␉␉return -1;␊ |
454 | ␉ }␊ |
455 | ␉} else if (!S_ISDIR(sb.st_mode)) {␊ |
456 | ␉ /* not a directory */␊ |
457 | ␉ return -1;␊ |
458 | ␉}␊ |
459 | ␉return 0;␊ |
460 | }␊ |
461 | ␊ |
462 | static inline void dumpBuffer(GOUROU_LOG_LEVEL level, const char* title, const unsigned char* data, unsigned int len)␊ |
463 | {␊ |
464 | ␉if (gourou::logLevel < level)␊ |
465 | ␉ return;␊ |
466 | ␉␊ |
467 | ␉printf("%s", title);␊ |
468 | ␉for(unsigned int i=0; i<len; i++)␊ |
469 | ␉{␊ |
470 | ␉ if (i && !(i%16)) printf("\n");␊ |
471 | ␉ printf("%02x ", data[i]);␊ |
472 | ␉}␊ |
473 | ␉printf("\n");␉␊ |
474 | }␊ |
475 | }␊ |
476 | ␊ |
477 | #endif␊ |