#define _EXPORT_PEP_ENGINE_DLL #include "platform.h" #include "pEp_internal.h" // #include "pgp_gpg.h" #include #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; }