1  /***************************************************************************
2                           tqslconvert.cpp  -  description
3                              -------------------
4     begin                : Sun Nov 17 2002
5     copyright            : (C) 2002 by ARRL
6     author               : Jon Bloom
7     email                : jbloom@arrl.org
8     revision             : $Id$
9  ***************************************************************************/
10 
11 
12 #define TQSLLIB_DEF
13 
14 #include "tqsllib.h"
15 
16 #include "tqslconvert.h"
17 #include <stdio.h>
18 #include <errno.h>
19 #include <sys/stat.h>
20 #include <signal.h>
21 #include "tqslerrno.h"
22 #include <cstring>
23 #include <string>
24 #include <ctype.h>
25 #include <set>
26 #ifdef USE_LMDB
27 #include <lmdb.h>
28 #define db_strerror mdb_strerror
29 #else
30 #include <db.h>
31 #endif
32 
33 #include <locale.h>
34 //#include <iostream>
35 
36 #ifndef _WIN32
37     #include <unistd.h>
38     #include <dirent.h>
39 #else
40     #include <direct.h>
41     #include "windirent.h"
42 #endif
43 
44 #include "winstrdefs.h"
45 
46 using std::set;
47 using std::string;
48 
49 static bool checkCallSign(const string& call);
50 
51 namespace tqsllib {
52 
53 class TQSL_CONVERTER {
54  public:
55 	TQSL_CONVERTER();
56 	~TQSL_CONVERTER();
57 	void clearRec();
58 	int sentinel;
59 //	FILE *file;
60 	tQSL_ADIF adif;
61 	tQSL_Cabrillo cab;
62 	tQSL_Cert *certs;
63 	int ncerts;
64 	tQSL_Location loc;
65 	TQSL_QSO_RECORD rec;
66 	bool rec_done;
67 	int cert_idx;
68 	int base_idx;
69 	bool need_station_rec;
70 	bool *certs_used;
71 	bool allow_bad_calls;
72 	set <string> modes;
73 	set <string> bands;
74 	set <string> propmodes;
75 	set <string> satellites;
76 	string rec_text;
77 	tQSL_Date start, end;
78 	bool db_open;
79 #ifdef USE_LMDB
80 	MDB_dbi seendb;
81 	MDB_env* dbenv;
82 	MDB_txn* txn;
83 	MDB_cursor* cursor;
84 #else
85 	DB *seendb;
86 	DB_ENV* dbenv;
87 	DB_TXN* txn;
88 	DBC* cursor;
89 #endif
90 	char *dbpath;
91 	FILE* errfile;
92 	char serial[512];
93 	char callsign[64];
94 	bool allow_dupes;
95 	bool need_ident_rec;
96 	char *appName;
97 };
98 
TQSL_CONVERTER()99 inline TQSL_CONVERTER::TQSL_CONVERTER()  : sentinel(0x4445) {
100 //	file = 0;
101 	adif = 0;
102 	cab = 0;
103 	cert_idx = -1;
104 	base_idx = 1;
105 	certs_used = 0;
106 	need_station_rec = false;
107 	rec_done = true;
108 	allow_bad_calls = false;
109 	allow_dupes = true; //by default, don't change existing behavior (also helps with commit)
110 	memset(&rec, 0, sizeof rec);
111 	memset(&start, 0, sizeof start);
112 	memset(&end, 0, sizeof end);
113 	db_open = false;
114 #ifndef USE_LMDB
115 	seendb = NULL;
116 #endif
117 	dbpath = NULL;
118 	dbenv = NULL;
119 	txn = NULL;
120 	cursor = NULL;
121 	errfile = NULL;
122 	memset(&serial, 0, sizeof serial);
123 	memset(&callsign, 0, sizeof callsign);
124 	appName = NULL;
125 	need_ident_rec = true;
126 	// Init the band data
127 	const char *val;
128 	int n = 0;
129 	tqsl_getNumBand(&n);
130 	for (int i = 0; i < n; i++) {
131 		val = 0;
132 		tqsl_getBand(i, &val, 0, 0, 0);
133 		if (val)
134 			bands.insert(val);
135 	}
136 	// Init the mode data
137 	n = 0;
138 	tqsl_getNumMode(&n);
139 	for (int i = 0; i < n; i++) {
140 		val = 0;
141 		tqsl_getMode(i, &val, 0);
142 		if (val)
143 			modes.insert(val);
144 	}
145 	// Init the propagation mode data
146 	n = 0;
147 	tqsl_getNumPropagationMode(&n);
148 	for (int i = 0; i < n; i++) {
149 		val = 0;
150 		tqsl_getPropagationMode(i, &val, 0);
151 		if (val)
152 			propmodes.insert(val);
153 	}
154 	// Init the satellite data
155 	n = 0;
156 	tqsl_getNumSatellite(&n);
157 	for (int i = 0; i < n; i++) {
158 		val = 0;
159 		tqsl_getSatellite(i, &val, 0, 0, 0);
160 		if (val)
161 			satellites.insert(val);
162 	}
163 }
164 
~TQSL_CONVERTER()165 inline TQSL_CONVERTER::~TQSL_CONVERTER() {
166 	clearRec();
167 //	if (file)
168 //		fclose(file);
169 	tqsl_endADIF(&adif);
170 	if (certs_used)
171 		delete[] certs_used;
172 	sentinel = 0;
173 }
174 
clearRec()175 inline void TQSL_CONVERTER::clearRec() {
176 	memset(&rec, 0, sizeof rec);
177 	rec_text = "";
178 }
179 
180 #define CAST_TQSL_CONVERTER(x) ((tqsllib::TQSL_CONVERTER *)(x))
181 
182 }	// namespace tqsllib
183 
184 using tqsllib::TQSL_CONVERTER;
185 
fix_freq(const char * in)186 static char * fix_freq(const char *in) {
187     static char out[128];
188     const char *p = in;
189     bool decimal = false;
190     char *o = out;
191     while (*p) {
192 	if (*p == '.') {
193 		if (decimal) {
194 			p++;
195 			continue;
196 		}
197 		decimal = true;
198 	}
199 	*o++ = *p++;
200     }
201     *o = '\0';
202     return out;
203 }
204 
205 static char *
tqsl_strtoupper(char * str)206 tqsl_strtoupper(char *str) {
207 	for (char *cp = str; *cp != '\0'; cp++)
208 		*cp = toupper(*cp);
209 	return str;
210 }
211 
212 static TQSL_CONVERTER *
check_conv(tQSL_Converter conv)213 check_conv(tQSL_Converter conv) {
214 	if (tqsl_init())
215 		return 0;
216 	if (conv == 0 || CAST_TQSL_CONVERTER(conv)->sentinel != 0x4445)
217 		return 0;
218 	return CAST_TQSL_CONVERTER(conv);
219 }
220 
221 static tqsl_adifFieldDefinitions adif_qso_record_fields[] = {
222 	{ "CALL", "", TQSL_ADIF_RANGE_TYPE_NONE, TQSL_CALLSIGN_MAX, 0, 0, NULL },
223 	{ "BAND", "", TQSL_ADIF_RANGE_TYPE_NONE, TQSL_BAND_MAX, 0, 0, NULL },
224 	{ "MODE", "", TQSL_ADIF_RANGE_TYPE_NONE, TQSL_MODE_MAX, 0, 0, NULL },
225 	{ "SUBMODE", "", TQSL_ADIF_RANGE_TYPE_NONE, TQSL_MODE_MAX, 0, 0, NULL },
226 	{ "QSO_DATE", "", TQSL_ADIF_RANGE_TYPE_NONE, 10, 0, 0, NULL },
227 	{ "TIME_ON", "", TQSL_ADIF_RANGE_TYPE_NONE, 10, 0, 0, NULL },
228 	{ "FREQ", "", TQSL_ADIF_RANGE_TYPE_NONE, TQSL_FREQ_MAX, 0, 0, NULL },
229 	{ "FREQ_RX", "", TQSL_ADIF_RANGE_TYPE_NONE, TQSL_FREQ_MAX, 0, 0, NULL },
230 	{ "BAND_RX", "", TQSL_ADIF_RANGE_TYPE_NONE, TQSL_BAND_MAX, 0, 0, NULL },
231 	{ "SAT_NAME", "", TQSL_ADIF_RANGE_TYPE_NONE, TQSL_SATNAME_MAX, 0, 0, NULL },
232 	{ "PROP_MODE", "", TQSL_ADIF_RANGE_TYPE_NONE, TQSL_PROPMODE_MAX, 0, 0, NULL },
233 	{ "eor", "", TQSL_ADIF_RANGE_TYPE_NONE, 0, 0, 0, NULL },
234 };
235 
236 DLLEXPORT int CALLCONVENTION
tqsl_beginConverter(tQSL_Converter * convp)237 tqsl_beginConverter(tQSL_Converter *convp) {
238 	tqslTrace("tqsl_beginConverter", NULL);
239 	if (tqsl_init())
240 		return 0;
241 	if (!convp) {
242 		tqslTrace("tqsl_beginConverter", "convp=NULL");
243 		tQSL_Error = TQSL_ARGUMENT_ERROR;
244 		return 1;
245 	}
246 	TQSL_CONVERTER *conv = new TQSL_CONVERTER();
247 	*convp = conv;
248 	return 0;
249 }
250 
251 DLLEXPORT int CALLCONVENTION
tqsl_beginADIFConverter(tQSL_Converter * convp,const char * filename,tQSL_Cert * certs,int ncerts,tQSL_Location loc)252 tqsl_beginADIFConverter(tQSL_Converter *convp, const char *filename, tQSL_Cert *certs,
253 	int ncerts, tQSL_Location loc) {
254 	tqslTrace("tqsl_beginADIFConverter", NULL);
255 	if (tqsl_init())
256 		return 0;
257 	if (!convp || !filename) {
258 		tqslTrace("tqsl_beginADIFConverter", "arg err convp=0x%lx filename=0x%lx certs=0x%lx", convp, filename, certs);
259 		tQSL_Error = TQSL_ARGUMENT_ERROR;
260 		return 1;
261 	}
262 	tQSL_ADIF adif;
263 	if (tqsl_beginADIF(&adif, filename)) {
264 		tqslTrace("tqsl_beginADIFConverter", "tqsl_beginADIF fail %d", tQSL_Error);
265 		return 1;
266 	}
267 	TQSL_CONVERTER *conv = new TQSL_CONVERTER();
268 	conv->adif = adif;
269 	conv->certs = certs;
270 	conv->ncerts = ncerts;
271 	if (ncerts > 0) {
272 		conv->certs_used = new bool[ncerts];
273 		for (int i = 0; i < ncerts; i++)
274 			conv->certs_used[i] = false;
275 	}
276 	conv->loc = loc;
277 	*convp = conv;
278 	return 0;
279 }
280 
281 DLLEXPORT int CALLCONVENTION
tqsl_beginCabrilloConverter(tQSL_Converter * convp,const char * filename,tQSL_Cert * certs,int ncerts,tQSL_Location loc)282 tqsl_beginCabrilloConverter(tQSL_Converter *convp, const char *filename, tQSL_Cert *certs,
283 	int ncerts, tQSL_Location loc) {
284 	tqslTrace("tqsl_beginCabrilloConverter", NULL);
285 
286 	if (tqsl_init())
287 		return 0;
288 	if (!convp || !filename) {
289 		tQSL_Error = TQSL_ARGUMENT_ERROR;
290 		tqslTrace("tqsl_beginCabrilloConverter", "arg error convp=0x%lx, filename=0x%lx, certs=0x%lx", convp, filename, certs);
291 		return 1;
292 	}
293 	tQSL_Cabrillo cab;
294 	if (tqsl_beginCabrillo(&cab, filename)) {
295 		tqslTrace("tqsl_beginCabrilloConverter", "tqsl_beginCabrillo fail %d", tQSL_Error);
296 		return 1;
297 	}
298 	TQSL_CONVERTER *conv = new TQSL_CONVERTER();
299 	conv->cab = cab;
300 	conv->certs = certs;
301 	conv->ncerts = ncerts;
302 	if (ncerts > 0) {
303 		conv->certs_used = new bool[ncerts];
304 		for (int i = 0; i < ncerts; i++)
305 			conv->certs_used[i] = false;
306 	}
307 	conv->loc = loc;
308 	*convp = conv;
309 	return 0;
310 }
311 
312 DLLEXPORT int CALLCONVENTION
tqsl_endConverter(tQSL_Converter * convp)313 tqsl_endConverter(tQSL_Converter *convp) {
314 	tqslTrace("tqsl_endConverter", NULL);
315 
316 	if (!convp || CAST_TQSL_CONVERTER(*convp) == 0)
317 		return 0;
318 
319 	TQSL_CONVERTER* conv;
320 
321 	if ((conv = check_conv(*convp))) {
322 #ifdef USE_LMDB
323 		if (conv->txn) mdb_txn_abort(conv->txn);
324 #else
325 		if (conv->txn) conv->txn->abort(conv->txn);
326 #endif
327 		if (conv->db_open) {
328 #ifdef USE_LMDB
329 			mdb_dbi_close(conv->dbenv, conv->seendb);
330 #else
331 			conv->seendb->compact(conv->seendb, NULL, NULL, NULL, NULL, 0, NULL);
332 			conv->seendb->close(conv->seendb, 0);
333 #endif
334 		}
335 		conv->db_open = false;
336 		if (conv->dbenv) {
337 #ifdef USE_LMDB
338 			mdb_env_close(conv->dbenv);
339 #else
340 			char **unused;
341 			conv->dbenv->txn_checkpoint(conv->dbenv, 0, 0, 0);
342 			conv->dbenv->log_archive(conv->dbenv, &unused, DB_ARCH_REMOVE);
343 			conv->dbenv->close(conv->dbenv, 0);
344 #endif
345 		}
346 		// close files and clean up converters, if any
347 		if (conv->adif) tqsl_endADIF(&conv->adif);
348 		if (conv->cab) tqsl_endCabrillo(&conv->cab);
349 		if (conv->dbpath) free(conv->dbpath);
350 		if (conv->errfile) fclose(conv->errfile);
351 	}
352 
353 	if (conv->appName) free(conv->appName);
354 	if (CAST_TQSL_CONVERTER(*convp)->sentinel == 0x4445)
355 		delete CAST_TQSL_CONVERTER(*convp);
356 	*convp = 0;
357 	return 0;
358 }
359 
360 static unsigned char *
adif_allocate(size_t size)361 adif_allocate(size_t size) {
362 	return new unsigned char[size];
363 }
364 
365 static int
find_matching_cert(TQSL_CONVERTER * conv)366 find_matching_cert(TQSL_CONVERTER *conv) {
367 	int i;
368 	for (i = 0; i < conv->ncerts; i++) {
369 		tQSL_Date cdate;
370 
371 		if (tqsl_getCertificateQSONotBeforeDate(conv->certs[i], &cdate))
372 			return -1;
373 		if (tqsl_compareDates(&(conv->rec.date), &cdate) < 0)
374 			continue;
375 		if (tqsl_getCertificateQSONotAfterDate(conv->certs[i], &cdate))
376 			return -1;
377 		if (tqsl_compareDates(&(conv->rec.date), &cdate) > 0)
378 			continue;
379 		return i;
380 	}
381 	return -1;
382 }
383 
384 static const char *notypes[] = { "D", "T", "M", "N", "C", "" };
385 
386 static const char *
tqsl_infer_band(const char * infreq)387 tqsl_infer_band(const char* infreq) {
388 	char *oldlocale = setlocale(LC_NUMERIC, "C");
389 	double freq = atof(infreq);
390 	setlocale(LC_NUMERIC, oldlocale);
391 	double freq_khz = freq * 1000.0;
392 	int nband = 0;
393 	tqsl_getNumBand(&nband);
394 	for (int i = 0; i < nband; i++) {
395 		const char *name;
396 		const char *spectrum;
397 		int low, high;
398 		if (tqsl_getBand(i, &name, &spectrum, &low, &high))
399 			break;
400 		bool match = false;
401 		if (!strcmp(spectrum, "HF")) {
402 			// Allow for cases where loggers that don't log the
403 			// real frequency.
404 			if (low == 10100) low = 10000;
405 			else if (low == 18068) low = 18000;
406 			else if (low == 24890) low = 24000;
407 			if (freq_khz >= low && freq_khz <= high) {
408 				match = true;
409 			}
410 		} else {
411 			if (freq >= low && freq <= high)
412 				match = true;
413 			if (freq >= low && high == 0)
414 				match = true;
415 		}
416 		if (match)
417 			 return name;
418 	}
419 	return "";
420 }
421 
422 DLLEXPORT int CALLCONVENTION
tqsl_setADIFConverterDateFilter(tQSL_Converter convp,tQSL_Date * start,tQSL_Date * end)423 tqsl_setADIFConverterDateFilter(tQSL_Converter convp, tQSL_Date *start, tQSL_Date *end) {
424 	TQSL_CONVERTER *conv;
425 	tqslTrace("tqsl_setADIFConverterDateFilter", NULL);
426 
427 	if (!(conv = check_conv(convp)))
428 		return 1;
429 	if (start == NULL)
430 		conv->start.year = conv->start.month = conv->start.day = 0;
431 	else
432 		conv->start = *start;
433 	if (end == NULL)
434 		conv->end.year = conv->end.month = conv->end.day = 0;
435 	else
436 		conv->end = *end;
437 	return 0;
438 }
439 
440 // Remove the dupes db files
441 static void
remove_db(const char * path)442 remove_db(const char *path)  {
443 	tqslTrace("remove_db", "path=%s", path);
444 #ifdef _WIN32
445 	wchar_t* wpath = utf8_to_wchar(path);
446 	_WDIR *dir = _wopendir(wpath);
447 	free_wchar(wpath);
448 #else
449 	DIR *dir = opendir(path);
450 #endif
451 	if (dir != NULL) {
452 #ifdef USE_LMDB
453 #ifdef _WIN32
454 		struct _wdirent *ent = NULL;
455 		while ((ent = _wreaddir(dir)) != NULL) {
456 			if (!wcscmp(ent->d_name, L"data.mdb") ||
457 			!wcscmp(ent->d_name, L"lock.mdb")) {
458 #else
459 		struct dirent *ent = NULL;
460 		while ((ent = readdir(dir)) != NULL) {
461 			if (!strcmp(ent->d_name, "data.mdb") ||
462 			!strcmp(ent->d_name, "lock.mdb")) {
463 #endif
464 #else // USE_LMDB
465 #ifdef _WIN32
466 		struct _wdirent *ent = NULL;
467 		while ((ent = _wreaddir(dir)) != NULL) {
468 			if (!wcscmp(ent->d_name, L"duplicates.db") ||
469 			!wcsncmp(ent->d_name, L"log.", 4) ||
470 			!wcsncmp(ent->d_name, L"__db.", 5)) {
471 #else
472 		struct dirent *ent = NULL;
473 		while ((ent = readdir(dir)) != NULL) {
474 			if (!strcmp(ent->d_name, "duplicates.db") ||
475 			!strncmp(ent->d_name, "log.", 4) ||
476 			!strncmp(ent->d_name, "__db.", 5)) {
477 #endif
478 #endif // USE_LMDB
479 				string fname = path;
480 				int rstat;
481 #ifdef _WIN32
482 				char dname[TQSL_MAX_PATH_LEN];
483 				wcstombs(dname, ent->d_name, TQSL_MAX_PATH_LEN);
484 				fname = fname + "/" + dname;
485 				wchar_t* wfname = utf8_to_wchar(fname.c_str());
486 				tqslTrace("remove_db", "unlinking %s", fname.c_str());
487 				rstat = _wunlink(wfname);
488 				free_wchar(wfname);
489 #else
490 				fname = fname + "/" + ent->d_name;
491 				tqslTrace("remove_db", "unlinking %s", fname.c_str());
492 				rstat = unlink(fname.c_str());
493 #endif
494 				if (rstat < 0) {
495 					tqslTrace("remove_db", "can't unlink %s: %s", fname.c_str(), strerror(errno));
496 				}
497 			}
498 		}
499 #ifdef _WIN32
500 		_wclosedir(dir);
501 #else
502 		closedir(dir);
503 #endif
504 	}
505 	return;
506 }
507 #if !defined(_WIN32) && !defined(USE_LMDB)
508 // Callback method for the dbenv->failchk() call
509 // Used to determine if the given pid/tid is
510 // alive.
511 static int isalive(DB_ENV *env, pid_t pid, db_threadid_t tid, uint32_t flags) {
512 	int alive = 0;
513 
514 	if (pid == getpid()) {
515 		alive = 1;
516 	} else if (kill(pid, 0) == 0) {
517 		alive = 1;
518 	} else if (errno == EPERM) {
519 		alive = 1;
520 	}
521 	return alive;
522 }
523 #endif // _WIN32
524 
525 // Open the duplicates database
526 
527 #ifdef USE_LMDB
528 static bool open_db(TQSL_CONVERTER *conv, bool readonly) {
529 	bool dbinit_cleanup = false;
530 	int dbret;
531 	bool triedRemove = false;
532 	bool triedDelete = false;
533 	string fixedpath = tQSL_BaseDir; //must be first because of gotos
534 	size_t found = fixedpath.find('\\');
535 
536 	tqslTrace("open_db", "path=%s", fixedpath.c_str());
537 	//bdb complains about \\s in path on windows...
538 
539 	while (found != string::npos) {
540 		fixedpath.replace(found, 1, "/");
541 		found = fixedpath.find('\\');
542 	}
543 
544 	conv->dbpath = strdup(fixedpath.c_str());
545 
546 #ifndef _WIN32
547 	// Clean up junk in that directory
548 	DIR *dir = opendir(fixedpath.c_str());
549 	if (dir != NULL) {
550 		struct dirent *ent;
551 		while ((ent = readdir(dir)) != NULL) {
552 			if (ent->d_name[0] == '.')
553 				continue;
554 			struct stat s;
555 			// If it's a symlink pointing to itself, remove it.
556 			string fname = fixedpath + "/" + ent->d_name;
557 			if (stat(fname.c_str(), &s)) {
558 				if (errno == ELOOP) {
559 #ifdef _WIN32
560 					_wunlink(ConvertFromUtf8ToUtf16(fname.c_str()));
561 #else
562 					unlink(fname.c_str());
563 #endif
564 				}
565 			}
566 		}
567 		closedir(dir);
568 	}
569 #endif
570 	string logpath = fixedpath + "/dberr.log";
571 #ifdef _WIN32
572 	wchar_t* wlogpath = utf8_to_wchar(logpath.c_str());
573 	conv->errfile = _wfopen(wlogpath, L"wb");
574 	free_wchar(wlogpath);
575 #else
576 	conv->errfile = fopen(logpath.c_str(), "wb");
577 #endif
578 
579  reopen:
580 
581 	// Try to open the database
582 	while (true) {
583 		if (!conv->dbenv) {
584 			// Create the database environment handle
585 			if ((dbret = mdb_env_create(&conv->dbenv))) {
586 				// can't make env handle
587 				tqslTrace("open_db", "mdb_env_create error %s", mdb_strerror(dbret));
588 				if (conv->errfile)
589 					fprintf(conv->errfile, "mdb_env_create error %s\n", mdb_strerror(dbret));
590 				dbinit_cleanup = true;
591 				goto dbinit_end;
592 			}
593 			tqslTrace("open_db", "dbenv=0x%lx", conv->dbenv);
594 		}
595 		mdb_env_set_maxdbs(conv->dbenv, 2);
596 		mdb_env_set_maxreaders(conv->dbenv, 2);
597 		mdb_env_set_mapsize(conv->dbenv, 1024 * 1024 * 1024);
598 		// Now open the database
599 		tqslTrace("open_db", "Opening the database at %s", conv->dbpath);
600 		if ((dbret = mdb_env_open(conv->dbenv, conv->dbpath, 0, 0600))) {
601 			tqslTrace("open_db", "dbenv->open %s error %s", conv->dbpath, mdb_strerror(dbret));
602 			if (conv->errfile)
603 				fprintf(conv->errfile, "opening DB %s returns status %s\n", conv->dbpath, mdb_strerror(dbret));
604 			// can't open environment - try to delete it and try again.
605 			tqslTrace("open_db", "Environment open fail, triedRemove=%d", triedRemove);
606 			if (!triedRemove) {
607 				// Remove the dross
608 				tqslTrace("open_db", "Removing environment");
609 				conv->dbenv = NULL;
610 				triedRemove = true;
611 				if (conv->errfile)
612 					fprintf(conv->errfile, "About to retry after removing the environment\n");
613 				tqslTrace("open_db", "About to retry after removing the environment");
614 				continue;
615 			}
616 			tqslTrace("open_db", "Retry attempt after removing the environment failed");
617 			if (conv->errfile) {
618 				fprintf(conv->errfile, "Retry attempt after removing the environment failed.\n");
619 			}
620 			// can't open environment and cleanup efforts failed.
621 			mdb_env_close(conv->dbenv);
622 			conv->dbenv = NULL;	// this can't be recovered
623 			dbinit_cleanup = true;
624 			tqslTrace("open_db", "can't fix. abandoning.");
625 			remove_db(fixedpath.c_str());
626 			goto dbinit_end;
627 		}
628 		break;		// Opened OK.
629 	}
630 
631 	tqslTrace("open_db", "starting transaction, readonly=%d", readonly);
632 	if ((dbret = mdb_txn_begin(conv->dbenv, NULL, readonly ? MDB_RDONLY : 0, &conv->txn))) {
633 		// can't start a txn
634 		tqslTrace("open_db", "can't create txn %s", mdb_strerror(dbret));
635 		if (conv->errfile)
636 			fprintf(conv->errfile, "Can't create transaction: %s\n", mdb_strerror(dbret));
637 		dbinit_cleanup = true;
638 		goto dbinit_end;
639 	}
640 
641 	tqslTrace("open_db", "opening database now");
642 	if ((dbret = mdb_dbi_open(conv->txn, NULL, 0, &conv->seendb))) {
643 		if (dbret == MDB_NOTFOUND) {
644 			tqslTrace("open_db", "DB not found, making a new one");
645 			dbret = mdb_dbi_open(conv->txn, NULL, MDB_CREATE, &conv->seendb);
646 		}
647 		if (dbret) {
648 			// can't open the db
649 			tqslTrace("open_db", "create failed with %s errno %d", mdb_strerror(dbret), errno);
650 			if (conv->errfile)
651 				fprintf(conv->errfile, "create failed with %s errno %d", mdb_strerror(dbret), errno);
652 			dbinit_cleanup = true;
653 			goto dbinit_end;
654 		}
655 	}
656 
657  dbinit_end:
658 	if (dbinit_cleanup) {
659 		tqslTrace("open_db", "DB open failed, triedDelete=%d", triedDelete);
660 		tQSL_Error = TQSL_DB_ERROR;
661 		tQSL_Errno = errno;
662 		strncpy(tQSL_CustomError, mdb_strerror(dbret), sizeof tQSL_CustomError);
663 		tqslTrace("open_db", "Error opening db: %s", tQSL_CustomError);
664 		if (conv->txn) mdb_txn_abort(conv->txn);
665 		conv->txn = NULL;
666 		if (conv->db_open) {
667 			mdb_dbi_close(conv->dbenv, conv->seendb);
668 			conv->db_open = false;
669 		}
670 		if (conv->dbenv) {
671 			if (conv->dbpath) {
672 				free(conv->dbpath);
673 				conv->dbpath = NULL;
674 			}
675 			mdb_drop(conv->txn, conv->seendb, 1);
676 			mdb_env_close(conv->dbenv);
677 		}
678 		if (conv->cursor) mdb_cursor_close(conv->cursor);
679 		if (conv->errfile) fclose(conv->errfile);
680 		conv->dbenv = NULL;
681 		conv->cursor = NULL;
682 		conv->errfile = NULL;
683 		// Handle case where the database is just broken
684 		if (dbret == EINVAL && !triedDelete) {
685 			tqslTrace("open_db", "EINVAL. Removing db");
686 			remove_db(fixedpath.c_str());
687 			triedDelete = true;
688 			goto reopen;
689 		}
690 		conv->db_open = false;
691 		return false;
692 	}
693 	conv->db_open = true;
694 	return true;
695 }
696 #else // USE_LMDB
697 static bool open_db(TQSL_CONVERTER *conv, bool readonly) {
698 	bool dbinit_cleanup = false;
699 	int dbret;
700 	bool triedRemove = false;
701 	bool triedDelete = false;
702 	int envflags = DB_INIT_TXN|DB_INIT_LOG|DB_INIT_MPOOL|DB_RECOVER|DB_REGISTER|DB_CREATE;
703 	string fixedpath = tQSL_BaseDir; //must be first because of gotos
704 	size_t found = fixedpath.find('\\');
705 
706 	tqslTrace("open_db", "path=%s", fixedpath.c_str());
707 	//bdb complains about \\s in path on windows...
708 
709 	while (found != string::npos) {
710 		fixedpath.replace(found, 1, "/");
711 		found = fixedpath.find('\\');
712 	}
713 
714 	conv->dbpath = strdup(fixedpath.c_str());
715 
716 #ifndef _WIN32
717 	// Clean up junk in that directory
718 	DIR *dir = opendir(fixedpath.c_str());
719 	if (dir != NULL) {
720 		struct dirent *ent;
721 		while ((ent = readdir(dir)) != NULL) {
722 			if (ent->d_name[0] == '.')
723 				continue;
724 			struct stat s;
725 			// If it's a symlink pointing to itself, remove it.
726 			string fname = fixedpath + "/" + ent->d_name;
727 			if (stat(fname.c_str(), &s)) {
728 				if (errno == ELOOP) {
729 #ifdef _WIN32
730 					_wunlink(ConvertFromUtf8ToUtf16(fname.c_str()));
731 #else
732 					unlink(fname.c_str());
733 #endif
734 				}
735 			}
736 		}
737 		closedir(dir);
738 	}
739 #endif
740 	string logpath = fixedpath + "/dberr.log";
741 #ifdef _WIN32
742 	wchar_t* wlogpath = utf8_to_wchar(logpath.c_str());
743 	conv->errfile = _wfopen(wlogpath, L"wb");
744 	free_wchar(wlogpath);
745 #else
746 	conv->errfile = fopen(logpath.c_str(), "wb");
747 #endif
748 
749  reopen:
750 
751 	// Try to open the database
752 	while (true) {
753 		if (!conv->dbenv) {
754 			// Create the database environment handle
755 			if ((dbret = db_env_create(&conv->dbenv, 0))) {
756 				// can't make env handle
757 				tqslTrace("open_db", "db_env_create error %s", db_strerror(dbret));
758 				dbinit_cleanup = true;
759 				goto dbinit_end;
760 			}
761 			tqslTrace("open_db", "dbenv=0x%lx", conv->dbenv);
762 			if (conv->errfile) {
763 				conv->dbenv->set_errfile(conv->dbenv, conv->errfile);
764 				conv->dbenv->set_verbose(conv->dbenv, DB_VERB_RECOVERY, 1);
765 			}
766 			// Enable stale lock removal
767 			conv->dbenv->set_thread_count(conv->dbenv, 8);
768 #ifndef _WIN32
769 			conv->dbenv->set_isalive(conv->dbenv, isalive);
770 #endif
771 			// Log files default to 10 Mb each. We don't need nearly that much.
772 			if (conv->dbenv->set_lg_max)
773 				conv->dbenv->set_lg_max(conv->dbenv, 256 * 1024);
774 			// Allocate additional locking resources - some have run out with
775 			// the default 1000 locks
776 			if (conv->dbenv->set_lk_max_locks)
777 				conv->dbenv->set_lk_max_locks(conv->dbenv, 20000);
778 			if (conv->dbenv->set_lk_max_objects)
779 				conv->dbenv->set_lk_max_objects(conv->dbenv, 20000);
780 		}
781 		// Now open the database
782 		tqslTrace("open_db", "Opening the database at %s", conv->dbpath);
783 		if ((dbret = conv->dbenv->open(conv->dbenv, conv->dbpath, envflags, 0600))) {
784 			int db_errno = errno;
785 			tqslTrace("open_db", "dbenv->open %s error %s", conv->dbpath, db_strerror(dbret));
786 			if (conv->errfile)
787 				fprintf(conv->errfile, "opening DB %s returns status %s\n", conv->dbpath, db_strerror(dbret));
788 			// Can't open the database - maybe try private?
789 			if ((dbret == EACCES || dbret == EROFS) || (dbret == EINVAL && errno == dbret)) {
790 				if (!(envflags & DB_PRIVATE)) {
791 					envflags |= DB_PRIVATE;
792 					continue;
793 				}
794 			}
795 			// can't open environment - try to delete it and try again.
796 			tqslTrace("open_db", "Environment open fail, triedRemove=%d", triedRemove);
797 			if (!triedRemove) {
798 				// Remove the dross
799 				tqslTrace("open_db", "Removing environment");
800 				conv->dbenv->remove(conv->dbenv, conv->dbpath, DB_FORCE);
801 				conv->dbenv = NULL;
802 				triedRemove = true;
803 				if (conv->errfile)
804 					fprintf(conv->errfile, "About to retry after removing the environment\n");
805 				tqslTrace("open_db", "About to retry after removing the environment");
806 				continue;
807 			}
808 			tqslTrace("open_db", "Retry attempt after removing the environment failed");
809 			if (conv->errfile) {
810 				fprintf(conv->errfile, "Retry attempt after removing the environment failed.\n");
811 			}
812 			// EINVAL means that the database is corrupted to the point
813 			// where it can't be opened. Remove it and try again.
814 			if ((dbret == EINVAL || db_errno == EINVAL) && !triedDelete) {
815 				tqslTrace("open_db", "EINVAL. Removing db");
816 				conv->dbenv->close(conv->dbenv, 0);
817 				conv->dbenv = NULL;
818 				remove_db(fixedpath.c_str());
819 				triedDelete = true;
820 				continue;
821 			}
822 
823 			// can't open environment and cleanup efforts failed.
824 			conv->dbenv->close(conv->dbenv, 0);
825 			conv->dbenv = NULL;	// this can't be recovered
826 			dbinit_cleanup = true;
827 			tqslTrace("open_db", "can't fix. abandoning.");
828 			remove_db(fixedpath.c_str());
829 			goto dbinit_end;
830 		}
831 		break;		// Opened OK.
832 	}
833 
834 #ifndef _WIN32		// isalive() method doesn't exist for WIN32.
835 	// Stale lock removal
836 	tqslTrace("open_db", "Removing stale locks");
837 	dbret = conv->dbenv->failchk(conv->dbenv, 0);
838 	if (dbret && conv->errfile) {
839 		fprintf(conv->errfile, "lock removal for DB %s returns status %s\n", conv->dbpath, db_strerror(dbret));
840 	}
841 #endif
842 
843 	tqslTrace("open_db", "calling db_create");
844 	if ((dbret = db_create(&conv->seendb, conv->dbenv, 0))) {
845 		// can't create db
846 		dbinit_cleanup = true;
847 		tqslTrace("open_db", "Can't create db");
848 		goto dbinit_end;
849 	}
850 
851 #ifndef DB_TXN_BULK
852 #define DB_TXN_BULK 0
853 #endif
854 	tqslTrace("open_db", "starting transaction, readonly=%d", readonly);
855 	if (!readonly && (dbret = conv->dbenv->txn_begin(conv->dbenv, NULL, &conv->txn, DB_TXN_BULK))) {
856 		// can't start a txn
857 		tqslTrace("open_db", "can't create txn %s", db_strerror(dbret));
858 		dbinit_cleanup = true;
859 		goto dbinit_end;
860 	}
861 
862 	// Probe the database type
863 	tqslTrace("open_db", "opening database now");
864 	if ((dbret = conv->seendb->open(conv->seendb, conv->txn, "duplicates.db", NULL, DB_UNKNOWN, 0, 0600))) {
865 		if (dbret == ENOENT) {
866 			tqslTrace("open_db", "DB not found, making a new one");
867 			dbret = conv->seendb->open(conv->seendb, conv->txn, "duplicates.db", NULL, DB_HASH, DB_CREATE, 0600);
868 		}
869 		if (dbret) {
870 			// can't open the db
871 			tqslTrace("open_db", "create failed with %s errno %d", db_strerror(dbret), errno);
872 			dbinit_cleanup = true;
873 			goto dbinit_end;
874 		}
875 	}
876 
877 	DBTYPE type;
878 	conv->seendb->get_type(conv->seendb, &type);
879 	tqslTrace("open_db", "type=%d", type);
880 	if (type ==  DB_BTREE) {
881 		tqslTrace("open_db", "BTREE type. Converting.");
882 		// Have to convert the database.
883 		string dumpfile = fixedpath + "/dupedump.txt";
884 #ifdef _WIN32
885 		wchar_t* wdumpfile = utf8_to_wchar(dumpfile.c_str());
886 		FILE *dmp = _wfopen(wdumpfile, L"wb+");
887 		free_wchar(wdumpfile);
888 #else
889 		FILE *dmp = fopen(dumpfile.c_str(), "wb+");
890 #endif
891 		if (!dmp) {
892 			tqslTrace("open_db", "Error opening dump file %s: %s", dumpfile.c_str(), strerror(errno));
893 			dbinit_cleanup = true;
894 			goto dbinit_end;
895 		}
896 		if (!conv->cursor) {
897 #ifndef DB_CURSOR_BULK
898 #define DB_CURSOR_BULK 0
899 #endif
900 			int err = conv->seendb->cursor(conv->seendb, conv->txn, &conv->cursor, DB_CURSOR_BULK);
901 			if (err) {
902 				strncpy(tQSL_CustomError, db_strerror(err), sizeof tQSL_CustomError);
903 				tQSL_Error = TQSL_DB_ERROR;
904 				tQSL_Errno = errno;
905 				tqslTrace("open_db", "Error setting cursor for old DB: %s", err);
906 				dbinit_cleanup = true;
907 				goto dbinit_end;
908 			}
909 		}
910 
911 		DBT dbkey, dbdata;
912 		char duprec[512];
913 		while (1) {
914 			memset(&dbkey, 0, sizeof dbkey);
915 			memset(&dbdata, 0, sizeof dbdata);
916 			int status = conv->cursor->c_get(conv->cursor, &dbkey, &dbdata, DB_NEXT);
917 			if (DB_NOTFOUND == status) {
918 				break;	// No more records
919 			}
920 			if (status != 0) {
921 				strncpy(tQSL_CustomError, db_strerror(status), sizeof tQSL_CustomError);
922 				tQSL_Error = TQSL_DB_ERROR;
923 				tQSL_Errno = errno;
924 				tqslTrace("open_db", "Error reading for dump: %s", db_strerror(status));
925 				dbinit_cleanup = true;
926 				goto dbinit_end;
927 			}
928 			memcpy(duprec, dbkey.data, dbkey.size);
929 			duprec[dbkey.size] = '\0';
930 			fprintf(dmp, "%s\n", duprec);
931 		}
932 		conv->cursor->close(conv->cursor);
933 		if (conv->txn) conv->txn->commit(conv->txn, 0);
934 		conv->seendb->close(conv->seendb, 0);
935 		conv->db_open = false;
936 		conv->dbenv->remove(conv->dbenv, conv->dbpath, DB_FORCE);
937 		conv->dbenv->close(conv->dbenv, 0);
938 		conv->cursor = NULL;
939 		conv->seendb = NULL;
940 		conv->dbenv = NULL;
941 
942 		// Remove the old dupe db
943 		tqslTrace("open_db", "Removing old format db");
944 		remove_db(fixedpath.c_str());
945 
946 		// Now create the new database
947 		if ((dbret = db_env_create(&conv->dbenv, 0))) {
948 			// can't make env handle
949 			tqslTrace("open_db", "Can't make db handle: %s", db_strerror(dbret));
950 			dbinit_cleanup = true;
951 			goto dbinit_end;
952 		}
953 		if (conv->errfile)
954 			conv->dbenv->set_errfile(conv->dbenv, conv->errfile);
955 		if (conv->dbenv->set_lg_max)
956 			conv->dbenv->set_lg_max(conv->dbenv, 256 * 1024);
957 		if (conv->dbenv->set_lk_max_locks)
958 			conv->dbenv->set_lk_max_locks(conv->dbenv, 20000);
959 		if (conv->dbenv->set_lk_max_objects)
960 			conv->dbenv->set_lk_max_objects(conv->dbenv, 20000);
961 		if ((dbret = conv->dbenv->open(conv->dbenv, conv->dbpath, envflags, 0600))) {
962 			tqslTrace("open_db", "Error opening db: %s", db_strerror(dbret));
963 			if (conv->errfile)
964 				fprintf(conv->errfile, "opening DB %s returns status %d\n", conv->dbpath, dbret);
965 			dbinit_cleanup = true;
966 			goto dbinit_end;
967 		}
968 
969 		if ((dbret = db_create(&conv->seendb, conv->dbenv, 0))) {
970 			// can't create db
971 			tqslTrace("open_db", "Error creating db: %s", db_strerror(dbret));
972 			dbinit_cleanup = true;
973 			goto dbinit_end;
974 		}
975 
976 		// Create the new database
977 		if ((dbret = conv->seendb->open(conv->seendb, NULL, "duplicates.db", NULL, DB_HASH, DB_CREATE, 0600))) {
978 			// can't open the db
979 			tqslTrace("open_db", "Error opening new db: %s", db_strerror(dbret));
980 			dbinit_cleanup = true;
981 			goto dbinit_end;
982 		}
983 		fseek(dmp, 0, SEEK_SET);
984 
985 		char d[1]= {'D'};
986 		memset(&dbkey, 0, sizeof dbkey);
987 		memset(&dbdata, 0, sizeof dbdata);
988 		dbdata.data = d;
989 		dbdata.size = 1;
990 
991 		while (fgets(duprec, sizeof duprec, dmp)) {
992 			dbkey.data = duprec;
993 			dbkey.size = strlen(duprec) - 1;
994 			conv->seendb->put(conv->seendb, NULL, &dbkey, &dbdata, 0);
995 		}
996 		conv->seendb->close(conv->seendb, 0);
997 		conv->dbenv->close(conv->dbenv, 0);
998 		goto reopen;
999 	}
1000 
1001  dbinit_end:
1002 	if (dbinit_cleanup) {
1003 		tqslTrace("open_db", "DB open failed, triedDelete=%d", triedDelete);
1004 		tQSL_Error = TQSL_DB_ERROR;
1005 		tQSL_Errno = errno;
1006 		strncpy(tQSL_CustomError, db_strerror(dbret), sizeof tQSL_CustomError);
1007 		tqslTrace("open_db", "Error opening db: %s", tQSL_CustomError);
1008 		if (conv->txn) conv->txn->abort(conv->txn);
1009 		if (conv->seendb) conv->seendb->close(conv->seendb, 0);
1010 		conv->db_open = false;
1011 		if (conv->dbenv) {
1012 			if (conv->dbpath) {
1013 				conv->dbenv->remove(conv->dbenv,  conv->dbpath, DB_FORCE);
1014 				free(conv->dbpath);
1015 				conv->dbpath = NULL;
1016 			}
1017 			conv->dbenv->close(conv->dbenv, 0);
1018 		}
1019 		if (conv->cursor) conv->cursor->close(conv->cursor);
1020 		if (conv->errfile) fclose(conv->errfile);
1021 		conv->txn = NULL;
1022 		conv->dbenv = NULL;
1023 		conv->cursor = NULL;
1024 		conv->seendb = NULL;
1025 		conv->errfile = NULL;
1026 		// Handle case where the database is just broken
1027 		if (dbret == EINVAL && !triedDelete) {
1028 			tqslTrace("open_db", "EINVAL. Removing db");
1029 			remove_db(fixedpath.c_str());
1030 			triedDelete = true;
1031 			goto reopen;
1032 		}
1033 		return false;
1034 	}
1035 	conv->db_open = true;
1036 	return true;
1037 }
1038 #endif // USE_LMDB
1039 
1040 DLLEXPORT const char* CALLCONVENTION
1041 tqsl_getConverterGABBI(tQSL_Converter convp) {
1042 	TQSL_CONVERTER *conv;
1043 	char signdata[1024];
1044 
1045 	if (!(conv = check_conv(convp)))
1046 		return 0;
1047 	if (conv->need_ident_rec) {
1048 		int major = 0, minor = 0, config_major = 0, config_minor = 0;
1049 		tqsl_getVersion(&major, &minor);
1050 		tqsl_getConfigVersion(&config_major, &config_minor);
1051 		char temp[512];
1052 		static char ident[512];
1053 		snprintf(temp, sizeof temp, "%s Lib: V%d.%d Config: V%d.%d AllowDupes: %s",
1054 			conv->appName ? conv->appName : "Unknown",
1055 			major, minor, config_major, config_minor,
1056 			conv->allow_dupes ? "true" : "false");
1057 		temp[sizeof temp - 1] = '\0';
1058 		int len = strlen(temp);
1059 		snprintf(ident, sizeof ident, "<TQSL_IDENT:%d>%s\n", len, temp);
1060 		ident[sizeof ident - 1] = '\0';
1061 		conv->need_ident_rec = false;
1062 		return ident;
1063 	}
1064 
1065 	if (conv->need_station_rec) {
1066 		int uid = conv->cert_idx + conv->base_idx;
1067 		conv->need_station_rec = false;
1068 		const char *tStation = tqsl_getGABBItSTATION(conv->loc, uid, uid);
1069 		tqsl_getCertificateSerialExt(conv->certs[conv->cert_idx], conv->serial, sizeof conv->serial);
1070 		tqsl_getCertificateCallSign(conv->certs[conv->cert_idx], conv->callsign, sizeof conv->callsign);
1071 		return tStation;
1072 	}
1073 	if (!conv->allow_dupes && !conv->db_open) {
1074 		if (!open_db(conv, false)) {	// If can't open dupes DB
1075 			return 0;
1076 		}
1077 	}
1078 
1079 	TQSL_ADIF_GET_FIELD_ERROR stat;
1080 
1081 	if (conv->rec_done) {
1082 //cerr << "Getting rec" << endl;
1083 		conv->rec_done = false;
1084 		conv->clearRec();
1085 		int cstat = 0;
1086 		int saveErr = 0;
1087 		if (conv->adif) {
1088 	 		while (1) {
1089 				tqsl_adifFieldResults result;
1090 				if (tqsl_getADIFField(conv->adif, &result, &stat, adif_qso_record_fields, notypes, adif_allocate))
1091 					break;
1092 				if (stat != TQSL_ADIF_GET_FIELD_SUCCESS && stat != TQSL_ADIF_GET_FIELD_NO_NAME_MATCH)
1093 					break;
1094 				if (!strcasecmp(result.name, "eor"))
1095 					break;
1096 				if (!strcasecmp(result.name, "CALL") && result.data) {
1097 					conv->rec.callsign_set = true;
1098 					strncpy(conv->rec.callsign, reinterpret_cast<char *>(result.data), sizeof conv->rec.callsign);
1099 				} else if (!strcasecmp(result.name, "BAND") && result.data) {
1100 					conv->rec.band_set = true;
1101 				  	strncpy(conv->rec.band, reinterpret_cast<char *>(result.data), sizeof conv->rec.band);
1102 				} else if (!strcasecmp(result.name, "MODE") && result.data) {
1103 					conv->rec.mode_set = true;
1104 					strncpy(conv->rec.mode, reinterpret_cast<char *>(result.data), sizeof conv->rec.mode);
1105 				} else if (!strcasecmp(result.name, "SUBMODE") && result.data) {
1106 					strncpy(conv->rec.submode, reinterpret_cast<char *>(result.data), sizeof conv->rec.submode);
1107 				} else if (!strcasecmp(result.name, "FREQ") && result.data) {
1108 					conv->rec.band_set = true;
1109 					strncpy(conv->rec.freq, fix_freq(reinterpret_cast<char *>(result.data)), sizeof conv->rec.freq);
1110 					if (atof(conv->rec.freq) == 0.0)
1111 						conv->rec.freq[0] = '\0';
1112 				} else if (!strcasecmp(result.name, "FREQ_RX") && result.data) {
1113 					strncpy(conv->rec.rxfreq, fix_freq(reinterpret_cast<char *>(result.data)), sizeof conv->rec.rxfreq);
1114 					if (atof(conv->rec.rxfreq) == 0.0)
1115 						conv->rec.rxfreq[0] = '\0';
1116 				} else if (!strcasecmp(result.name, "BAND_RX") && result.data) {
1117 					strncpy(conv->rec.rxband, reinterpret_cast<char *>(result.data), sizeof conv->rec.rxband);
1118 				} else if (!strcasecmp(result.name, "SAT_NAME") && result.data) {
1119 					strncpy(conv->rec.satname, reinterpret_cast<char *>(result.data), sizeof conv->rec.satname);
1120 				} else if (!strcasecmp(result.name, "PROP_MODE") && result.data) {
1121 					strncpy(conv->rec.propmode, reinterpret_cast<char *>(result.data), sizeof conv->rec.propmode);
1122 				} else if (!strcasecmp(result.name, "QSO_DATE") && result.data) {
1123 					conv->rec.date_set = true;
1124 					cstat = tqsl_initDate(&(conv->rec.date), (const char *)result.data);
1125 					if (cstat)
1126 						saveErr = tQSL_Error;
1127 				} else if (!strcasecmp(result.name, "TIME_ON") && result.data) {
1128 					conv->rec.time_set = true;
1129 					cstat = tqsl_initTime(&(conv->rec.time), (const char *)result.data);
1130 					if (cstat)
1131 						saveErr = tQSL_Error;
1132 				}
1133 				if (stat == TQSL_ADIF_GET_FIELD_SUCCESS) {
1134 					conv->rec_text += string(reinterpret_cast<char *>(result.name)) + ": ";
1135 					if (result.data)
1136 						conv->rec_text += string(reinterpret_cast<char *>(result.data));
1137 					conv->rec_text += "\n";
1138 				}
1139 				if (result.data)
1140 					delete[] result.data;
1141 			}
1142 			if (saveErr) {
1143 				tQSL_Error = saveErr;
1144 				conv->rec_done = true;
1145 				return 0;
1146 			}
1147 			if (stat == TQSL_ADIF_GET_FIELD_EOF)
1148 				return 0;
1149 			if (stat != TQSL_ADIF_GET_FIELD_SUCCESS) {
1150 				tQSL_ADIF_Error = stat;
1151 				tQSL_Error = TQSL_ADIF_ERROR;
1152 				return 0;
1153 			}
1154 			// ADIF record is complete. See if we need to infer the BAND fields.
1155 			if (conv->rec.band[0] == 0)
1156 				strncpy(conv->rec.band, tqsl_infer_band(conv->rec.freq), sizeof conv->rec.band);
1157 			if (conv->rec.rxband[0] == 0)
1158 				strncpy(conv->rec.rxband, tqsl_infer_band(conv->rec.rxfreq), sizeof conv->rec.rxband);
1159 		} else if (conv->cab) {
1160 			TQSL_CABRILLO_ERROR_TYPE stat;
1161 			do {
1162 				tqsl_cabrilloField field;
1163 				if (tqsl_getCabrilloField(conv->cab, &field, &stat))
1164 					return 0;
1165 				if (stat == TQSL_CABRILLO_NO_ERROR || stat == TQSL_CABRILLO_EOR) {
1166 					// Field found
1167 					if (!strcasecmp(field.name, "CALL")) {
1168 						conv->rec.callsign_set = true;
1169 						strncpy(conv->rec.callsign, field.value, sizeof conv->rec.callsign);
1170 					} else if (!strcasecmp(field.name, "BAND")) {
1171 						conv->rec.band_set = true;
1172 						strncpy(conv->rec.band, field.value, sizeof conv->rec.band);
1173 					} else if (!strcasecmp(field.name, "MODE")) {
1174 						conv->rec.mode_set = true;
1175 						strncpy(conv->rec.mode, field.value, sizeof conv->rec.mode);
1176 					} else if (!strcasecmp(field.name, "FREQ")) {
1177 						conv->rec.band_set = true;
1178 						strncpy(conv->rec.freq, field.value, sizeof conv->rec.freq);
1179 					} else if (!strcasecmp(field.name, "QSO_DATE")) {
1180 						conv->rec.date_set = true;
1181 						cstat = tqsl_initDate(&(conv->rec.date), field.value);
1182 						if (cstat)
1183 							saveErr = tQSL_Error;
1184 					} else if (!strcasecmp(field.name, "TIME_ON")) {
1185 						conv->rec.time_set = true;
1186 						cstat = tqsl_initTime(&(conv->rec.time), field.value);
1187 						if (cstat)
1188 							saveErr = tQSL_Error;
1189 					}
1190 					if (conv->rec_text != "")
1191 						conv->rec_text += "\n";
1192 					conv->rec_text += string(field.name) + ": " + field.value;
1193 				}
1194 			} while (stat == TQSL_CABRILLO_NO_ERROR);
1195 			if (saveErr)
1196 				tQSL_Error = saveErr;
1197 			if (saveErr || stat != TQSL_CABRILLO_EOR) {
1198 				conv->rec_done = true;
1199 				return 0;
1200 			}
1201 		} else {
1202 			tQSL_Error = TQSL_CUSTOM_ERROR;
1203 			strncpy(tQSL_CustomError, "Converter not initialized", sizeof tQSL_CustomError);
1204 			tqslTrace("tqsl_getConverterGABBI", "Converter not initialized");
1205 			return 0;
1206 		}
1207 	}
1208 	// Does the QSO have the basic required elements?
1209 	if (!conv->rec.callsign_set) {
1210 		conv->rec_done = true;
1211 		snprintf(tQSL_CustomError, sizeof tQSL_CustomError, "Invalid contact - QSO does not specify a Callsign");
1212 		tQSL_Error = TQSL_CUSTOM_ERROR;
1213 		return 0;
1214 	}
1215 	if (!conv->rec.band_set) {
1216 		conv->rec_done = true;
1217 		snprintf(tQSL_CustomError, sizeof tQSL_CustomError, "Invalid contact - QSO does not specify a band or frequency");
1218 		tQSL_Error = TQSL_CUSTOM_ERROR;
1219 		return 0;
1220 	}
1221 	if (!conv->rec.mode_set) {
1222 		conv->rec_done = true;
1223 		snprintf(tQSL_CustomError, sizeof tQSL_CustomError, "Invalid contact - QSO does not specify a mode");
1224 		tQSL_Error = TQSL_CUSTOM_ERROR;
1225 		return 0;
1226 	}
1227 	if (!conv->rec.date_set) {
1228 		conv->rec_done = true;
1229 		snprintf(tQSL_CustomError, sizeof tQSL_CustomError, "Invalid contact - QSO does not specify a date");
1230 		tQSL_Error = TQSL_CUSTOM_ERROR;
1231 		return 0;
1232 	}
1233 	if (!conv->rec.time_set) {
1234 		conv->rec_done = true;
1235 		snprintf(tQSL_CustomError, sizeof tQSL_CustomError, "Invalid contact - QSO does not specify a time");
1236 		tQSL_Error = TQSL_CUSTOM_ERROR;
1237 		return 0;
1238 	}
1239 
1240 	// Check QSO date against user-specified date range.
1241 	if (tqsl_isDateValid(&(conv->rec.date))) {
1242 		if (tqsl_isDateValid(&(conv->start)) && tqsl_compareDates(&(conv->rec.date), &(conv->start)) < 0) {
1243 			conv->rec_done = true;
1244 			tQSL_Error = TQSL_DATE_OUT_OF_RANGE;
1245 			return 0;
1246 		}
1247 		if (tqsl_isDateValid(&(conv->end)) && tqsl_compareDates(&(conv->rec.date), &(conv->end)) > 0) {
1248 			conv->rec_done = true;
1249 			tQSL_Error = TQSL_DATE_OUT_OF_RANGE;
1250 			return 0;
1251 		}
1252 	}
1253 
1254 	// Do field value mapping
1255 	tqsl_strtoupper(conv->rec.callsign);
1256 	if (!conv->allow_bad_calls) {
1257 		if (!checkCallSign(conv->rec.callsign)) {
1258 			conv->rec_done = true;
1259 			snprintf(tQSL_CustomError, sizeof tQSL_CustomError, "Invalid amateur CALL (%s)", conv->rec.callsign);
1260 			tQSL_Error = TQSL_CUSTOM_ERROR;
1261 			return 0;
1262 		}
1263 	}
1264 	tqsl_strtoupper(conv->rec.band);
1265 	tqsl_strtoupper(conv->rec.rxband);
1266 	tqsl_strtoupper(conv->rec.mode);
1267 	tqsl_strtoupper(conv->rec.submode);
1268 	char val[256] = "";
1269 	// Try to find the GABBI mode several ways.
1270 	val[0] = '\0';
1271 	if (conv->rec.submode[0] != '\0') {
1272 		char modeSub[256];
1273 		strncpy(modeSub, conv->rec.mode, sizeof modeSub);
1274 		size_t left = sizeof modeSub - strlen(modeSub);
1275 		strncat(modeSub, "%", left);
1276 		left = sizeof modeSub - strlen(modeSub);
1277 		strncat(modeSub, conv->rec.submode, left);
1278 		if (tqsl_getADIFMode(modeSub, val, sizeof val)) {	// mode%submode lookup failed
1279 			// Try just the submode.
1280 			if (tqsl_getADIFMode(conv->rec.submode, val, sizeof val)) { // bare submode failed
1281 				if (tqsl_getADIFMode(conv->rec.mode, val, sizeof val)) {
1282 					val[0] = '\0';
1283 				}
1284 			}
1285 		}
1286 	} else {
1287 		// Just a mode, no submode. Look that up.
1288 		tqsl_getADIFMode(conv->rec.mode, val, sizeof val);
1289 	}
1290 	if (val[0] != '\0')
1291 		strncpy(conv->rec.mode, val, sizeof conv->rec.mode);
1292 	// Check field validities
1293 	if (conv->modes.find(conv->rec.mode) == conv->modes.end()) {
1294 		conv->rec_done = true;
1295 		snprintf(tQSL_CustomError, sizeof tQSL_CustomError, "Invalid MODE (%s)", conv->rec.mode);
1296 		tQSL_Error = TQSL_CUSTOM_ERROR;
1297 		return 0;
1298 	}
1299 	if (conv->bands.find(conv->rec.band) == conv->bands.end()) {
1300 		conv->rec_done = true;
1301 		snprintf(tQSL_CustomError, sizeof tQSL_CustomError, "Invalid BAND (%s)", conv->rec.band);
1302 		tQSL_Error = TQSL_CUSTOM_ERROR;
1303 		return 0;
1304 	}
1305 	if (conv->rec.rxband[0] && (conv->bands.find(conv->rec.rxband) == conv->bands.end())) {
1306 		conv->rec_done = true;
1307 		snprintf(tQSL_CustomError, sizeof tQSL_CustomError, "Invalid RX BAND (%s)", conv->rec.rxband);
1308 		tQSL_Error = TQSL_CUSTOM_ERROR;
1309 		return 0;
1310 	}
1311 	if (conv->rec.freq[0] && strcmp(conv->rec.band, tqsl_infer_band(conv->rec.freq))) {
1312 		conv->rec_done = true;
1313 		snprintf(tQSL_CustomError, sizeof tQSL_CustomError, "Frequency %s is out of range for band %s", conv->rec.freq, conv->rec.band);
1314 		tQSL_Error = TQSL_CUSTOM_ERROR;
1315 		return 0;
1316 	}
1317 	if (conv->rec.rxfreq[0] && strcmp(conv->rec.rxband, tqsl_infer_band(conv->rec.rxfreq))) {
1318 		conv->rec_done = true;
1319 		snprintf(tQSL_CustomError, sizeof tQSL_CustomError, "RX Frequency %s is out of range for band %s", conv->rec.rxfreq, conv->rec.rxband);
1320 		tQSL_Error = TQSL_CUSTOM_ERROR;
1321 		return 0;
1322 	}
1323 	if (conv->rec.propmode[0] != '\0'
1324 		&& conv->propmodes.find(conv->rec.propmode) == conv->propmodes.end()) {
1325 		conv->rec_done = true;
1326 		snprintf(tQSL_CustomError, sizeof tQSL_CustomError, "Invalid PROP_MODE (%s)", conv->rec.propmode);
1327 		tQSL_Error = TQSL_CUSTOM_ERROR;
1328 		return 0;
1329 	}
1330 	if (conv->rec.satname[0] != '\0'
1331 		&& conv->satellites.find(conv->rec.satname) == conv->satellites.end()) {
1332 		conv->rec_done = true;
1333 		snprintf(tQSL_CustomError, sizeof tQSL_CustomError, "Invalid SAT_NAME (%s)", conv->rec.satname);
1334 		tQSL_Error = TQSL_CUSTOM_ERROR;
1335 		return 0;
1336 	}
1337 	if (!strcmp(conv->rec.propmode, "SAT") && conv->rec.satname[0] == '\0') {
1338 		conv->rec_done = true;
1339 		snprintf(tQSL_CustomError, sizeof tQSL_CustomError, "PROP_MODE = 'SAT' but no SAT_NAME");
1340 		tQSL_Error = TQSL_CUSTOM_ERROR;
1341 		return 0;
1342 	}
1343 	if (strcmp(conv->rec.propmode, "SAT") && conv->rec.satname[0] != '\0') {
1344 		conv->rec_done = true;
1345 		snprintf(tQSL_CustomError, sizeof tQSL_CustomError, "SAT_NAME set but PROP_MODE is not 'SAT'");
1346 		tQSL_Error = TQSL_CUSTOM_ERROR;
1347 		return 0;
1348 	}
1349 
1350 	// Check cert
1351 	if (conv->ncerts <= 0) {
1352 		conv->rec_done = true;
1353 		tQSL_Error = TQSL_CERT_NOT_FOUND;
1354 		return 0;
1355 	}
1356 
1357 	int cidx = find_matching_cert(conv);
1358 	if (cidx < 0) {
1359 		conv->rec_done = true;
1360 		tQSL_Error = TQSL_CERT_DATE_MISMATCH;
1361 		return 0;
1362 	}
1363 	if (cidx != conv->cert_idx) {
1364 		// Switching certs
1365 		conv->cert_idx = cidx;
1366 		if (!conv->certs_used[conv->cert_idx]) {
1367 			// Need to output tCERT, tSTATION
1368 			conv->need_station_rec = true;
1369 			conv->certs_used[conv->cert_idx] = true;
1370 			return tqsl_getGABBItCERT(conv->certs[conv->cert_idx], conv->cert_idx + conv->base_idx);
1371 		}
1372 	}
1373 	const char *grec = tqsl_getGABBItCONTACTData(conv->certs[conv->cert_idx], conv->loc, &(conv->rec),
1374 		conv->cert_idx + conv->base_idx, signdata, sizeof(signdata));
1375 	if (grec) {
1376 		conv->rec_done = true;
1377 		if (!conv->allow_dupes) {
1378 			char stnloc[128];
1379 			char qso[128];
1380 			if (tqsl_getLocationStationDetails(conv->loc, stnloc, sizeof stnloc)) {
1381 				stnloc[0] = '\0';
1382 			}
1383 			if (tqsl_getLocationQSODetails(conv->loc, qso, sizeof qso)) {
1384 				qso[0] = '\0';
1385 			}
1386 			// Old-style Lookup uses signdata and cert serial number
1387 #ifdef USE_LMDB
1388 			MDB_val dbkey, dbdata;
1389 #else
1390 			DBT dbkey, dbdata;
1391 			memset(&dbkey, 0, sizeof dbkey);
1392 			memset(&dbdata, 0, sizeof dbdata);
1393 #endif
1394 			// append signing key serial
1395 			strncat(signdata, conv->serial, sizeof(signdata) - strlen(signdata)-1);
1396 			// Updated dupe database entry. Key is formed from
1397 			// local callsign concatenated with the QSO details
1398 			char dupekey[128];
1399 			snprintf(dupekey, sizeof dupekey, "%s%s", conv->callsign, qso);
1400 #ifdef USE_LMDB
1401 			dbkey.mv_size = strlen(signdata);
1402 			dbkey.mv_data = signdata;
1403 			int dbget_err = mdb_get(conv->txn, conv->seendb, &dbkey, &dbdata);
1404 #else
1405 			dbkey.size = strlen(signdata);
1406 			dbkey.data = signdata;
1407 			int dbget_err = conv->seendb->get(conv->seendb, conv->txn, &dbkey, &dbdata, 0);
1408 #endif
1409 			if (0 == dbget_err) {
1410 				//lookup was successful; thus this is a duplicate.
1411 				tQSL_Error = TQSL_DUPLICATE_QSO;
1412 				tQSL_CustomError[0] = '\0';
1413 				// delete the old record
1414 
1415 				int dbput_err;
1416 #ifdef USE_LMDB
1417 				mdb_del(conv->txn, conv->seendb, &dbkey, &dbdata);
1418 				// Update this to the current format
1419 				dbkey.mv_size = strlen(dupekey);
1420 				dbkey.mv_data = dupekey;
1421 				dbdata.mv_data = stnloc;
1422 				dbdata.mv_size = strlen(stnloc);
1423 				dbput_err = mdb_put(conv->txn, conv->seendb, &dbkey, &dbdata, 0);
1424 #else
1425 				conv->seendb->del(conv->seendb, conv->txn, &dbkey, 0);
1426 				// Update this to the current format
1427 				memset(&dbkey, 0, sizeof dbkey);
1428 				dbkey.size = strlen(dupekey);
1429 				dbkey.data = dupekey;
1430 				memset(&dbdata, 0, sizeof dbdata);
1431 				dbdata.data = stnloc;
1432 				dbdata.size = strlen(stnloc);
1433 				dbput_err = conv->seendb->put(conv->seendb, conv->txn, &dbkey, &dbdata, 0);
1434 #endif
1435 				if (0 != dbput_err) {
1436 					strncpy(tQSL_CustomError, db_strerror(dbput_err), sizeof tQSL_CustomError);
1437 					tQSL_Error = TQSL_DB_ERROR;
1438 					return 0;
1439 				}
1440 				return 0;
1441 #ifdef USE_LMDB
1442 			} else if (dbget_err != MDB_NOTFOUND) {
1443 #else
1444 			} else if (dbget_err != DB_NOTFOUND) {
1445 #endif
1446 				//non-zero return, but not "not found" - thus error
1447 				strncpy(tQSL_CustomError, db_strerror(dbget_err), sizeof tQSL_CustomError);
1448 				tQSL_Error = TQSL_DB_ERROR;
1449 				return 0;
1450 				// could be more specific but there's very little the user can do at this point anyway
1451 			}
1452 #ifdef USE_LMDB
1453 			dbkey.mv_size = strlen(dupekey);
1454 			dbkey.mv_data = dupekey;
1455 			dbget_err = mdb_get(conv->txn, conv->seendb, &dbkey, &dbdata);
1456 #else
1457 			memset(&dbkey, 0, sizeof dbkey);
1458 			memset(&dbdata, 0, sizeof dbdata);
1459 
1460 			dbkey.size = strlen(dupekey);
1461 			dbkey.data = dupekey;
1462 			dbget_err = conv->seendb->get(conv->seendb, conv->txn, &dbkey, &dbdata, 0);
1463 #endif
1464 			if (0 == dbget_err) {
1465 				//lookup was successful; thus this is a duplicate.
1466 				tQSL_Error = TQSL_DUPLICATE_QSO;
1467 				// Save the original and new station location details so those can be provided
1468 				// with an error by the caller
1469 #ifdef USE_LMDB
1470 				char *olddup = reinterpret_cast<char *> (malloc(dbdata.mv_size + 2));
1471 				memcpy(olddup, dbdata.mv_data, dbdata.mv_size);
1472 				olddup[dbdata.mv_size] = '\0';
1473 #else
1474 				char *olddup = reinterpret_cast<char *> (malloc(dbdata.size + 2));
1475 				memcpy(olddup, dbdata.data, dbdata.size);
1476 				olddup[dbdata.size] = '\0';
1477 #endif
1478 				snprintf(tQSL_CustomError, sizeof tQSL_CustomError, "%s|%s", olddup, stnloc);
1479 				free(olddup);
1480 				return 0;
1481 #ifdef USE_LMDB
1482 			} else if (dbget_err != MDB_NOTFOUND) {
1483 #else
1484 			} else if (dbget_err != DB_NOTFOUND) {
1485 #endif
1486 				//non-zero return, but not "not found" - thus error
1487 				strncpy(tQSL_CustomError, db_strerror(dbget_err), sizeof tQSL_CustomError);
1488 				tQSL_Error = TQSL_DB_ERROR;
1489 				return 0;
1490 				// could be more specific but there's very little the user can do at this point anyway
1491 			}
1492 
1493 			int dbput_err;
1494 #ifdef USE_LMDB
1495 			dbdata.mv_data = stnloc;
1496 			dbdata.mv_size = strlen(stnloc);
1497 			dbput_err = mdb_put(conv->txn, conv->seendb, &dbkey, &dbdata, 0);
1498 #else
1499 			memset(&dbdata, 0, sizeof dbdata);
1500 			dbdata.data = stnloc;
1501 			dbdata.size = strlen(stnloc);
1502 			dbput_err = conv->seendb->put(conv->seendb, conv->txn, &dbkey, &dbdata, 0);
1503 #endif
1504 			if (0 != dbput_err) {
1505 				strncpy(tQSL_CustomError, db_strerror(dbput_err), sizeof tQSL_CustomError);
1506 				tQSL_Error = TQSL_DB_ERROR;
1507 				return 0;
1508 			}
1509 		}
1510 	}
1511 	return grec;
1512 }
1513 
1514 DLLEXPORT int CALLCONVENTION
1515 tqsl_getConverterCert(tQSL_Converter convp, tQSL_Cert *certp) {
1516 	TQSL_CONVERTER *conv;
1517 	if (!(conv = check_conv(convp)))
1518 		return 1;
1519 	if (certp == 0) {
1520 		tQSL_Error = TQSL_ARGUMENT_ERROR;
1521 		return 1;
1522 	}
1523 	*certp = conv->certs[conv->cert_idx];
1524 	return 0;
1525 }
1526 
1527 DLLEXPORT int CALLCONVENTION
1528 tqsl_getConverterLine(tQSL_Converter convp, int *lineno) {
1529 	TQSL_CONVERTER *conv;
1530 	if (!(conv = check_conv(convp)))
1531 		return 1;
1532 	if (lineno == 0) {
1533 		tQSL_Error = TQSL_ARGUMENT_ERROR;
1534 		return 1;
1535 	}
1536 	if (conv->cab)
1537 		return tqsl_getCabrilloLine(conv->cab, lineno);
1538 	else if (conv->adif)
1539 		return tqsl_getADIFLine(conv->adif, lineno);
1540 	*lineno = 0;
1541 	return 0;
1542 }
1543 
1544 DLLEXPORT const char* CALLCONVENTION
1545 tqsl_getConverterRecordText(tQSL_Converter convp) {
1546 	TQSL_CONVERTER *conv;
1547 	if (!(conv = check_conv(convp)))
1548 		return 0;
1549 	return conv->rec_text.c_str();
1550 }
1551 
1552 DLLEXPORT int CALLCONVENTION
1553 tqsl_setConverterAllowBadCall(tQSL_Converter convp, int allow) {
1554 	TQSL_CONVERTER *conv;
1555 	if (!(conv = check_conv(convp)))
1556 		return 1;
1557 	conv->allow_bad_calls = (allow != 0);
1558 	return 0;
1559 }
1560 
1561 DLLEXPORT int CALLCONVENTION
1562 tqsl_setConverterAllowDuplicates(tQSL_Converter convp, int allow) {
1563 	TQSL_CONVERTER *conv;
1564 	if (!(conv = check_conv(convp)))
1565 		return 1;
1566 	conv->allow_dupes = (allow != 0);
1567 	return 0;
1568 }
1569 
1570 DLLEXPORT int CALLCONVENTION
1571 tqsl_setConverterAppName(tQSL_Converter convp, const char *app) {
1572 	TQSL_CONVERTER *conv;
1573 	if (!(conv = check_conv(convp)))
1574 		return 1;
1575 	if (!app) {
1576 		tQSL_Error = TQSL_ARGUMENT_ERROR;
1577 		return 1;
1578 	}
1579 	conv->appName = strdup(app);
1580 	return 0;
1581 }
1582 
1583 DLLEXPORT int CALLCONVENTION
1584 tqsl_converterRollBack(tQSL_Converter convp) {
1585 	TQSL_CONVERTER *conv;
1586 
1587 	tqslTrace("tqsl_converterRollBack", NULL);
1588 	if (!(conv = check_conv(convp)))
1589 		return 1;
1590 	if (!conv->db_open)
1591 		return 0;
1592 	if (conv->txn)
1593 #ifdef USE_LMDB
1594 		mdb_txn_abort(conv->txn);
1595 #else
1596 		conv->txn->abort(conv->txn);
1597 #endif
1598 	conv->txn = NULL;
1599 	return 0;
1600 }
1601 
1602 DLLEXPORT int CALLCONVENTION
1603 tqsl_converterCommit(tQSL_Converter convp) {
1604 	TQSL_CONVERTER *conv;
1605 
1606 	tqslTrace("tqsl_converterCommit", NULL);
1607 	if (!(conv = check_conv(convp)))
1608 		return 1;
1609 	if (!conv->db_open)
1610 		return 0;
1611 	if (conv->txn)
1612 #ifdef USE_LMDB
1613 		mdb_txn_commit(conv->txn);
1614 #else
1615 		conv->txn->commit(conv->txn, 0);
1616 #endif
1617 	conv->txn = NULL;
1618 	return 0;
1619 }
1620 
1621 DLLEXPORT int CALLCONVENTION
1622 tqsl_getDuplicateRecords(tQSL_Converter convp, char *key, char *data, int keylen) {
1623 	TQSL_CONVERTER *conv;
1624 
1625 	if (!(conv = check_conv(convp)))
1626 		return 1;
1627 
1628 	if (!conv->db_open) {
1629 		if (!open_db(conv, true)) {	// If can't open dupes DB
1630 			return 1;
1631 		}
1632 	}
1633 	if (!conv->cursor) {
1634 #ifdef USE_LMDB
1635 		int err = mdb_cursor_open(conv->txn, conv->seendb, &conv->cursor);
1636 #else
1637 		int err = conv->seendb->cursor(conv->seendb, conv->txn, &conv->cursor, DB_CURSOR_BULK);
1638 #endif
1639 		if (err) {
1640 			strncpy(tQSL_CustomError, db_strerror(err), sizeof tQSL_CustomError);
1641 			tQSL_Error = TQSL_DB_ERROR;
1642 			tQSL_Errno = errno;
1643 			return 1;
1644 		}
1645 	}
1646 
1647 #ifdef USE_LMDB
1648 	MDB_val dbkey, dbdata;
1649 	int status = mdb_cursor_get(conv->cursor, &dbkey, &dbdata, MDB_NEXT);
1650 	if (MDB_NOTFOUND == status) {
1651 #else
1652 	DBT dbkey, dbdata;
1653 	memset(&dbkey, 0, sizeof dbkey);
1654 	memset(&dbdata, 0, sizeof dbdata);
1655 	int status = conv->cursor->c_get(conv->cursor, &dbkey, &dbdata, DB_NEXT);
1656 	if (DB_NOTFOUND == status) {
1657 #endif
1658 		return -1;	// No more records
1659 	}
1660 	if (status != 0) {
1661 		strncpy(tQSL_CustomError, db_strerror(status), sizeof tQSL_CustomError);
1662 		tQSL_Error = TQSL_DB_ERROR;
1663 		tQSL_Errno = errno;
1664 		return 1;
1665 	}
1666 #ifdef USE_LMDB
1667 	memcpy(key, dbkey.mv_data, dbkey.mv_size);
1668 	key[dbkey.mv_size] = '\0';
1669 
1670 	if (dbdata.mv_size > 9) dbdata.mv_size = 9;
1671 	memcpy(data, dbdata.mv_data, dbdata.mv_size);
1672 	data[dbdata.mv_size] = '\0';
1673 #else
1674 	memcpy(key, dbkey.data, dbkey.size);
1675 	key[dbkey.size] = '\0';
1676 
1677 	if (dbdata.size > 9) dbdata.size = 9;
1678 	memcpy(data, dbdata.data, dbdata.size);
1679 	data[dbdata.size] = '\0';
1680 #endif
1681 	return 0;
1682 }
1683 
1684 DLLEXPORT int CALLCONVENTION
1685 tqsl_getDuplicateRecordsV2(tQSL_Converter convp, char *key, char *data, int keylen) {
1686 	TQSL_CONVERTER *conv;
1687 
1688 	if (!(conv = check_conv(convp)))
1689 		return 1;
1690 
1691 	if (!conv->db_open) {
1692 		if (!open_db(conv, true)) {	// If can't open dupes DB
1693 			return 1;
1694 		}
1695 	}
1696 	if (!conv->cursor) {
1697 #ifdef USE_LMDB
1698 		int err = mdb_cursor_open(conv->txn, conv->seendb, &conv->cursor);
1699 #else
1700 		int err = conv->seendb->cursor(conv->seendb, conv->txn, &conv->cursor, DB_CURSOR_BULK);
1701 #endif
1702 		if (err) {
1703 			strncpy(tQSL_CustomError, db_strerror(err), sizeof tQSL_CustomError);
1704 			tQSL_Error = TQSL_DB_ERROR;
1705 			tQSL_Errno = errno;
1706 			return 1;
1707 		}
1708 	}
1709 
1710 #ifdef USE_LMDB
1711 	MDB_val dbkey, dbdata;
1712 	int status = mdb_cursor_get(conv->cursor, &dbkey, &dbdata, MDB_NEXT);
1713 	if (MDB_NOTFOUND == status) {
1714 #else
1715 	DBT dbkey, dbdata;
1716 	memset(&dbkey, 0, sizeof dbkey);
1717 	memset(&dbdata, 0, sizeof dbdata);
1718 	int status = conv->cursor->c_get(conv->cursor, &dbkey, &dbdata, DB_NEXT);
1719 	if (DB_NOTFOUND == status) {
1720 #endif
1721 		return -1;	// No more records
1722 	}
1723 	if (status != 0) {
1724 		strncpy(tQSL_CustomError, db_strerror(status), sizeof tQSL_CustomError);
1725 		tQSL_Error = TQSL_DB_ERROR;
1726 		tQSL_Errno = errno;
1727 		return 1;
1728 	}
1729 #ifdef USE_LMDB
1730 	memcpy(key, dbkey.mv_data, dbkey.mv_size);
1731 	key[dbkey.mv_size] = '\0';
1732 	if (dbdata.mv_size > 255) dbdata.mv_size = 255;
1733 	memcpy(data, dbdata.mv_data, dbdata.mv_size);
1734 	data[dbdata.mv_size] = '\0';
1735 #else
1736 	memcpy(key, dbkey.data, dbkey.size);
1737 	key[dbkey.size] = '\0';
1738 	if (dbdata.size > 255) dbdata.size = 255;
1739 	memcpy(data, dbdata.data, dbdata.size);
1740 	data[dbdata.size] = '\0';
1741 #endif
1742 	return 0;
1743 }
1744 
1745 DLLEXPORT int CALLCONVENTION
1746 tqsl_putDuplicateRecord(tQSL_Converter convp, const char *key, const char *data, int keylen) {
1747 	TQSL_CONVERTER *conv;
1748 
1749 	if (!(conv = check_conv(convp)))
1750 		return 0;
1751 
1752 	if (!conv->db_open) {
1753 		if (!open_db(conv, false)) {	// If can't open dupes DB
1754 			return 0;
1755 		}
1756 	}
1757 #ifdef USE_LMDB
1758 	MDB_val dbkey, dbdata;
1759 	dbkey.mv_size = keylen;
1760 	dbkey.mv_data = const_cast<char *>(key);
1761 
1762 	dbdata.mv_size = strlen(data);
1763 	dbdata.mv_data = const_cast<char *>(data);
1764 
1765 	int status = mdb_put(conv->txn, conv->seendb, &dbkey, &dbdata, 0);
1766 
1767 	if (MDB_KEYEXIST == status) {
1768 		return -1;	// OK, but already there
1769 	}
1770 
1771 #else
1772 	DBT dbkey, dbdata;
1773 	memset(&dbkey, 0, sizeof dbkey);
1774 	memset(&dbdata, 0, sizeof dbdata);
1775 	dbkey.size = keylen;
1776 	dbkey.data = const_cast<char *>(key);
1777 
1778 	dbdata.size = strlen(data);
1779 	dbdata.data = const_cast<char *>(data);
1780 
1781 	int status = conv->seendb->put(conv->seendb, conv->txn, &dbkey, &dbdata, 0);
1782 
1783 	if (DB_KEYEXIST == status) {
1784 		return -1;	// OK, but already there
1785 	}
1786 #endif
1787 
1788 	if (status != 0) {
1789 		strncpy(tQSL_CustomError, db_strerror(status), sizeof tQSL_CustomError);
1790 		tQSL_Error = TQSL_DB_ERROR;
1791 		tQSL_Errno = errno;
1792 		return 1;
1793 	}
1794 	return 0;
1795 }
1796 
1797 static bool
1798 hasValidCallSignChars(const string& call) {
1799 	// Check for invalid characters
1800 	if (call.find_first_not_of("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/") != string::npos)
1801 		return false;
1802 	// Need at least one letter
1803 	if (call.find_first_of("ABCDEFGHIJKLMNOPQRSTUVWXYZ") == string::npos)
1804 		return false;
1805 	// Need at least one number
1806 	if (call.find_first_of("0123456789") == string::npos)
1807 		return false;
1808 	// Invalid callsign patterns
1809 	// Starting with 0, Q
1810 	// 1x other than 1A, 1M, 1S
1811 	string first = call.substr(0, 1);
1812 	string second = call.substr(1, 1);
1813 	if (first == "0" || first == "Q" ||
1814 #ifdef MARK_C7_4Y_INVALID
1815 	    (first == "C" && second == "7") ||
1816 	    (first == "4" && second == "Y") ||
1817 #endif
1818 	    (first == "1" && second != "A" && second != "M" && second != "S"))
1819 		return false;
1820 
1821 	return true;
1822 }
1823 
1824 static bool
1825 checkCallSign(const string& call) {
1826 	if (!hasValidCallSignChars(call))
1827 		return false;
1828 	if (call.length() < 3)
1829 		return false;
1830 	string::size_type idx, newidx;
1831 	for (idx = 0; idx != string::npos; idx = newidx+1) {
1832 		string s;
1833 		newidx = call.find('/', idx);
1834 		if (newidx == string::npos)
1835 			s = call.substr(idx);
1836 		else
1837 			s = call.substr(idx, newidx - idx);
1838 		if (s.length() == 0)
1839 			return false;	// Leading or trailing '/' is bad, bad!
1840 		if (newidx == string::npos)
1841 			break;
1842 	}
1843 	return true;
1844 }
1845