Line data Source code
1 : /**
2 : * @file
3 : *
4 : * @brief Source for curlget plugin
5 : *
6 : * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
7 : *
8 : */
9 :
10 : #include "curlget.h"
11 :
12 : #include <curl/curl.h>
13 : #include <curl/easy.h>
14 : #include <errno.h>
15 : #include <fcntl.h>
16 : #include <kdberrors.h>
17 : #include <kdbhelper.h>
18 : #include <kdbproposal.h>
19 : #include <libgen.h>
20 : #include <openssl/md5.h>
21 : #include <stdio.h>
22 : #include <stdlib.h>
23 : #include <string.h>
24 : #include <strings.h>
25 : #include <sys/stat.h>
26 : #include <sys/types.h>
27 : #include <time.h>
28 : #include <unistd.h>
29 :
30 : #include "../resolver/shared.h"
31 : #include <kdbinvoke.h>
32 :
33 : #define TMP_NAME "/tmp/elektraCurlTempXXXXXX"
34 :
35 : #define DEFAULT_POSTFIELDNAME "file"
36 :
37 : typedef enum
38 : {
39 : NA = 0,
40 : PUT,
41 : POST
42 : } HttpUploadMethods;
43 : typedef enum
44 : {
45 : PROTO_INVALID = 0,
46 : PROTO_HTTP,
47 : PROTO_HTTPS,
48 : PROTO_FTP,
49 : PROTO_FTPS,
50 : PROTO_SFTP,
51 : PROTO_SCP,
52 : PROTO_SMB,
53 : } ElektraCurlProtocol;
54 : typedef enum
55 : {
56 : SSH_ANY = 0,
57 : SSH_AGENT,
58 : SSH_PASSWORD,
59 : SSH_PUBLICKEY,
60 : SSH_PUBKEYPW,
61 : } SSHAuthType;
62 : typedef struct
63 : {
64 : char * path;
65 : const char * tmpFile;
66 : time_t mtime;
67 : unsigned char lastHash[MD5_DIGEST_LENGTH];
68 : const char * getUrl;
69 : const char * uploadUrl;
70 : const char * user;
71 : const char * password;
72 : const char * postFieldName;
73 : const char * uploadFileName;
74 : char * __uploadFileName;
75 : HttpUploadMethods uploadMethod;
76 : unsigned short preferRemote;
77 : unsigned short setPhase;
78 : unsigned short useLocalCopy;
79 : unsigned short useSSL;
80 : unsigned short sslVerifyPeer;
81 : unsigned short sslVerifyHost;
82 : const char * keyFile;
83 : const char * keyFilePasswd;
84 : ElektraCurlProtocol getProto;
85 : ElektraCurlProtocol putProto;
86 : SSHAuthType sshAuth;
87 : } Data;
88 :
89 0 : static int elektraResolveFilename (Key * parentKey, ElektraResolveTempfile tmpFile)
90 : {
91 0 : int rc = 0;
92 0 : void * handle = elektraInvokeOpen ("resolver", 0, 0);
93 0 : if (!handle)
94 : {
95 : rc = -1;
96 : goto RESOLVE_FAILED;
97 : }
98 0 : ElektraResolved * resolved = NULL;
99 : typedef ElektraResolved * (*resolveFileFunc) (elektraNamespace, const char *, ElektraResolveTempfile, Key *);
100 0 : resolveFileFunc resolveFunc = *(resolveFileFunc *) elektraInvokeGetFunction (handle, "filename");
101 :
102 0 : if (!resolveFunc)
103 : {
104 : rc = -1;
105 : goto RESOLVE_FAILED;
106 : }
107 :
108 : typedef void (*freeHandleFunc) (ElektraResolved *);
109 0 : freeHandleFunc freeHandle = *(freeHandleFunc *) elektraInvokeGetFunction (handle, "freeHandle");
110 :
111 0 : if (!freeHandle)
112 : {
113 : rc = -1;
114 : goto RESOLVE_FAILED;
115 : }
116 :
117 0 : resolved = resolveFunc (keyGetNamespace (parentKey), keyString (parentKey), tmpFile, parentKey);
118 :
119 0 : if (!resolved)
120 : {
121 : rc = -1;
122 : goto RESOLVE_FAILED;
123 : }
124 : else
125 : {
126 0 : keySetString (parentKey, resolved->fullPath);
127 0 : freeHandle (resolved);
128 : }
129 :
130 : RESOLVE_FAILED:
131 0 : elektraInvokeClose (handle, 0);
132 0 : return rc;
133 : }
134 :
135 0 : int elektraCurlgetCheckFile (const char * filename)
136 : {
137 0 : if (filename[0] == '/') return 0;
138 :
139 0 : return 1;
140 : }
141 :
142 20 : int elektraCurlgetClose (Plugin * handle ELEKTRA_UNUSED, Key * errorKey ELEKTRA_UNUSED)
143 : {
144 20 : Data * data = elektraPluginGetData (handle);
145 20 : if (!data) return 0;
146 2 : if (data->tmpFile)
147 : {
148 0 : unlink (data->tmpFile);
149 0 : data->tmpFile = NULL;
150 : }
151 2 : if (!data->useLocalCopy && data->path)
152 : {
153 0 : unlink (data->path);
154 0 : elektraFree (data->path);
155 0 : data->path = NULL;
156 : }
157 2 : else if (data->path)
158 : {
159 2 : elektraFree (data->path);
160 2 : data->path = NULL;
161 : }
162 2 : if (data->uploadFileName) elektraFree (data->__uploadFileName);
163 2 : elektraFree (data);
164 2 : data = NULL;
165 2 : elektraPluginSetData (handle, data);
166 2 : return 1;
167 : }
168 :
169 0 : int elektraCurlgetError (Plugin * handle ELEKTRA_UNUSED, KeySet * returned ELEKTRA_UNUSED, Key * parentKey ELEKTRA_UNUSED)
170 : {
171 0 : return 1;
172 : }
173 :
174 2 : static ElektraCurlProtocol isValidURL (const char * str)
175 : {
176 2 : if (str == NULL) return PROTO_INVALID;
177 :
178 : struct checkProtocolStruct
179 : {
180 : ElektraCurlProtocol proto;
181 : const char * url;
182 : };
183 :
184 2 : const struct checkProtocolStruct urlPrefix[] = {
185 : { PROTO_HTTP, "http://" }, { PROTO_HTTPS, "https://" }, { PROTO_FTP, "ftp://" }, { PROTO_FTPS, "ftps://" },
186 : { PROTO_SFTP, "sftp://" }, { PROTO_SCP, "scp://" }, { PROTO_SMB, "smb://" }, { PROTO_INVALID, NULL },
187 : };
188 16 : for (int i = 0; urlPrefix[i].proto != PROTO_INVALID; ++i)
189 : {
190 14 : if (!strncasecmp (str, urlPrefix[i].url, strlen (urlPrefix[i].url))) return urlPrefix[i].proto;
191 : }
192 : return PROTO_INVALID;
193 : }
194 :
195 2 : static unsigned short parseURLPath (Data * data, KeySet * config)
196 : {
197 2 : const char * path = keyString (ksLookupByName (config, "/path", 0));
198 2 : ElektraCurlProtocol proto = isValidURL (path);
199 2 : Key * key = NULL;
200 2 : if (proto == PROTO_INVALID)
201 : {
202 2 : data->path = elektraStrDup (path);
203 2 : data->useLocalCopy = 1;
204 2 : key = ksLookupByName (config, "/url", KDB_O_NONE);
205 2 : if (key)
206 : {
207 0 : proto = isValidURL (keyString (key));
208 0 : if (proto != PROTO_INVALID)
209 : {
210 0 : data->getUrl = keyString (key);
211 0 : data->getProto = proto;
212 0 : data->uploadUrl = data->getUrl;
213 0 : data->putProto = data->getProto;
214 : }
215 : else
216 : {
217 0 : key = ksLookupByName (config, "/url/get", KDB_O_NONE);
218 0 : if (!key)
219 : {
220 : return 0;
221 : }
222 : else
223 : {
224 0 : proto = isValidURL (keyString (key));
225 0 : if (proto == PROTO_INVALID)
226 : {
227 : return 0;
228 : }
229 : else
230 : {
231 0 : data->getProto = proto;
232 0 : data->getUrl = keyString (key);
233 : }
234 : }
235 0 : key = ksLookupByName (config, "/url/put", KDB_O_NONE);
236 0 : if (key)
237 : {
238 0 : proto = isValidURL (keyString (key));
239 0 : if (proto == PROTO_INVALID)
240 : {
241 0 : data->putProto = data->getProto;
242 0 : data->uploadUrl = data->getUrl;
243 : }
244 : else
245 : {
246 0 : data->putProto = proto;
247 0 : data->uploadUrl = keyString (key);
248 : }
249 : }
250 : else
251 : {
252 0 : data->putProto = data->getProto;
253 0 : data->uploadUrl = data->getUrl;
254 : }
255 : }
256 : }
257 : }
258 : else
259 : {
260 0 : data->useLocalCopy = 0;
261 0 : if (data->path) elektraFree (data->path);
262 0 : data->path = NULL;
263 0 : data->getUrl = path;
264 0 : data->getProto = proto;
265 0 : key = ksLookupByName (config, "/url/put", KDB_O_NONE);
266 0 : if (!key)
267 : {
268 0 : data->uploadUrl = data->getUrl;
269 0 : data->putProto = data->getProto;
270 : }
271 : else
272 : {
273 0 : proto = isValidURL (keyString (key));
274 0 : if (proto == PROTO_INVALID)
275 : {
276 : return 0;
277 : }
278 : else
279 : {
280 0 : data->uploadUrl = keyString (key);
281 0 : data->putProto = proto;
282 : }
283 : }
284 : }
285 : return 1;
286 : }
287 :
288 20 : int elektraCurlgetOpen (Plugin * handle, Key * errorKey ELEKTRA_UNUSED)
289 : {
290 20 : KeySet * config = elektraPluginGetConfig (handle);
291 20 : if (ksLookupByName (config, "/module", 0)) return 0;
292 2 : Data * data = elektraCalloc (sizeof (Data));
293 2 : if (!parseURLPath (data, config))
294 : {
295 0 : elektraFree (data);
296 0 : data = NULL;
297 0 : return 0;
298 : }
299 2 : Key * key = ksLookupByName (config, "/user", KDB_O_NONE);
300 2 : if (key)
301 : {
302 0 : data->user = keyString (key);
303 : }
304 2 : key = ksLookupByName (config, "/password", KDB_O_NONE);
305 2 : if (key)
306 : {
307 0 : data->password = keyString (key);
308 : }
309 2 : if (data->putProto == PROTO_HTTP || data->putProto == PROTO_HTTPS)
310 : {
311 0 : key = ksLookupByName (config, "/upload/method", KDB_O_NONE);
312 0 : if (key)
313 : {
314 0 : if (!(strcasecmp (keyString (key), "POST")))
315 : {
316 0 : data->uploadMethod = POST;
317 0 : key = ksLookupByName (config, "/upload/postfield", KDB_O_NONE);
318 0 : if (key)
319 : {
320 0 : data->postFieldName = keyString (key);
321 : }
322 : else
323 : {
324 0 : data->postFieldName = DEFAULT_POSTFIELDNAME;
325 : }
326 : }
327 0 : else if (!(strcasecmp (keyString (key), "PUT")))
328 : {
329 0 : data->uploadMethod = PUT;
330 : }
331 : else
332 : {
333 0 : data->uploadMethod = NA;
334 : }
335 : }
336 : }
337 2 : key = ksLookupByName (config, "upload/filename", KDB_O_NONE);
338 2 : if (key)
339 : {
340 0 : data->__uploadFileName = elektraStrDup (keyString (key));
341 0 : data->uploadFileName = data->__uploadFileName;
342 : }
343 : else
344 : {
345 2 : if (data->uploadMethod == POST)
346 : {
347 0 : if (!data->useLocalCopy)
348 : {
349 0 : data->__uploadFileName = elektraStrDup (data->getUrl);
350 0 : data->uploadFileName = basename (data->__uploadFileName);
351 : }
352 : else
353 : {
354 0 : data->__uploadFileName = elektraStrDup (data->path);
355 0 : data->uploadFileName = basename (data->__uploadFileName);
356 : }
357 : }
358 : }
359 :
360 2 : key = ksLookupByName (config, "/ssl/verify", KDB_O_NONE);
361 2 : if (key)
362 : {
363 0 : if (!strcmp (keyString (key), "1"))
364 : {
365 0 : data->sslVerifyPeer = 1;
366 0 : data->sslVerifyHost = 1;
367 0 : data->useSSL = 1;
368 : }
369 0 : key = ksLookupByName (config, "/ssl/verify/peer", KDB_O_NONE);
370 0 : if (key)
371 : {
372 0 : data->useSSL = 1;
373 0 : if (!strcmp (keyString (key), "1"))
374 0 : data->sslVerifyPeer = 1;
375 0 : else if (!strcmp (keyString (key), "0"))
376 0 : data->sslVerifyPeer = 0;
377 : }
378 0 : key = ksLookupByName (config, "/ssl/verify/host", KDB_O_NONE);
379 0 : if (key)
380 : {
381 0 : data->useSSL = 1;
382 0 : if (!strcmp (keyString (key), "1"))
383 0 : data->sslVerifyHost = 1;
384 0 : else if (!strcmp (keyString (key), "0"))
385 0 : data->sslVerifyHost = 0;
386 : }
387 : }
388 2 : key = ksLookupByName (config, "/prefer", KDB_O_NONE);
389 2 : data->preferRemote = 1;
390 2 : if (key && !strcasecmp (keyString (key), "local"))
391 : {
392 0 : data->preferRemote = 0;
393 : }
394 2 : key = ksLookupByName (config, "/ssh/auth", KDB_O_NONE);
395 2 : if (key)
396 : {
397 0 : if (!strcasecmp (keyString (key), "password"))
398 0 : data->sshAuth = SSH_PASSWORD;
399 0 : else if (!strcasecmp (keyString (key), "agent"))
400 0 : data->sshAuth = SSH_AGENT;
401 0 : else if (!strcasecmp (keyString (key), "pubkey"))
402 0 : data->sshAuth = SSH_PUBLICKEY;
403 0 : else if (!strcasecmp (keyString (key), "any"))
404 0 : data->sshAuth = SSH_ANY;
405 0 : else if (!strcasecmp (keyString (key), "pubkeypw"))
406 0 : data->sshAuth = SSH_PUBKEYPW;
407 : }
408 2 : key = ksLookupByName (config, "/ssh/key", KDB_O_NONE);
409 2 : if (!key)
410 : {
411 2 : data->keyFile = NULL;
412 : }
413 : else
414 : {
415 0 : data->keyFile = keyString (key);
416 : }
417 2 : key = ksLookupByName (config, "/ssh/key/passwd", KDB_O_NONE);
418 2 : if (key)
419 : {
420 0 : data->keyFilePasswd = keyString (key);
421 : }
422 : else
423 : {
424 2 : data->keyFilePasswd = NULL;
425 : }
426 2 : if (data->sshAuth == SSH_PASSWORD)
427 : {
428 0 : if (!data->password)
429 : {
430 0 : ELEKTRA_SET_VALIDATION_SEMANTIC_ERROR (
431 : errorKey, "No password specified for SSH password authentication in plugin configuration");
432 0 : if (data->uploadFileName) elektraFree (data->__uploadFileName);
433 0 : elektraFree (data);
434 0 : data = NULL;
435 0 : return 0;
436 : }
437 : }
438 :
439 2 : elektraPluginSetData (handle, data);
440 2 : return 1;
441 : }
442 :
443 0 : static unsigned char * hashBuffer (void * buffer, size_t size)
444 : {
445 : MD5_CTX c;
446 0 : unsigned char * out = elektraMalloc (MD5_DIGEST_LENGTH);
447 0 : MD5_Init (&c);
448 0 : MD5_Update (&c, buffer, size);
449 0 : MD5_Final (out, &c);
450 0 : return out;
451 : }
452 :
453 0 : static void setupSSH (CURL * curl, Data * data)
454 : {
455 0 : if (data->sshAuth == SSH_PUBLICKEY || data->sshAuth == SSH_PUBKEYPW)
456 : {
457 0 : if (data->sshAuth == SSH_PUBKEYPW)
458 0 : curl_easy_setopt (curl, CURLOPT_SSH_AUTH_TYPES, CURLSSH_AUTH_PUBLICKEY | CURLSSH_AUTH_PASSWORD);
459 : else
460 0 : curl_easy_setopt (curl, CURLOPT_SSH_AUTH_TYPES, CURLSSH_AUTH_PUBLICKEY);
461 :
462 0 : curl_easy_setopt (curl, CURLOPT_SSH_PRIVATE_KEYFILE, data->keyFile);
463 0 : if (data->keyFilePasswd)
464 : {
465 0 : curl_easy_setopt (curl, CURLOPT_KEYPASSWD, data->keyFilePasswd);
466 : }
467 : }
468 0 : else if (data->sshAuth == SSH_AGENT)
469 : {
470 0 : curl_easy_setopt (curl, CURLOPT_SSH_AUTH_TYPES, CURLSSH_AUTH_AGENT);
471 : }
472 0 : else if (data->sshAuth == SSH_PASSWORD)
473 : {
474 0 : curl_easy_setopt (curl, CURLOPT_SSH_AUTH_TYPES, CURLSSH_AUTH_PASSWORD);
475 : }
476 : else
477 : {
478 0 : curl_easy_setopt (curl, CURLOPT_SSH_AUTH_TYPES, CURLSSH_AUTH_ANY);
479 0 : if (data->keyFilePasswd)
480 : {
481 0 : curl_easy_setopt (curl, CURLOPT_KEYPASSWD, data->keyFilePasswd);
482 : }
483 : }
484 0 : }
485 :
486 0 : static FILE * fetchFile (Data * data, int fd)
487 : {
488 0 : CURL * curl = curl_easy_init ();
489 0 : if (!curl)
490 : {
491 0 : curl_easy_cleanup (curl);
492 0 : return NULL;
493 : }
494 : #if VERBOSE
495 : curl_easy_setopt (curl, CURLOPT_VERBOSE, 1L);
496 : #endif
497 0 : curl_easy_setopt (curl, CURLOPT_URL, data->getUrl);
498 0 : if (data->user)
499 : {
500 0 : curl_easy_setopt (curl, CURLOPT_USERNAME, data->user);
501 : }
502 0 : if (data->password)
503 : {
504 0 : curl_easy_setopt (curl, CURLOPT_PASSWORD, data->password);
505 : }
506 0 : if (data->getProto == PROTO_HTTPS || data->getProto == PROTO_FTPS || data->useSSL)
507 : {
508 0 : curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, (long) data->sslVerifyPeer);
509 0 : curl_easy_setopt (curl, CURLOPT_SSL_VERIFYHOST, (long) data->sslVerifyHost);
510 0 : curl_easy_setopt (curl, CURLOPT_USE_SSL, CURLUSESSL_ALL);
511 : }
512 : else
513 : {
514 0 : curl_easy_setopt (curl, CURLOPT_USE_SSL, CURLUSESSL_TRY);
515 : }
516 0 : if (data->getProto == PROTO_SCP || data->getProto == PROTO_SFTP)
517 : {
518 0 : setupSSH (curl, data);
519 : }
520 0 : curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, NULL);
521 0 : FILE * fp = fdopen (fd, "w+");
522 0 : curl_easy_setopt (curl, CURLOPT_WRITEDATA, fp);
523 : CURLcode res;
524 0 : res = curl_easy_perform (curl);
525 0 : long respCode = 0;
526 0 : curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &respCode);
527 0 : curl_easy_cleanup (curl);
528 0 : if (res != CURLE_OK || respCode != 200)
529 : {
530 0 : fclose (fp);
531 0 : return NULL;
532 : }
533 : return fp;
534 : }
535 :
536 0 : static int moveFile (const char * source, const char * dest)
537 : {
538 0 : FILE * inFile = NULL;
539 0 : FILE * outFile = NULL;
540 : struct stat buf;
541 0 : if (stat (source, &buf) == -1) return -1;
542 0 : size_t fileSize = buf.st_size;
543 0 : char * buffer = elektraMalloc (fileSize);
544 0 : inFile = fopen (source, "rb");
545 0 : size_t bytesRead = 0;
546 0 : while (bytesRead < fileSize)
547 : {
548 0 : size_t bytes = fread (buffer + bytesRead, 1, (size_t) fileSize, inFile);
549 0 : if (bytes == 0) break;
550 0 : bytesRead += bytes;
551 : }
552 0 : if (bytesRead < fileSize)
553 : {
554 0 : elektraFree (buffer);
555 0 : fclose (inFile);
556 0 : return -1;
557 : }
558 0 : fclose (inFile);
559 0 : outFile = fopen (dest, "wb+");
560 :
561 0 : size_t bytesWritten = 0;
562 0 : while (bytesWritten < fileSize)
563 : {
564 0 : size_t bytes = fwrite (buffer, 1, fileSize, outFile);
565 0 : if (bytes == 0) break;
566 0 : bytesWritten += bytes;
567 : }
568 0 : fclose (outFile);
569 0 : elektraFree (buffer);
570 :
571 0 : if (bytesWritten < fileSize)
572 : {
573 : return -1;
574 : }
575 0 : if (unlink (source))
576 : {
577 : return -1;
578 : }
579 0 : return 0;
580 : }
581 :
582 20 : int elektraCurlgetGet (Plugin * handle ELEKTRA_UNUSED, KeySet * returned ELEKTRA_UNUSED, Key * parentKey)
583 20 : {
584 20 : if (!elektraStrCmp (keyName (parentKey), "system/elektra/modules/curlget"))
585 : {
586 20 : KeySet * contract =
587 20 : ksNew (30, keyNew ("system/elektra/modules/curlget", KEY_VALUE, "curlget plugin waits for your orders", KEY_END),
588 : keyNew ("system/elektra/modules/curlget/exports", KEY_END),
589 : keyNew ("system/elektra/modules/curlget/exports/get", KEY_FUNC, elektraCurlgetGet, KEY_END),
590 : keyNew ("system/elektra/modules/curlget/exports/set", KEY_FUNC, elektraCurlgetSet, KEY_END),
591 : keyNew ("system/elektra/modules/curlget/exports/commit", KEY_FUNC, elektraCurlgetCommit, KEY_END),
592 : keyNew ("system/elektra/modules/curlget/exports/open", KEY_FUNC, elektraCurlgetOpen, KEY_END),
593 : keyNew ("system/elektra/modules/curlget/exports/close", KEY_FUNC, elektraCurlgetClose, KEY_END),
594 : keyNew ("system/elektra/modules/curlget/exports/error", KEY_FUNC, elektraCurlgetError, KEY_END),
595 : keyNew ("system/elektra/modules/curlget/exports/checkfile", KEY_FUNC, elektraCurlgetCheckFile, KEY_END),
596 : #include ELEKTRA_README
597 : keyNew ("system/elektra/modules/curlget/infos/version", KEY_VALUE, PLUGINVERSION, KEY_END), KS_END);
598 20 : ksAppend (returned, contract);
599 20 : ksDel (contract);
600 :
601 20 : return 1; // success
602 : }
603 :
604 0 : Data * data = elektraPluginGetData (handle);
605 0 : if (!data)
606 : {
607 : return -1;
608 : }
609 0 : int fd = 0;
610 0 : char name[] = TMP_NAME;
611 0 : fd = mkstemp (name);
612 0 : if (*(data->lastHash)) unlink (data->tmpFile);
613 0 : data->tmpFile = name;
614 :
615 0 : if (data->path) keySetString (parentKey, data->path);
616 0 : if (elektraResolveFilename (parentKey, ELEKTRA_RESOLVER_TEMPFILE_NONE) == -1)
617 : {
618 : return -1;
619 : }
620 0 : if (data->path) elektraFree (data->path);
621 0 : data->path = elektraStrDup (keyString (parentKey));
622 :
623 0 : if (fd == -1)
624 : {
625 0 : ELEKTRA_SET_RESOURCE_ERRORF (parentKey, "Failed to open %s for reading. Reason: %s", data->path, strerror (errno));
626 0 : return -1;
627 : }
628 0 : FILE * fp = fetchFile (data, fd);
629 0 : if (!fp)
630 : {
631 0 : close (fd);
632 0 : unlink (data->tmpFile);
633 0 : data->tmpFile = NULL;
634 0 : fp = fopen (data->path, "rb");
635 0 : if (fp && data->useLocalCopy)
636 : {
637 0 : ELEKTRA_ADD_RESOURCE_WARNINGF (parentKey, "Failed to fetch configuration from %s, falling back to local copy %s\n",
638 : data->getUrl, data->path);
639 : }
640 : else
641 : {
642 0 : ELEKTRA_SET_RESOURCE_ERRORF (parentKey, "Failed to read configuration. Reason: %s\n", strerror (errno));
643 0 : return -1;
644 : }
645 : }
646 0 : fseek (fp, 0L, SEEK_END);
647 0 : size_t size = ftell (fp);
648 0 : rewind (fp);
649 0 : unsigned char buffer[size];
650 0 : fread (&buffer, sizeof (char), size, fp);
651 0 : fclose (fp);
652 0 : unsigned char * hash = hashBuffer (buffer, size);
653 0 : if (!*(data->lastHash))
654 : {
655 0 : memcpy (data->lastHash, hash, MD5_DIGEST_LENGTH);
656 0 : if (data->useLocalCopy)
657 : {
658 0 : moveFile (data->tmpFile, data->path);
659 : }
660 : else
661 : {
662 0 : if (data->path) elektraFree (data->path);
663 0 : data->path = elektraStrDup (data->tmpFile);
664 : }
665 0 : data->tmpFile = NULL;
666 0 : keySetString (parentKey, data->path);
667 : }
668 0 : else if (data->tmpFile)
669 : {
670 0 : if (strncmp ((char *) data->lastHash, (char *) hash, MD5_DIGEST_LENGTH))
671 : {
672 : // remote file has changed
673 : // if preferRemote is set: replace local copy with
674 : // the remote version.
675 0 : if (data->preferRemote)
676 : {
677 0 : moveFile (data->tmpFile, data->path);
678 0 : data->tmpFile = NULL;
679 0 : keySetString (parentKey, data->path);
680 0 : memcpy (data->lastHash, hash, MD5_DIGEST_LENGTH);
681 : }
682 : else
683 : {
684 : // else drop remote version
685 : goto UNLINK_TMP;
686 : }
687 : }
688 : else
689 : {
690 : UNLINK_TMP:
691 : // remote file is the same as our local copy
692 0 : if (data->tmpFile) unlink (data->tmpFile);
693 0 : data->tmpFile = NULL;
694 0 : keySetString (parentKey, data->path);
695 : }
696 : }
697 0 : elektraFree (hash);
698 0 : return 1; // success
699 : }
700 :
701 0 : int elektraCurlgetSet (Plugin * handle ELEKTRA_UNUSED, KeySet * returned ELEKTRA_UNUSED, Key * parentKey ELEKTRA_UNUSED)
702 : {
703 : // get all keys
704 0 : Data * data = elektraPluginGetData (handle);
705 0 : if (!data) return -1;
706 0 : int retval = 1;
707 0 : if (data->setPhase == 0)
708 : {
709 0 : char name[] = TMP_NAME;
710 0 : int fd = mkstemp (name);
711 0 : if (data->tmpFile) unlink (data->tmpFile);
712 0 : data->tmpFile = name;
713 0 : FILE * fp = fetchFile (data, fd);
714 0 : if (fp)
715 0 : {
716 0 : fseek (fp, 0L, SEEK_END);
717 0 : size_t size = ftell (fp);
718 0 : rewind (fp);
719 0 : unsigned char buffer[size];
720 0 : fread (&buffer, sizeof (char), size, fp);
721 0 : fclose (fp);
722 0 : unsigned char * hash = hashBuffer (buffer, size);
723 0 : ++(data->setPhase);
724 0 : if (strncmp ((char *) data->lastHash, (char *) hash, MD5_DIGEST_LENGTH))
725 : {
726 0 : ELEKTRA_SET_CONFLICTING_STATE_ERROR (parentKey, "Remote file has changed");
727 0 : retval = -1;
728 : }
729 0 : elektraFree (hash);
730 0 : if (data->tmpFile) unlink (data->tmpFile);
731 0 : data->tmpFile = NULL;
732 0 : keySetString (parentKey, name);
733 : }
734 : else
735 : {
736 0 : close (fd);
737 0 : ELEKTRA_SET_CONFLICTING_STATE_ERRORF (
738 : parentKey, "Failed to fetch configuration from %s. Aborting because consistency can't be ensured",
739 : data->getUrl);
740 0 : if (data->tmpFile) unlink (data->tmpFile);
741 0 : data->tmpFile = NULL;
742 0 : if (data->useLocalCopy) keySetString (parentKey, data->path);
743 :
744 : retval = -1;
745 : }
746 0 : close (fd);
747 0 : if (retval != -1)
748 : {
749 0 : keySetString (parentKey, name);
750 0 : data->tmpFile = name;
751 0 : retval = 1;
752 : }
753 0 : if (!data->useLocalCopy && data->path)
754 : {
755 0 : unlink (data->path);
756 0 : if (data->path) elektraFree (data->path);
757 0 : data->path = NULL;
758 : }
759 : }
760 0 : else if (data->setPhase == 1)
761 : {
762 : FILE * fp;
763 0 : const char * tmpFile = keyString (parentKey);
764 0 : fp = fopen (tmpFile, "rb");
765 0 : if (!fp)
766 : {
767 0 : ELEKTRA_SET_RESOURCE_ERRORF (parentKey, "Failed to open %s for reading. Reason: %s", tmpFile, strerror (errno));
768 0 : return -1;
769 : }
770 0 : fseek (fp, 0L, SEEK_END);
771 0 : size_t size = ftell (fp);
772 0 : rewind (fp);
773 : CURL * curl;
774 0 : CURLcode res = 0;
775 0 : curl = curl_easy_init ();
776 0 : if (curl)
777 : {
778 : #if VERBOSE
779 : curl_easy_setopt (curl, CURLOPT_VERBOSE, 1L);
780 : #endif
781 0 : if (data->user)
782 : {
783 0 : curl_easy_setopt (curl, CURLOPT_USERNAME, data->user);
784 : }
785 0 : if (data->password)
786 : {
787 0 : curl_easy_setopt (curl, CURLOPT_PASSWORD, data->password);
788 : }
789 0 : curl_easy_setopt (curl, CURLOPT_VERBOSE, 1L);
790 0 : if (data->putProto == PROTO_HTTP || data->putProto == PROTO_HTTPS)
791 : {
792 0 : if (data->putProto == PROTO_HTTPS || data->useSSL)
793 : {
794 0 : curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, (long) data->sslVerifyPeer);
795 0 : curl_easy_setopt (curl, CURLOPT_SSL_VERIFYHOST, (long) data->sslVerifyHost);
796 0 : curl_easy_setopt (curl, CURLOPT_USE_SSL, CURLUSESSL_ALL);
797 : }
798 : else
799 : {
800 0 : curl_easy_setopt (curl, CURLOPT_USE_SSL, CURLUSESSL_TRY);
801 : }
802 :
803 0 : curl_easy_setopt (curl, CURLOPT_URL, data->uploadUrl);
804 :
805 0 : if (data->uploadMethod == POST)
806 : {
807 0 : struct curl_httppost * formpost = NULL;
808 0 : struct curl_httppost * lastptr = NULL;
809 0 : struct curl_slist * headerlist = NULL;
810 : static const char buf[] = "Expect:";
811 0 : curl_formadd (&formpost, &lastptr, CURLFORM_COPYNAME, data->postFieldName, CURLFORM_FILE, tmpFile,
812 : CURLFORM_FILENAME, data->uploadFileName, CURLFORM_END);
813 0 : headerlist = curl_slist_append (headerlist, buf);
814 0 : curl_easy_setopt (curl, CURLOPT_HTTPHEADER, headerlist);
815 0 : curl_easy_setopt (curl, CURLOPT_HTTPPOST, formpost);
816 0 : curl_easy_setopt (curl, CURLOPT_AUTOREFERER, 1L);
817 0 : res = curl_easy_perform (curl);
818 0 : if (res != CURLE_OK)
819 : {
820 :
821 0 : ELEKTRA_SET_RESOURCE_ERRORF (parentKey, "Curl upload (HTTP POST) failed: %s\n",
822 : curl_easy_strerror (res));
823 0 : retval = -1;
824 : }
825 0 : curl_formfree (formpost);
826 0 : curl_slist_free_all (headerlist);
827 : }
828 0 : else if (data->uploadMethod == PUT)
829 : {
830 0 : curl_easy_setopt (curl, CURLOPT_UPLOAD, 1L);
831 0 : curl_easy_setopt (curl, CURLOPT_READDATA, fp);
832 0 : curl_easy_setopt (curl, CURLOPT_CUSTOMREQUEST, "PUT");
833 0 : curl_easy_setopt (curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t) size);
834 0 : res = curl_easy_perform (curl);
835 0 : if (res != CURLE_OK)
836 : {
837 0 : ELEKTRA_SET_RESOURCE_ERRORF (parentKey, "Curl upload (HTTP PUT) failed. Reason: %s",
838 : curl_easy_strerror (res));
839 0 : retval = -1;
840 : }
841 : }
842 : else
843 : {
844 0 : curl_easy_setopt (curl, CURLOPT_UPLOAD, 1L);
845 0 : curl_easy_setopt (curl, CURLOPT_READDATA, fp);
846 0 : curl_easy_setopt (curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t) size);
847 0 : res = curl_easy_perform (curl);
848 0 : if (res != CURLE_OK)
849 : {
850 0 : ELEKTRA_SET_RESOURCE_ERRORF (parentKey, "Curl upload (HTTP PUT) failed. Reason: %s",
851 : curl_easy_strerror (res));
852 0 : retval = -1;
853 : }
854 : }
855 : }
856 0 : else if (data->putProto == PROTO_FTP || data->putProto == PROTO_FTPS)
857 : {
858 0 : if (data->putProto == PROTO_FTPS || data->useSSL)
859 : {
860 0 : curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, (long) data->sslVerifyPeer);
861 0 : curl_easy_setopt (curl, CURLOPT_SSL_VERIFYHOST, (long) data->sslVerifyHost);
862 0 : curl_easy_setopt (curl, CURLOPT_USE_SSL, CURLUSESSL_ALL);
863 : }
864 : else
865 : {
866 0 : curl_easy_setopt (curl, CURLOPT_USE_SSL, CURLUSESSL_TRY);
867 : }
868 :
869 0 : if (data->uploadFileName)
870 0 : {
871 0 : char uploadUrl[strlen (data->uploadUrl) + strlen (data->uploadFileName) + 2];
872 0 : snprintf (uploadUrl, sizeof (uploadUrl), "%s/%s", data->uploadUrl, data->uploadFileName);
873 0 : curl_easy_setopt (curl, CURLOPT_URL, uploadUrl);
874 : }
875 : else
876 : {
877 0 : curl_easy_setopt (curl, CURLOPT_URL, data->uploadUrl);
878 : }
879 :
880 0 : curl_easy_setopt (curl, CURLOPT_UPLOAD, 1L);
881 0 : curl_easy_setopt (curl, CURLOPT_READDATA, fp);
882 0 : curl_easy_setopt (curl, CURLOPT_UPLOAD, 1L);
883 0 : curl_easy_setopt (curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t) size);
884 0 : res = curl_easy_perform (curl);
885 0 : if (res != CURLE_OK)
886 : {
887 0 : ELEKTRA_SET_RESOURCE_ERRORF (parentKey, "Curl upload (FTP PUT) failed. Reason: %s",
888 : curl_easy_strerror (res));
889 0 : retval = -1;
890 : }
891 : }
892 0 : else if (data->putProto == PROTO_SCP || data->putProto == PROTO_SFTP)
893 : {
894 0 : setupSSH (curl, data);
895 0 : if (data->uploadFileName)
896 0 : {
897 0 : char uploadUrl[strlen (data->uploadUrl) + strlen (data->uploadFileName) + 2];
898 0 : snprintf (uploadUrl, sizeof (uploadUrl), "%s/%s", data->uploadUrl, data->uploadFileName);
899 0 : curl_easy_setopt (curl, CURLOPT_URL, uploadUrl);
900 : }
901 : else
902 : {
903 0 : curl_easy_setopt (curl, CURLOPT_URL, data->uploadUrl);
904 : }
905 :
906 0 : curl_easy_setopt (curl, CURLOPT_UPLOAD, 1L);
907 0 : curl_easy_setopt (curl, CURLOPT_READDATA, fp);
908 0 : curl_easy_setopt (curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t) size);
909 0 : res = curl_easy_perform (curl);
910 0 : if (res != CURLE_OK)
911 : {
912 0 : if (data->putProto == PROTO_SCP)
913 0 : ELEKTRA_SET_RESOURCE_ERRORF (parentKey, "Curl upload (SCP) failed. Reason: %s",
914 : curl_easy_strerror (res));
915 0 : else if (data->putProto == PROTO_SFTP)
916 0 : ELEKTRA_SET_RESOURCE_ERRORF (parentKey, "Curl upload (SFTP) failed. Reason: %s",
917 : curl_easy_strerror (res));
918 : retval = -1;
919 : }
920 : }
921 0 : else if (data->putProto == PROTO_SMB) // lgtm [cpp/empty-block]
922 : {
923 : }
924 : else
925 : {
926 0 : curl_easy_setopt (curl, CURLOPT_URL, data->uploadUrl);
927 0 : curl_easy_setopt (curl, CURLOPT_UPLOAD, 1L);
928 0 : curl_easy_setopt (curl, CURLOPT_READDATA, fp);
929 0 : curl_easy_setopt (curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t) size);
930 0 : res = curl_easy_perform (curl);
931 0 : if (res != CURLE_OK)
932 : {
933 0 : ELEKTRA_SET_RESOURCE_ERRORF (parentKey, "Curl upload (default) . Reason: %s",
934 : curl_easy_strerror (res));
935 0 : retval = -1;
936 : }
937 : }
938 0 : curl_easy_cleanup (curl);
939 0 : fclose (fp);
940 : }
941 0 : if (retval != -1)
942 : {
943 0 : if (data->useLocalCopy)
944 : {
945 0 : if (moveFile (tmpFile, data->path))
946 : {
947 0 : if (data->tmpFile)
948 : {
949 0 : unlink (data->tmpFile);
950 : }
951 : }
952 0 : data->tmpFile = NULL;
953 0 : keySetString (parentKey, data->path);
954 : }
955 : else
956 : {
957 0 : if (data->tmpFile)
958 : {
959 0 : unlink (data->tmpFile);
960 0 : data->tmpFile = NULL;
961 : }
962 0 : if (data->path)
963 : {
964 0 : unlink (data->path);
965 0 : elektraFree (data->path);
966 0 : data->path = NULL;
967 : }
968 0 : if (tmpFile)
969 : {
970 0 : unlink (tmpFile);
971 : }
972 : }
973 : }
974 : else
975 : {
976 0 : if (!data->useLocalCopy)
977 : {
978 0 : if (data->tmpFile)
979 : {
980 0 : unlink (data->tmpFile);
981 0 : data->tmpFile = NULL;
982 : }
983 0 : if (data->path)
984 : {
985 0 : unlink (data->path);
986 0 : elektraFree (data->path);
987 0 : data->path = NULL;
988 : }
989 0 : if (tmpFile)
990 : {
991 0 : unlink (tmpFile);
992 : }
993 : }
994 : else
995 : {
996 0 : if (tmpFile) unlink (tmpFile);
997 0 : if (data->tmpFile)
998 : {
999 0 : unlink (data->tmpFile);
1000 0 : data->tmpFile = NULL;
1001 : }
1002 : }
1003 : }
1004 : }
1005 :
1006 : return retval; // success
1007 : }
1008 :
1009 0 : int elektraCurlgetCommit (Plugin * handle ELEKTRA_UNUSED, KeySet * returned ELEKTRA_UNUSED, Key * parentKey ELEKTRA_UNUSED)
1010 : {
1011 0 : return elektraCurlgetSet (handle, returned, parentKey);
1012 : }
1013 :
1014 20 : Plugin * ELEKTRA_PLUGIN_EXPORT
1015 : {
1016 : // clang-format off
1017 20 : return elektraPluginExport ("curlget",
1018 : ELEKTRA_PLUGIN_GET, &elektraCurlgetGet,
1019 : ELEKTRA_PLUGIN_SET, &elektraCurlgetSet,
1020 : ELEKTRA_PLUGIN_OPEN, &elektraCurlgetOpen,
1021 : ELEKTRA_PLUGIN_CLOSE, &elektraCurlgetClose,
1022 : ELEKTRA_PLUGIN_ERROR, &elektraCurlgetError,
1023 : ELEKTRA_PLUGIN_COMMIT, &elektraCurlgetCommit,
1024 : ELEKTRA_PLUGIN_END);
1025 : }
1026 :
|