You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
pEpJSONServerAdapter/hotfixes/main.c

581 lines
17 KiB
C

#define _EXPORT_PEP_ENGINE_DLL
#include "platform.h"
#include "pEp_internal.h"
#include <limits.h>
#include "wrappers.h"
#define _GPGERR(X) ((X) & 0xffffL)
static void *gpgme;
static struct gpg_s gpg;
struct _str_ptr_and_bit {
const char* key;
int bit;
};
typedef struct _str_ptr_and_bit str_ptr_and_bit;
int strptrcmp(const void* a, const void* b) {
return (int)((((str_ptr_and_bit*)(a))->key) - (((str_ptr_and_bit*)(b))->key));
}
bool quickfix_config(stringlist_t* keys, const char* config_file_path) {
static char buf[MAX_LINELENGTH];
size_t num_keys = stringlist_length(keys);
// This function does:
// 1. find a non-existent backup name numbered from 0 to 99 (otherwise fails)
// 2. read the original file and meanwhile write the backup copy
// 3. write the new config file to a temporary file in the same directory
// 4. rename the temp file replacing the original config file
// 5. on Windows remove the left-overs
/* Find a suitable backup file name, without trashing previous ones */
char* backup_file_path = NULL;
size_t backup_file_path_baselen = strlen(config_file_path);
FILE *backup_file = 0;
int ret;
int found = 0;
int i;
char* temp_config_file_path = NULL;
char* s = NULL;
stringlist_t* _k;
stringlist_t* lines = new_stringlist(NULL);
FILE *f;
FILE *temp_config_file;
stringlist_t* cur_string;
bool status = false;
#ifdef WIN32
WIN32_FIND_DATA FindFileData;
HANDLE handle;
const char* line_end = "\r\n";
#else
const char* line_end = "\n";
#endif
// If we bork it up somehow, we don't go beyond 100 tries...
for (int nr = 0; nr < 99; nr++) {
backup_file_path = (char*)calloc(backup_file_path_baselen + 12, 1); // .99.pep.old\0
ret = snprintf(backup_file_path, backup_file_path_baselen + 12,
"%s.%d.pep.bkp", config_file_path, nr);
assert(ret >= 0); // snprintf(2)
if (ret < 0) {
goto quickfix_error; // frees backup_file_path
}
#ifdef WIN32
// The fopen(.., "x") is not documented on Windows (fopen_s actually respects it, but...).
// So we make an extra check for the existence of the file. This introduces a possible
// race-condition, but it has little effect even if we incur into it.
handle = FindFirstFile(backup_file_path, &FindFileData);
if (handle != INVALID_HANDLE_VALUE) {
FindClose(handle);
free(backup_file_path);
backup_file_path = NULL;
continue;
}
FindClose(handle);
backup_file = Fopen(backup_file_path, "wb");
#else
backup_file = Fopen(backup_file_path, "wbx"); // the 'x' is important
#endif
if (backup_file <= 0) {
free(backup_file_path);
backup_file_path = NULL;
continue;
}
break;
}
if (!backup_file_path)
goto quickfix_error;
if (backup_file <= 0)
goto quickfix_error;
// Open original file, parse it, and meanwhile write a backup copy
f = Fopen(config_file_path, "rb");
if (f == NULL)
goto quickfix_error;
ret = Fprintf(backup_file, "# Backup created by pEp.%s"
"# If GnuPG and pEp work smoothly this file may safely be removed.%s%s",
line_end, line_end, line_end);
// Go through every line in the file
str_ptr_and_bit *found_keys = NULL;
while ((s = Fgets(buf, MAX_LINELENGTH, f))) {
// pointers to the keys found in this string
found_keys = (str_ptr_and_bit*)(calloc(num_keys, sizeof(str_ptr_and_bit)));
int num_found_keys = 0;
ret = Fprintf(backup_file, "%s", s);
assert(ret >= 0);
if (ret < 0) {
free(found_keys);
found_keys = NULL;
goto quickfix_error;
}
char* rest;
char* line_token = strtok_r(s, "\r\n", &rest);
if (!line_token)
line_token = s;
if (*line_token == '\n' || *line_token == '\r')
line_token = "";
if (*line_token == '#' || *line_token == '\0') {
stringlist_add(lines, strdup(line_token));
continue;
}
bool only_key_on_line = false;
for (_k = keys, i = 1; _k; _k = _k->next, i<<=1) {
char* keypos = strstr(line_token, _k->value);
if (!keypos)
continue;
size_t keystr_len = strlen(_k->value);
char* nextpos = keypos + keystr_len;
bool notkey = false;
if (keypos != line_token) {
char prevchar = *(keypos - 1);
switch (prevchar) {
case '-':
case ':':
case '/':
case '\\':
notkey = true;
break;
default:
break;
}
}
if (*nextpos && !notkey) {
char nextchar = *nextpos;
switch (nextchar) {
case '-':
case ':':
case '/':
case '\\':
notkey = true;
break;
default:
break;
}
}
else if (line_token == keypos) {
only_key_on_line = true;
if (!(found & i)) {
found |= i;
stringlist_add(lines, strdup(line_token));
num_found_keys++;
}
break;
}
if (!notkey) {
// Ok, it's not just the key with a null terminator. So...
// add a pointer to the key to the list from this string
found_keys[num_found_keys].key = keypos;
found_keys[num_found_keys].bit = i;
num_found_keys++;
}
// Check to see if there are more annoying occurences of this
// key in the string
for (keypos = strstr(nextpos, _k->value);
keypos; keypos = strstr(nextpos, _k->value)) {
notkey = false;
nextpos = keypos + keystr_len;
char prevchar = *(keypos - 1);
switch (prevchar) {
case '-':
case ':':
case '/':
case '\\':
notkey = true;
break;
default:
break;
}
if (!notkey) {
char nextchar = *nextpos;
switch (nextchar) {
case '-':
case ':':
case '/':
case '\\':
notkey = true;
break;
default:
break;
}
}
if (notkey)
continue;
if (num_found_keys >= num_keys)
found_keys = (str_ptr_and_bit*)realloc(found_keys, (num_found_keys + 1) * sizeof(str_ptr_and_bit));
found_keys[num_found_keys].key = keypos;
found_keys[num_found_keys].bit = i;
num_found_keys++;
}
}
if (!only_key_on_line) {
if (num_found_keys == 0)
stringlist_add(lines, strdup(line_token));
else if (num_found_keys == 1 && (line_token == found_keys[0].key)) {
if (!(found & found_keys[0].bit)) {
stringlist_add(lines, strdup(line_token));
found |= found_keys[0].bit;
}
}
else {
qsort(found_keys, num_found_keys, sizeof(str_ptr_and_bit), strptrcmp);
int j;
const char* curr_start = line_token;
const char* next_start = NULL;
for (j = 0; j < num_found_keys; j++, curr_start = next_start) {
next_start = found_keys[j].key;
if (curr_start == next_start)
continue;
size_t copy_len = next_start - curr_start;
const char* movable_end = next_start - 1;
while (copy_len > 0 && (*movable_end == ' ' || *movable_end == '\t' || *movable_end == '\0')) {
movable_end--;
copy_len--;
}
if (copy_len > 0) {
if (j == 0 || !(found & found_keys[j - 1].bit)) {
// if j is 0 here, the first thing in the string wasn't a key, or we'd have continued.
// otherwise, regardless of the value of j, we check that the "last" key (j-1) isn't already
// found and dealt with.
// Having passed that, we copy.
stringlist_add(lines, strndup(curr_start, copy_len));
if (j > 0)
found |= found_keys[j-1].bit;
}
}
}
if (!(found & found_keys[num_found_keys - 1].bit)) {
stringlist_add(lines, strdup(found_keys[num_found_keys - 1].key));
found |= found_keys[num_found_keys - 1].bit;
}
}
}
free(found_keys);
found_keys = NULL;
} // End of file
// Now do the failsafe writing dance
ret = Fclose(f);
assert(ret == 0);
if (ret != 0)
goto quickfix_error;
ret = Fclose(backup_file);
assert(ret == 0);
if (ret != 0)
goto quickfix_error;
// 2. Write the new config file to a temporary file in the same directory
assert(backup_file_path_baselen != NULL);
temp_config_file_path = (char*)calloc(backup_file_path_baselen + 8, 1); // .XXXXXX\0
ret = snprintf(temp_config_file_path, backup_file_path_baselen + 8, "%s.XXXXXX", config_file_path);
assert(ret >= 0);
if (ret < 0)
goto quickfix_error;
int temp_config_filedesc = Mkstemp(temp_config_file_path);
assert(temp_config_filedesc != -1);
if (temp_config_filedesc == -1)
goto quickfix_error;
temp_config_file = Fdopen(temp_config_filedesc, "wb"); // no "b" in fdopen() is documentend, use freopen()
assert(temp_config_file != NULL);
if (temp_config_file == NULL)
goto quickfix_error;
// temp_config_file = Freopen(config_file_path, "wb", temp_config_file);
// assert(temp_config_file != NULL);
// if (temp_config_file == NULL)
// goto quickfix_error;
ret = Fprintf(temp_config_file, "# File re-created by pEp%s"
"# See backup in '%s'%s%s", line_end,
backup_file_path,
line_end, line_end);
assert(ret >= 0);
if (ret < 0)
goto quickfix_error;
for (cur_string = lines; cur_string; cur_string = cur_string->next) {
assert(cur_string->value != NULL);
ret = Fprintf(temp_config_file, "%s%s", cur_string->value, line_end);
assert(ret >= 0);
if (ret < 0)
goto quickfix_error;
}
ret = Fclose(temp_config_file);
assert(ret == 0);
if (ret != 0)
goto quickfix_error;
#ifdef WIN32
ret = !(0 == ReplaceFile(config_file_path, temp_config_file_path, NULL, 0, NULL, NULL));
assert(ret == 0);
if (ret != 0)
goto quickfix_error;
ret = unlink(temp_config_file_path);
#else
ret = rename(temp_config_file_path, config_file_path);
// ret = 0;
#endif
assert(ret == 0);
if (ret != 0)
goto quickfix_error;
free(temp_config_file_path);
temp_config_file_path = NULL;
status = true;
free(backup_file_path);
goto quickfix_success;
quickfix_error:
assert(status == false);
free(backup_file_path);
quickfix_success:
// assert(found_keys == NULL);
if (found_keys) {
free(found_keys);
found_keys = NULL;
}
if (temp_config_file_path)
free(temp_config_file_path);
free_stringlist(lines);
return status;
}
static bool ensure_config_values(stringlist_t *keys, stringlist_t *values, const char* config_file_path)
{
static char buf[MAX_LINELENGTH];
int r;
stringlist_t *_k;
stringlist_t *_v;
unsigned int i;
unsigned int found = 0;
bool eof_nl = 0;
char * rest;
char * token;
char * s;
const char* line_end;
#ifdef WIN32
line_end = "\r\n";
#else
line_end = "\n";
#endif
FILE *f = Fopen(config_file_path, "rb");
if (f == NULL && errno == ENOMEM)
return false;
if (f != NULL) {
int length = stringlist_length(keys);
// make sure we 1) have the same number of keys and values
// and 2) we don't have more key/value pairs than
// the size of the bitfield used to hold the indices
// of key/value pairs matching keys in the config file.
assert(length <= sizeof(unsigned int) * CHAR_BIT);
assert(length == stringlist_length(values));
if (!(length == stringlist_length(values) &&
length <= sizeof(unsigned int) * CHAR_BIT)) {
Fclose(f);
return false;
}
while ((s = Fgets(buf, MAX_LINELENGTH, f))) {
token = strtok_r(s, " \t\r\n", &rest);
for (_k = keys, _v = values, i = 1;
_k != NULL;
_k = _k->next, _v = _v->next, i <<= 1) {
if (((found & i) != i) && token &&
(strncmp(token, _k->value, strlen(_k->value)) == 0)) {
found |= i;
break;
}
}
if (feof(f)) {
eof_nl = 1;
break;
}
}
if (!s && ferror(f))
return false;
f = Freopen(config_file_path, "ab", f);
}
else {
#ifdef WIN32
f = Fopen(config_file_path, "wb");
#else
f = Fopen(config_file_path, "wbx");
#endif
}
assert(f);
if (f == NULL)
return false;
if (eof_nl)
r = Fprintf(f, line_end);
for (i = 1, _k = keys, _v = values; _k != NULL; _k = _k->next,
_v = _v->next, i <<= 1) {
if ((found & i) == 0) {
r = Fprintf(f, "%s %s%s", _k->value, _v->value, line_end);
assert(r >= 0);
if (r < 0)
return false;
}
}
r = Fclose(f);
assert(r == 0);
if (r != 0)
return false;
return true;
}
int main(int argc, char** argv)
{
PEP_STATUS status = PEP_STATUS_OK;
bool bResult;
#if defined(WIN32) || defined(NDEBUG)
printf("gpg_conf %s\n", gpg_conf());
printf("gpg_agent_conf %s\n", gpg_agent_conf());
#else
printf("gpg_conf %s\n", gpg_conf(false));
printf("gpg_agent_conf %s\n", gpg_agent_conf(false));
#endif
stringlist_t *conf_keys = new_stringlist("keyserver");
stringlist_t *conf_values = new_stringlist("hkp://keys.gnupg.net");
stringlist_add(conf_keys, "cert-digest-algo");
stringlist_add(conf_values, "SHA256");
stringlist_add(conf_keys, "no-emit-version");
stringlist_add(conf_values, "");
stringlist_add(conf_keys, "no-comments");
stringlist_add(conf_values, "");
stringlist_add(conf_keys, "personal-cipher-preferences");
stringlist_add(conf_values, "AES AES256 AES192 CAST5");
stringlist_add(conf_keys, "personal-digest-preferences");
stringlist_add(conf_values, "SHA256 SHA512 SHA384 SHA224");
stringlist_add(conf_keys, "ignore-time-conflict");
stringlist_add(conf_values, "");
stringlist_add(conf_keys, "allow-freeform-uid");
stringlist_add(conf_values, "");
bResult = true;
if (1)
#if defined(WIN32) || defined(NDEBUG)
bResult = quickfix_config(conf_keys, gpg_conf());
if (bResult)
bResult = ensure_config_values(conf_keys, conf_values, gpg_conf());
#else
bResult = quickfix_config(conf_keys, gpg_conf(false));
if (bResult)
bResult = ensure_config_values(conf_keys, conf_values, gpg_conf(false));
#endif
status = PEP_STATUS_OK;
free_stringlist(conf_keys);
free_stringlist(conf_values);
assert(bResult);
if (!bResult) {
status = PEP_INIT_NO_GPG_HOME;
goto pep_error;
}
conf_keys = new_stringlist("default-cache-ttl");
conf_values = new_stringlist("300");
stringlist_add(conf_keys, "max-cache-ttl");
stringlist_add(conf_values, "1200");
if (1)
#if defined(WIN32) || defined(NDEBUG)
bResult = quickfix_config(conf_keys, gpg_agent_conf());
if (bResult)
bResult = ensure_config_values(conf_keys, conf_values, gpg_agent_conf());
#else
bResult = quickfix_config(conf_keys, gpg_agent_conf(false));
if (bResult)
bResult = ensure_config_values(conf_keys, conf_values, gpg_agent_conf(false));
#endif
free_stringlist(conf_keys);
free_stringlist(conf_values);
return 0;
pep_error:
return 1;
}