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 | };␊ |
69 | ␊ |
70 | enum DOWNLOAD_ERROR {␊ |
71 | ␉DW_NO_ITEM = 0x1200,␊ |
72 | ␉DW_NO_EBX_HANDLER,␊ |
73 | };␊ |
74 | ␊ |
75 | enum SIGNIN_ERROR {␊ |
76 | ␉SIGN_INVALID_CREDENTIALS = 0x1300,␊ |
77 | };␊ |
78 | ␊ |
79 | enum ACTIVATE_ERROR {␊ |
80 | ␉ACTIVATE_NOT_SIGNEDIN = 0x1400␊ |
81 | };␊ |
82 | ␊ |
83 | enum DEV_ERROR {␊ |
84 | ␉DEV_MKPATH = 0x2000,␊ |
85 | ␉DEV_MAC_ERROR,␊ |
86 | ␉DEV_INVALID_DEVICE_FILE,␊ |
87 | ␉DEV_INVALID_DEVICE_KEY_FILE,␊ |
88 | ␉DEV_INVALID_DEV_PROPERTY,␊ |
89 | };␊ |
90 | ␊ |
91 | enum USER_ERROR {␊ |
92 | ␉USER_MKPATH = 0x3000,␊ |
93 | ␉USER_INVALID_ACTIVATION_FILE,␊ |
94 | ␉USER_NO_AUTHENTICATION_URL,␊ |
95 | ␉USER_NO_PROPERTY,␊ |
96 | ␉USER_INVALID_INPUT,␊ |
97 | };␊ |
98 | ␊ |
99 | enum FULFILL_ITEM_ERROR {␊ |
100 | ␉FFI_INVALID_FULFILLMENT_DATA = 0x4000,␊ |
101 | ␉FFI_INVALID_LOAN_TOKEN␊ |
102 | };␊ |
103 | ␊ |
104 | enum CLIENT_ERROR {␊ |
105 | ␉CLIENT_BAD_PARAM = 0x5000,␊ |
106 | ␉CLIENT_INVALID_PKCS12,␊ |
107 | ␉CLIENT_INVALID_CERTIFICATE,␊ |
108 | ␉CLIENT_NO_PRIV_KEY,␊ |
109 | ␉CLIENT_NO_PUB_KEY,␊ |
110 | ␉CLIENT_RSA_ERROR,␊ |
111 | ␉CLIENT_BAD_CHAINING,␊ |
112 | ␉CLIENT_BAD_KEY_SIZE,␊ |
113 | ␉CLIENT_BAD_ZIP_FILE,␊ |
114 | ␉CLIENT_ZIP_ERROR,␊ |
115 | ␉CLIENT_GENERIC_EXCEPTION,␊ |
116 | ␉CLIENT_NETWORK_ERROR,␊ |
117 | ␉CLIENT_INVALID_PKCS8,␊ |
118 | ␉CLIENT_FILE_ERROR,␊ |
119 | ␉CLIENT_OSSL_ERROR,␊ |
120 | ␉CLIENT_CRYPT_ERROR,␊ |
121 | ␉CLIENT_DIGEST_ERROR,␊ |
122 | };␊ |
123 | ␊ |
124 | enum DRM_REMOVAL_ERROR {␊ |
125 | ␉DRM_ERR_ENCRYPTION_KEY = 0x6000,␊ |
126 | ␉DRM_VERSION_NOT_SUPPORTED,␊ |
127 | ␉DRM_FILE_ERROR,␊ |
128 | ␉DRM_FORMAT_NOT_SUPPORTED,␊ |
129 | ␉DRM_IN_OUT_EQUALS,␊ |
130 | ␉DRM_MISSING_PARAMETER,␊ |
131 | ␉DRM_INVALID_KEY_SIZE,␊ |
132 | ␉DRM_ERR_ENCRYPTION_KEY_FP␊ |
133 | };␊ |
134 | ␊ |
135 | /**␊ |
136 | * Generic exception class␊ |
137 | */␊ |
138 | class Exception : public std::exception␊ |
139 | {␊ |
140 | public:␊ |
141 | ␉Exception(int code, const char* message, const char* file, int line):␊ |
142 | ␉ code(code), line(line), file(file)␊ |
143 | ␉{␊ |
144 | ␉ std::stringstream msg;␊ |
145 | ␉ msg << "Exception code : 0x" << std::setbase(16) << code << std::endl;␊ |
146 | ␉ msg << "Message : " << message << std::endl;␊ |
147 | ␉ if (logLevel >= LG_LOG_DEBUG)␊ |
148 | ␉␉msg << "File : " << file << ":" << std::setbase(10) << line << std::endl;␊ |
149 | ␉ fullmessage = strdup(msg.str().c_str());␊ |
150 | ␉}␊ |
151 | ␊ |
152 | ␉Exception(const Exception& other)␊ |
153 | ␉{␊ |
154 | ␉ this->code = other.code;␊ |
155 | ␉ this->line = line;␊ |
156 | ␉ this->file = file;␊ |
157 | ␉ this->fullmessage = strdup(other.fullmessage);␊ |
158 | ␉}␊ |
159 | ␊ |
160 | ␉~Exception()␊ |
161 | ␉{␊ |
162 | ␉ free(fullmessage);␊ |
163 | ␉}␊ |
164 | ␊ |
165 | ␉const char * what () const throw () { return fullmessage; }␊ |
166 | ␉␊ |
167 | ␉int getErrorCode() {return code;}␊ |
168 | ␉␊ |
169 | ␉private:␊ |
170 | ␉int code, line;␊ |
171 | ␉const char* message, *file;␊ |
172 | ␉char* fullmessage;␊ |
173 | };␊ |
174 | ␊ |
175 | /**␊ |
176 | * @brief Throw an exception␊ |
177 | */␊ |
178 | #define EXCEPTION(code, message)␉␉␉␉␉\␊ |
179 | {std::stringstream __msg;__msg << message; throw gourou::Exception(code, __msg.str().c_str(), __FILE__, __LINE__);}␊ |
180 | ␊ |
181 | /**␊ |
182 | * Stream writer for pugi::xml␊ |
183 | */␊ |
184 | class StringXMLWriter : public pugi::xml_writer␊ |
185 | {␊ |
186 | public:␊ |
187 | ␉virtual void write(const void* data, size_t size)␊ |
188 | ␉{␊ |
189 | ␉ result.append(static_cast<const char*>(data), size);␊ |
190 | ␉}␊ |
191 | ␊ |
192 | ␉const std::string& getResult() {return result;}␊ |
193 | ␊ |
194 | private:␊ |
195 | ␉std::string result;␊ |
196 | };␊ |
197 | ␊ |
198 | static const char* ws = " \t\n\r\f\v";␊ |
199 | ␊ |
200 | /**␊ |
201 | * @brief trim from end of string (right)␊ |
202 | */␊ |
203 | inline std::string& rtrim(std::string& s, const char* t = ws)␊ |
204 | {␊ |
205 | ␉s.erase(s.find_last_not_of(t) + 1);␊ |
206 | ␉return s;␊ |
207 | }␊ |
208 | ␊ |
209 | /**␊ |
210 | * @brief trim from beginning of string (left)␊ |
211 | */␊ |
212 | inline std::string& ltrim(std::string& s, const char* t = ws)␊ |
213 | {␊ |
214 | ␉s.erase(0, s.find_first_not_of(t));␊ |
215 | ␉return s;␊ |
216 | }␊ |
217 | ␊ |
218 | /**␊ |
219 | * @brief trim from both ends of string (right then left)␊ |
220 | */␊ |
221 | inline std::string& trim(std::string& s, const char* t = ws)␊ |
222 | {␊ |
223 | ␉return ltrim(rtrim(s, t), t);␊ |
224 | }␊ |
225 | ␊ |
226 | /**␊ |
227 | * @brief Extract text node from tag in document␊ |
228 | * It can throw an exception if tag does not exists␊ |
229 | * or just return an empty value␊ |
230 | */␊ |
231 | static inline std::string extractTextElem(const pugi::xml_node& root, const char* tagName, bool throwOnNull=true)␊ |
232 | {␊ |
233 | pugi::xpath_node xpath_node = root.select_node(tagName);␊ |
234 | ␊ |
235 | if (!xpath_node)␊ |
236 | ␉{␊ |
237 | ␉ if (throwOnNull)␊ |
238 | ␉␉EXCEPTION(GOUROU_TAG_NOT_FOUND, "Tag " << tagName << " not found");␊ |
239 | ␉ ␊ |
240 | return "";␊ |
241 | ␉}␊ |
242 | ␊ |
243 | ␉pugi::xml_node node = xpath_node.node().first_child();␊ |
244 | ␊ |
245 | ␉if (!node)␊ |
246 | ␉{␊ |
247 | ␉ if (throwOnNull)␊ |
248 | ␉␉EXCEPTION(GOUROU_TAG_NOT_FOUND, "Text element for tag " << tagName << " not found");␊ |
249 | ␉ ␊ |
250 | return "";␊ |
251 | ␉}␊ |
252 | ␊ |
253 | ␉std::string res = node.value();␊ |
254 | return trim(res);␊ |
255 | }␊ |
256 | ␊ |
257 | /**␊ |
258 | * @brief Extract text attribute from tag in document␊ |
259 | * It can throw an exception if attribute does not exists␊ |
260 | * or just return an empty value␊ |
261 | */␊ |
262 | static inline std::string extractTextAttribute(const pugi::xml_node& root, const char* tagName, const char* attributeName, bool throwOnNull=true)␊ |
263 | {␊ |
264 | pugi::xpath_node xpath_node = root.select_node(tagName);␊ |
265 | ␊ |
266 | if (!xpath_node)␊ |
267 | ␉{␊ |
268 | ␉ if (throwOnNull)␊ |
269 | ␉␉EXCEPTION(GOUROU_TAG_NOT_FOUND, "Tag " << tagName << " not found");␊ |
270 | ␉ ␊ |
271 | return "";␊ |
272 | ␉}␊ |
273 | ␊ |
274 | ␉pugi::xml_attribute attr = xpath_node.node().attribute(attributeName);␊ |
275 | ␊ |
276 | ␉if (!attr)␊ |
277 | ␉{␊ |
278 | ␉ if (throwOnNull)␊ |
279 | ␉␉EXCEPTION(GOUROU_TAG_NOT_FOUND, "Attribute element " << attributeName << " for tag " << tagName << " not found");␊ |
280 | ␉ ␊ |
281 | return "";␊ |
282 | ␉}␊ |
283 | ␊ |
284 | ␉std::string res = attr.value();␊ |
285 | ␉return trim(res);␊ |
286 | }␊ |
287 | ␊ |
288 | /**␊ |
289 | * @brief Append an element to root with a sub text element␊ |
290 | *␊ |
291 | * @param root Root node where to put child␊ |
292 | * @param name Tag name for child␊ |
293 | * @param value Text child value of tag element␊ |
294 | */␊ |
295 | static inline void appendTextElem(pugi::xml_node& root, const std::string& name, const std::string& value)␊ |
296 | {␊ |
297 | ␉pugi::xml_node node = root.append_child(name.c_str());␊ |
298 | ␉node.append_child(pugi::node_pcdata).set_value(value.c_str());␊ |
299 | }␊ |
300 | ␊ |
301 | /**␊ |
302 | * Remove "urn:uuid:" prefix and all '-' from uuid␊ |
303 | * urn:uuid:9cb786e8-586a-4950-8901-fff8d2ee6025␊ |
304 | * ->␊ |
305 | * 9cb786e8586a49508901fff8d2ee6025␊ |
306 | */␊ |
307 | static inline std::string extractIdFromUUID(const std::string& uuid)␊ |
308 | {␊ |
309 | ␉unsigned int i = 0;␊ |
310 | ␉std::string res;␊ |
311 | ␉␊ |
312 | ␉if (uuid.find("urn:uuid:") == 0)␊ |
313 | ␉ i = 9;␊ |
314 | ␊ |
315 | ␉for(; i<uuid.size(); i++)␊ |
316 | ␉{␊ |
317 | ␉ if (uuid[i] != '-')␊ |
318 | ␉␉res += uuid[i];␊ |
319 | ␉}␊ |
320 | ␊ |
321 | ␉return res;␊ |
322 | }␊ |
323 | ␊ |
324 | /**␊ |
325 | * @brief Open a file descriptor on path. If it already exists and truncate == true, it's truncated␊ |
326 | *␊ |
327 | * @return Created fd, must be closed␊ |
328 | */␊ |
329 | static inline int createNewFile(std::string path, bool truncate=true)␊ |
330 | {␊ |
331 | ␉int options = O_CREAT|O_WRONLY;␊ |
332 | ␉if (truncate)␊ |
333 | ␉ options |= O_TRUNC;␊ |
334 | ␉else␊ |
335 | ␉ options |= O_APPEND;␊ |
336 | ␊ |
337 | ␉int fd = open(path.c_str(), options, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH);␊ |
338 | ␊ |
339 | ␉if (fd <= 0)␊ |
340 | ␉ EXCEPTION(GOUROU_FILE_ERROR, "Unable to create " << path);␊ |
341 | ␊ |
342 | ␉return fd;␊ |
343 | }␊ |
344 | ␊ |
345 | /**␊ |
346 | * @brief Write data in a file. If it already exists, it's truncated␊ |
347 | */␊ |
348 | static inline void writeFile(std::string path, const unsigned char* data, unsigned int length)␊ |
349 | {␊ |
350 | ␉int fd = createNewFile(path);␊ |
351 | ␉␊ |
352 | ␉if (write(fd, data, length) != length)␊ |
353 | ␉ EXCEPTION(GOUROU_FILE_ERROR, "Write error for file " << path);␊ |
354 | ␊ |
355 | ␉close (fd);␊ |
356 | }␊ |
357 | ␊ |
358 | /**␊ |
359 | * @brief Write data in a file. If it already exists, it's truncated␊ |
360 | */␊ |
361 | static inline void writeFile(std::string path, ByteArray& data)␊ |
362 | {␊ |
363 | ␉writeFile(path, data.data(), data.length());␊ |
364 | }␊ |
365 | ␊ |
366 | /**␊ |
367 | * @brief Write data in a file. If it already exists, it's truncated␊ |
368 | */␊ |
369 | static inline void writeFile(std::string path, const std::string& data)␊ |
370 | {␊ |
371 | ␉writeFile(path, (const unsigned char*)data.c_str(), data.length());␊ |
372 | }␊ |
373 | ␊ |
374 | /**␊ |
375 | * Read data from file␊ |
376 | */␊ |
377 | static inline void readFile(std::string path, const unsigned char* data, unsigned int length)␊ |
378 | {␊ |
379 | ␉int fd = open(path.c_str(), O_RDONLY);␊ |
380 | ␊ |
381 | ␉if (fd <= 0)␊ |
382 | ␉ EXCEPTION(GOUROU_FILE_ERROR, "Unable to open " << path);␊ |
383 | ␊ |
384 | ␉if (read(fd, (void*)data, length) != length)␊ |
385 | ␉ EXCEPTION(GOUROU_FILE_ERROR, "Read error for file " << path);␊ |
386 | ␊ |
387 | ␉close (fd);␊ |
388 | }␊ |
389 | ␊ |
390 | #define PATH_MAX_STRING_SIZE 256␊ |
391 | ␊ |
392 | // https://gist.github.com/ChisholmKyle/0cbedcd3e64132243a39␊ |
393 | /* recursive mkdir */␊ |
394 | static inline int mkdir_p(const char *dir, const mode_t mode) {␊ |
395 | ␉char tmp[PATH_MAX_STRING_SIZE];␊ |
396 | ␉char *p = NULL;␊ |
397 | ␉struct stat sb;␊ |
398 | ␉size_t len;␊ |
399 | ␊ |
400 | ␉/* copy path */␊ |
401 | ␉len = strnlen (dir, PATH_MAX_STRING_SIZE);␊ |
402 | ␉if (len == 0 || len == PATH_MAX_STRING_SIZE) {␊ |
403 | ␉ return -1;␊ |
404 | ␉}␊ |
405 | ␉memcpy (tmp, dir, len);␊ |
406 | ␉tmp[len] = '\0';␊ |
407 | ␊ |
408 | ␉/* remove trailing slash */␊ |
409 | ␉if(tmp[len - 1] == '/') {␊ |
410 | ␉ tmp[len - 1] = '\0';␊ |
411 | ␉}␊ |
412 | ␊ |
413 | ␉/* check if path exists and is a directory */␊ |
414 | ␉if (stat (tmp, &sb) == 0) {␊ |
415 | ␉ if (S_ISDIR (sb.st_mode)) {␊ |
416 | ␉␉return 0;␊ |
417 | ␉ }␊ |
418 | ␉}␊ |
419 | ␊ |
420 | ␉/* recursive mkdir */␊ |
421 | ␉for(p = tmp + 1; *p; p++) {␊ |
422 | ␉ if(*p == '/') {␊ |
423 | ␉␉*p = 0;␊ |
424 | ␉␉/* test path */␊ |
425 | ␉␉if (stat(tmp, &sb) != 0) {␊ |
426 | ␉␉ /* path does not exist - create directory */␊ |
427 | ␉␉ if (mkdir(tmp, mode) < 0) {␊ |
428 | ␉␉␉return -1;␊ |
429 | ␉␉ }␊ |
430 | ␉␉} else if (!S_ISDIR(sb.st_mode)) {␊ |
431 | ␉␉ /* not a directory */␊ |
432 | ␉␉ return -1;␊ |
433 | ␉␉}␊ |
434 | ␉␉*p = '/';␊ |
435 | ␉ }␊ |
436 | ␉}␊ |
437 | ␉/* test path */␊ |
438 | ␉if (stat(tmp, &sb) != 0) {␊ |
439 | ␉ /* path does not exist - create directory */␊ |
440 | ␉ if (mkdir(tmp, mode) < 0) {␊ |
441 | ␉␉return -1;␊ |
442 | ␉ }␊ |
443 | ␉} else if (!S_ISDIR(sb.st_mode)) {␊ |
444 | ␉ /* not a directory */␊ |
445 | ␉ return -1;␊ |
446 | ␉}␊ |
447 | ␉return 0;␊ |
448 | }␊ |
449 | ␊ |
450 | static inline void dumpBuffer(GOUROU_LOG_LEVEL level, const char* title, const unsigned char* data, unsigned int len)␊ |
451 | {␊ |
452 | ␉if (gourou::logLevel < level)␊ |
453 | ␉ return;␊ |
454 | ␉␊ |
455 | ␉printf("%s", title);␊ |
456 | ␉for(unsigned int i=0; i<len; i++)␊ |
457 | ␉{␊ |
458 | ␉ if (i && !(i%16)) printf("\n");␊ |
459 | ␉ printf("%02x ", data[i]);␊ |
460 | ␉}␊ |
461 | ␉printf("\n");␉␊ |
462 | }␊ |
463 | }␊ |
464 | ␊ |
465 | #endif␊ |