LCOV - code coverage report
Current view: top level - src/plugins/fcrypt - fcrypt.c (source / functions) Hit Total Coverage
Test: coverage-filtered.info Lines: 26 240 10.8 %
Date: 2019-09-12 12:28:41 Functions: 4 15 26.7 %

          Line data    Source code
       1             : /**
       2             :  * @file
       3             :  *
       4             :  * @brief filter plugin providing cryptographic operations
       5             :  *
       6             :  * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
       7             :  *
       8             :  */
       9             : 
      10             : #ifndef HAVE_KDBCONFIG
      11             : #include "kdbconfig.h"
      12             : #endif
      13             : 
      14             : #include "fcrypt.h"
      15             : 
      16             : 
      17             : #include <errno.h>
      18             : #include <fcntl.h>
      19             : #include <gpg.h>
      20             : #include <libgen.h> // provides basename()
      21             : #include <stdlib.h>
      22             : #include <string.h>
      23             : #include <sys/stat.h>
      24             : #include <sys/time.h>
      25             : #include <sys/types.h>
      26             : #include <unistd.h>
      27             : 
      28             : #include <kdb.h>
      29             : #include <kdberrors.h>
      30             : #include <kdbmacros.h>
      31             : #include <kdbtypes.h>
      32             : 
      33             : /**
      34             :  * @brief Defines the plugin state during the <code>kdb get</code> phase.
      35             :  */
      36             : enum FcryptGetState
      37             : {
      38             : 
      39             :         /** Perform a decryption run before <code>kdb get</code> reads from the storage. */
      40             :         PREGETSTORAGE = 0,
      41             : 
      42             :         /** Perform an encryption run after <code>kdb get</code> has read from the storage. */
      43             :         POSTGETSTORAGE = 1
      44             : };
      45             : 
      46             : struct _fcryptState
      47             : {
      48             :         enum FcryptGetState getState;
      49             :         int tmpFileFd;
      50             :         char * tmpFilePath;
      51             :         char * originalFilePath;
      52             : };
      53             : typedef struct _fcryptState fcryptState;
      54             : 
      55             : #define ELEKTRA_FCRYPT_TMP_FILE_SUFFIX "XXXXXX"
      56             : 
      57             : /**
      58             :  * @brief Allocates a new string holding the name of the temporary file.
      59             :  * This method makes use of the "fcrypt/tmpdir" plugin configuration option.
      60             :  * @param conf holds the plugin configuration
      61             :  * @param file holds the path to the original file
      62             :  * @param fd will hold the file descriptor to the temporary file in case of success
      63             :  * @returns an allocated string holding the name of the encrypted file. Must be freed by the caller.
      64             :  */
      65           0 : static char * getTemporaryFileName (KeySet * conf, const char * file, int * fd)
      66             : {
      67             :         // read the temporary directory to use from the plugin configuration
      68             :         // NOTE the string contained in tmpDir must not be modified!
      69           0 :         const char * tmpDir = NULL;
      70           0 :         Key * k = ksLookupByName (conf, ELEKTRA_FCRYPT_CONFIG_TMPDIR, 0);
      71           0 :         if (k)
      72             :         {
      73           0 :                 tmpDir = keyString (k);
      74             :         }
      75             : 
      76           0 :         if (!tmpDir)
      77             :         {
      78             :                 // check the environment; returns NULL if no match is found
      79           0 :                 tmpDir = getenv ("TMPDIR");
      80             :         }
      81             : 
      82           0 :         if (!tmpDir)
      83             :         {
      84             :                 // fallback
      85           0 :                 tmpDir = ELEKTRA_FCRYPT_DEFAULT_TMPDIR;
      86             :         }
      87             : 
      88             :         // extract the file name (base name) from the path
      89           0 :         char * fileDup = elektraStrDup (file);
      90           0 :         if (!fileDup) goto error;
      91           0 :         const char * baseName = basename (fileDup);
      92             : 
      93             :         // + 1 to add an additional '/' as path separator
      94             :         // + 1 to reserve space for the NULL terminator
      95             :         // ----------------------------------------------
      96             :         // + 2 characters in total
      97           0 :         const size_t newFileAllocated = strlen (tmpDir) + strlen (baseName) + strlen (ELEKTRA_FCRYPT_TMP_FILE_SUFFIX) + 2;
      98           0 :         char * newFile = elektraMalloc (newFileAllocated);
      99           0 :         if (!newFile) goto error;
     100           0 :         snprintf (newFile, newFileAllocated, "%s/%s" ELEKTRA_FCRYPT_TMP_FILE_SUFFIX, tmpDir, baseName);
     101           0 :         *fd = mkstemp (newFile);
     102           0 :         if (*fd < 0)
     103             :         {
     104           0 :                 elektraFree (newFile);
     105           0 :                 goto error;
     106             :         }
     107             : 
     108           0 :         elektraFree (fileDup);
     109           0 :         return newFile;
     110             : 
     111             : error:
     112           0 :         elektraFree (fileDup);
     113           0 :         return NULL;
     114             : }
     115             : 
     116             : /**
     117             :  * @brief Overwrites the content of the given file with zeroes.
     118             :  * @param fd holds the file descriptor to the temporary file to be shredded
     119             :  * @param errorKey holds an error description in case of failure
     120             :  * @retval 1 on success
     121             :  * @retval -1 on failure. In this case errorKey holds an error description.
     122             :  */
     123           0 : static int shredTemporaryFile (int fd, Key * errorKey)
     124             : {
     125           0 :         kdb_octet_t buffer[512] = { 0 };
     126             :         struct stat tmpStat;
     127             : 
     128           0 :         if (fstat (fd, &tmpStat))
     129             :         {
     130           0 :                 ELEKTRA_SET_RESOURCE_ERRORF (
     131             :                         errorKey,
     132             :                         "Failed to overwrite the temporary data. Cannot retrieve file status. Unencrypted data may leak. Errno: %s",
     133             :                         strerror (errno));
     134           0 :                 return -1;
     135             :         }
     136             : 
     137           0 :         if (lseek (fd, 0, SEEK_SET))
     138             :         {
     139             :                 goto error;
     140             :         }
     141             : 
     142           0 :         for (off_t i = 0; i < tmpStat.st_size; i += sizeof (buffer))
     143             :         {
     144           0 :                 if (write (fd, buffer, sizeof (buffer)) != sizeof (buffer))
     145             :                 {
     146             :                         goto error;
     147             :                 }
     148             :         }
     149             :         return 1;
     150             : 
     151             : error:
     152           0 :         ELEKTRA_SET_RESOURCE_ERRORF (errorKey, "Failed to overwrite the temporary data. Unencrypted data may leak. Reason: %s",
     153             :                                      strerror (errno));
     154           0 :         return -1;
     155             : }
     156             : 
     157             : /**
     158             :  * @brief lookup if the test mode for unit testing is enabled.
     159             :  * @param conf KeySet holding the plugin configuration.
     160             :  * @retval 0 test mode is not enabled
     161             :  * @retval 1 test mode is enabled
     162             :  */
     163           0 : static int inTestMode (KeySet * conf)
     164             : {
     165           0 :         Key * k = ksLookupByName (conf, ELEKTRA_CRYPTO_PARAM_GPG_UNIT_TEST, 0);
     166           0 :         if (k && !strcmp (keyString (k), "1"))
     167             :         {
     168             :                 return 1;
     169             :         }
     170             :         return 0;
     171             : }
     172             : 
     173             : /**
     174             :  * @brief lookup if the text mode is disabled in the plugin config.
     175             :  * It is enabled per default.
     176             :  * @param conf KeySet holding the plugin configuration.
     177             :  * @retval 0 text mode is not enabled
     178             :  * @retval 1 text mode is enabled
     179             :  */
     180           0 : static int inTextMode (KeySet * conf)
     181             : {
     182           0 :         Key * k = ksLookupByName (conf, ELEKTRA_FCRYPT_CONFIG_TEXTMODE, 0);
     183           0 :         if (k && !strcmp (keyString (k), "0"))
     184             :         {
     185             :                 return 0;
     186             :         }
     187             :         return 1;
     188             : }
     189             : 
     190             : /**
     191             :  * @brief Read number of total GPG recipient keys from the plugin configuration.
     192             :  * @param config holds the plugin configuration
     193             :  * @param keyName holds the name of the root key to look up
     194             :  * @returns the number of GPG recipient keys.
     195             :  */
     196           0 : static size_t getRecipientCount (KeySet * config, const char * keyName)
     197             : {
     198             :         Key * k;
     199           0 :         size_t recipientCount = 0;
     200           0 :         Key * root = ksLookupByName (config, keyName, 0);
     201             : 
     202           0 :         if (!root) return 0;
     203             : 
     204             :         // toplevel
     205           0 :         if (strlen (keyString (root)) > 0)
     206             :         {
     207           0 :                 recipientCount++;
     208             :         }
     209             : 
     210           0 :         ksRewind (config);
     211           0 :         while ((k = ksNext (config)) != 0)
     212             :         {
     213           0 :                 if (keyIsBelow (k, root) && strlen (keyString (k)) > 0)
     214             :                 {
     215           0 :                         recipientCount++;
     216             :                 }
     217             :         }
     218             :         return recipientCount;
     219             : }
     220             : 
     221           0 : static int fcryptGpgCallAndCleanup (Key * parentKey, KeySet * pluginConfig, char ** argv, int argc, int tmpFileFd, char * tmpFile)
     222             : {
     223           0 :         int parentKeyFd = -1;
     224           0 :         int result = ELEKTRA_PLUGIN_FUNCTION (gpgCall) (pluginConfig, parentKey, NULL, argv, argc);
     225             : 
     226           0 :         if (result == 1)
     227             :         {
     228           0 :                 parentKeyFd = open (keyString (parentKey), O_WRONLY);
     229             : 
     230             :                 // gpg call returned success, overwrite the original file with the gpg payload data
     231           0 :                 if (rename (tmpFile, keyString (parentKey)) != 0)
     232             :                 {
     233           0 :                         ELEKTRA_SET_RESOURCE_ERRORF (parentKey, "Renaming file %s to %s failed. Reason: %s", tmpFile, keyString (parentKey),
     234             :                                                      strerror (errno));
     235           0 :                         result = -1;
     236             :                 }
     237             :         }
     238             : 
     239           0 :         if (result == 1)
     240             :         {
     241           0 :                 if (parentKeyFd >= 0)
     242             :                 {
     243           0 :                         shredTemporaryFile (parentKeyFd, parentKey);
     244             :                 }
     245             :         }
     246             :         else
     247             :         {
     248             :                 // if anything went wrong above the temporary file is shredded and removed
     249           0 :                 shredTemporaryFile (tmpFileFd, parentKey);
     250           0 :                 if (unlink (tmpFile))
     251             :                 {
     252           0 :                         ELEKTRA_ADD_RESOURCE_WARNINGF (
     253             :                                 parentKey,
     254             :                                 "Failed to unlink a temporary file. WARNING: unencrypted data may leak! Please try to delete "
     255             :                                 "the file manually. Affected file: %s. Reason: %s",
     256             :                                 tmpFile, strerror (errno));
     257             :                 }
     258             :         }
     259             : 
     260           0 :         if (parentKeyFd >= 0 && close (parentKeyFd))
     261             :         {
     262           0 :                 ELEKTRA_ADD_RESOURCE_WARNINGF (parentKey, "Failed to close a file descriptor: %s", strerror (errno));
     263             :         }
     264           0 :         if (close (tmpFileFd))
     265             :         {
     266           0 :                 ELEKTRA_ADD_RESOURCE_WARNINGF (parentKey, "Failed to close a file descriptor: %s", strerror (errno));
     267             :         }
     268           0 :         elektraFree (tmpFile);
     269           0 :         return result;
     270             : }
     271             : 
     272             : /**
     273             :  * @brief encrypt or sign the file specified at parentKey
     274             :  * @param pluginConfig holds the plugin configuration
     275             :  * @param parentKey holds the path to the file to be encrypted. Will hold an error description in case of failure.
     276             :  * @retval 1 on success
     277             :  * @retval -1 on error, errorKey holds an error description
     278             :  */
     279           0 : static int fcryptEncrypt (KeySet * pluginConfig, Key * parentKey)
     280           0 : {
     281             :         Key * k;
     282           0 :         const size_t recipientCount = getRecipientCount (pluginConfig, ELEKTRA_RECIPIENT_KEY);
     283           0 :         const size_t signatureCount = getRecipientCount (pluginConfig, ELEKTRA_SIGNATURE_KEY);
     284             : 
     285           0 :         if (recipientCount == 0 && signatureCount == 0)
     286             :         {
     287           0 :                 ELEKTRA_SET_INSTALLATION_ERRORF (
     288             :                         parentKey,
     289             :                         "Missing GPG recipient key (specified as %s) or GPG signature key (specified as %s) in plugin configuration",
     290             :                         ELEKTRA_RECIPIENT_KEY, ELEKTRA_SIGNATURE_KEY);
     291           0 :                 return -1;
     292             :         }
     293             : 
     294           0 :         int tmpFileFd = -1;
     295           0 :         char * tmpFile = getTemporaryFileName (pluginConfig, keyString (parentKey), &tmpFileFd);
     296           0 :         if (!tmpFile)
     297             :         {
     298           0 :                 ELEKTRA_SET_OUT_OF_MEMORY_ERROR (parentKey, "Memory allocation failed");
     299           0 :                 return -1;
     300             :         }
     301             : 
     302           0 :         const size_t testMode = inTestMode (pluginConfig);
     303           0 :         const size_t textMode = inTextMode (pluginConfig);
     304             : 
     305             :         // prepare argument vector for gpg call
     306             :         // 7 static arguments (magic number below) are:
     307             :         //   1. path to the binary
     308             :         //   2. --batch
     309             :         //   3. -o
     310             :         //   4. path to tmp file
     311             :         //   5. yes
     312             :         //   6. file to be encrypted
     313             :         //   7. NULL terminator
     314           0 :         int argc = 7 + (2 * recipientCount) + (2 * signatureCount) + (2 * testMode) + textMode + (recipientCount > 0 ? 1 : 0) +
     315             :                    (signatureCount > 0 ? 1 : 0);
     316           0 :         kdb_unsigned_short_t i = 0;
     317           0 :         char * argv[argc];
     318           0 :         argv[i++] = NULL;
     319           0 :         argv[i++] = "--batch";
     320           0 :         argv[i++] = "-o";
     321           0 :         argv[i++] = tmpFile;
     322           0 :         argv[i++] = "--yes"; // overwrite files if they exist
     323             : 
     324             :         // add recipients
     325           0 :         Key * gpgRecipientRoot = ksLookupByName (pluginConfig, ELEKTRA_RECIPIENT_KEY, 0);
     326             : 
     327             :         // append root (gpg/key) as gpg recipient
     328           0 :         if (gpgRecipientRoot && strlen (keyString (gpgRecipientRoot)) > 0)
     329             :         {
     330           0 :                 argv[i++] = "-r";
     331             :                 // NOTE argv[] values will not be modified, so const can be discarded safely
     332           0 :                 argv[i++] = (char *) keyString (gpgRecipientRoot);
     333             :         }
     334             : 
     335             :         // append keys beneath root (crypto/key/#_) as gpg recipients
     336           0 :         if (gpgRecipientRoot)
     337             :         {
     338           0 :                 ksRewind (pluginConfig);
     339           0 :                 while ((k = ksNext (pluginConfig)) != 0)
     340             :                 {
     341           0 :                         const char * kStringVal = keyString (k);
     342           0 :                         if (keyIsBelow (k, gpgRecipientRoot) && strlen (kStringVal) > 0)
     343             :                         {
     344           0 :                                 argv[i++] = "-r";
     345             :                                 // NOTE argv[] values will not be modified, so const can be discarded safely
     346           0 :                                 argv[i++] = (char *) kStringVal;
     347             :                         }
     348             :                 }
     349             :         }
     350             : 
     351             : 
     352             :         // add signature keys
     353           0 :         Key * gpgSignatureRoot = ksLookupByName (pluginConfig, ELEKTRA_SIGNATURE_KEY, 0);
     354             : 
     355             :         // append root signature key
     356           0 :         if (gpgSignatureRoot && strlen (keyString (gpgSignatureRoot)) > 0)
     357             :         {
     358           0 :                 argv[i++] = "-u";
     359             :                 // NOTE argv[] values will not be modified, so const can be discarded safely
     360           0 :                 argv[i++] = (char *) keyString (gpgSignatureRoot);
     361             :         }
     362             : 
     363             :         // append keys beneath root (fcrypt/sign/#_) as gpg signature keys
     364           0 :         if (gpgSignatureRoot)
     365             :         {
     366           0 :                 ksRewind (pluginConfig);
     367           0 :                 while ((k = ksNext (pluginConfig)) != 0)
     368             :                 {
     369           0 :                         const char * kStringVal = keyString (k);
     370           0 :                         if (keyIsBelow (k, gpgSignatureRoot) && strlen (kStringVal) > 0)
     371             :                         {
     372           0 :                                 argv[i++] = "-u";
     373             :                                 // NOTE argv[] values will not be modified, so const can be discarded safely
     374           0 :                                 argv[i++] = (char *) kStringVal;
     375             :                         }
     376             :                 }
     377             :         }
     378             : 
     379             :         // if we are in test mode we add the trust model
     380           0 :         if (testMode > 0)
     381             :         {
     382           0 :                 argv[i++] = "--trust-model";
     383           0 :                 argv[i++] = "always";
     384             :         }
     385             : 
     386             :         // ASCII armor in text mode
     387           0 :         if (textMode)
     388             :         {
     389           0 :                 argv[i++] = "--armor";
     390             :         }
     391             : 
     392             :         // prepare rest of the argument vector
     393           0 :         if (recipientCount > 0)
     394             :         {
     395             :                 // encrypt the file
     396           0 :                 argv[i++] = "-e";
     397             :         }
     398             : 
     399           0 :         if (signatureCount > 0)
     400             :         {
     401           0 :                 if (textMode && recipientCount == 0)
     402             :                 {
     403             :                         // clear-sign the file
     404           0 :                         argv[i++] = "--clearsign";
     405             :                 }
     406             :                 else
     407             :                 {
     408             :                         // sign the file
     409           0 :                         argv[i++] = "-s";
     410             :                 }
     411             :         }
     412             : 
     413           0 :         argv[i++] = (char *) keyString (parentKey);
     414           0 :         argv[i++] = NULL;
     415             : 
     416             :         // NOTE the encryption process works like this:
     417             :         // gpg2 --batch --yes -o encryptedFile -r keyID -e configFile
     418             :         // mv encryptedFile configFile
     419             : 
     420           0 :         return fcryptGpgCallAndCleanup (parentKey, pluginConfig, argv, argc, tmpFileFd, tmpFile);
     421             : }
     422             : 
     423             : /**
     424             :  * @brief decrypt the file specified at parentKey
     425             :  * @param pluginConfig holds the plugin configuration
     426             :  * @param parentKey holds the path to the file to be encrypted. Will hold an error description in case of failure.
     427             :  * @param state holds the plugin state
     428             :  * @retval 1 on success
     429             :  * @retval -1 on error, errorKey holds an error description
     430             :  */
     431           0 : static int fcryptDecrypt (KeySet * pluginConfig, Key * parentKey, fcryptState * state)
     432           0 : {
     433           0 :         int tmpFileFd = -1;
     434           0 :         char * tmpFile = getTemporaryFileName (pluginConfig, keyString (parentKey), &tmpFileFd);
     435           0 :         if (!tmpFile)
     436             :         {
     437           0 :                 ELEKTRA_SET_OUT_OF_MEMORY_ERROR (parentKey, "Memory allocation failed");
     438           0 :                 return -1;
     439             :         }
     440             : 
     441           0 :         const size_t testMode = inTestMode (pluginConfig);
     442             : 
     443             :         // prepare argument vector for gpg call
     444             :         // 8 static arguments (magic number below) are:
     445             :         //   1. path to the binary
     446             :         //   2. --batch
     447             :         //   3. -o
     448             :         //   4. path to tmp file
     449             :         //   5. yes
     450             :         //   6. -d
     451             :         //   7. file to be encrypted
     452             :         //   8. NULL terminator
     453           0 :         int argc = 8 + (2 * testMode);
     454           0 :         char * argv[argc];
     455           0 :         int i = 0;
     456             : 
     457           0 :         argv[i++] = NULL;
     458           0 :         argv[i++] = "--batch";
     459           0 :         argv[i++] = "--yes";
     460             : 
     461             :         // if we are in test mode we add the trust model
     462           0 :         if (testMode)
     463             :         {
     464           0 :                 argv[i++] = "--trust-model";
     465           0 :                 argv[i++] = "always";
     466             :         }
     467             : 
     468           0 :         argv[i++] = "-o";
     469           0 :         argv[i++] = tmpFile;
     470           0 :         argv[i++] = "-d";
     471             :         // safely discarding const from keyString() return value
     472           0 :         argv[i++] = (char *) keyString (parentKey);
     473           0 :         argv[i++] = NULL;
     474             : 
     475             :         // NOTE the decryption process works like this:
     476             :         // gpg2 --batch --yes -o tmpfile -d configFile
     477           0 :         int result = ELEKTRA_PLUGIN_FUNCTION (gpgCall) (pluginConfig, parentKey, NULL, argv, argc);
     478           0 :         if (result == 1)
     479             :         {
     480           0 :                 state->originalFilePath = elektraStrDup (keyString (parentKey));
     481           0 :                 state->tmpFilePath = tmpFile;
     482           0 :                 state->tmpFileFd = tmpFileFd;
     483           0 :                 keySetString (parentKey, tmpFile);
     484             :         }
     485             :         else
     486             :         {
     487             :                 // if anything went wrong above the temporary file is shredded and removed
     488           0 :                 shredTemporaryFile (tmpFileFd, parentKey);
     489           0 :                 if (unlink (tmpFile))
     490             :                 {
     491           0 :                         ELEKTRA_ADD_RESOURCE_WARNINGF (
     492             :                                 parentKey,
     493             :                                 "Failed to unlink a temporary file. WARNING: unencrypted data may leak! Please try to delete "
     494             :                                 "the file manually. Affected file: %s, error description: %s",
     495             :                                 tmpFile, strerror (errno));
     496             :                 }
     497           0 :                 if (close (tmpFileFd))
     498             :                 {
     499           0 :                         ELEKTRA_ADD_RESOURCE_WARNINGF (parentKey, "Failed to close a file descriptor: %s", strerror (errno));
     500             :                 }
     501           0 :                 elektraFree (tmpFile);
     502             :         }
     503             :         return result;
     504             : }
     505             : 
     506             : /**
     507             :  * @brief allocates plugin state handle and initializes the plugin state
     508             :  * @retval 1 on success
     509             :  * @retval -1 on failure
     510             :  */
     511          20 : int ELEKTRA_PLUGIN_FUNCTION (open) (Plugin * handle, KeySet * ks ELEKTRA_UNUSED, Key * parentKey)
     512             : {
     513          20 :         fcryptState * s = elektraMalloc (sizeof (fcryptState));
     514          20 :         if (!s)
     515             :         {
     516           0 :                 ELEKTRA_SET_OUT_OF_MEMORY_ERROR (parentKey, "Memory allocation failed");
     517           0 :                 return -1;
     518             :         }
     519             : 
     520          20 :         s->getState = PREGETSTORAGE;
     521          20 :         s->tmpFileFd = -1;
     522          20 :         s->tmpFilePath = NULL;
     523          20 :         s->originalFilePath = NULL;
     524             : 
     525          20 :         elektraPluginSetData (handle, s);
     526          20 :         return 1;
     527             : }
     528             : 
     529             : /**
     530             :  * @brief frees the plugin state handle
     531             :  * @retval 1 on success
     532             :  * @retval -1 on failure
     533             :  */
     534          20 : int ELEKTRA_PLUGIN_FUNCTION (close) (Plugin * handle, KeySet * ks ELEKTRA_UNUSED, Key * parentKey ELEKTRA_UNUSED)
     535             : {
     536          20 :         fcryptState * s = (fcryptState *) elektraPluginGetData (handle);
     537          20 :         if (s)
     538             :         {
     539          20 :                 if (s->tmpFileFd > 0 && close (s->tmpFileFd))
     540             :                 {
     541           0 :                         ELEKTRA_ADD_RESOURCE_WARNINGF (parentKey, "Failed to close a file descriptor: %s", strerror (errno));
     542             :                 }
     543          20 :                 if (s->tmpFilePath)
     544             :                 {
     545           0 :                         elektraFree (s->tmpFilePath);
     546             :                 }
     547          20 :                 if (s->originalFilePath)
     548             :                 {
     549           0 :                         elektraFree (s->originalFilePath);
     550             :                 }
     551          20 :                 elektraFree (s);
     552          20 :                 elektraPluginSetData (handle, NULL);
     553             :         }
     554          20 :         return 1;
     555             : }
     556             : 
     557             : /**
     558             :  * @brief establish the Elektra plugin contract and decrypt the file provided at parentKey using GPG.
     559             :  * @retval 1 on success
     560             :  * @retval -1 on failure
     561             :  */
     562          20 : int ELEKTRA_PLUGIN_FUNCTION (get) (Plugin * handle, KeySet * ks ELEKTRA_UNUSED, Key * parentKey)
     563             : {
     564             :         // Publish module configuration to Elektra (establish the contract)
     565          20 :         if (!strcmp (keyName (parentKey), "system/elektra/modules/" ELEKTRA_PLUGIN_NAME))
     566             :         {
     567          20 :                 KeySet * moduleConfig = ksNew (30,
     568             : #include "contract.h"
     569             :                                                KS_END);
     570          20 :                 ksAppend (ks, moduleConfig);
     571          20 :                 ksDel (moduleConfig);
     572          20 :                 return 1;
     573             :         }
     574             : 
     575             :         // check plugin state
     576           0 :         KeySet * pluginConfig = elektraPluginGetConfig (handle);
     577           0 :         fcryptState * s = (fcryptState *) elektraPluginGetData (handle);
     578           0 :         if (!s)
     579             :         {
     580           0 :                 ELEKTRA_SET_INSTALLATION_ERROR (parentKey, "No plugin state is available");
     581           0 :                 return -1;
     582             :         }
     583             : 
     584           0 :         if (s->getState == POSTGETSTORAGE)
     585             :         {
     586             :                 // postgetstorage call will re-direct the parent key to the original encrypted/signed file
     587           0 :                 if (s->originalFilePath)
     588             :                 {
     589           0 :                         keySetString (parentKey, s->originalFilePath);
     590             :                 }
     591             :                 else
     592             :                 {
     593           0 :                         ELEKTRA_SET_INTERNAL_ERROR (parentKey, "The path to the original file is lost");
     594             :                         // clean-up is performed by kdb close
     595           0 :                         return -1;
     596             :                 }
     597             : 
     598           0 :                 if (s->tmpFileFd > 0)
     599             :                 {
     600           0 :                         shredTemporaryFile (s->tmpFileFd, parentKey);
     601           0 :                         if (close (s->tmpFileFd))
     602             :                         {
     603           0 :                                 ELEKTRA_ADD_RESOURCE_WARNINGF (parentKey, "Failed to close a file descriptor: %s", strerror (errno));
     604             :                         }
     605           0 :                         s->tmpFileFd = -1;
     606           0 :                         if (unlink (s->tmpFilePath))
     607             :                         {
     608           0 :                                 ELEKTRA_ADD_RESOURCE_WARNINGF (
     609             :                                         parentKey,
     610             :                                         "Failed to unlink a temporary file. WARNING: unencrypted data may leak! Please try "
     611             :                                         "to delete the file manually. Affected file: %s, error description: %s",
     612             :                                         s->tmpFilePath, strerror (errno));
     613             :                         }
     614           0 :                         elektraFree (s->tmpFilePath);
     615           0 :                         s->tmpFilePath = NULL;
     616             :                 }
     617             :                 return 1;
     618             :         }
     619             : 
     620             :         // now this is a pregetstorage call
     621             :         // next time treat the kdb get call as postgetstorage call to trigger encryption after the file has been read
     622           0 :         s->getState = POSTGETSTORAGE;
     623           0 :         return fcryptDecrypt (pluginConfig, parentKey, s);
     624             : }
     625             : 
     626             : /**
     627             :  * @brief Encrypt the file provided at parentKey using GPG.
     628             :  * @retval 1 on success
     629             :  * @retval -1 on failure
     630             :  */
     631           0 : int ELEKTRA_PLUGIN_FUNCTION (set) (Plugin * handle, KeySet * ks ELEKTRA_UNUSED, Key * parentKey)
     632             : {
     633           0 :         KeySet * pluginConfig = elektraPluginGetConfig (handle);
     634           0 :         int encryptionResult = fcryptEncrypt (pluginConfig, parentKey);
     635           0 :         if (encryptionResult != 1) return encryptionResult;
     636             : 
     637             :         /* set all keys */
     638           0 :         const char * configFile = keyString (parentKey);
     639           0 :         if (!strcmp (configFile, "")) return 1; // no underlying config file
     640           0 :         int fd = open (configFile, O_RDWR);
     641           0 :         if (fd == -1)
     642             :         {
     643           0 :                 ELEKTRA_SET_RESOURCE_ERRORF (parentKey, "Could not open config file %s. Reason: %s", configFile, strerror (errno));
     644           0 :                 return -1;
     645             :         }
     646           0 :         if (fsync (fd) == -1)
     647             :         {
     648           0 :                 ELEKTRA_SET_RESOURCE_ERRORF (parentKey, "Could not fsync config file %s. Reason: %s", configFile, strerror (errno));
     649           0 :                 if (close (fd))
     650             :                 {
     651           0 :                         ELEKTRA_ADD_RESOURCE_WARNINGF (parentKey, "Failed to close a file descriptor: %s", strerror (errno));
     652             :                 }
     653             :                 return -1;
     654             :         }
     655           0 :         if (close (fd))
     656             :         {
     657           0 :                 ELEKTRA_ADD_RESOURCE_WARNINGF (parentKey, "Failed to close a file descriptor: %s", strerror (errno));
     658             :         }
     659             :         return 1;
     660             : }
     661             : 
     662             : /**
     663             :  * @brief Checks if at least one GPG recipient or at least one GPG signature key has been provided within the plugin configuration.
     664             :  *
     665             :  * @retval 0 no changes were made to the configuration
     666             :  * @retval 1 the master password has been appended to the configuration
     667             :  * @retval -1 an error occurred. Check errorKey
     668             :  */
     669           0 : int ELEKTRA_PLUGIN_FUNCTION (checkconf) (Key * errorKey, KeySet * conf)
     670             : {
     671           0 :         const size_t recipientCount = getRecipientCount (conf, ELEKTRA_RECIPIENT_KEY);
     672           0 :         const size_t signatureCount = getRecipientCount (conf, ELEKTRA_SIGNATURE_KEY);
     673             : 
     674           0 :         if (recipientCount == 0 && signatureCount == 0)
     675             :         {
     676           0 :                 char * errorDescription = ELEKTRA_PLUGIN_FUNCTION (getMissingGpgKeyErrorText) (conf);
     677           0 :                 ELEKTRA_SET_INSTALLATION_ERROR (errorKey, errorDescription);
     678           0 :                 elektraFree (errorDescription);
     679           0 :                 return -1;
     680             :         }
     681           0 :         if (ELEKTRA_PLUGIN_FUNCTION (gpgVerifyGpgKeysInConfig) (conf, errorKey) != 1)
     682             :         {
     683             :                 // error has been set by ELEKTRA_PLUGIN_FUNCTION (gpgVerifyGpgKeysInConfig)
     684             :                 return -1;
     685             :         }
     686           0 :         return 0;
     687             : }
     688             : 
     689          20 : Plugin * ELEKTRA_PLUGIN_EXPORT
     690             : {
     691             :         // clang-format off
     692          20 :         return elektraPluginExport(ELEKTRA_PLUGIN_NAME,
     693             :                         ELEKTRA_PLUGIN_OPEN,  &ELEKTRA_PLUGIN_FUNCTION(open),
     694             :                         ELEKTRA_PLUGIN_CLOSE, &ELEKTRA_PLUGIN_FUNCTION(close),
     695             :                         ELEKTRA_PLUGIN_GET,   &ELEKTRA_PLUGIN_FUNCTION(get),
     696             :                         ELEKTRA_PLUGIN_SET,   &ELEKTRA_PLUGIN_FUNCTION(set),
     697             :                         ELEKTRA_PLUGIN_END);
     698             : }

Generated by: LCOV version 1.13