1 /***************************************************************************
2                                   tqsl.cpp
3                              -------------------
4     begin                : Mon Nov 4 2002
5     copyright            : (C) 2002-2018 by ARRL and the TrustedQSL Developers
6     author               : Jon Bloom
7     email                : jbloom@arrl.org
8  ***************************************************************************/
9 
10 #include <curl/curl.h> // has to be before something else in this list
11 #include <stdlib.h>
12 #include <errno.h>
13 #ifndef _WIN32
14 #include <unistd.h>
15 #include <fcntl.h>
16 #endif
17 #include <expat.h>
18 #include <sys/stat.h>
19 
20 #ifndef _WIN32
21     #include <unistd.h>
22     #include <dirent.h>
23 #else
24     #include <direct.h>
25     #include "windirent.h"
26 #endif
27 
28 #include <wx/wxprec.h>
29 #include <wx/object.h>
30 #include <wx/wxchar.h>
31 #include <wx/config.h>
32 #include <wx/regex.h>
33 #include <wx/tokenzr.h>
34 #include <wx/hashmap.h>
35 #include <wx/hyperlink.h>
36 #include <wx/cmdline.h>
37 #include <wx/notebook.h>
38 #include <wx/statline.h>
39 #include <wx/app.h>
40 #include <wx/stdpaths.h>
41 #include <wx/intl.h>
42 #include <wx/cshelp.h>
43 
44 #ifdef __BORLANDC__
45 	#pragma hdrstop
46 #endif
47 
48 #ifndef WX_PRECOMP
49 	#include <wx/wx.h>
50 #endif
51 
52 #include <wx/wxhtml.h>
53 #include <wx/wfstream.h>
54 
55 #ifdef _MSC_VER //could probably do a more generic check here...
56 // stdint exists on vs2012 and (I think) 2010, but not 2008 or its platform
57   #define uint8_t unsigned char
58 #else
59 #include <stdint.h> //for uint8_t; should be cstdint but this is C++11 and not universally supported
60 #endif
61 
62 #ifdef _WIN32
63 	#include <io.h>
64 #endif
65 #include <zlib.h>
66 #include <openssl/opensslv.h> // only for version info!
67 #ifdef USE_LMDB
68 #include <lmdb.h> //only for version info!
69 #else
70 #include <db.h> //only for version info!
71 #endif
72 
73 #include <iostream>
74 #include <fstream>
75 #include <memory>
76 #include <string>
77 #include <vector>
78 #include <map>
79 
80 #ifdef HAVE_CONFIG_H
81 #include "sysconfig.h"
82 #endif
83 
84 #include "tqslhelp.h"
85 
86 #include "crqwiz.h"
87 #include "getpassword.h"
88 #include "loadcertwiz.h"
89 #include "tqsllib.h"
90 #include "util.h"
91 #include "wxutil.h"
92 
93 #include "tqslwiz.h"
94 #include "qsodatadialog.h"
95 #include "tqslerrno.h"
96 #include "tqslexcept.h"
97 #include "tqslpaths.h"
98 #include "stationdial.h"
99 #include "tqslconvert.h"
100 #include "dxcc.h"
101 #include "tqsl_prefs.h"
102 #include "tqslbuild.h"
103 #include "tqslapp.h"
104 #include "tqsltrace.h"
105 
106 #include "winstrdefs.h"
107 
108 #include "jsonval.h"
109 #include "jsonreader.h"
110 
111 using std::ifstream;
112 using std::ios;
113 using std::ofstream;
114 using std::cerr;
115 using std::endl;
116 using std::vector;
117 using std::string;
118 
119 #ifdef _WIN32
120 #include "key.xpm"
121 #else
122 #include "key-new.xpm"
123 #endif
124 #include "save.xpm"
125 #include "upload.xpm"
126 #include "upload_dis.xpm"
127 #include "file_edit.xpm"
128 #include "file_edit_dis.xpm"
129 #include "loc_add.xpm"
130 #include "loc_add_dis.xpm"
131 #include "delete.xpm"
132 #include "delete_dis.xpm"
133 #include "edit.xpm"
134 #include "edit_dis.xpm"
135 #include "download.xpm"
136 #include "download_dis.xpm"
137 #include "properties.xpm"
138 #include "properties_dis.xpm"
139 #include "import.xpm"
140 #include "lotw.xpm"
141 
142 /// GEOMETRY
143 
144 #define LABEL_HEIGHT 20
145 
146 #define CERTLIST_FLAGS TQSL_SELECT_CERT_WITHKEYS | TQSL_SELECT_CERT_SUPERCEDED | TQSL_SELECT_CERT_EXPIRED
147 
148 static wxMenu *stn_menu;
149 
150 static wxString flattenCallSign(const wxString& call);
151 
152 static wxString ErrorTitle(_("TQSL Error"));
153 
154 static bool verify_cert(tQSL_Location loc, bool editing);
155 
156 static CURL* tqsl_curl_init(const char *logTitle, const char *url, FILE **curlLogFile, bool newFile);
157 
158 static wxString origCommandLine = wxT("");
159 static MyFrame *frame = 0;
160 
161 static char unipwd[64];
162 static bool quiet = false;
163 static bool verifyCA = true;
164 
165 static int lock_db(bool wait);
166 static void unlock_db(void);
167 int get_address_field(const char *callsign, const char *field, string& result);
168 
exitNow(int status,bool quiet)169 static void exitNow(int status, bool quiet) {
170 	const char *errors[] = { __("Success"),
171 				 __("User Cancelled"),
172 				 __("Upload Rejected"),
173 				 __("Unexpected LoTW Response"),
174 				 __("TQSL Error"),
175 				 __("TQSLLib Error"),
176 				 __("Error opening input file"),
177 				 __("Error opening output file"),
178 				 __("No QSOs written"),
179 				 __("Some QSOs suppressed"),
180 				 __("Command Syntax Error"),
181 				 __("LoTW Connection Failed"),
182 				 __("Unknown"),
183 				 __("The duplicates database is locked")
184 				};
185 	int stat = status;
186 	if (stat > TQSL_EXIT_UNKNOWN || stat < 0) stat = TQSL_EXIT_UNKNOWN;
187 	wxString msg = wxGetTranslation(wxString::FromUTF8(errors[stat]));
188 	char emsg[512];
189 	strncpy(emsg, msg.ToUTF8(), sizeof emsg);
190 	if (quiet)
191 		wxLogMessage(_("Final Status: %hs (%d)"), emsg, status);
192 	else
193 		cerr << "Final Status: " << emsg << "(" << status << ")" << endl;
194 	exit(status);
195 }
196 
197 /////////// Application //////////////
198 
199 class QSLApp : public wxApp {
200  public:
201 	QSLApp();
202 	virtual ~QSLApp();
203 	class MyFrame *GUIinit(bool checkUpdates, bool quiet = false);
204 	virtual bool OnInit();
205 	virtual int OnRun();
GetLang()206 	wxLanguage GetLang() {return lang; }
207 //	virtual wxLog *CreateLogTarget();
208  protected:
209 	wxLanguage lang;		// Language specified by user
210 	wxLocale* locale;		// Locale we're using
211 };
212 
~QSLApp()213 QSLApp::~QSLApp() {
214 	wxConfigBase *c = wxConfigBase::Set(0);
215 	if (c)
216 		delete c;
217 	tqsl_closeDiagFile();
218 }
219 
IMPLEMENT_APP(QSLApp)220 IMPLEMENT_APP(QSLApp)
221 
222 static int
223 getCertPassword(char *buf, int bufsiz, tQSL_Cert cert) {
224 	tqslTrace("getCertPassword", "buf = %lx, bufsiz=%d, cert=%lx", buf, bufsiz, cert);
225 	char call[TQSL_CALLSIGN_MAX+1] = "";
226 	int dxcc = 0;
227 	tqsl_getCertificateCallSign(cert, call, sizeof call);
228 	tqsl_getCertificateDXCCEntity(cert, &dxcc);
229 	DXCC dx;
230 	dx.getByEntity(dxcc);
231 
232 	// TRANSLATORS: this is followed by the callsign and entity name
233 	wxString fmt = _("Enter the password to unlock the callsign certificate for %hs -- %hs\n"
234 		"(This is the password you made up when you installed the callsign certificate.)");
235 	wxString message = wxString::Format(fmt, call, dx.name());
236 
237 	wxWindow* top = wxGetApp().GetTopWindow();
238 	if (frame->IsQuiet()) {
239 		frame->Show(true);
240 	}
241 	top->SetFocus();
242 	top->Raise();
243 
244 	wxString pwd;
245 	int ret = getPasswordFromUser(pwd, message, _("Enter password"), wxT(""), top);
246 	if (ret != wxID_OK)
247 		return 1;
248 	strncpy(buf, pwd.ToUTF8(), bufsiz);
249 	utf8_to_ucs2(buf, unipwd, sizeof unipwd);
250 	return 0;
251 }
252 
253 class ConvertingDialog : public wxDialog {
254  public:
255 	explicit ConvertingDialog(wxWindow *parent, const char *filename = "");
256 	void OnCancel(wxCommandEvent&);
257 	bool running;
258 	wxStaticText *msg;
259  private:
260 	wxButton *canbut;
261 
262 	DECLARE_EVENT_TABLE()
263 };
264 
BEGIN_EVENT_TABLE(ConvertingDialog,wxDialog)265 BEGIN_EVENT_TABLE(ConvertingDialog, wxDialog)
266 	EVT_BUTTON(TQSL_CD_CANBUT, ConvertingDialog::OnCancel)
267 END_EVENT_TABLE()
268 
269 void
270 ConvertingDialog::OnCancel(wxCommandEvent&) {
271 	running = false;
272 	canbut->Enable(FALSE);
273 }
274 
ConvertingDialog(wxWindow * parent,const char * filename)275 ConvertingDialog::ConvertingDialog(wxWindow *parent, const char *filename)
276 	: wxDialog(parent, -1, wxString(_("Signing QSO Data"))),
277 	running(true) {
278 	wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);
279 	wxString label = wxString(_("Converting ")) + wxString::FromUTF8(filename) + wxT(" ") + _("to TQSL format");
280 	sizer->Add(new wxStaticText(this, -1, label), 0, wxALL|wxALIGN_CENTER, 10);
281 	msg = new wxStaticText(this, TQSL_CD_MSG, wxT(" "));
282 	sizer->Add(msg, 0, wxALL|wxALIGN_LEFT, 10);
283 	canbut = new wxButton(this, TQSL_CD_CANBUT, _("Cancel"));
284 	sizer->Add(canbut, 0, wxALL|wxEXPAND, 10);
285 	SetAutoLayout(TRUE);
286 	SetSizer(sizer);
287 	sizer->Fit(this);
288 	sizer->SetSizeHints(this);
289 	CenterOnParent();
290 }
291 
292 #define TQSL_PROG_CANBUT TQSL_ID_LOW+30
293 
294 DECLARE_EVENT_TYPE(wxEVT_LOGUPLOAD_DONE, -1)
295 DEFINE_EVENT_TYPE(wxEVT_LOGUPLOAD_DONE)
296 
297 class UploadDialog : public wxDialog {
298  public:
299 	explicit UploadDialog(wxWindow *parent, wxString title = wxString(_("Uploading Signed Data")), wxString label = wxString(_("Uploading signed log data...")));
300 	void OnCancel(wxCommandEvent&);
301 	void OnDone(wxCommandEvent&);
302 	int doUpdateProgress(double dltotal, double dlnow, double ultotal, double ulnow);
UpdateProgress(void * clientp,double dltotal,double dlnow,double ultotal,double ulnow)303 	static int UpdateProgress(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow) {
304 		return (reinterpret_cast<UploadDialog*>(clientp))->doUpdateProgress(dltotal, dlnow, ultotal, ulnow);
305 	}
306  private:
307 	wxButton *canbut;
308 	wxGauge* progress;
309 	bool cancelled;
310 	DECLARE_EVENT_TABLE()
311 };
312 
BEGIN_EVENT_TABLE(UploadDialog,wxDialog)313 BEGIN_EVENT_TABLE(UploadDialog, wxDialog)
314 	EVT_BUTTON(TQSL_PROG_CANBUT, UploadDialog::OnCancel)
315 	EVT_COMMAND(wxID_ANY, wxEVT_LOGUPLOAD_DONE, UploadDialog::OnDone)
316 END_EVENT_TABLE()
317 
318 void
319 UploadDialog::OnCancel(wxCommandEvent&) {
320 	cancelled = true;
321 	canbut->Enable(false);
322 }
323 
UploadDialog(wxWindow * parent,wxString title,wxString label)324 UploadDialog::UploadDialog(wxWindow *parent, wxString title, wxString label)
325 	: wxDialog(parent, -1, title), cancelled(false) {
326 	tqslTrace("UploadDialog::UploadDialog", "parent = %lx", parent);
327 	wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);
328 	sizer->Add(new wxStaticText(this, -1, label), 0, wxALL|wxALIGN_CENTER, 10);
329 
330 	progress = new wxGauge(this, -1, 100);
331 	progress->SetValue(0);
332 	sizer->Add(progress, 0, wxALL|wxEXPAND);
333 
334 	canbut = new wxButton(this, TQSL_PROG_CANBUT, _("Cancel"));
335 	sizer->Add(canbut, 0, wxALL|wxEXPAND, 10);
336 	SetAutoLayout(TRUE);
337 	SetSizer(sizer);
338 	sizer->Fit(this);
339 	sizer->SetSizeHints(this);
340 	CenterOnParent();
341 }
342 
OnDone(wxCommandEvent &)343 void UploadDialog::OnDone(wxCommandEvent&) {
344 	tqslTrace("UploadDialog::OnDone", "Upload complete");
345 	EndModal(1);
346 }
347 
doUpdateProgress(double dltotal,double dlnow,double ultotal,double ulnow)348 int UploadDialog::doUpdateProgress(double dltotal, double dlnow, double ultotal, double ulnow) {
349 	static double lastDlnow = 0.0;
350 	if (dlnow != lastDlnow) {
351 		tqslTrace("UploadDialog::doUpdateProgresss", "dltotal=%f, dlnow=%f, ultotal=%f, ulnow=%f", dltotal, dlnow, ultotal, ulnow);
352 		lastDlnow = dlnow;
353 	}
354 	if (cancelled) return 1;
355 	if (ultotal > 0.0000001) progress->SetValue(static_cast<int>((100*(ulnow/ultotal))));
356 	return 0;
357 }
358 
359 #define TQSL_DR_START TQSL_ID_LOW+10
360 #define TQSL_DR_END TQSL_ID_LOW+11
361 #define TQSL_DR_OK TQSL_ID_LOW+12
362 #define TQSL_DR_CAN TQSL_ID_LOW+13
363 #define TQSL_DR_MSG TQSL_ID_LOW+14
364 
365 class DateRangeDialog : public wxDialog {
366  public:
367 	explicit DateRangeDialog(wxWindow *parent = 0);
368 	tQSL_Date start, end;
369  private:
370 	void OnOk(wxCommandEvent&);
371 	void OnCancel(wxCommandEvent&);
372 	virtual bool TransferDataFromWindow();
373 	wxTextCtrl *start_tc, *end_tc;
374 	wxStaticText *msg;
375 	DECLARE_EVENT_TABLE()
376 };
377 
BEGIN_EVENT_TABLE(DateRangeDialog,wxDialog)378 BEGIN_EVENT_TABLE(DateRangeDialog, wxDialog)
379 	EVT_BUTTON(TQSL_DR_OK, DateRangeDialog::OnOk)
380 	EVT_BUTTON(TQSL_DR_CAN, DateRangeDialog::OnCancel)
381 END_EVENT_TABLE()
382 
383 DateRangeDialog::DateRangeDialog(wxWindow *parent) : wxDialog(parent, -1, wxString(_("QSO Date Range"))) {
384 	tqslTrace("DateRangeDialog::DateRangeDialog", NULL);
385 	wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);
386 	wxString msgstr = _("You may set the starting and/or ending QSO dates "
387 		  "in order to select QSOs from the input file.");
388 		msgstr += wxT("\n\n");
389 		msgstr += _("QSOs prior to the starting date or after the ending "
390 		  "date will not be signed or included in the output file.");
391 		msgstr += wxT("\n\n");
392 		msgstr += _("You may leave either date (or both dates) blank.");
393 	wxSize sz = getTextSize(this);
394 	int em_w = sz.GetWidth();
395 	wxStaticText *st = new wxStaticText(this, -1, msgstr);
396 	st->Wrap(em_w * 30);
397 	sizer->Add(st, 0, wxALL|wxALIGN_CENTER, 10);
398 
399 	wxBoxSizer *hsizer = new wxBoxSizer(wxHORIZONTAL);
400 	hsizer->Add(new wxStaticText(this, -1, _("Start Date (YYYY-MM-DD)")), 0, wxRIGHT, 5);
401 	start_tc = new wxTextCtrl(this, TQSL_DR_START);
402 	hsizer->Add(start_tc, 0, 0, 0);
403 	sizer->Add(hsizer, 0, wxALL|wxALIGN_CENTER, 10);
404 	hsizer = new wxBoxSizer(wxHORIZONTAL);
405 	hsizer->Add(new wxStaticText(this, -1, _("End Date (YYYY-MM-DD)")), 0, wxRIGHT, 5);
406 	end_tc = new wxTextCtrl(this, TQSL_DR_END);
407 	hsizer->Add(end_tc, 0, 0, 0);
408 	sizer->Add(hsizer, 0, wxALL|wxALIGN_CENTER, 10);
409 	msg = new wxStaticText(this, TQSL_DR_MSG, wxT(""));
410 	sizer->Add(msg, 0, wxALL, 5);
411 	hsizer = new wxBoxSizer(wxHORIZONTAL);
412 	hsizer->Add(new wxButton(this, TQSL_DR_OK, _("OK")), 0, wxRIGHT, 5);
413 	hsizer->Add(new wxButton(this, TQSL_DR_CAN, _("Cancel")), 0, wxLEFT, 10);
414 	sizer->Add(hsizer, 0, wxALIGN_CENTER|wxALL, 10);
415 	SetAutoLayout(TRUE);
416 	SetSizer(sizer);
417 	sizer->Fit(this);
418 	sizer->SetSizeHints(this);
419 	CenterOnParent();
420 }
421 
422 bool
TransferDataFromWindow()423 DateRangeDialog::TransferDataFromWindow() {
424 	tqslTrace("DateRangeDialog::TransferDataFromWindow", NULL);
425 	wxString text = start_tc->GetValue();
426 	tqslTrace("DateRangeDialog::TransferDataFromWindow", "start=%s", S(text));
427 	if (text.Trim() == wxT("")) {
428 		start.year = start.month = start.day = 0;
429 	} else if (tqsl_initDate(&start, text.ToUTF8()) || !tqsl_isDateValid(&start)) {
430 		msg->SetLabel(_("Start date is invalid"));
431 		return false;
432 	}
433 	text = end_tc->GetValue();
434 	tqslTrace("DateRangeDialog::TransferDataFromWindow", "end=%s", S(text));
435 	if (text.Trim() == wxT("")) {
436 		end.year = end.month = end.day = 0;
437 	} else if (tqsl_initDate(&end, text.ToUTF8()) || !tqsl_isDateValid(&end)) {
438 		msg->SetLabel(_("End date is invalid"));
439 		return false;
440 	}
441 	return true;
442 }
443 
444 void
OnOk(wxCommandEvent &)445 DateRangeDialog::OnOk(wxCommandEvent&) {
446 	tqslTrace("DateRangeDialog::OnOk", NULL);
447 	if (TransferDataFromWindow())
448 		EndModal(wxOK);
449 }
450 
451 void
OnCancel(wxCommandEvent &)452 DateRangeDialog::OnCancel(wxCommandEvent&) {
453 	tqslTrace("DateRangeDialog::OnCancel", NULL);
454 	EndModal(wxCANCEL);
455 }
456 
457 #define TQSL_DP_OK TQSL_ID_LOW+20
458 #define TQSL_DP_CAN TQSL_ID_LOW+21
459 #define TQSL_DP_ALLOW TQSL_ID_LOW+22
460 
461 class DupesDialog : public wxDialog {
462  public:
463 	explicit DupesDialog(wxWindow *parent = 0, int qso_count = 0, int dupes = 0, int action = TQSL_ACTION_ASK);
464  private:
465 	void OnOk(wxCommandEvent&);
466 	void OnCancel(wxCommandEvent&);
467 	void OnAllow(wxCommandEvent&);
468 	DECLARE_EVENT_TABLE()
469 };
470 
BEGIN_EVENT_TABLE(DupesDialog,wxDialog)471 BEGIN_EVENT_TABLE(DupesDialog, wxDialog)
472 	EVT_BUTTON(TQSL_DP_OK, DupesDialog::OnOk)
473 	EVT_BUTTON(TQSL_DP_CAN, DupesDialog::OnCancel)
474 	EVT_BUTTON(TQSL_DP_ALLOW, DupesDialog::OnAllow)
475 END_EVENT_TABLE()
476 
477 DupesDialog::DupesDialog(wxWindow *parent, int qso_count, int dupes, int action)
478 		: wxDialog(parent, -1, wxString(_("Duplicate QSOs Detected")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE) {
479 	tqslTrace("DupesDialog::DupesDialog", "qso_count = %d, dupes =%d, action= =%d", qso_count, dupes, action);
480 	wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);
481 	wxString message;
482 
483 	if (qso_count == dupes) {
484 		wxString fmt = _("This log contains %d QSO(s) which appear "
485 		"to have already been signed for upload to LoTW, and no new QSOs.");
486 		fmt += wxT("\n\n");
487 		fmt += _("Click 'Cancel' to abandon processing this log file (Recommended).");
488 		fmt += wxT("\n");
489 		fmt += _("Click 'Allow Duplicates' to re-process this "
490 		"log while allowing duplicate QSOs.");
491 		message = wxString::Format(fmt, qso_count);
492 	} else {
493 		int newq = qso_count - dupes;
494 		wxString fmt = _("This log contains %d QSO(s) which appear "
495 			"to have already been signed for upload to LoTW, and "
496 			"%d QSOs which are new.");
497 			fmt += wxT("\n\n");
498 		  	fmt += _("Click 'Exclude duplicates' to sign normally, without the duplicate QSOs (Recommended).");
499 			fmt += wxT("\n");
500 			fmt += _("Click 'Cancel' to abandon processing this log file.");
501 			fmt += wxT("\n");
502 			fmt += _("Click 'Allow duplicates' to re-process this log "
503 			"while allowing duplicate QSOs.");
504 		wxString fmt1 = _("This log contains %d QSO(s) which appear "
505 			"to have already been signed for upload to LoTW, and "
506 			"one QSO which is new.");
507 			fmt1 += wxT("\n\n");
508 		  	fmt1 += _("Click 'Exclude duplicates' to sign normally, without the duplicate QSOs (Recommended).");
509 			fmt1 += wxT("\n");
510 			fmt1 += _("Click 'Cancel' to abandon processing this log file.");
511 			fmt1 += wxT("\n");
512 			fmt1 += _("Click 'Allow duplicates' to re-process this log "
513 			"while allowing duplicate QSOs.");
514 		if (newq == 1) {
515 			message = wxString::Format(fmt1, dupes);
516 		} else {
517 			message = wxString::Format(fmt, dupes, newq);
518 		}
519 	}
520 
521 	if (action == TQSL_ACTION_UNSPEC) {
522 		if (qso_count == dupes) {
523 			message+= wxT("\n\n");
524 			message += _("The log file you are uploading using your QSO Logging system consists entirely of previously uploaded "
525 				  "QSOs (duplicates) that create unnecessary work for LoTW. There may be a more recent version of your QSO "
526 				  "Logging system that would prevent this. Please check with your QSO Logging system's vendor for an updated version.");
527 			message += wxT("\n");
528 			message += _("In the meantime, please note that some loggers may exhibit strange behavior if an option other than 'Allow duplicates' "
529 				  "is clicked. Choosing 'Cancel' is usually safe, but a defective logger not checking the status messages reported by TrustedQSL may produce "
530 				  "strange (but harmless) behavior such as attempting to upload an empty file or marking all chosen QSOs as 'sent'");
531 		} else {
532 			message+= wxT("\n\n");
533 			message += _("The log file you are uploading using your QSO Logging system includes some previously uploaded "
534 				  "QSOs (duplicates) that create unnecessary work for LoTW. There may be a more recent version of your "
535 				  "QSO Logging system that would prevent this. Please check with your QSO Logging system's vendor for an updated version.");
536 			message += wxT("\n");
537 			message += _("In the meantime, please note that some loggers may exhibit strange behavior if an option other than 'Allow duplicates' "
538 				  "is clicked. 'Exclude duplicates' is recommended, but a logger that does its own duplicate tracking may incorrectly "
539 				  "set the status in this case. A logger that doesn't track duplicates should be unaffected by choosing 'Exclude duplicates' "
540 				  "and if it tracks 'QSO sent' status, will correctly mark all selected QSOs as sent - they are in your account even though "
541 				  "they would not be in this specific batch");
542 			message += wxT("\n");
543 			message += _("Choosing 'Cancel' is usually safe, but a defective logger not checking the status messages reported by TrustedQSL may produce "
544 				  "strange (but harmless) behavior such as attempting to upload an empty file or marking all chosen QSOs as 'sent'");
545 		}
546 	}
547 	wxStaticText* mtext = new wxStaticText(this, -1, message);
548 	sizer->Add(mtext, 0, wxALL|wxALIGN_CENTER, 10);
549 
550 	wxSize sz = getTextSize(this);
551 	int em_w = sz.GetWidth();
552 	mtext->Wrap(em_w * 50);
553 	wxBoxSizer *hsizer = new wxBoxSizer(wxHORIZONTAL);
554 	if (qso_count != dupes)
555 		hsizer->Add(new wxButton(this, TQSL_DP_OK, _("Exclude duplicates")), 0, wxRIGHT, 5);
556 	hsizer->Add(new wxButton(this, TQSL_DP_CAN, _("Cancel")), 0, wxLEFT, 10);
557 	hsizer->Add(new wxButton(this, TQSL_DP_ALLOW, _("Allow duplicates")), 0, wxLEFT, 20);
558 	sizer->Add(hsizer, 0, wxALIGN_CENTER|wxALL, 10);
559 	SetAutoLayout(TRUE);
560 	SetSizer(sizer);
561 	sizer->Fit(this);
562 	sizer->SetSizeHints(this);
563 	CenterOnParent();
564 }
565 
566 void
OnOk(wxCommandEvent &)567 DupesDialog::OnOk(wxCommandEvent&) {
568 	tqslTrace("DupesDialog::OnOk", NULL);
569 	EndModal(TQSL_DP_OK);
570 }
571 
572 void
OnCancel(wxCommandEvent &)573 DupesDialog::OnCancel(wxCommandEvent&) {
574 	tqslTrace("DupesDialog::OnCancel", NULL);
575 	EndModal(TQSL_DP_CAN);
576 }
577 
578 void
OnAllow(wxCommandEvent &)579 DupesDialog::OnAllow(wxCommandEvent&) {
580 	tqslTrace("DupesDialog::OnAllow", NULL);
581 
582 	wxString msg = _("The only reason to re-sign duplicate QSOs is if a previous upload "
583 		"was not processed by LoTW, either because it was never uploaded, or there was a server failure");
584 		msg += wxT("\n\n");
585 		msg += _("Are you sure you want to proceed? Click 'No' to review the choices");
586 	if (wxMessageBox(msg, _("Are you sure?"), wxYES_NO|wxICON_EXCLAMATION, this) == wxYES) {
587 		EndModal(TQSL_DP_ALLOW);
588 	}
589 }
590 
591 #define TQSL_AE_OK TQSL_ID_LOW+40
592 #define TQSL_AE_CAN TQSL_ID_LOW+41
593 
594 class ErrorsDialog : public wxDialog {
595  public:
596 	explicit ErrorsDialog(wxWindow *parent = 0, wxString msg = wxT(""));
597  private:
598 	void OnOk(wxCommandEvent&);
599 	void OnCancel(wxCommandEvent&);
600 	DECLARE_EVENT_TABLE()
601 };
602 
BEGIN_EVENT_TABLE(ErrorsDialog,wxDialog)603 BEGIN_EVENT_TABLE(ErrorsDialog, wxDialog)
604 	EVT_BUTTON(TQSL_AE_OK, ErrorsDialog::OnOk)
605 	EVT_BUTTON(TQSL_AE_CAN, ErrorsDialog::OnCancel)
606 END_EVENT_TABLE()
607 
608 ErrorsDialog::ErrorsDialog(wxWindow *parent, wxString msg)
609 		: wxDialog(parent, -1, wxString(_("Errors Detected")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE) {
610 	tqslTrace("ErrorsDialog::ErrorsDialog", "msg=%s", S(msg));
611 	wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);
612 
613 	sizer->Add(new wxStaticText(this, -1, msg), 0, wxALL|wxALIGN_CENTER, 10);
614 
615 	wxBoxSizer *hsizer = new wxBoxSizer(wxHORIZONTAL);
616 	hsizer->Add(new wxButton(this, TQSL_AE_OK, _("Ignore")), 0, wxRIGHT, 5);
617 	hsizer->Add(new wxButton(this, TQSL_AE_CAN, _("Cancel")), 0, wxLEFT, 10);
618 	sizer->Add(hsizer, 0, wxALIGN_CENTER|wxALL, 10);
619 	SetAutoLayout(TRUE);
620 	SetSizer(sizer);
621 	sizer->Fit(this);
622 	sizer->SetSizeHints(this);
623 	CenterOnParent();
624 }
625 
626 void
OnOk(wxCommandEvent &)627 ErrorsDialog::OnOk(wxCommandEvent&) {
628 	tqslTrace("ErrorsDialog::OnOk", NULL);
629 	EndModal(TQSL_AE_OK);
630 }
631 
632 void
OnCancel(wxCommandEvent &)633 ErrorsDialog::OnCancel(wxCommandEvent&) {
634 	tqslTrace("ErrorsDialog::OnCancel", NULL);
635 	EndModal(TQSL_AE_CAN);
636 }
637 
638 static void
init_modes()639 init_modes() {
640 	tqslTrace("init_modes", NULL);
641 	tqsl_clearADIFModes();
642 	wxConfig *config = reinterpret_cast<wxConfig *>(wxConfig::Get());
643 	long cookie;
644 	wxString key, value;
645 	vector<wxString> badModes;
646 	config->SetPath(wxT("/modeMap"));
647 	bool stat = config->GetFirstEntry(key, cookie);
648 	while (stat) {
649 		value = config->Read(key, wxT(""));
650 		bool newMode = true;
651 		int numModes;
652 		if (tqsl_getNumMode(&numModes) == 0) {
653 			for (int i = 0; i < numModes; i++) {
654 				const char *modestr;
655 				if (tqsl_getMode(i, &modestr, NULL) == 0) {
656 					if (strcasecmp(key.ToUTF8(), modestr) == 0) {
657 						wxLogWarning(_("Your custom mode map %s conflicts with the standard mode definition for %hs and was deleted."), key.c_str(), modestr);
658 						newMode = false;
659 						badModes.push_back(key);
660 						break;
661 					}
662 				}
663 			}
664 		}
665 		if (newMode)
666 			tqsl_setADIFMode(key.ToUTF8(), value.ToUTF8());
667 		stat = config->GetNextEntry(key, cookie);
668 	}
669 	// Delete the conflicting entries
670 	for (int i = 0; i < static_cast<int>(badModes.size()); i++) {
671 		config->DeleteEntry(badModes[i]);
672 	}
673 	config->SetPath(wxT("/"));
674 }
675 
676 static void
init_contests()677 init_contests() {
678 	tqslTrace("init_contests", NULL);
679 	tqsl_clearCabrilloMap();
680 	wxConfig *config = reinterpret_cast<wxConfig *>(wxConfig::Get());
681 	long cookie;
682 	wxString key, value;
683 	config->SetPath(wxT("/cabrilloMap"));
684 	bool stat = config->GetFirstEntry(key, cookie);
685 	while (stat) {
686 		value = config->Read(key, wxT(""));
687 		int contest_type = strtol(value.ToUTF8(), NULL, 10);
688 		int callsign_field = strtol(value.AfterFirst(';').ToUTF8(), NULL, 10);
689 		tqsl_setCabrilloMapEntry(key.ToUTF8(), callsign_field, contest_type);
690 		stat = config->GetNextEntry(key, cookie);
691 	}
692 	config->SetPath(wxT("/"));
693 }
694 
695 static void
check_tqsl_error(int rval)696 check_tqsl_error(int rval) {
697 	if (rval == 0)
698 		return;
699 	tqslTrace("check_tqsl_error", "rval=%d", rval);
700 	wxString msg = getLocalizedErrorString();
701 	tqslTrace("check_tqsl_error", "msg=%s", S(msg));
702 	throw TQSLException(S(msg));
703 }
704 
705 static tQSL_Cert *certlist = 0;
706 static int ncerts;
707 
708 static void
free_certlist()709 free_certlist() {
710 	tqslTrace("free_certlist", NULL);
711 	if (certlist) {
712 		tqsl_freeCertificateList(certlist, ncerts);
713 		certlist = 0;
714 	}
715 	ncerts = 0;
716 }
717 
718 static void
get_certlist(string callsign,int dxcc,bool expired,bool superceded,bool withkeys)719 get_certlist(string callsign, int dxcc, bool expired, bool superceded, bool withkeys) {
720 	tqslTrace("get_certlist", "callsign=%s, dxcc=%d, expired=%d, superceded=%d withkeys=%d", callsign.c_str(), dxcc, expired, superceded, withkeys);
721 	free_certlist();
722 	int select = 0;
723 	if (expired) select |= TQSL_SELECT_CERT_EXPIRED;
724 	if (superceded) select |= TQSL_SELECT_CERT_SUPERCEDED;
725 	if (withkeys) select |= TQSL_SELECT_CERT_WITHKEYS;
726 	tqsl_selectCertificates(&certlist, &ncerts,
727 		(callsign == "") ? 0 : callsign.c_str(), dxcc, 0, 0, select);
728 }
729 
730 class LogList : public wxLog {
731  public:
LogList(MyFrame * frame)732 	explicit LogList(MyFrame *frame) : wxLog(), _frame(frame) {}
733 	virtual void DoLogString(const wxChar *szString, time_t t);
734  private:
735 	MyFrame *_frame;
736 };
737 
DoLogString(const wxChar * szString,time_t)738 void LogList::DoLogString(const wxChar *szString, time_t) {
739 	wxTextCtrl *_logwin = 0;
740 	const char *msg = wxString(szString).ToUTF8();
741 	tqslTrace(NULL, "%s", msg);
742 	if (wxString(szString).StartsWith(wxT("Debug:")))
743 		return;
744 	if (wxString(szString).StartsWith(wxT("Error: Unable to open requested HTML document:")))
745 		return;
746 	if (_frame != 0)
747 		_logwin = _frame->logwin;
748 	if (_logwin == 0) {
749 #ifdef _WIN32
750 		cerr << szString << endl;
751 #else
752 		fprintf(stderr, "%ls\n", szString);
753 #endif
754 		return;
755 	}
756 	_logwin->AppendText(szString);
757 	_logwin->AppendText(wxT("\n"));
758 	// Select the log tab when there's some new message
759 	if (_frame->notebook->GetPageCount() > TQSL_LOG_TAB)
760 		_frame->notebook->SetSelection(TQSL_LOG_TAB);
761 }
762 
763 class LogStderr : public wxLog {
764  public:
LogStderr(void)765 	LogStderr(void) : wxLog() {}
766 	virtual void DoLogString(const wxChar *szString, time_t t);
767 };
768 
DoLogString(const wxChar * szString,time_t)769 void LogStderr::DoLogString(const wxChar *szString, time_t) {
770 	const char *msg = wxString(szString).ToUTF8();
771 	tqslTrace(NULL, "%s", msg);
772 	if (wxString(szString).StartsWith(wxT("Debug:")))
773 		return;
774 #ifdef _WIN32
775 	cerr << szString << endl;
776 #else
777 	fprintf(stderr, "%ls\n", szString);
778 #endif
779 	return;
780 }
781 
BEGIN_EVENT_TABLE(MyFrame,wxFrame)782 BEGIN_EVENT_TABLE(MyFrame, wxFrame)
783 	EVT_MENU(tm_s_add, MyFrame::AddStationLocation)
784 	EVT_BUTTON(tl_AddLoc, MyFrame::AddStationLocation)
785 	EVT_MENU(tm_s_edit, MyFrame::EditStationLocation)
786 	EVT_BUTTON(tl_EditLoc, MyFrame::EditStationLocation)
787 	EVT_MENU(tm_f_new, MyFrame::EnterQSOData)
788 	EVT_BUTTON(tl_Edit, MyFrame::EnterQSOData)
789 	EVT_MENU(tm_f_edit, MyFrame::EditQSOData)
790 	EVT_MENU(tm_f_import_compress, MyFrame::ImportQSODataFile)
791 	EVT_BUTTON(tl_Save, MyFrame::ImportQSODataFile)
792 	EVT_MENU(tm_f_upload, MyFrame::UploadQSODataFile)
793 	EVT_BUTTON(tl_Upload, MyFrame::UploadQSODataFile)
794 	EVT_MENU(tm_f_exit, MyFrame::DoExit)
795 	EVT_MENU(tm_f_preferences, MyFrame::OnPreferences)
796 	EVT_MENU(tm_f_loadconfig, MyFrame::OnLoadConfig)
797 	EVT_MENU(tm_f_saveconfig, MyFrame::OnSaveConfig)
798 	EVT_MENU(tm_h_contents, MyFrame::OnHelpContents)
799 	EVT_MENU(tm_h_about, MyFrame::OnHelpAbout)
800 	EVT_MENU(tm_f_diag, MyFrame::OnHelpDiagnose)
801 	EVT_MENU(tm_f_lang, MyFrame::OnChooseLanguage)
802 
803 	EVT_MENU(tm_h_update, MyFrame::CheckForUpdates)
804 
805 	EVT_CLOSE(MyFrame::OnExit)
806 
807 	EVT_MENU(tc_CRQWizard, MyFrame::CRQWizard)
808 	EVT_MENU(tc_c_New, MyFrame::CRQWizard)
809 	EVT_MENU(tc_c_Load, MyFrame::OnLoadCertificateFile)
810 	EVT_BUTTON(tc_Load, MyFrame::OnLoadCertificateFile)
811 	EVT_MENU(tc_c_Properties, MyFrame::OnCertProperties)
812 	EVT_BUTTON(tc_CertProp, MyFrame::OnCertProperties)
813 	EVT_MENU(tc_c_Export, MyFrame::OnCertExport)
814 	EVT_BUTTON(tc_CertSave, MyFrame::OnCertExport)
815 	EVT_MENU(tc_c_Delete, MyFrame::OnCertDelete)
816 	EVT_MENU(tc_c_Undelete, MyFrame::OnCertUndelete)
817 //	EVT_MENU(tc_c_Import, MyFrame::OnCertImport)
818 //	EVT_MENU(tc_c_Sign, MyFrame::OnSign)
819 	EVT_MENU(tc_c_Renew, MyFrame::CRQWizardRenew)
820 	EVT_BUTTON(tc_CertRenew, MyFrame::CRQWizardRenew)
821 	EVT_MENU(tc_h_Contents, MyFrame::OnHelpContents)
822 	EVT_MENU(tc_h_About, MyFrame::OnHelpAbout)
823 	EVT_MENU(tl_c_Properties, MyFrame::OnLocProperties)
824 	EVT_MENU(tm_s_Properties, MyFrame::OnLocProperties)
825 	EVT_MENU(tm_s_undelete, MyFrame::OnLocUndelete)
826 	EVT_BUTTON(tl_PropLoc, MyFrame::OnLocProperties)
827 	EVT_MENU(tl_c_Delete, MyFrame::OnLocDelete)
828 	EVT_BUTTON(tl_DeleteLoc, MyFrame::OnLocDelete)
829 	EVT_MENU(tl_c_Edit, MyFrame::OnLocEdit)
830 	EVT_BUTTON(tl_Login, MyFrame::OnLoginToLogbook)
831 	EVT_TREE_SEL_CHANGED(tc_CertTree, MyFrame::OnCertTreeSel)
832 	EVT_TREE_SEL_CHANGED(tc_LocTree, MyFrame::OnLocTreeSel)
833 
834 	EVT_MENU(bg_updateCheck, MyFrame::OnUpdateCheckDone)
835 	EVT_MENU(bg_expiring, MyFrame::OnExpiredCertFound)
836 
837 END_EVENT_TABLE()
838 
839 void
840 MyFrame::SaveOldBackups(const wxString& directory, const wxString& filename, const wxString& ext) {
841 #ifdef _WIN32
842 	wxString bfile = directory + wxT("\\") + filename + wxT(".") + ext;
843 	struct _stat32 s;
844 	wchar_t* lfn = utf8_to_wchar(bfile.ToUTF8());
845 	int ret = _wstat32(lfn, &s);
846 	free_wchar(lfn);
847 	if (ret != 0) {					// Does it exist?
848 #else
849 	wxString bfile = directory + wxT("/") + filename + wxT(".") + ext;
850 	struct stat s;
851 	if (lstat(bfile.ToUTF8(), &s) != 0) {		// Does it exist?
852 #endif
853 		return;					// No. No need to back it up.
854 	}
855 
856 	// There's a file with that name already.
857 	// Rename it for backup purposes
858 
859 	struct tm *t;
860 
861 #ifdef _WIN32
862 	t = _gmtime32(&s.st_mtime);
863 	wxString newName = directory + wxT("\\") + filename +
864 #else
865 	t = gmtime(&s.st_mtime);
866 	wxString newName = directory + wxT("/") + filename +
867 #endif
868 		wxString::Format(wxT("-%4.4d-%2.2d-%2.2d-%2.2d-%2.2d."),
869 					t->tm_year+1900, t->tm_mon+1, t->tm_mday,
870 					t->tm_hour, t->tm_min) + ext;
871 #ifdef _WIN32
872 	lfn = utf8_to_wchar(bfile.ToUTF8());
873 	wchar_t* newlfn = utf8_to_wchar(newName.ToUTF8());
874 	ret = _wrename(lfn, newlfn);
875 	free_wchar(lfn);
876 	free_wchar(newlfn);
877 #else
878 	int ret = rename(bfile.ToUTF8(), newName.ToUTF8());
879 #endif
880 	if (ret) {
881 		tQSL_Error = TQSL_SYSTEM_ERROR;
882 		tQSL_Errno = errno;
883 		tqslTrace("MyFrame::SaveOldBackups", "Error renaming: %s", strerror(errno));
884 		wxLogError(_("Error renaming backup file %s: %hs"), bfile.c_str(), strerror(errno));
885 		return;
886 	}
887 
888 	// Find any backups and delete older ones
889 
890 	wxArrayString bfiles;
891 
892 #ifdef _WIN32
893 	wchar_t* wpath = utf8_to_wchar(directory);
894 	_WDIR *dir = _wopendir(wpath);
895 	free_wchar(wpath);
896 #else
897 	DIR *dir = opendir(directory.ToUTF8());
898 #endif
899 
900 	if (dir != NULL) {
901 #ifdef _WIN32
902 		struct _wdirent *ent = NULL;
903 		while ((ent = _wreaddir(dir)) != NULL) {
904 			if (wcsstr(ent->d_name, L"tqslconfig-") == ent->d_name && wcsstr(ent->d_name, L".tbk")) {
905 				bfiles.Add(wxString(ent->d_name));
906 			}
907 		}
908 #else
909 		struct dirent *ent = NULL;
910 		while ((ent = readdir(dir)) != NULL) {
911 			if (strstr(ent->d_name, "tqslconfig-") == ent->d_name && strstr(ent->d_name, ".tbk")) {
912 				bfiles.Add(wxString::FromUTF8(ent->d_name));
913 			}
914 		}
915 #endif
916 	}
917 	bfiles.Sort();
918 	long vlimit = DEFAULT_BACKUP_VERSIONS;
919 	wxConfig::Get()->Read(wxT("BackupVersions"), &vlimit, DEFAULT_BACKUP_VERSIONS);
920 
921 	if (vlimit <= 0)
922 		vlimit = DEFAULT_BACKUP_VERSIONS;
923 	int toRemove = bfiles.GetCount() - vlimit;
924 	if (toRemove <= 0)
925 		return;			// Nothing to remove
926 
927 	// Remove, starting from the oldest
928 	for (int i = 0; i < toRemove; i++) {
929 #ifdef _WIN32
930 		wxString removeIt = directory + wxT("\\") + bfiles[i];
931 		wchar_t* wfname = utf8_to_wchar(removeIt.ToUTF8());
932 		_wunlink(wfname);
933 		free_wchar(wfname);
934 #else
935 		wxString removeIt = directory + wxT("/") + bfiles[i];
936 		unlink(removeIt.ToUTF8());
937 #endif
938 	}
939 }
940 
941 void
942 MyFrame::SaveWindowLayout() {
943 	int x, y, w, h;
944 	// Don't save window size/position if minimized or too small
945 	wxConfig *config = reinterpret_cast<wxConfig *>(wxConfig::Get());
946 	if (!IsIconized()) {
947 		GetPosition(&x, &y);
948 		GetSize(&w, &h);
949 		if (w >= MAIN_WINDOW_MIN_WIDTH && h >= MAIN_WINDOW_MIN_HEIGHT) {
950 			config->Write(wxT("MainWindowX"), x);
951 			config->Write(wxT("MainWindowY"), y);
952 			config->Write(wxT("MainWindowWidth"), w);
953 			config->Write(wxT("MainWindowHeight"), h);
954 			config->Write(wxT("MainWindowMaximized"), IsMaximized());
955 			config->Flush(false);
956 		}
957 	}
958 }
959 void
960 MyFrame::OnExit(TQ_WXCLOSEEVENT& WXUNUSED(event)) {
961 	tqslTrace("MyFrame::OnExit", "exiting");
962 	if (logConv) {
963 		tqsl_converterRollBack(logConv);
964 		tqsl_endConverter(&logConv);
965 	}
966 	SaveWindowLayout();
967 	unlock_db();
968 	wxConfig *config = reinterpret_cast<wxConfig *>(wxConfig::Get());
969 	bool ab;
970 	config->Read(wxT("AutoBackup"), &ab, DEFAULT_AUTO_BACKUP);
971 	if (ab) {
972 		wxString bdir = config->Read(wxT("BackupFolder"), wxString::FromUTF8(tQSL_BaseDir));
973 		if (bdir.Trim(true).Trim(false) == wxT(""))
974 			bdir = wxString::FromUTF8(tQSL_BaseDir);
975 		SaveOldBackups(bdir, wxT("tqslconfig"), wxT("tbk"));
976 #ifdef _WIN32
977 		bdir += wxT("\\tqslconfig.tbk");
978 #else
979 		bdir += wxT("/tqslconfig.tbk");
980 #endif
981 		tqslTrace("MyFrame::OnExit", "Auto Backup %s", S(bdir));
982 		BackupConfig(bdir, true);
983 	}
984 	tqslTrace("MyFrame::OnExit", "GUI Destroy");
985 	Destroy();		// close the window
986 	tqslTrace("MyFrame::OnExit", "Done");
987 }
988 
989 void
990 MyFrame::DoExit(wxCommandEvent& WXUNUSED(event)) {
991 	tqslTrace("MyFrame::DoExit", "About to close");
992 	Close();
993 	tqslTrace("MyFrame::DoExit", "About to destroy GUI");
994 	Destroy();
995 	tqslTrace("MyFrame::DoExit", "Done");
996 }
997 
998 static wxSemaphore *updateLocker;
999 
1000 class UpdateThread : public wxThread {
1001  public:
1002 	UpdateThread(MyFrame *frame, bool silent, bool noGUI) : wxThread(wxTHREAD_DETACHED) {
1003 		_frame = frame;
1004 		_silent = silent;
1005 		_noGUI = noGUI;
1006 	}
1007 	virtual void *Entry() {
1008 		_frame->DoCheckForUpdates(_silent, _noGUI);
1009 		updateLocker->Post();
1010 		return NULL;
1011 	}
1012  private:
1013 	MyFrame *_frame;
1014 	bool _silent;
1015 	bool _noGUI;
1016 };
1017 
1018 void
1019 MyFrame::DoUpdateCheck(bool silent, bool noGUI) {
1020 	tqslTrace("MyFrame::DoUpdateCheck", "silent=%d noGUI=%d", silent, noGUI);
1021 	//check for updates
1022 	if (!noGUI) {
1023 		wxBeginBusyCursor();
1024 		wxTheApp->Yield(true);
1025 		wxLogMessage(_("Checking for TQSL updates..."));
1026 		wxTheApp->Yield(true);
1027 	}
1028 	updateLocker = new wxSemaphore(0, 1);		// One waiter
1029 	UpdateThread* thread = new UpdateThread(this, silent, noGUI);
1030 	thread->Create();
1031 	wxTheApp->Yield(true);
1032 	thread->Run();
1033 	wxTheApp->Yield(true);
1034 	while (updateLocker->TryWait() == wxSEMA_BUSY) {
1035 		wxTheApp->Yield(true);
1036 #if (wxMAJOR_VERSION == 2 && wxMINOR_VERSION == 9)
1037 		wxGetApp().DoIdle();
1038 #endif
1039 		wxMilliSleep(300);
1040 	}
1041 	delete updateLocker;
1042 	if (!noGUI) {
1043 		wxString val = logwin->GetValue();
1044 		wxString chk(_("Checking for TQSL updates..."));
1045 		val.Replace(chk + wxT("\n"), wxT(""));
1046 		val.Replace(chk, wxT(""));
1047 		logwin->SetValue(val);		// Clear the checking message
1048 		notebook->SetSelection(0);
1049 		// Refresh the cert tree in case any new info on expires/supercedes
1050 		cert_tree->Build(CERTLIST_FLAGS);
1051 		CertTreeReset();
1052 		wxEndBusyCursor();
1053 	}
1054 }
1055 
1056 MyFrame::MyFrame(const wxString& title, int x, int y, int w, int h, bool checkUpdates, bool quiet, wxLocale* loca)
1057 	: wxFrame(0, -1, title, wxPoint(x, y), wxSize(w, h)), locale(loca) {
1058 	_quiet = quiet;
1059 
1060 #ifdef __WXMAC__
1061 	DocPaths docpaths(wxT("tqsl.app"));
1062 #else
1063 	DocPaths docpaths(wxT("tqslapp"));
1064 #endif
1065 	wxBitmap savebm(save_xpm);
1066 	wxBitmap uploadbm(upload_xpm);
1067 	wxBitmap upload_disbm(upload_dis_xpm);
1068 	wxBitmap file_editbm(file_edit_xpm);
1069 	wxBitmap file_edit_disbm(file_edit_dis_xpm);
1070 	wxBitmap locaddbm(loc_add_xpm);
1071 	wxBitmap locadd_disbm(loc_add_dis_xpm);
1072 	wxBitmap editbm(edit_xpm);
1073 	wxBitmap edit_disbm(edit_dis_xpm);
1074 	wxBitmap deletebm(delete_xpm);
1075 	wxBitmap delete_disbm(delete_dis_xpm);
1076 	wxBitmap downloadbm(download_xpm);
1077 	wxBitmap download_disbm(download_dis_xpm);
1078 	wxBitmap propertiesbm(properties_xpm);
1079 	wxBitmap properties_disbm(properties_dis_xpm);
1080 	wxBitmap importbm(import_xpm);
1081 	wxBitmap lotwbm(lotw_xpm);
1082 	loc_edit_button = NULL;
1083 	cert_save_label = NULL;
1084 	req = NULL;
1085 	curlReq = NULL;
1086 	curlLogFile = NULL;
1087 	logConv = NULL;
1088 
1089 	// File menu
1090 	file_menu = new wxMenu;
1091 	file_menu->Append(tm_f_upload, _("Sign and &upload ADIF or Cabrillo File..."));
1092 	file_menu->Append(tm_f_import_compress, _("&Sign and save ADIF or Cabrillo file..."));
1093 	file_menu->AppendSeparator();
1094 	file_menu->Append(tm_f_saveconfig, _("&Backup Station Locations, Certificates, and Preferences..."));
1095 	file_menu->Append(tm_f_loadconfig, _("&Restore Station Locations, Certificates, and Preferences..."));
1096 	file_menu->AppendSeparator();
1097 	file_menu->Append(tm_f_new, _("Create &New ADIF file..."));
1098 	file_menu->Append(tm_f_edit, _("&Edit existing ADIF file..."));
1099 	file_menu->AppendSeparator();
1100 #ifdef __WXMAC__	// On Mac, Preferences not on File menu
1101 	file_menu->Append(tm_f_preferences, _("&Preferences..."));
1102 #else
1103 	file_menu->Append(tm_f_preferences, _("Display or Modify &Preferences..."));
1104 #endif
1105 	file_menu->AppendSeparator();
1106 	file_menu->Append(tm_f_lang, _("Language"));
1107 	file_menu->AppendSeparator();
1108 	file_menu->AppendCheckItem(tm_f_diag, _("Dia&gnostic Mode"));
1109 	file_menu->Check(tm_f_diag, false);
1110 #ifndef __WXMAC__	// On Mac, Exit not on File menu
1111 	file_menu->AppendSeparator();
1112 #endif
1113 	file_menu->Append(tm_f_exit, _("E&xit TQSL\tAlt-X"));
1114 
1115 	cert_menu = makeCertificateMenu(false);
1116 	// Station menu
1117 	stn_menu = new wxMenu;
1118 	stn_menu->Append(tm_s_Properties, _("&Display Station Location Properties"));
1119 	stn_menu->Enable(tm_s_Properties, false);
1120 	stn_menu->Append(tm_s_edit, _("&Edit Station Location"));
1121 	stn_menu->Append(tm_s_add, _("&Add Station Location"));
1122 	stn_menu->AppendSeparator();
1123 
1124 	int nloc = 0;
1125 	char **locp = NULL;
1126 	tqsl_getDeletedStationLocations(&locp, &nloc);
1127 	stn_menu->Append(tm_s_undelete, _("&Restore a Deleted Station Location"));
1128 	stn_menu->Enable(tm_s_undelete, nloc > 0);
1129 	if (nloc > 0)
1130 		tqsl_freeDeletedLocationList(locp, nloc);
1131 
1132 	// Help menu
1133 	help = new wxHtmlHelpController(wxHF_DEFAULT_STYLE | wxHF_OPEN_FILES);
1134 	help_menu = new wxMenu;
1135 	help->UseConfig(wxConfig::Get());
1136 	wxString hhp = docpaths.FindAbsoluteValidPath(wxT("tqslapp.hhp"));
1137 	if (wxFileNameFromPath(hhp) != wxT("")) {
1138 		if (help->AddBook(hhp))
1139 #ifdef __WXMAC__
1140 		help_menu->Append(tm_h_contents, _("&Contents"));
1141 #else
1142 		help_menu->Append(tm_h_contents, _("Display &Documentation"));
1143 		help_menu->AppendSeparator();
1144 #endif
1145 	}
1146 
1147 	help_menu->Append(tm_h_update, _("Check for &Updates..."));
1148 
1149 	help_menu->Append(tm_h_about, _("&About"));
1150 	// Main menu
1151 	wxMenuBar *menu_bar = new wxMenuBar;
1152 	menu_bar->Append(file_menu, _("&File"));
1153 	menu_bar->Append(stn_menu, _("&Station Location"));
1154 	menu_bar->Append(cert_menu, _("Callsign &Certificate"));
1155 	menu_bar->Append(help_menu, _("&Help"));
1156 
1157 	SetMenuBar(menu_bar);
1158 
1159 	wxPanel* topPanel = new wxPanel(this);
1160 	wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL);
1161 	topPanel->SetSizer(topSizer);
1162 
1163 	bool logTab = DEFAULT_LOG_TAB;
1164 	wxConfig::Get()->Read(wxT("LogTab"), &logTab, DEFAULT_LOG_TAB);
1165 
1166 	// Log operations
1167 
1168 	topSizer->AddSpacer(2);
1169 
1170 	notebook = new wxNotebook(topPanel, -1, wxDefaultPosition, wxSize(400, 300), wxNB_TOP /* | wxNB_FIXEDWIDTH*/, _("Log Operations"));
1171 
1172 	notebook->SetBackgroundColour(wxColour(255, 255, 255));
1173 	topSizer->Add(notebook, 1, wxEXPAND | wxALL, 1);
1174 
1175 	if (!logTab) {
1176 		topSizer->Add(new wxStaticText(topPanel, -1, _("Status Log")), 0, wxEXPAND | wxALL, 1);
1177 
1178 		logwin = new wxTextCtrl(topPanel, -1, wxT(""), wxDefaultPosition, wxSize(400, 300),
1179 			wxTE_MULTILINE|wxTE_READONLY);
1180 		topSizer->Add(logwin, 1, wxEXPAND | wxALL, 1);
1181 	}
1182 
1183 	wxPanel* buttons = new wxPanel(notebook, -1);
1184 	buttons->SetBackgroundColour(wxColour(255, 255, 255));
1185 
1186 	wxBoxSizer* bsizer = new wxBoxSizer(wxVERTICAL);
1187 	buttons->SetSizer(bsizer);
1188 
1189 	wxPanel* b1Panel = new wxPanel(buttons);
1190 	b1Panel->SetBackgroundColour(wxColour(255, 255, 255));
1191 	wxBoxSizer* b1sizer = new wxBoxSizer(wxHORIZONTAL);
1192 	b1Panel->SetSizer(b1sizer);
1193 
1194 	wxBitmapButton *up = new wxBitmapButton(b1Panel, tl_Upload, uploadbm);
1195 	// Use a really tiny label font on the buttons, as the labels are there
1196 	// for accessibility only.
1197 	wxFont f(1, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
1198 	up->SetLabel(_("Sign a log and upload it automatically to LoTW"));
1199 	up->SetBitmapDisabled(upload_disbm);
1200 	up->SetFont(f);
1201 	b1sizer->Add(up, 0, wxALL, 1);
1202 	wxString b1lbl = wxT("\n");
1203 	b1lbl += _("Sign a log and upload it automatically to LoTW");
1204 	b1sizer->Add(new wxStaticText(b1Panel, -1, b1lbl), 1, wxALL, 1);
1205 	bsizer->Add(b1Panel, 0, wxALL, 1);
1206 
1207 	wxPanel* b2Panel = new wxPanel(buttons);
1208 	wxBoxSizer* b2sizer = new wxBoxSizer(wxHORIZONTAL);
1209 	b2Panel->SetBackgroundColour(wxColour(255, 255, 255));
1210 	b2Panel->SetSizer(b2sizer);
1211 	wxBitmapButton *signsave = new wxBitmapButton(b2Panel, tl_Save, savebm);
1212 	signsave->SetLabel(_("Sign a log and save it for uploading later"));
1213 	signsave->SetFont(f);
1214 	b2sizer->Add(signsave, 0, wxALL, 1);
1215 	wxString b2lbl = wxT("\n");
1216 	b2lbl += _("Sign a log and save it for uploading later");
1217 	b2sizer->Add(new wxStaticText(b2Panel, -1, b2lbl), 1, wxALL, 1);
1218 	bsizer->Add(b2Panel, 0, wxALL, 1);
1219 
1220 	wxPanel* b3Panel = new wxPanel(buttons);
1221 	wxBoxSizer* b3sizer = new wxBoxSizer(wxHORIZONTAL);
1222 	b3Panel->SetBackgroundColour(wxColour(255, 255, 255));
1223 	b3Panel->SetSizer(b3sizer);
1224 	wxBitmapButton *fed = new wxBitmapButton(b3Panel, tl_Edit, file_editbm);
1225 	fed->SetBitmapDisabled(file_edit_disbm);
1226 	fed->SetLabel(_("Create an ADIF file for signing and uploading"));
1227 	fed->SetFont(f);
1228 	b3sizer->Add(fed, 0, wxALL, 1);
1229 	wxString b3lbl = wxT("\n");
1230 	b3lbl += _("Create an ADIF file for signing and uploading");
1231 	b3sizer->Add(new wxStaticText(b3Panel, -1, b3lbl), 1, wxALL, 1);
1232 	bsizer->Add(b3Panel, 0, wxALL, 1);
1233 
1234 	wxPanel* b4Panel = new wxPanel(buttons);
1235 	wxBoxSizer* b4sizer = new wxBoxSizer(wxHORIZONTAL);
1236 	b4Panel->SetBackgroundColour(wxColour(255, 255, 255));
1237 	b4Panel->SetSizer(b4sizer);
1238 	wxBitmapButton *lotw = new wxBitmapButton(b4Panel, tl_Login, lotwbm);
1239 	lotw->SetLabel(_("Log in to the Logbook of the World Site"));
1240 	lotw->SetFont(f);
1241 	b4sizer->Add(lotw, 0, wxALL, 1);
1242 	wxString b4lbl = wxT("\n");
1243 	b4lbl += _("Log in to the Logbook of the World Site");
1244 	b4sizer->Add(new wxStaticText(b4Panel, -1, b4lbl), 1, wxALL, 1);
1245 	bsizer->Add(b4Panel, 0, wxALL, 1);
1246 
1247 	notebook->AddPage(buttons, _("Log Operations"));
1248 
1249 //	notebook->InvalidateBestSize();
1250 //	logwin->FitInside();
1251 
1252 	// Location tab
1253 
1254 	wxPanel* loctab = new wxPanel(notebook, -1);
1255 
1256 	wxBoxSizer* locsizer = new wxBoxSizer(wxHORIZONTAL);
1257 	loctab->SetSizer(locsizer);
1258 
1259 	wxPanel* locgrid = new wxPanel(loctab, -1);
1260 	locgrid->SetBackgroundColour(wxColour(255, 255, 255));
1261 	wxBoxSizer* lgsizer = new wxBoxSizer(wxVERTICAL);
1262 	locgrid->SetSizer(lgsizer);
1263 
1264 	loc_tree = new LocTree(locgrid, tc_LocTree, wxDefaultPosition,
1265 		wxDefaultSize, wxTR_DEFAULT_STYLE | wxBORDER_NONE);
1266 
1267 	loc_tree->SetBackgroundColour(wxColour(255, 255, 255));
1268 	loc_tree->Build();
1269 	LocTreeReset();
1270 	lgsizer->Add(loc_tree, 1, wxEXPAND);
1271 
1272 	wxString lsl = wxT("\n");
1273 	lsl += _("Select a Station Location to process      ");
1274 	loc_select_label = new wxStaticText(locgrid, -1, lsl);
1275 	lgsizer->Add(loc_select_label, 0, wxALL, 1);
1276 
1277 	locsizer->Add(locgrid, 50, wxEXPAND);
1278 
1279 	wxStaticLine *locsep = new wxStaticLine(loctab, -1, wxDefaultPosition, wxSize(2, -1), wxLI_VERTICAL);
1280 	locsizer->Add(locsep, 0, wxEXPAND);
1281 
1282 	wxPanel* lbuttons = new wxPanel(loctab, -1);
1283 	lbuttons->SetBackgroundColour(wxColour(255, 255, 255));
1284 	locsizer->Add(lbuttons, 50, wxEXPAND);
1285 	wxBoxSizer* lbsizer = new wxBoxSizer(wxVERTICAL);
1286 	lbuttons->SetSizer(lbsizer);
1287 
1288 	wxPanel* lb1Panel = new wxPanel(lbuttons);
1289 	lb1Panel->SetBackgroundColour(wxColour(255, 255, 255));
1290 	wxBoxSizer* lb1sizer = new wxBoxSizer(wxHORIZONTAL);
1291 	lb1Panel->SetSizer(lb1sizer);
1292 
1293 	loc_add_button = new wxBitmapButton(lb1Panel, tl_AddLoc, locaddbm);
1294 	loc_add_button->SetFont(f);
1295 	loc_add_button->SetBitmapDisabled(locadd_disbm);
1296 	lb1sizer->Add(loc_add_button, 0, wxALL, 1);
1297 	// Note - the doubling below is to size the label to allow the control to stretch later
1298 	loc_add_label = new wxStaticText(lb1Panel, -1, wxT("\nCreate a new Station LocationCreate a new Station\n"));
1299 	lb1sizer->Add(loc_add_label, 1, wxFIXED_MINSIZE | wxALL, 1);
1300 	lbsizer->Add(lb1Panel, 0, wxALL, 1);
1301 	int tw, th;
1302 	loc_add_label->GetSize(&tw, &th);
1303 	wxString lal = wxT("\n");
1304 	lal += _("Create a new Station Location");
1305 	loc_add_label->SetLabel(lal);
1306 	loc_add_button->SetLabel(lal);
1307 
1308 	wxPanel* lb2Panel = new wxPanel(lbuttons);
1309 	lb2Panel->SetBackgroundColour(wxColour(255, 255, 255));
1310 	wxBoxSizer* lb2sizer = new wxBoxSizer(wxHORIZONTAL);
1311 	lb2Panel->SetSizer(lb2sizer);
1312 
1313 	loc_edit_button = new wxBitmapButton(lb2Panel, tl_EditLoc, editbm);
1314 	loc_edit_button->SetFont(f);
1315 	loc_edit_button->SetBitmapDisabled(edit_disbm);
1316 	loc_edit_button->Enable(false);
1317 	lb2sizer->Add(loc_edit_button, 0, wxALL, 1);
1318 	wxString lel = wxT("\n");
1319 	lel += _("Edit a Station Location");
1320 	loc_edit_label = new wxStaticText(lb2Panel, -1, lel, wxDefaultPosition, wxSize(tw, th));
1321 	lb2sizer->Add(loc_edit_label, 1, wxFIXED_MINSIZE | wxALL, 1);
1322 	lbsizer->Add(lb2Panel, 0, wxALL, 1);
1323 
1324 	wxPanel* lb3Panel = new wxPanel(lbuttons);
1325 	lb3Panel->SetBackgroundColour(wxColour(255, 255, 255));
1326 	wxBoxSizer* lb3sizer = new wxBoxSizer(wxHORIZONTAL);
1327 	lb3Panel->SetSizer(lb3sizer);
1328 
1329 	loc_delete_button = new wxBitmapButton(lb3Panel, tl_DeleteLoc, deletebm);
1330 	loc_delete_button->SetFont(f);
1331 	loc_delete_button->SetBitmapDisabled(delete_disbm);
1332 	loc_delete_button->Enable(false);
1333 	lb3sizer->Add(loc_delete_button, 0, wxALL, 1);
1334 	wxString ldl = wxT("\n");
1335 	ldl += _("Delete a Station Location");
1336 	loc_delete_label = new wxStaticText(lb3Panel, -1, ldl, wxDefaultPosition, wxSize(tw, th));
1337 	lb3sizer->Add(loc_delete_label, 1, wxFIXED_MINSIZE | wxALL, 1);
1338 	lbsizer->Add(lb3Panel, 0, wxALL, 1);
1339 
1340 	wxPanel* lb4Panel = new wxPanel(lbuttons);
1341 	lb4Panel->SetBackgroundColour(wxColour(255, 255, 255));
1342 	wxBoxSizer* lb4sizer = new wxBoxSizer(wxHORIZONTAL);
1343 	lb4Panel->SetSizer(lb4sizer);
1344 
1345 	loc_prop_button = new wxBitmapButton(lb4Panel, tl_PropLoc, propertiesbm);
1346 	loc_prop_button->SetFont(f);
1347 	loc_prop_button->SetBitmapDisabled(properties_disbm);
1348 	loc_prop_button->Enable(false);
1349 	lb4sizer->Add(loc_prop_button, 0, wxALL, 1);
1350 	wxString lpl = wxT("\n");
1351 	lpl += _("Display Station Location Properties");
1352 	loc_prop_label = new wxStaticText(lb4Panel, -1, lpl, wxDefaultPosition, wxSize(tw, th));
1353 	lb4sizer->Add(loc_prop_label, 1, wxFIXED_MINSIZE | wxALL, 1);
1354 	lbsizer->Add(lb4Panel, 0, wxALL, 1);
1355 
1356 	notebook->AddPage(loctab, _("Station Locations"));
1357 
1358 	// Certificates tab
1359 
1360 	wxPanel* certtab = new wxPanel(notebook, -1);
1361 
1362 	wxBoxSizer* certsizer = new wxBoxSizer(wxHORIZONTAL);
1363 	certtab->SetSizer(certsizer);
1364 
1365 	wxPanel* certgrid = new wxPanel(certtab, -1);
1366 	certgrid->SetBackgroundColour(wxColour(255, 255, 255));
1367 	wxBoxSizer* cgsizer = new wxBoxSizer(wxVERTICAL);
1368 	certgrid->SetSizer(cgsizer);
1369 
1370 	cert_tree = new CertTree(certgrid, tc_CertTree, wxDefaultPosition,
1371 		wxDefaultSize, wxTR_DEFAULT_STYLE | wxBORDER_NONE); //wxTR_HAS_BUTTONS | wxSUNKEN_BORDER);
1372 
1373 	cert_tree->SetBackgroundColour(wxColour(255, 255, 255));
1374 	cgsizer->Add(cert_tree, 1, wxEXPAND);
1375 
1376 	wxString csq = wxT("\n");
1377 	csq += _("Select a Callsign Certificate to process");
1378 	cert_select_label = new wxStaticText(certgrid, -1, csq);
1379 	cgsizer->Add(cert_select_label, 0, wxALL, 1);
1380 
1381 	certsizer->Add(certgrid, 50, wxEXPAND);
1382 
1383 	wxStaticLine *certsep = new wxStaticLine(certtab, -1, wxDefaultPosition, wxSize(2, -1), wxLI_VERTICAL);
1384 	certsizer->Add(certsep, 0, wxEXPAND);
1385 
1386 	wxPanel* cbuttons = new wxPanel(certtab, -1);
1387 	cbuttons->SetBackgroundColour(wxColour(255, 255, 255));
1388 	certsizer->Add(cbuttons, 50, wxEXPAND, 0);
1389 
1390 	wxBoxSizer* cbsizer = new wxBoxSizer(wxVERTICAL);
1391 	cbuttons->SetSizer(cbsizer);
1392 
1393 	wxPanel* cb1Panel = new wxPanel(cbuttons);
1394 	cb1Panel->SetBackgroundColour(wxColour(255, 255, 255));
1395 	wxBoxSizer* cb1sizer = new wxBoxSizer(wxHORIZONTAL);
1396 	cb1Panel->SetSizer(cb1sizer);
1397 
1398 	cert_load_button = new wxBitmapButton(cb1Panel, tc_Load, importbm);
1399 	cert_load_button->SetFont(f);
1400 	cert_load_button->SetBitmapDisabled(delete_disbm);
1401 	cb1sizer->Add(cert_load_button, 0, wxALL, 1);
1402 	wxString lcl = wxT("\n");
1403 	lcl += _("Load a Callsign Certificate");
1404 	cert_load_label = new wxStaticText(cb1Panel, -1, lcl, wxDefaultPosition, wxSize(tw, th));
1405 	cert_load_button->SetLabel(lcl);
1406 	cb1sizer->Add(cert_load_label, 1, wxFIXED_MINSIZE | wxALL, 1);
1407 	cbsizer->Add(cb1Panel, 0, wxALL, 1);
1408 
1409 	wxPanel* cb2Panel = new wxPanel(cbuttons);
1410 	cb2Panel->SetBackgroundColour(wxColour(255, 255, 255));
1411 	wxBoxSizer* cb2sizer = new wxBoxSizer(wxHORIZONTAL);
1412 	cb2Panel->SetSizer(cb2sizer);
1413 
1414 	cert_save_button = new wxBitmapButton(cb2Panel, tc_CertSave, downloadbm);
1415 	cert_save_button->SetFont(f);
1416 	cert_save_button->SetBitmapDisabled(download_disbm);
1417 	cert_save_button->Enable(false);
1418 	cb2sizer->Add(cert_save_button, 0, wxALL, 1);
1419 	wxString csl = wxT("\n");
1420 	csl += _("Save a Callsign Certificate");
1421 	cert_save_label = new wxStaticText(cb2Panel, -1, csl, wxDefaultPosition, wxSize(tw, th));
1422 	cb2sizer->Add(cert_save_label, 1, wxFIXED_MINSIZE | wxALL, 1);
1423 	cbsizer->Add(cb2Panel, 0, wxALL, 1);
1424 
1425 	wxPanel* cb3Panel = new wxPanel(cbuttons);
1426 	cb3Panel->SetBackgroundColour(wxColour(255, 255, 255));
1427 	wxBoxSizer* cb3sizer = new wxBoxSizer(wxHORIZONTAL);
1428 	cb3Panel->SetSizer(cb3sizer);
1429 
1430 	cert_renew_button = new wxBitmapButton(cb3Panel, tc_CertRenew, uploadbm);
1431 	cert_renew_button->SetFont(f);
1432 	cert_renew_button->SetBitmapDisabled(upload_disbm);
1433 	cert_renew_button->Enable(false);
1434 	cb3sizer->Add(cert_renew_button, 0, wxALL, 1);
1435 	wxString crl = wxT("\n");
1436 	crl += _("Renew a Callsign Certificate");
1437 	cert_renew_label = new wxStaticText(cb3Panel, -1, crl, wxDefaultPosition, wxSize(tw, th));
1438 	cb3sizer->Add(cert_renew_label, 1, wxFIXED_MINSIZE | wxALL, 1);
1439 	cbsizer->Add(cb3Panel, 0, wxALL, 1);
1440 
1441 	wxPanel* cb4Panel = new wxPanel(cbuttons);
1442 	cb4Panel->SetBackgroundColour(wxColour(255, 255, 255));
1443 	wxBoxSizer* cb4sizer = new wxBoxSizer(wxHORIZONTAL);
1444 	cb4Panel->SetSizer(cb4sizer);
1445 
1446 	cert_prop_button = new wxBitmapButton(cb4Panel, tc_CertProp, propertiesbm);
1447 	cert_prop_button->SetFont(f);
1448 	cert_prop_button->SetBitmapDisabled(properties_disbm);
1449 	cert_prop_button->Enable(false);
1450 	cb4sizer->Add(cert_prop_button, 0, wxALL, 1);
1451 	wxString dcl = wxT("\n");
1452 	dcl += _("Display a Callsign Certificate's Properties");
1453 	cert_prop_label = new wxStaticText(cb4Panel, -1, dcl, wxDefaultPosition, wxSize(tw, th));
1454 	cb4sizer->Add(cert_prop_label, 1, wxFIXED_MINSIZE | wxALL, 1);
1455 	cbsizer->Add(cb4Panel, 0, wxALL, 1);
1456 
1457 	notebook->AddPage(certtab, _("Callsign Certificates"));
1458 
1459 	// Status Log tab (if enabled)
1460 	 if (logTab) {
1461 		wxPanel* logtab = new wxPanel(notebook, -1);
1462 		wxBoxSizer* ltsizer = new wxBoxSizer(wxHORIZONTAL);
1463 		logtab->SetSizer(ltsizer);
1464 		logwin = new wxTextCtrl(logtab, -1, wxT(""), wxDefaultPosition, wxSize(800, 600),
1465 			wxTE_MULTILINE|wxTE_READONLY);
1466 		ltsizer->Add(logwin, 1, wxEXPAND);
1467 		notebook->AddPage(logtab, _("Status Log"));
1468 	}
1469 
1470 	//app icon
1471 	SetIcon(wxIcon(key_xpm));
1472 
1473 	if (checkUpdates) {
1474 		LogList *log = new LogList(this);
1475 		wxLog::SetActiveTarget(log);
1476 	}
1477 }
1478 
1479 static wxString
1480 run_station_wizard(wxWindow *parent, tQSL_Location loc, wxHtmlHelpController *help = 0,
1481 	bool expired = false, wxString title = _("Add Station Location"), wxString dataname = wxT(""), wxString callsign = wxT("")) {
1482 	tqslTrace("run_station_wizard", "loc=%lx, expired=%d, title=%s, dataname=%s, callsign=%s", loc, expired, S(title), S(dataname), S(callsign));
1483 	wxString rval(wxT(""));
1484 	get_certlist("", 0, expired, false, false);
1485 	if (ncerts == 0)
1486 		throw TQSLException("No certificates available");
1487 	TQSLWizard *wiz = new TQSLWizard(loc, parent, help, title, expired);
1488 	wiz->SetDefaultCallsign(callsign);
1489 	wiz->GetPage(true);
1490 	TQSLWizCertPage *cpage = reinterpret_cast<TQSLWizCertPage*>(wiz->GetPage());
1491 	if (cpage && !callsign.IsEmpty())
1492 		cpage->UpdateFields(0);
1493 	TQSLWizPage *page = wiz->GetPage();
1494 	if (page == 0)
1495 		throw TQSLException("Error getting first wizard page");
1496 	wiz->AdjustSize();
1497 	// Note: If dynamically created pages are larger than the pages already
1498 	// created (the initial page and the final page), the wizard will need to
1499 	// be resized, but we don't presently have that happening. (The final
1500 	// page is larger than all expected dynamic pages.)
1501 	bool okay = wiz->RunWizard(page);
1502 	rval = wiz->GetLocationName();
1503 	wiz->Destroy();
1504 	if (!okay)
1505 		return rval;
1506 	wxCharBuffer locname = rval.ToUTF8();
1507 	check_tqsl_error(tqsl_setStationLocationCaptureName(loc, locname.data()));
1508 	check_tqsl_error(tqsl_saveStationLocationCapture(loc, 1));
1509 	return rval;
1510 }
1511 
1512 void
1513 MyFrame::OnHelpContents(wxCommandEvent& WXUNUSED(event)) {
1514 	help->Display(wxT("main.htm"));
1515 }
1516 
1517 // Return the "About" string
1518 //
1519 static wxString getAbout() {
1520 	wxString msg = wxT("TQSL V") wxT(VERSION) wxT(" build ") wxT(BUILD) wxT("\n(c) 2001-2016 American Radio Relay League\n\n");
1521 	int major, minor;
1522 	if (tqsl_getVersion(&major, &minor))
1523 		wxLogError(getLocalizedErrorString());
1524 	else
1525 		msg += wxString::Format(wxT("TrustedQSL library V%d.%d\n"), major, minor);
1526 	if (tqsl_getConfigVersion(&major, &minor))
1527 		wxLogError(getLocalizedErrorString());
1528 	else
1529 		msg += wxString::Format(wxT("\nConfiguration data V%d.%d\n\n"), major, minor);
1530 	msg += wxVERSION_STRING;
1531 #ifdef wxUSE_UNICODE
1532 	if (wxUSE_UNICODE)
1533 		msg += wxT(" (Unicode)");
1534 #endif
1535 	msg+=wxString::Format(wxT("\nlibcurl V%hs\n"), LIBCURL_VERSION);
1536 	msg+=wxString::Format(wxT("%hs\n"), OPENSSL_VERSION_TEXT);
1537 	msg+=wxString::Format(wxT("zlib V%hs\n"), ZLIB_VERSION);
1538 #ifdef USE_LMDB
1539 	msg+=wxString::Format(wxT("%hs"), MDB_VERSION_STRING);
1540 #else
1541 	msg+=wxString::Format(wxT("%hs"), DB_VERSION_STRING);
1542 #endif
1543 	msg+=wxT("\n\n\nTranslators:\n"
1544 		"German: Andreas Rehberg, DF4WC\n"
1545 		"Spanish: Jordi Quintero, EA3GCV\n"
1546 		"Italian: Salvatore Besso, I4FYV\n"
1547 		"Japanese: Akihiro KODA, JL3OXR\n"
1548 		"Finnish: Juhani Tapaninen, OH8MXL\n"
1549 		"Portuguese: Nuno Lopes, CT2IRY\n"
1550 		"Russian: Vic Goncharsky, US5WE\n"
1551 		"Chinese: Caros, BH4TXN\n"
1552 		"Hindi: Manmohan Bhagat, VU3YBH\n");
1553 	return msg;
1554 }
1555 
1556 void
1557 MyFrame::OnHelpAbout(wxCommandEvent& WXUNUSED(event)) {
1558 	wxMessageBox(getAbout(), _("About"), wxOK | wxCENTRE | wxICON_INFORMATION, this);
1559 }
1560 
1561 void
1562 MyFrame::OnHelpDiagnose(wxCommandEvent& event) {
1563 	wxString s_fname;
1564 
1565 	if (tqsl_diagFileOpen()) {
1566 		file_menu->Check(tm_f_diag, false);
1567 		tqsl_closeDiagFile();
1568 		wxMessageBox(wxT("Diagnostic log closed"), wxT("Diagnostics"), wxOK | wxCENTRE| wxICON_INFORMATION, this);
1569 		return;
1570 	}
1571 	s_fname = wxFileSelector(_("Log File"), wxT(""), wxT("tqsldiag.log"), wxT("log"),
1572 			_("Log files (*.log)|*.log|All files (*.*)|*.*"),
1573 			wxFD_SAVE|wxFD_OVERWRITE_PROMPT, this);
1574 	if (s_fname == wxT("")) {
1575 		file_menu->Check(tm_f_diag, false); //would be better to not check at all, but no, apparently that's a crazy thing to want
1576 		return;
1577 	}
1578 	if (tqsl_openDiagFile(s_fname.ToUTF8())) {
1579 		wxString errmsg = wxString::Format(_("Error opening diagnostic log %s: %hs"), s_fname.c_str(), strerror(errno));
1580 		wxMessageBox(errmsg, _("Log File Error"), wxOK | wxICON_EXCLAMATION);
1581 		return;
1582 	}
1583 	file_menu->Check(tm_f_diag, true);
1584 	wxString about = getAbout();
1585 #ifdef _WIN32
1586 	about.Replace(wxT("\\n"), wxT("\\r\\n"));
1587 #endif
1588 	tqslTrace(NULL, "TQSL Diagnostics\r\n%s\r\n\r\n", (const char *)about.ToUTF8());
1589 	tqslTrace(NULL, "Command Line: %s\r\n", (const char *)origCommandLine.ToUTF8());
1590 	tqslTrace(NULL, "Working Directory:%s\r\n", tQSL_BaseDir);
1591 }
1592 
1593 class FileUploadHandler {
1594  public:
1595 	string s;
1596 	FileUploadHandler(): s() { s.reserve(2000); }
1597 
1598 	size_t internal_recv(char *ptr, size_t size, size_t nmemb) {
1599 		s.append(ptr, size*nmemb);
1600 		return size*nmemb;
1601 	}
1602 
1603 	static size_t recv(char *ptr, size_t size, size_t nmemb, void *userdata) {
1604 		return (reinterpret_cast<FileUploadHandler*>(userdata))->internal_recv(ptr, size, nmemb);
1605 	}
1606 };
1607 
1608 static void
1609 AddEditStationLocation(tQSL_Location loc, bool expired = false, const wxString& title = _("Add Station Location"), const wxString& callsign = wxT("")) {
1610 	tqslTrace("AddEditStationLocation", "loc=%lx, expired=%lx, title=%s, callsign=%s", loc, expired, S(title), S(callsign));
1611 	try {
1612 		run_station_wizard(frame, loc, frame->help, expired, title, wxT(""), callsign);
1613 		frame->loc_tree->Build();
1614 	}
1615 	catch(TQSLException& x) {
1616 		wxLogError(wxT("%hs"), x.what());
1617 	}
1618 }
1619 
1620 void
1621 MyFrame::AddStationLocation(wxCommandEvent& WXUNUSED(event)) {
1622 	tqslTrace("MyFrame::AddStationLocation", NULL);
1623 	wxTreeItemId root = loc_tree->GetRootItem();
1624 	wxTreeItemId id = loc_tree->GetSelection();
1625 	wxString call;
1626 	if (id != root && id.IsOk()) {		// If something selected
1627 		LocTreeItemData *data = reinterpret_cast<LocTreeItemData *>(loc_tree->GetItemData(id));
1628 		if (data) {
1629 			call = data->getCallSign();		// Station location
1630 		} else {
1631 			call = loc_tree->GetItemText(id);	// Callsign folder selected
1632 		}
1633 		tqslTrace("MyFrame::AddStationLocation", "Call selected is %s", S(call));
1634 	}
1635 	tQSL_Location loc;
1636 	if (tqsl_initStationLocationCapture(&loc)) {
1637 		wxLogError(getLocalizedErrorString());
1638 	}
1639 	AddEditStationLocation(loc, false, _("Add Station Location"), call);
1640 	if (tqsl_endStationLocationCapture(&loc)) {
1641 		wxLogError(getLocalizedErrorString());
1642 	}
1643 	loc_tree->Build();
1644 	LocTreeReset();
1645 }
1646 
1647 void
1648 MyFrame::EditStationLocation(wxCommandEvent& event) {
1649 	tqslTrace("MyFrame::EditStationLocation", NULL);
1650 	if (event.GetId() == tl_EditLoc) {
1651 		try {
1652 			LocTreeItemData *data = reinterpret_cast<LocTreeItemData *>(loc_tree->GetItemData(loc_tree->GetSelection()));
1653 			tQSL_Location loc;
1654 			wxString selname;
1655 			char errbuf[512];
1656 
1657 			if (data == NULL) return;
1658 
1659 			check_tqsl_error(tqsl_getStationLocation(&loc, data->getLocname().ToUTF8()));
1660 			if (!verify_cert(loc, true))	// Check if there is a certificate before editing
1661 				return;
1662 			check_tqsl_error(tqsl_getStationLocationErrors(loc, errbuf, sizeof(errbuf)));
1663 			if (strlen(errbuf) > 0) {
1664 				wxString fmt = wxT("%hs\n");
1665 				// TRANSLATORS: uncommon error - error in a station location, followed by the ignore message that follows.
1666 				fmt += _("The invalid data was ignored.");
1667 				wxMessageBox(wxString::Format(fmt, errbuf), _("Location data error"), wxOK | wxICON_EXCLAMATION, this);
1668 			}
1669 			char loccall[512];
1670 			check_tqsl_error(tqsl_getLocationCallSign(loc, loccall, sizeof loccall));
1671 			selname = run_station_wizard(this, loc, help, true, wxString::Format(_("Edit Station Location : %hs - %s"), loccall, data->getLocname().c_str()), data->getLocname());
1672 			check_tqsl_error(tqsl_endStationLocationCapture(&loc));
1673 			loc_tree->Build();
1674 			LocTreeReset();
1675 			return;
1676 		}
1677 		catch(TQSLException& x) {
1678 			wxLogError(wxT("%hs"), x.what());
1679 			return;
1680 		}
1681 	}
1682 	// How many locations are there?
1683 	try {
1684 		int n;
1685 		tQSL_Location loc;
1686 		check_tqsl_error(tqsl_initStationLocationCapture(&loc));
1687 		check_tqsl_error(tqsl_getNumStationLocations(loc, &n));
1688 		if (n == 1) {
1689 			// There's only one station location. Use that and don't prompt.
1690 			char deflocn[512];
1691 			check_tqsl_error(tqsl_getStationLocationName(loc, 0, deflocn, sizeof deflocn));
1692 			wxString locname = wxString::FromUTF8(deflocn);
1693 			tqsl_endStationLocationCapture(&loc);
1694 			check_tqsl_error(tqsl_getStationLocation(&loc, deflocn));
1695 			char loccall[512];
1696 			check_tqsl_error(tqsl_getLocationCallSign(loc, loccall, sizeof loccall));
1697 			run_station_wizard(this, loc, help, true, wxString::Format(_("Edit Station Location : %hs - %s"), loccall, locname.c_str()), locname);
1698 			check_tqsl_error(tqsl_endStationLocationCapture(&loc));
1699 			loc_tree->Build();
1700 			LocTreeReset();
1701 			return;
1702 		}
1703 		// More than one location or not selected in the tree. Prompt for the location.
1704 		check_tqsl_error(tqsl_endStationLocationCapture(&loc));
1705 		SelectStationLocation(_("Edit Station Location"), _("Close"), true);
1706 		loc_tree->Build();
1707 		LocTreeReset();
1708 	}
1709 	catch(TQSLException& x) {
1710 		wxLogError(wxT("%hs"), x.what());
1711 		return;
1712 	}
1713 }
1714 
1715 static tqsl_adifFieldDefinitions fielddefs[] = {
1716 	{ "CALL", "", TQSL_ADIF_RANGE_TYPE_NONE, TQSL_CALLSIGN_MAX, 0, 0, 0, 0 },
1717 	{ "BAND", "", TQSL_ADIF_RANGE_TYPE_NONE, TQSL_BAND_MAX, 0, 0, 0, 0 },
1718 	{ "BAND_RX", "", TQSL_ADIF_RANGE_TYPE_NONE, TQSL_BAND_MAX, 0, 0, 0, 0 },
1719 	{ "MODE", "", TQSL_ADIF_RANGE_TYPE_NONE, TQSL_MODE_MAX, 0, 0, 0, 0 },
1720 	{ "FREQ", "", TQSL_ADIF_RANGE_TYPE_NONE, TQSL_FREQ_MAX, 0, 0, 0, 0 },
1721 	{ "FREQ_RX", "", TQSL_ADIF_RANGE_TYPE_NONE, TQSL_FREQ_MAX, 0, 0, 0, 0 },
1722 	{ "QSO_DATE", "", TQSL_ADIF_RANGE_TYPE_NONE, 8, 0, 0, 0, 0 },
1723 	{ "TIME_ON", "", TQSL_ADIF_RANGE_TYPE_NONE, 6, 0, 0, 0, 0 },
1724 	{ "SAT_NAME", "", TQSL_ADIF_RANGE_TYPE_NONE, TQSL_SATNAME_MAX, 0, 0, 0, 0 },
1725 	{ "PROP_MODE", "", TQSL_ADIF_RANGE_TYPE_NONE, TQSL_PROPMODE_MAX, 0, 0, 0, 0 },
1726 	{ "EOR", "", TQSL_ADIF_RANGE_TYPE_NONE, 0, 0, 0, 0, 0 },
1727 	{ "", "", TQSL_ADIF_RANGE_TYPE_NONE, 0, 0, 0, 0, 0 },
1728 };
1729 
1730 static const char *defined_types[] = { "T", "D", "M", "C", "N", "6" };
1731 
1732 static unsigned char *
1733 adif_alloc(size_t n) {
1734 	return new unsigned char[n];
1735 }
1736 
1737 static void
1738 loadQSOfile(wxString& file, QSORecordList& recs) {
1739 	init_modes();
1740 	tqsl_adifFieldResults field;
1741 	TQSL_ADIF_GET_FIELD_ERROR stat;
1742 	tQSL_ADIF adif;
1743 	if (tqsl_beginADIF(&adif, file.ToUTF8())) {
1744 		wxLogError(getLocalizedErrorString());
1745 	}
1746 	QSORecord rec;
1747 	do {
1748 		if (tqsl_getADIFField(adif, &field, &stat, fielddefs, defined_types, adif_alloc)) {
1749 			wxLogError(getLocalizedErrorString());
1750 		}
1751 		if (stat == TQSL_ADIF_GET_FIELD_SUCCESS) {
1752 			if (!strcasecmp(field.name, "CALL")) {
1753 				rec._call = wxString::FromUTF8((const char *)field.data);
1754 			} else if (!strcasecmp(field.name, "BAND")) {
1755 				rec._band = wxString::FromUTF8((const char *)field.data);
1756 			} else if (!strcasecmp(field.name, "BAND_RX")) {
1757 				rec._rxband = wxString::FromUTF8((const char *)field.data);
1758 			} else if (!strcasecmp(field.name, "MODE")) {
1759 				rec._mode = wxString::FromUTF8((const char *)field.data);
1760 				char amode[40];
1761 				if (tqsl_getADIFMode(rec._mode.ToUTF8(), amode, sizeof amode) == 0 && amode[0] != '\0')
1762 					rec._mode = wxString::FromUTF8(amode);
1763 			} else if (!strcasecmp(field.name, "FREQ")) {
1764 				rec._freq = wxString::FromUTF8((const char *)field.data);
1765 			} else if (!strcasecmp(field.name, "FREQ_RX")) {
1766 				rec._rxfreq = wxString::FromUTF8((const char *)field.data);
1767 			} else if (!strcasecmp(field.name, "PROP_MODE")) {
1768 				rec._propmode = wxString::FromUTF8((const char *)field.data);
1769 			} else if (!strcasecmp(field.name, "SAT_NAME")) {
1770 				rec._satellite = wxString::FromUTF8((const char *)field.data);
1771 			} else if (!strcasecmp(field.name, "QSO_DATE")) {
1772 				char *cp = reinterpret_cast<char *>(field.data);
1773 				if (strlen(cp) == 8) {
1774 					rec._date.day = strtol(cp+6, NULL, 10);
1775 					*(cp+6) = '\0';
1776 					rec._date.month = strtol(cp+4, NULL, 10);
1777 					*(cp+4) = '\0';
1778 					rec._date.year = strtol(cp, NULL, 10);
1779 				}
1780 			} else if (!strcasecmp(field.name, "TIME_ON")) {
1781 				char *cp = reinterpret_cast<char *>(field.data);
1782 				if (strlen(cp) >= 4) {
1783 					rec._time.second = (strlen(cp) > 4) ? strtol(cp+4, NULL, 10) : 0;
1784 					*(cp+4) = 0;
1785 					rec._time.minute = strtol(cp+2, NULL, 10);
1786 					*(cp+2) = '\0';
1787 					rec._time.hour = strtol(cp, NULL, 10);
1788 				}
1789 			} else if (!strcasecmp(field.name, "EOR")) {
1790 				recs.push_back(rec);
1791 				rec = QSORecord();
1792 			}
1793 			delete[] field.data;
1794 		}
1795 	} while (stat == TQSL_ADIF_GET_FIELD_SUCCESS || stat == TQSL_ADIF_GET_FIELD_NO_NAME_MATCH);
1796 	tqsl_endADIF(&adif);
1797 	return;
1798 }
1799 
1800 void
1801 MyFrame::EditQSOData(wxCommandEvent& WXUNUSED(event)) {
1802 	tqslTrace("MyFrame::EditQSOData", NULL);
1803 	QSORecordList recs;
1804 	wxString file = wxFileSelector(_("Open File"), wxConfig::Get()->Read(wxT("QSODataPath"), wxT("")), wxT(""), wxT("adi"),
1805 #if !defined(__APPLE__) && !defined(_WIN32)
1806 			_("ADIF files (*.adi;*.adif;*.ADI;*.ADIF)|*.adi;*.adif;*.ADI;*.ADIF|All files (*.*)|*.*"),
1807 #else
1808 			_("ADIF files (*.adi;*.adif)|*.adi;*.adif|All files (*.*)|*.*"),
1809 #endif
1810 			wxFD_OPEN|wxFD_FILE_MUST_EXIST, this);
1811 	if (file == wxT(""))
1812 		return;
1813 	loadQSOfile(file, recs);
1814 	wxMessageBox(_("Warning: The TQSL ADIF editor only processes a limited number of ADIF fields.\n\nUsing the editor on an ADIF file can cause QSO details to be lost!"), _("Warning"), wxOK | wxICON_EXCLAMATION, frame);
1815 	try {
1816 		QSODataDialog dial(this, file, help, &recs);
1817 		dial.ShowModal();
1818 	} catch(TQSLException& x) {
1819 		wxLogError(wxT("%hs"), x.what());
1820 	}
1821 }
1822 
1823 void
1824 MyFrame::EnterQSOData(wxCommandEvent& WXUNUSED(event)) {
1825 	tqslTrace("MyFrame::EnterQSOData", NULL);
1826 	QSORecordList recs;
1827 	wxString file = wxT("tqsl.adi");
1828 	try {
1829 		QSODataDialog dial(this, file, help, &recs);
1830 		dial.ShowModal();
1831 	} catch(TQSLException& x) {
1832 		wxLogError(wxT("%hs"), x.what());
1833 	}
1834 }
1835 
1836 int MyFrame::ConvertLogToString(tQSL_Location loc, const wxString& infile, wxString& output, int& n, bool suppressdate, tQSL_Date* startdate, tQSL_Date* enddate, int action, const char* password, const char* defcall) {
1837 	tqslTrace("MyFrame::ConvertLogToString", "loc = %lx, infile=%s, suppressdate=%d, startdate=0x%lx, enddate=0x%lx, action=%d, defcall=%s", reinterpret_cast<void *>(loc), S(infile), suppressdate, reinterpret_cast<void *>(startdate), reinterpret_cast<void *>(enddate), action, defcall ? defcall : "");
1838 	static const char *iam = "TQSL V" VERSION;
1839 	const char *cp;
1840 	char callsign[40];
1841 	int dxcc = 0;
1842 	wxString name, ext;
1843 	bool allow_dupes = false;
1844 	bool restarting = false;
1845 	bool ignore_err = false;
1846 	bool show_dupes = false;
1847 
1848 	wxConfig *config = reinterpret_cast<wxConfig *>(wxConfig::Get());
1849 	config->Read(wxT("DispDupes"), &show_dupes, DEFAULT_DISP_DUPES);
1850 
1851 	try {
1852 		if (defcall) {
1853 			strncpy(callsign, defcall, sizeof callsign);
1854 			check_tqsl_error(tqsl_setLocationCallSign(loc, callsign));
1855 		} else {
1856 			check_tqsl_error(tqsl_getLocationCallSign(loc, callsign, sizeof callsign));
1857 		}
1858 	} catch(TQSLException &x) {
1859 		wxLogError(wxT("%hs"), x.what());
1860 		return TQSL_EXIT_LIB_ERROR;
1861 	}
1862 	tqsl_getLocationDXCCEntity(loc, &dxcc);
1863 	DXCC dx;
1864 	dx.getByEntity(dxcc);
1865 
1866 	get_certlist(callsign, dxcc, false, false, false);
1867 	if (ncerts == 0) {
1868 		// No certificates for this callsign/dxcc pair.
1869 		// If the callsign for this location is set to "[None]", go look
1870 		// for a suitable callsign certificate
1871 		if (strcmp(callsign, "[None]") == 0) {
1872 			// Get all of the certificates for this location's DXCC entity
1873 			get_certlist("", dxcc, false, false, false);
1874 			// If only one callsign matches the dxcc entity, just use that
1875 			if (ncerts == 1) {
1876 				tqsl_getCertificateCallSign(certlist[0], callsign, sizeof callsign);
1877 				tqsl_setLocationCallSign(loc, callsign);
1878 			} else if (ncerts > 1) {
1879 				wxString *choices = new wxString[ncerts];
1880 				// Get today's date
1881 				time_t t = time(0);
1882 				struct tm *tm = gmtime(&t);
1883 				tQSL_Date d;
1884 				d.year = tm->tm_year + 1900;
1885 				d.month = tm->tm_mon + 1;
1886 				d.day = tm->tm_mday;
1887 				tQSL_Date exp;
1888 				int def_index = 0;
1889 				int oldest = -1;
1890 				for (int i = 0; i < ncerts; i++) {
1891 					tqsl_getCertificateCallSign(certlist[i], callsign, sizeof callsign);
1892 					choices[i] = wxString::FromUTF8(callsign);
1893 					if (0 == tqsl_getCertificateNotAfterDate(certlist[i], &exp)) {
1894 						int days_left;
1895 						tqsl_subtractDates(&d, &exp, &days_left);
1896 						if (days_left > oldest) {
1897 							def_index = i;
1898 							oldest = days_left;
1899 						}
1900 					}
1901 				}
1902 				wxSingleChoiceDialog dialog(this, _("Please choose a callsign for this Station Location"),
1903 							_("Select Callsign"), ncerts, choices);
1904 				dialog.SetSelection(def_index);
1905 				int idx;
1906 				if (dialog.ShowModal() == wxID_OK)
1907 					idx = dialog.GetSelection();
1908 				else
1909 					return TQSL_EXIT_CANCEL;
1910 				tqsl_getCertificateCallSign(certlist[idx], callsign, sizeof callsign);
1911 				get_certlist(callsign, dxcc, false, false, false);
1912 				tqsl_setLocationCallSign(loc, callsign);
1913 			}
1914 			// TRANSLATORS: filename being signed, station location and callsign added later.
1915 			wxString fmt = _("The file (%s) will be signed using:");
1916 				 fmt += wxT("\n");
1917 				 fmt += _("Station Location:");
1918 				 fmt += wxT(" %hs\n");
1919 				 fmt += _("Call sign:");
1920 				 fmt += wxT(" %hs\n");
1921 				 fmt += _("DXCC:");
1922 				 fmt += wxT(" %hs\n");
1923 				 fmt += _("Is this correct?");
1924 			char loc_name[128];
1925 			if (tqsl_getStationLocationCaptureName(loc, loc_name, sizeof loc_name)) {
1926 				strncpy(loc_name, "Unknown", sizeof loc_name);
1927 			}
1928 			if (wxMessageBox(wxString::Format(fmt, infile.c_str(), loc_name,
1929 				callsign, dx.name()), _("TQSL - Confirm signing"), wxYES_NO | wxICON_QUESTION, this) != wxYES) {
1930 				return TQSL_EXIT_CANCEL;
1931 			}
1932 		}
1933 	} else {
1934 		// The defcall or callsign from the location has matching certificates
1935 		// for this DXCC location. If we used the default call, set that
1936 		// for the location
1937 		if (defcall) {
1938 			tqsl_setLocationCallSign(loc, defcall);
1939 		}
1940 	}
1941 	if (ncerts == 0) {
1942 		wxString msg;
1943 		wxString fmt = _("There are no valid callsign certificates for callsign");
1944 		if (dxcc != 0) {
1945 			fmt += wxT(" %hs ");
1946 			fmt += _("in entity");
1947 			fmt += wxT(" %hs.\n");
1948 			fmt += _("Signing aborted.");
1949 			fmt + wxT("\n");
1950 			msg = wxString::Format(fmt, callsign, dx.name());
1951 		} else {
1952 			fmt += wxT(" %hs.\n");
1953 			fmt += _("Signing aborted.");
1954 			fmt + wxT("\n");
1955 			msg = wxString::Format(fmt, callsign);
1956 		}
1957 		wxLogError(msg);
1958 		return TQSL_EXIT_TQSL_ERROR;
1959 	}
1960 
1961 	wxString msg = _("Signing using Callsign %hs, DXCC Entity %hs");
1962 	msg += wxT("\n");
1963 	wxLogMessage(msg, callsign, dx.name());
1964 
1965 	init_modes();
1966 	init_contests();
1967 
1968 	if (lock_db(false) < 0) {
1969 		if (quiet) {			// If the database is locked, don't stall if in batch mode.
1970 			return TQSL_EXIT_BUSY;
1971 		}
1972 		wxSafeYield();
1973 		wxLogMessage(_("TQSL must wait for other running copies of TQSL to exit before signing..."));
1974 		wxSafeYield();
1975 		lock_db(true);
1976 	}
1977 
1978  restart:
1979 
1980 	ConvertingDialog *conv_dial = new ConvertingDialog(this, infile.ToUTF8());
1981 	n = 0;
1982 	bool cancelled = false;
1983 	bool aborted = false;
1984 	int lineno = 0;
1985 	int out_of_range = 0;
1986 	int duplicates = 0;
1987 	int processed = 0;
1988 	int errors = 0;
1989 	try {
1990 		if (tqsl_beginCabrilloConverter(&logConv, infile.ToUTF8(), certlist, ncerts, loc)) {
1991 			if (tQSL_Error != TQSL_CABRILLO_ERROR || tQSL_Cabrillo_Error != TQSL_CABRILLO_NO_START_RECORD)
1992 				check_tqsl_error(1);	// A bad error
1993 			lineno = 0;
1994 			check_tqsl_error(tqsl_beginADIFConverter(&logConv, infile.ToUTF8(), certlist, ncerts, loc));
1995 		}
1996 		bool range = true;
1997 		config->Read(wxT("DateRange"), &range);
1998 		if (range && !suppressdate && !restarting) {
1999 			DateRangeDialog dial(this);
2000 			if (dial.ShowModal() != wxOK) {
2001 				wxLogMessage(_("Cancelled"));
2002 				unlock_db();
2003 				return TQSL_EXIT_CANCEL;
2004 			}
2005 			tqsl_setADIFConverterDateFilter(logConv, &dial.start, &dial.end);
2006 			if (this->IsQuiet()) {
2007 				this->Show(false);
2008 				wxSafeYield(this);
2009 			}
2010 		}
2011 		if (startdate || enddate) {
2012 			tqslTrace("MyFrame::ConvertLogToString", "startdate %d/%d/%d, enddate %d/%d/%d",
2013 					startdate ? startdate->year : 0,
2014 					startdate ? startdate->month : 0,
2015 					startdate ? startdate->day : 0,
2016 					enddate ? enddate->year : 0,
2017 					enddate ? enddate->month : 0,
2018 					enddate ? enddate->day : 0);
2019 			tqsl_setADIFConverterDateFilter(logConv, startdate, enddate);
2020 		}
2021 		bool allow = false;
2022 		config->Read(wxT("BadCalls"), &allow);
2023 		tqsl_setConverterAllowBadCall(logConv, allow);
2024 		tqsl_setConverterAllowDuplicates(logConv, allow_dupes);
2025 		tqsl_setConverterAppName(logConv, iam);
2026 
2027 		wxFileName::SplitPath(infile, 0, &name, &ext);
2028 		if (ext != wxT(""))
2029 			name += wxT(".") + ext;
2030 		// Only display windows if notin batch mode -- KD6PAG
2031 		if (!this->IsQuiet()) {
2032 			conv_dial->Show(TRUE);
2033 		}
2034 		this->Enable(FALSE);
2035 
2036 		output = wxT("");
2037 
2038 		do {
2039 			while ((cp = tqsl_getConverterGABBI(logConv)) != 0) {
2040 				if (!this->IsQuiet())
2041 					wxSafeYield(conv_dial);
2042 				if (!conv_dial->running)
2043 					break;
2044 				// Only count QSO records
2045 				if (strstr(cp, "tCONTACT")) {
2046 					++n;
2047 					++processed;
2048 				}
2049 				if ((processed % 10) == 0) {
2050 					wxString progress = wxString::Format(_("QSOs: %d"), processed);
2051 					if (duplicates > 0)
2052 						progress += wxT(" ") + wxString::Format(_("Duplicates: %d"), duplicates);
2053 					if (errors > 0 || out_of_range > 0)
2054 						progress += wxT(" ") + wxString::Format(_("Errors: %d"), errors + out_of_range);
2055 					conv_dial->msg->SetLabel(progress);
2056 				}
2057 				output << (wxString::FromUTF8(cp) + wxT("\n"));
2058 			}
2059 			if ((processed % 10) == 0) {
2060 				wxString progress = wxString::Format(_("QSOs: %d"), processed);
2061 				if (duplicates > 0)
2062 					progress += wxT(" ") + wxString::Format(_("Duplicates: %d"), duplicates);
2063 				if (errors > 0 || out_of_range > 0)
2064 					progress += wxT(" ") + wxString::Format(_("Errors: %d"), errors + out_of_range);
2065 				conv_dial->msg->SetLabel(progress);
2066 			}
2067 			if (cp == 0) {
2068 				if (!this->IsQuiet())
2069 					wxSafeYield(conv_dial);
2070 				if (!conv_dial->running)
2071 					break;
2072 			}
2073 			if (tQSL_Error == TQSL_SIGNINIT_ERROR) {
2074 				tQSL_Cert cert;
2075 				int rval;
2076 				check_tqsl_error(tqsl_getConverterCert(logConv, &cert));
2077 				do {
2078 					if ((rval = tqsl_beginSigning(cert, const_cast<char *>(password), getCertPassword, cert)) == 0)
2079 						break;
2080 					if (tQSL_Error == TQSL_PASSWORD_ERROR) {
2081 						if ((rval = tqsl_beginSigning(cert, const_cast<char *>(unipwd), NULL, cert)) == 0)
2082 							break;
2083 						wxLogMessage(_("Password error"));
2084 						if (password)
2085 							free(reinterpret_cast<void *>(const_cast<char *>(password)));
2086 						password = NULL;
2087 					}
2088 				} while (tQSL_Error == TQSL_PASSWORD_ERROR);
2089 				if (tQSL_Error == TQSL_CUSTOM_ERROR && (tQSL_Errno == ENOENT || tQSL_Errno == EPERM)) {
2090 					snprintf(tQSL_CustomError, sizeof tQSL_CustomError,
2091 						"Can't open the private key file for %s: %s", callsign, strerror(tQSL_Errno));
2092 				}
2093 				check_tqsl_error(rval);
2094 				if (this->IsQuiet()) {
2095 					this->Show(false);
2096 					wxSafeYield(this);
2097 				}
2098 				continue;
2099 			}
2100 			if (tQSL_Error == TQSL_DATE_OUT_OF_RANGE) {
2101 				processed++;
2102 				out_of_range++;
2103 				continue;
2104 			}
2105 			char dupeErrString[256];
2106 			dupeErrString[0] = '\0';
2107 			bool dupe_error = (tQSL_Error == TQSL_DUPLICATE_QSO);
2108 			if (dupe_error) {
2109 				strncpy(dupeErrString, tQSL_CustomError, sizeof dupeErrString);
2110 				duplicates++;
2111 				if (!show_dupes) {
2112 					processed++;
2113 					continue;
2114 				}
2115 			}
2116 			bool has_error = (tQSL_Error != TQSL_NO_ERROR);
2117 			if (has_error) {
2118 				processed++;
2119 				if (!dupe_error) {
2120 					errors++;
2121 				}
2122 				try {
2123 					check_tqsl_error(1);
2124 				} catch(TQSLException& x) {
2125 					tqsl_getConverterLine(logConv, &lineno);
2126 					wxString msg = wxGetTranslation(wxString::FromUTF8(x.what()));
2127 					if (lineno)
2128 						msg += wxT(" ") + wxString::Format(_("on line %d"), lineno);
2129 					const char *bad_text = tqsl_getConverterRecordText(logConv);
2130 					if (bad_text)
2131 						msg += wxString(wxT("\n")) + wxString::FromUTF8(bad_text);
2132 
2133 					if (dupeErrString[0] != '\0') {
2134 						wxStringTokenizer dupes(wxString::FromUTF8(dupeErrString), wxT("|"));
2135 						wxString olddupe = dupes.GetNextToken();
2136 						wxString newdupe = dupes.GetNextToken();
2137 						if (olddupe != newdupe) {
2138 							msg += wxT("\n") + wxString::Format(_("Your QTH Details changed for this QSO.\n\nOriginally these were: %s\nNow they are:%s\n\nPlease verify that you intended to change this QSO!\n"), olddupe.c_str(), newdupe.c_str());
2139 						}
2140 					}
2141 					wxLogError(wxT("%s"), msg.c_str());
2142 					if (frame->IsQuiet()) {
2143 						switch (action) {
2144 							case TQSL_ACTION_ABORT:
2145 								aborted = true;
2146 								ignore_err = true;
2147 								goto abortSigning;
2148 							case TQSL_ACTION_NEW:		// For ALL or NEW, let the signing proceed
2149 							case TQSL_ACTION_ALL:
2150 								ignore_err = true;
2151 								break;
2152 							case TQSL_ACTION_ASK:
2153 							case TQSL_ACTION_UNSPEC:
2154 								break;			// The error will show as a popup
2155 						}
2156 					}
2157 					if (!ignore_err) {
2158 						tqslTrace("MyFrame::ConvertLogToString", "Error: %s/asking for action", S(msg));
2159 						wxString errmsg = msg + wxT("\n");
2160 						errmsg += _("Click 'Ignore' to continue signing this log while ignoring errors.");
2161 						errmsg += wxT("\n");
2162 						errmsg += _("Click 'Cancel' to abandon signing this log.");
2163 						ErrorsDialog dial(this, errmsg);
2164 						int choice = dial.ShowModal();
2165 						if (choice == TQSL_AE_CAN) {
2166 							cancelled = true;
2167 							goto abortSigning;
2168 						}
2169 						if (choice == TQSL_AE_OK) {
2170 							ignore_err = true;
2171 							if (this->IsQuiet()) {
2172 								this->Show(false);
2173 								wxSafeYield(this);
2174 							}
2175 						}
2176 					}
2177 				}
2178 			}
2179 			tqsl_getErrorString();	// Clear error
2180 			if (has_error && ignore_err)
2181 				continue;
2182 			break;
2183 		} while (1);
2184 		cancelled = !conv_dial->running;
2185 
2186  abortSigning:
2187 		this->Enable(TRUE);
2188 
2189 		if (cancelled) {
2190 			wxLogWarning(_("Signing cancelled"));
2191 			n = 0;
2192 		} else if (aborted) {
2193 			wxLogWarning(_("Signing aborted"));
2194 			n = 0;
2195 		} else if (tQSL_Error != TQSL_NO_ERROR) {
2196 			check_tqsl_error(1);
2197 		}
2198 		delete conv_dial;
2199 	} catch(TQSLException& x) {
2200 		this->Enable(TRUE);
2201 		delete conv_dial;
2202 		string msg = x.what();
2203 		tqsl_getConverterLine(logConv, &lineno);
2204 		tqsl_converterRollBack(logConv);
2205 		tqsl_endConverter(&logConv);
2206 		unlock_db();
2207 		if (lineno) {
2208 			msg += " ";
2209 			msg += wxString::Format(_("on line %d"), lineno).ToUTF8();
2210 		}
2211 
2212 		wxLogError(_("Signing aborted due to errors"));
2213 		throw TQSLException(msg.c_str());
2214 	}
2215 	if (!cancelled && out_of_range > 0)
2216 		wxLogMessage(_("%s: %d QSO records were outside the selected date range"),
2217 			infile.c_str(), out_of_range);
2218 	if (duplicates > 0) {
2219 		if (cancelled) {
2220 			tqsl_converterRollBack(logConv);
2221 			tqsl_endConverter(&logConv);
2222 			unlock_db();
2223 			return TQSL_EXIT_CANCEL;
2224 		}
2225 		if (action == TQSL_ACTION_ASK || action == TQSL_ACTION_UNSPEC) { // want to ask the user
2226 			DupesDialog dial(this, processed - errors - out_of_range, duplicates, action);
2227 			int choice = dial.ShowModal();
2228 			if (choice == TQSL_DP_CAN) {
2229 				wxLogMessage(_("Cancelled"));
2230 				tqsl_converterRollBack(logConv);
2231 				tqsl_endConverter(&logConv);
2232 				unlock_db();
2233 				return TQSL_EXIT_CANCEL;
2234 			}
2235 			if (choice == TQSL_DP_ALLOW) {
2236 				allow_dupes = true;
2237 				tqsl_converterRollBack(logConv);
2238 				tqsl_endConverter(&logConv);
2239 				restarting = true;
2240 				if (this->IsQuiet()) {
2241 					this->Show(false);
2242 					wxSafeYield(this);
2243 				}
2244 				goto restart;
2245 			}
2246 		} else if (action == TQSL_ACTION_ABORT) {
2247 				if (processed == duplicates) {
2248 					wxLogMessage(_("All QSOs are duplicates; aborted"));
2249 					tqsl_converterRollBack(logConv);
2250 					tqsl_endConverter(&logConv);
2251 					unlock_db();
2252 					n = 0;
2253 					return TQSL_EXIT_NO_QSOS;
2254 				} else {
2255 					wxLogMessage(_("%d of %d QSOs are duplicates; aborted"), duplicates, processed);
2256 					tqsl_converterRollBack(logConv);
2257 					tqsl_endConverter(&logConv);
2258 					unlock_db();
2259 					n = 0;
2260 					return TQSL_EXIT_NO_QSOS;
2261 				}
2262 		} else if (action == TQSL_ACTION_ALL) {
2263 			allow_dupes = true;
2264 			tqsl_converterRollBack(logConv);
2265 			tqsl_endConverter(&logConv);
2266 			restarting = true;
2267 			goto restart;
2268 		}
2269 		// Otherwise it must be TQSL_ACTION_NEW, so fall through
2270 		// and output the new records.
2271 		wxLogMessage(_("%s: %d QSO records were duplicates"),
2272 			infile.c_str(), duplicates);
2273 	}
2274 	//if (!cancelled) tqsl_converterCommit(logConv);
2275 	if (cancelled || processed == 0) {
2276 		tqsl_converterRollBack(logConv);
2277 		tqsl_endConverter(&logConv);
2278 	}
2279 	unlock_db();
2280 	if (cancelled)
2281 		return TQSL_EXIT_CANCEL;
2282 	if (processed == 0)
2283 		return TQSL_EXIT_NO_QSOS;
2284 	if (aborted || duplicates > 0 || out_of_range > 0 || errors > 0)
2285 		return TQSL_EXIT_QSOS_SUPPRESSED;
2286 	return TQSL_EXIT_SUCCESS;
2287 }
2288 
2289 // Errors from tqsllib that indicate bad QSO data.
2290 // Add here so they're picked up for translation.
2291 #ifdef tqsltranslate
2292 static const char* qsoerrs[] = {
2293 	__("Invalid contact - QSO does not specify a Callsign"),
2294 	__("Invalid contact - QSO does not specify a band or frequency"),
2295 	__("Invalid contact - QSO does not specify a mode"),
2296 	__("Invalid contact - QSO does not specify a date"),
2297 	__("Invalid contact - QSO does not specify a time"),
2298 	// Piggy-back this error as well
2299 	__("This callsign certificate is already active and cannot be restored.")
2300 }
2301 #endif
2302 
2303 int
2304 MyFrame::ConvertLogFile(tQSL_Location loc, const wxString& infile, const wxString& outfile,
2305 	bool compressed, bool suppressdate, tQSL_Date* startdate, tQSL_Date* enddate, int action, const char *password, const char *defcall) {
2306 	tqslTrace("MyFrame::ConvertLogFile", "loc=%lx, infile=%s, outfile=%s, compressed=%d, suppressdate=%d, startdate=0x%lx enddate=0x%lx action=%d", reinterpret_cast<void *>(loc), S(infile), S(outfile), compressed, suppressdate, reinterpret_cast<void *>(startdate), reinterpret_cast<void*>(enddate), action);
2307 	gzFile gout = 0;
2308 #ifdef _WIN32
2309 	int fd = -1;
2310 #endif
2311 	ofstream out;
2312 
2313 	if (compressed) {
2314 #ifdef _WIN32
2315 		wchar_t* lfn = utf8_to_wchar(outfile.ToUTF8());
2316 		fd = _wopen(lfn, _O_WRONLY |_O_CREAT|_O_BINARY, _S_IREAD|_S_IWRITE);
2317 		free_wchar(lfn);
2318 		if (fd != -1)
2319 			gout = gzdopen(fd, "wb9");
2320 #else
2321 		gout = gzopen(outfile.ToUTF8(), "wb9");
2322 #endif
2323 	} else {
2324 #ifdef _WIN32
2325 		wchar_t* lfn = utf8_to_wchar(outfile.ToUTF8());
2326 		out.open(lfn, ios::out|ios::trunc|ios::binary);
2327 		free_wchar(lfn);
2328 #else
2329 		out.open(outfile.ToUTF8(), ios::out|ios::trunc|ios::binary);
2330 #endif
2331 	}
2332 
2333 	if ((compressed && !gout) || (!compressed && !out)) {
2334 		wxLogError(_("Unable to open %s for output"), outfile.c_str());
2335 		return TQSL_EXIT_ERR_OPEN_OUTPUT;
2336 	}
2337 
2338 	wxString output;
2339 	int numrecs = 0;
2340 	int status = this->ConvertLogToString(loc, infile, output, numrecs, suppressdate, startdate, enddate, action, password, defcall);
2341 
2342 	if (numrecs == 0) {
2343 		wxLogMessage(_("No records output"));
2344 		if (compressed) {
2345 			gzclose(gout);
2346 		} else {
2347 			out.close();
2348 		}
2349 #ifdef _WIN32
2350 		wchar_t* lfn = utf8_to_wchar(outfile.ToUTF8());
2351 		_wunlink(lfn);
2352 		free_wchar(lfn);
2353 #else
2354 		unlink(outfile.ToUTF8());
2355 #endif
2356 		if (status == TQSL_EXIT_CANCEL || TQSL_EXIT_QSOS_SUPPRESSED)
2357 			return status;
2358 		else
2359 			return TQSL_EXIT_NO_QSOS;
2360 	} else {
2361 		if(compressed) {
2362 			if (gzwrite(gout, output.ToUTF8(), output.size()) <= 0) {
2363 				tqsl_converterRollBack(logConv);
2364 				tqsl_endConverter(&logConv);
2365 				gzclose(gout);
2366 				return TQSL_EXIT_LIB_ERROR;
2367 			}
2368 			if (gzflush(gout, Z_FINISH) != Z_OK) {
2369 				tqsl_converterRollBack(logConv);
2370 				tqsl_endConverter(&logConv);
2371 				gzclose(gout);
2372 				return TQSL_EXIT_LIB_ERROR;
2373 			}
2374 			if (gzclose(gout) != Z_OK) {
2375 				tqsl_converterRollBack(logConv);
2376 				tqsl_endConverter(&logConv);
2377 				return TQSL_EXIT_LIB_ERROR;
2378 			}
2379 		} else {
2380 			out << output;
2381 			if (out.fail()) {
2382 				tqsl_converterRollBack(logConv);
2383 				tqsl_endConverter(&logConv);
2384 				out.close();
2385 				return TQSL_EXIT_LIB_ERROR;
2386 			}
2387 			out.close();
2388 			if (out.fail()) {
2389 				tqsl_converterRollBack(logConv);
2390 				tqsl_endConverter(&logConv);
2391 				return TQSL_EXIT_LIB_ERROR;
2392 			}
2393 		}
2394 
2395 		tqsl_converterCommit(logConv);
2396 		tqsl_endConverter(&logConv);
2397 
2398 		wxLogMessage(_("%s: wrote %d records to %s"), infile.c_str(), numrecs,
2399 			outfile.c_str());
2400 		wxLogMessage(_("%s is ready to be emailed or uploaded."), outfile.c_str());
2401 		wxLogMessage(_("Note: TQSL assumes that this file will be uploaded to LoTW."));
2402 		wxLogMessage(_("Resubmitting these QSOs will cause them to be reported as duplicates."));
2403 	}
2404 
2405 	return status;
2406 }
2407 
2408 long compressToBuf(string& buf, const char* input) {
2409 	tqslTrace("compressToBuf", NULL);
2410 	const size_t TBUFSIZ = 128*1024;
2411 	uint8_t* tbuf = new uint8_t[TBUFSIZ];
2412 
2413 	//vector<uint8_t> buf;
2414 	z_stream stream;
2415 	stream.zalloc = 0;
2416 	stream.zfree = 0;
2417 	stream.next_in = reinterpret_cast<Bytef*>(const_cast<char *>(input));
2418 	stream.avail_in = strlen(input);
2419 	stream.next_out = tbuf;
2420 	stream.avail_out = TBUFSIZ;
2421 
2422 	//deflateInit(&stream, Z_BEST_COMPRESSION);
2423 	deflateInit2(&stream, Z_BEST_COMPRESSION, Z_DEFLATED, 16+15, 9, Z_DEFAULT_STRATEGY); //use gzip header
2424 
2425 	while (stream.avail_in) { //while still some left
2426 		int res = deflate(&stream, Z_NO_FLUSH);
2427 		assert(res == Z_OK);
2428 		if (!stream.avail_out) {
2429 			buf.insert(buf.end(), tbuf, tbuf+TBUFSIZ);
2430 			stream.next_out = tbuf;
2431 			stream.avail_out = TBUFSIZ;
2432 		}
2433 	}
2434 
2435 	do {
2436 		if (stream.avail_out == 0) {
2437 			buf.insert(buf.end(), tbuf, tbuf+TBUFSIZ);
2438 			stream.next_out = tbuf;
2439 			stream.avail_out = TBUFSIZ;
2440 		}
2441 	} while (deflate(&stream, Z_FINISH) == Z_OK);
2442 
2443 	buf.insert(buf.end(), tbuf, tbuf+TBUFSIZ-stream.avail_out);
2444 	deflateEnd(&stream);
2445 
2446 	delete tbuf;
2447 
2448 	return buf.length();
2449 }
2450 
2451 
2452 class UploadThread: public wxThread {
2453  public:
2454   UploadThread(CURL* handle_, wxDialog* dial_): wxThread(wxTHREAD_JOINABLE),
2455 						handle(handle_), dial(dial_) {
2456 		wxThread::Create();
2457 	}
2458  protected:
2459 	CURL* handle;
2460 	wxDialog* dial;
2461 	virtual wxThread::ExitCode Entry() {
2462 		int ret = curl_easy_perform(handle);
2463 		wxCommandEvent evt(wxEVT_LOGUPLOAD_DONE, wxID_ANY);
2464 		dial->GetEventHandler()->AddPendingEvent(evt);
2465 		return (wxThread::ExitCode)((intptr_t)ret);
2466 	}
2467 };
2468 
2469 int MyFrame::UploadLogFile(tQSL_Location loc, const wxString& infile, bool compressed, bool suppressdate, tQSL_Date* startdate, tQSL_Date* enddate, int action, const char* password, const char *defcall) {
2470 	tqslTrace("MyFrame::UploadLogFile", "infile=%s, compressed=%d, suppressdate=%d, action=%d", S(infile), compressed, suppressdate, action);
2471 	int numrecs = 0;
2472 	wxString signedOutput;
2473 
2474 	tqslTrace("MyFrame::UploadLogFile", "About to convert log to string");
2475 	int status = this->ConvertLogToString(loc, infile, signedOutput, numrecs, suppressdate, startdate, enddate, action, password, defcall);
2476 	tqslTrace("MyFrame::UploadLogFile", "Log converted, status = %d, numrecs=%d", status, numrecs);
2477 
2478 	if (numrecs == 0) {
2479 		wxLogMessage(_("No records to upload"));
2480 		if (status == TQSL_EXIT_CANCEL || TQSL_EXIT_QSOS_SUPPRESSED)
2481 			return status;
2482 		else
2483 			return TQSL_EXIT_NO_QSOS;
2484 	} else {
2485 		//compress the upload
2486 		tqslTrace("MyFrame::UploadLogFile", "Compressing");
2487 		string compressed;
2488 		size_t compressedSize = compressToBuf(compressed, (const char*)signedOutput.ToUTF8());
2489 		tqslTrace("MyFrame::UploadLogFile", "Compressed to %d bytes", compressedSize);
2490 		//ofstream f; f.open("testzip.tq8", ios::binary); f<<compressed; f.close(); //test of compression routine
2491 		if (compressedSize == 0) {
2492 			wxLogMessage(_("Error compressing before upload"));
2493 			return TQSL_EXIT_TQSL_ERROR;
2494 		}
2495 
2496 		tqslTrace("MyFrame::UploadLogFile", "Creating filename");
2497 		wxDateTime now = wxDateTime::Now().ToUTC();
2498 
2499 		wxString name, ext;
2500 		wxFileName::SplitPath(infile, 0, &name, &ext);
2501 		name += wxT(".tq8");
2502 		tqslTrace("MyFrame::UploadLogFile", "file=%s", S(name));
2503 		//unicode mess. can't just use mb_str directly because it's a temp ptr
2504 		// and the curl form expects it to still be there during perform() so
2505 		// we have to do all this copying around to please the unicode gods
2506 
2507 		char filename[1024];
2508 		strncpy(filename, wxString::Format(wxT("<TQSLUpl %s-%s> %s"),
2509 			now.Format(wxT("%Y%m%d")).c_str(),
2510 			now.Format(wxT("%H%M")).c_str(),
2511 			name.c_str()).ToUTF8(), sizeof filename);
2512 		filename[sizeof filename - 1] = '\0';
2513 		tqslTrace("MyFrame::UploadLogFile", "Upload Name=%s", filename);
2514 
2515 		wxString fileType(wxT("Log"));
2516 		tqslTrace("MyFrame::UploadLogFile", "About to call UploadFile");
2517 		int retval = UploadFile(infile, filename, numrecs, reinterpret_cast<void *>(const_cast<char *>(compressed.c_str())),
2518 					compressedSize, fileType);
2519 
2520 		tqslTrace("MyFrame::UploadLogFile", "UploadFile returns %d", retval);
2521 		if (retval == 0) {
2522 			tqsl_converterCommit(logConv);
2523 			tqslTrace("MyFrame::UploadLogFile", "Committing");
2524 		} else {
2525 			tqslTrace("MyFrame::UploadLogFile", "Rollback");
2526 			tqsl_converterRollBack(logConv);
2527 		}
2528 
2529 		tqsl_endConverter(&logConv);
2530 		tqslTrace("MyFrame::UploadLogFile", "Upload done");
2531 		return retval;
2532 	}
2533 }
2534 
2535 static CURL*
2536 tqsl_curl_init(const char *logTitle, const char *url, FILE **curlLogFile, bool newFile) {
2537 	tqslTrace("tqsl_curl_init", "title=%s url=%s newFile=%d", logTitle, url, newFile);
2538 	CURL* curlReq = curl_easy_init();
2539 	if (!curlReq)
2540 		return NULL;
2541 
2542 	wxString filename;
2543 #ifdef _WIN32
2544 	filename.Printf(wxT("%hs\\curl.log"), wxString::FromUTF8(tQSL_BaseDir));
2545 #else
2546 	filename.Printf(wxT("%hs/curl.log"), tQSL_BaseDir);
2547 #endif
2548 #ifdef _WIN32
2549 	wchar_t*lfn = utf8_to_wchar(filename.ToUTF8());
2550 	*curlLogFile = _wfopen(lfn, newFile ? L"wb" : L"ab");
2551 	free_wchar(lfn);
2552 #else
2553 	*curlLogFile = fopen(filename.ToUTF8(), newFile ? "wb" : "ab");
2554 #endif
2555 	if (*curlLogFile) {
2556 		curl_easy_setopt(curlReq, CURLOPT_VERBOSE, 1);
2557 		curl_easy_setopt(curlReq, CURLOPT_STDERR, *curlLogFile);
2558 		fprintf(*curlLogFile, "%s:\n", logTitle);
2559 	}
2560 	//set up options
2561 	curl_easy_setopt(curlReq, CURLOPT_URL, url);
2562 
2563 #ifdef __WXMAC__
2564 	DocPaths docpaths(wxT("tqsl.app"));
2565 #else
2566 	DocPaths docpaths(wxT("tqslapp"));
2567 #endif
2568 	docpaths.Add(wxString::FromUTF8(tQSL_BaseDir));
2569 #ifdef CONFDIR
2570 	docpaths.Add(wxT(CONFDIR));
2571 #endif
2572 #ifdef _WIN32
2573 	wxStandardPaths sp;
2574 	wxString exePath;
2575 	wxFileName::SplitPath(sp.GetExecutablePath(), &exePath, 0, 0);
2576 	docpaths.Add(exePath);
2577 #endif
2578 	wxString caBundlePath = docpaths.FindAbsoluteValidPath(wxT("ca-bundle.crt"));
2579 	if (!caBundlePath.IsEmpty()) {
2580 		char caBundle[256];
2581 		strncpy(caBundle, caBundlePath.ToUTF8(), sizeof caBundle);
2582 		curl_easy_setopt(curlReq, CURLOPT_CAINFO, caBundle);
2583 	} else {
2584 		tqslTrace("tqsl_curl_init", "Can't find ca-bundle.crt in the docpaths!");
2585 	}
2586 	// Get the proxy configuration and pass it to cURL
2587 	wxConfig *config = reinterpret_cast<wxConfig *>(wxConfig::Get());
2588 	config->SetPath(wxT("/Proxy"));
2589 
2590 	bool enabled = false;
2591 	config->Read(wxT("ProxyEnabled"), &enabled, false);
2592 	wxString pHost = config->Read(wxT("proxyHost"), wxT(""));
2593 	wxString pPort = config->Read(wxT("proxyPort"), wxT(""));
2594 	wxString pType = config->Read(wxT("proxyType"), wxT(""));
2595 	config->SetPath(wxT("/"));
2596 
2597 	if (!enabled) return curlReq;	// No proxy defined
2598 
2599 	long port = strtol(pPort.ToUTF8(), NULL, 10);
2600 	if (port == 0 || pHost.IsEmpty())
2601 		return curlReq;		// Invalid proxy. Ignore it.
2602 
2603 	curl_easy_setopt(curlReq, CURLOPT_PROXY, (const char *)pHost.ToUTF8());
2604 	curl_easy_setopt(curlReq, CURLOPT_PROXYPORT, port);
2605 	if (pType == wxT("HTTP")) {
2606 		curl_easy_setopt(curlReq, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);	// Default is HTTP
2607 	} else if (pType == wxT("Socks4")) {
2608 		curl_easy_setopt(curlReq, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS4);
2609 	} else if (pType == wxT("Socks5")) {
2610 		curl_easy_setopt(curlReq, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
2611 	}
2612 	curl_easy_setopt(curlReq, CURLOPT_SSL_VERIFYPEER, verifyCA);
2613 	return curlReq;
2614 }
2615 
2616 int MyFrame::UploadFile(const wxString& infile, const char* filename, int numrecs, void *content, size_t clen, const wxString& fileType) {
2617 	tqslTrace("MyFrame::UploadFile", "infile=%s, filename=%s, numrecs=%d, content=0x%lx, clen=%d fileType=%s",  S(infile), filename, numrecs, reinterpret_cast<void *>(content), clen, S(fileType));
2618 
2619 	//upload the file
2620 
2621 	//get the url from the config, can be overridden by an installer
2622 	//defaults are valid for LoTW as of 1/31/2013
2623 
2624 	wxConfig *config = reinterpret_cast<wxConfig *>(wxConfig::Get());
2625 	config->SetPath(wxT("/LogUpload"));
2626 	wxString uploadURL = config->Read(wxT("UploadURL"), DEFAULT_UPL_URL);
2627 	wxString uploadField = config->Read(wxT("PostField"), DEFAULT_UPL_FIELD);
2628 	wxString uplStatus = config->Read(wxT("StatusRegex"), DEFAULT_UPL_STATUSRE);
2629 	wxString uplStatusSuccess = config->Read(wxT("StatusSuccess"), DEFAULT_UPL_STATUSOK).Lower();
2630 	wxString uplMessage = config->Read(wxT("MessageRegex"), DEFAULT_UPL_MESSAGERE);
2631 	bool uplVerifyCA;
2632 	config->Read(wxT("VerifyCA"), &uplVerifyCA, DEFAULT_UPL_VERIFYCA);
2633 	config->SetPath(wxT("/"));
2634 
2635 	// Copy the strings so they remain around
2636 	char *urlstr = strdup(uploadURL.ToUTF8());
2637 	char *cpUF = strdup(uploadField.ToUTF8());
2638 
2639  retry_upload:
2640 
2641 	curlReq = tqsl_curl_init("Upload Log", urlstr, &curlLogFile, true);
2642 
2643 	if (!curlReq) {
2644 		wxLogMessage(_("Error: Could not upload file (CURL Init error)"));
2645 		free(urlstr);
2646 		free(cpUF);
2647 		return TQSL_EXIT_TQSL_ERROR;
2648 	}
2649 
2650 	//the following allow us to write our log and read the result
2651 
2652 	FileUploadHandler handler;
2653 
2654 	curl_easy_setopt(curlReq, CURLOPT_WRITEFUNCTION, &FileUploadHandler::recv);
2655 	curl_easy_setopt(curlReq, CURLOPT_WRITEDATA, &handler);
2656 	curl_easy_setopt(curlReq, CURLOPT_SSL_VERIFYPEER, uplVerifyCA);
2657 
2658 	char errorbuf[CURL_ERROR_SIZE];
2659 	errorbuf[0] = '\0';
2660 	curl_easy_setopt(curlReq, CURLOPT_ERRORBUFFER, errorbuf);
2661 
2662 	struct curl_httppost* post = NULL, *lastitem = NULL;
2663 
2664 	curl_formadd(&post, &lastitem,
2665 		CURLFORM_PTRNAME, cpUF,
2666 		CURLFORM_BUFFER, filename,
2667 		CURLFORM_BUFFERPTR, content,
2668 		CURLFORM_BUFFERLENGTH, clen,
2669 		CURLFORM_END);
2670 
2671 	curl_easy_setopt(curlReq, CURLOPT_HTTPPOST, post);
2672 
2673 	intptr_t retval;
2674 
2675 	UploadDialog* upload = NULL;
2676 
2677 	if (numrecs > 0) {
2678 		if (numrecs == 1) {
2679 			wxLogMessage(_("Attempting to upload one QSO"));
2680 		} else {
2681 			wxLogMessage(_("Attempting to upload %d QSOs"), numrecs);
2682 		}
2683 	} else {
2684 		wxLogMessage(_("Attempting to upload %s"), fileType.c_str());
2685 	}
2686 
2687 	if(this && !quiet) {
2688 		if (fileType == wxT("Log")) {
2689 			upload = new UploadDialog(this);
2690 		} else {
2691 			upload = new UploadDialog(this, wxString(_("Uploading Callsign Certificate")), wxString(_("Uploading Callsign Certificate Request...")));
2692 		}
2693 
2694 		curl_easy_setopt(curlReq, CURLOPT_PROGRESSFUNCTION, &UploadDialog::UpdateProgress);
2695 		curl_easy_setopt(curlReq, CURLOPT_PROGRESSDATA, upload);
2696 		curl_easy_setopt(curlReq, CURLOPT_NOPROGRESS, 0);
2697 
2698 		UploadThread thread(curlReq, upload);
2699 		if (thread.Run() != wxTHREAD_NO_ERROR) {
2700 			wxLogError(_("Could not spawn upload thread!"));
2701 			upload->Destroy();
2702 			free(urlstr);
2703 			free(cpUF);
2704 			if (curlLogFile) {
2705 				fclose(curlLogFile);
2706 				curlLogFile = NULL;
2707 			}
2708 			return TQSL_EXIT_TQSL_ERROR;
2709 		}
2710 
2711 		upload->ShowModal();
2712 		retval = ((intptr_t)thread.Wait());
2713 	} else { retval = curl_easy_perform(curlReq); }
2714 
2715 	if (retval == 0) { //success
2716 		//check the result
2717 
2718 		wxString uplresult = wxString::FromAscii(handler.s.c_str());
2719 
2720 		wxRegEx uplStatusRE(uplStatus);
2721 		wxRegEx uplMessageRE(uplMessage);
2722 		wxRegEx stripSpacesRE(wxT("\\n +"), wxRE_ADVANCED);
2723 		wxRegEx certUploadRE(wxT("(Started processing.*For QSOs not after: [0-9\\-: <none>]*)\\n(.*Your certificate request contains error.*)"), wxRE_ADVANCED);
2724 
2725 		if (uplStatusRE.Matches(uplresult)) { //we can make sense of the error
2726 			//sometimes has leading/trailing spaces
2727 			if (uplStatusRE.GetMatch(uplresult, 1).Lower().Trim(true).Trim(false) == uplStatusSuccess) { //success
2728 				if (uplMessageRE.Matches(uplresult)) { //and a message
2729 					wxString lotwmessage = uplMessageRE.GetMatch(uplresult, 1).Trim(true).Trim(false);
2730 					stripSpacesRE.ReplaceAll(&lotwmessage, wxString(wxT("\n")));
2731 					if (fileType == wxT("Log")) {
2732 						wxLogMessage(_("%s: Log uploaded successfully with result:\n\n%s"),
2733 							infile.c_str(), lotwmessage.c_str());
2734 						wxLogMessage(_("After reading this message, you may close this program."));
2735 
2736 						retval = TQSL_EXIT_SUCCESS;
2737 					} else {
2738 						// Split the log message when there is an error detected
2739 						if (certUploadRE.Matches(lotwmessage)) {
2740 							wxString log = certUploadRE.GetMatch(lotwmessage, 1).Trim(true).Trim(false);
2741 							wxString err = certUploadRE.GetMatch(lotwmessage, 2).Trim(true).Trim(false);
2742 							wxLogMessage(_("%s uploaded with result:\n\n%s"),
2743 								fileType.c_str(), log.c_str());
2744 							wxMessageBox(wxString::Format(_("%s Uploaded with result:\n\n%s"), fileType.c_str(), err.c_str()), _("Error"), wxOK | wxICON_EXCLAMATION);
2745 							retval = TQSL_EXIT_REJECTED;
2746 						} else {
2747 							wxLogMessage(_("%s uploaded with result:\n\n%s"),
2748 								fileType.c_str(), lotwmessage.c_str());
2749 							retval = TQSL_EXIT_SUCCESS;
2750 						}
2751 					}
2752 				} else { // no message we could find
2753 					if (fileType == wxT("Log")) {
2754 						wxLogMessage(_("%s: Log uploaded successfully"), infile.c_str());
2755 						wxLogMessage(_("After reading this message, you may close this program."));
2756 					} else {
2757 						wxLogMessage(_("%s uploaded successfully"), fileType.c_str());
2758 					}
2759 					retval = TQSL_EXIT_SUCCESS;
2760 				}
2761 
2762 			} else { // failure, but site is working
2763 				if (uplMessageRE.Matches(uplresult)) { //and a message
2764 					wxLogMessage(_("%s: %s upload was rejected with result \"%s\""),
2765 						infile.c_str(), fileType.c_str(), uplMessageRE.GetMatch(uplresult, 1).c_str());
2766 
2767 				} else { // no message we could find
2768 					wxLogMessage(_("%s: %s upload was rejected"), infile.c_str(), fileType.c_str());
2769 				}
2770 
2771 				retval = TQSL_EXIT_REJECTED;
2772 			}
2773 		} else { //site isn't working
2774 			wxLogMessage(_("%s: Got an unexpected response on %s upload! Maybe the site is down?"), infile.c_str(), fileType.c_str());
2775 			retval = TQSL_EXIT_UNEXP_RESP;
2776 		}
2777 
2778 	} else {
2779 		tqslTrace("MyFrame::UploadFile", "cURL Error: %s (%s)\n", curl_easy_strerror((CURLcode)retval), errorbuf);
2780 		if (retval == CURLE_SSL_CACERT && verifyCA) {
2781 			tqslTrace("MyFrame::UploadFile", "cURL SSL Certificate error - disabling verify and retry");
2782 			verifyCA = false;
2783 			goto retry_upload;
2784 		}
2785 		if (retval == CURLE_COULDNT_RESOLVE_HOST || retval == CURLE_COULDNT_CONNECT) {
2786 			wxLogMessage(_("%s: Unable to upload - either your Internet connection is down or LoTW is unreachable."), infile.c_str());
2787 			wxLogMessage(_("Please try uploading the %s later."), fileType.c_str());
2788 			retval = TQSL_EXIT_CONNECTION_FAILED;
2789 		} else if (retval == CURLE_WRITE_ERROR || retval == CURLE_SEND_ERROR || retval == CURLE_RECV_ERROR) {
2790 			wxLogMessage(_("%s: Unable to upload. The nework is down or the LoTW site is too busy."), infile.c_str());
2791 			wxLogMessage(_("Please try uploading the %s later."), fileType.c_str());
2792 			retval = TQSL_EXIT_CONNECTION_FAILED;
2793 		} else if (retval == CURLE_SSL_CONNECT_ERROR) {
2794 			wxLogMessage(_("%s: Unable to connect to the upload site."), infile.c_str());
2795 			wxLogMessage(_("Please try uploading the %s later."), fileType.c_str());
2796 			retval = TQSL_EXIT_CONNECTION_FAILED;
2797 		} else if (retval == CURLE_ABORTED_BY_CALLBACK) { //cancelled.
2798 			wxLogMessage(_("%s: Upload cancelled"), infile.c_str());
2799 			retval = TQSL_EXIT_CANCEL;
2800 		} else { //error
2801 			//don't know why the conversion from char* -> wxString -> char* is necessary but it
2802 			// was turned into garbage otherwise
2803 			wxLogMessage(_("%s: Couldn't upload the file: CURL returned \"%hs\" (%hs)"), infile.c_str(), curl_easy_strerror((CURLcode)retval), errorbuf);
2804 			retval = TQSL_EXIT_TQSL_ERROR;
2805 		}
2806 	}
2807 	if (this && upload) upload->Destroy();
2808 
2809 	curl_formfree(post);
2810 	curl_easy_cleanup(curlReq);
2811 	curlReq = NULL;
2812 
2813 	// If there's a GUI and we didn't successfully upload and weren't cancelled,
2814 	// ask the user if we should retry the upload.
2815 	if (this && retval != TQSL_EXIT_CANCEL && retval != TQSL_EXIT_SUCCESS) {
2816 		if (wxMessageBox(_("Your upload appears to have failed. Should TQSL try again?"), _("Retry?"), wxYES_NO | wxICON_QUESTION, this) == wxYES)
2817 			goto retry_upload;
2818 	}
2819 
2820 	if (urlstr) free(urlstr);
2821 	if (cpUF) free (cpUF);
2822 	if (curlLogFile) {
2823 		fclose(curlLogFile);
2824 		curlLogFile = NULL;
2825 	}
2826 	return retval;
2827 }
2828 
2829 // Verify that a certificate exists for this station location
2830 // before allowing the location to be edited
2831 static bool verify_cert(tQSL_Location loc, bool editing) {
2832 	tqslTrace("verify_cert", "loc=%lx, editing=%d", reinterpret_cast<void *>(loc), editing);
2833 	char call[128];
2834 	tQSL_Cert *certlist;
2835 	int ncerts;
2836 	// Get the callsign from the location
2837 	check_tqsl_error(tqsl_getLocationCallSign(loc, call, sizeof(call)));
2838 	// See if there is a certificate for that call
2839 	int flags = 0;
2840 	if (editing)
2841 		flags = TQSL_SELECT_CERT_WITHKEYS | TQSL_SELECT_CERT_EXPIRED;
2842 	tqsl_selectCertificates(&certlist, &ncerts, call, 0, 0, 0, flags);
2843 	if (ncerts == 0 && strcmp(call, "NONE") && strcmp(call, "[None]")) {
2844 		if (editing) {
2845 			wxMessageBox(wxString::Format(_("There are no callsign certificates for callsign %hs. This station location cannot be edited."), call), _("No Certificate"), wxOK | wxICON_EXCLAMATION);
2846 		} else {
2847 			wxMessageBox(wxString::Format(_("There are no current callsign certificates for callsign %hs. This station location cannot be used to sign a log file."), call), _("No Certificate"), wxOK | wxICON_EXCLAMATION);
2848 		}
2849 		return false;
2850 	}
2851 	tqsl_freeCertificateList(certlist, ncerts);
2852 	return true;
2853 }
2854 
2855 tQSL_Location
2856 MyFrame::SelectStationLocation(const wxString& title, const wxString& okLabel, bool editonly) {
2857 	tqslTrace("MyFrame::SelectStationLocation", "title=%s, okLabel=%s, editonly=%d", S(title), S(okLabel), editonly);
2858 	int rval;
2859 	tQSL_Location loc;
2860 	wxString selname;
2861 	char errbuf[512];
2862 	do {
2863 		TQSLGetStationNameDialog station_dial(this, help, wxDefaultPosition, false, title, okLabel, editonly);
2864 		if (selname != wxT(""))
2865 			station_dial.SelectName(selname);
2866 		rval = station_dial.ShowModal();
2867 		switch (rval) {
2868 			case wxID_CANCEL:	// User hit Close
2869 				return 0;
2870 			case wxID_APPLY:	// User hit New
2871 				try {
2872 					check_tqsl_error(tqsl_initStationLocationCapture(&loc));
2873 					selname = run_station_wizard(this, loc, help, false);
2874 					check_tqsl_error(tqsl_endStationLocationCapture(&loc));
2875 					frame->loc_tree->Build();
2876 					break;
2877 				}
2878 				catch(TQSLException& x) {
2879 					wxLogError(wxT("%s"), x.what());
2880 					return 0;
2881 				}
2882 			case wxID_MORE:		// User hit Edit
2883 				try {
2884 					check_tqsl_error(tqsl_getStationLocation(&loc, station_dial.Selected().ToUTF8()));
2885 					if (verify_cert(loc, true)) {	// Check if there is a certificate before editing
2886 						check_tqsl_error(tqsl_getStationLocationErrors(loc, errbuf, sizeof(errbuf)));
2887 						if (strlen(errbuf) > 0) {
2888 							wxString fmt = wxT("%hs\n");
2889 							fmt += _("The invalid data was ignored.");
2890 							wxMessageBox(wxString::Format(fmt, errbuf), _("Station Location data error"), wxOK | wxICON_EXCLAMATION, this);
2891 						}
2892 						char loccall[512];
2893 						check_tqsl_error(tqsl_getLocationCallSign(loc, loccall, sizeof loccall));
2894 						selname = run_station_wizard(this, loc, help, true, wxString::Format(_("Edit Station Location : %hs - %s"), loccall, station_dial.Selected().c_str()), station_dial.Selected());
2895 						check_tqsl_error(tqsl_endStationLocationCapture(&loc));
2896 					}
2897 					break;
2898 				}
2899 				catch(TQSLException& x) {
2900 					wxLogError(wxT("%hs"), x.what());
2901 					return 0;
2902 				}
2903 			case wxID_OK:		// User hit OK
2904 				try {
2905 					check_tqsl_error(tqsl_getStationLocation(&loc, station_dial.Selected().ToUTF8()));
2906 					check_tqsl_error(tqsl_getStationLocationErrors(loc, errbuf, sizeof(errbuf)));
2907 					if (strlen(errbuf) > 0) {
2908 						wxString fmt = wxT("%hs\n");
2909 						fmt += _("This should be corrected before signing a log file.");
2910 						wxMessageBox(wxString::Format(fmt, errbuf), _("Station Location data error"), wxOK | wxICON_EXCLAMATION, this);
2911 					}
2912 					break;
2913 				}
2914 				catch(TQSLException& x) {
2915 					wxLogError(wxT("%hs"), x.what());
2916 					return 0;
2917 				}
2918 		}
2919 	} while (rval != wxID_OK);
2920 	return loc;
2921 }
2922 
2923 void MyFrame::CheckForUpdates(wxCommandEvent&) {
2924 	tqslTrace("MyFrame::CheckForUpdates", NULL);
2925 	DoUpdateCheck(false, false);
2926 }
2927 
2928 wxString GetUpdatePlatformString() {
2929 	tqslTrace("GetUpdatePlatformString", NULL);
2930 	wxString ret;
2931 #if defined(_WIN32)
2932 	#if defined(_WIN64)
2933 		//this is 64 bit code already; if we are running we support 64
2934 		ret = wxT("win64 win32");
2935 
2936 	#else // this is not 64 bit code, but we are on windows
2937 		// are we 64-bit compatible? if so prefer it
2938 		BOOL val = false;
2939 		typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL);
2940 		LPFN_ISWOW64PROCESS fnIsWow64Process =
2941 			(LPFN_ISWOW64PROCESS) GetProcAddress(GetModuleHandle(TEXT("kernel32")), "IsWow64Process");
2942 		if (fnIsWow64Process != NULL) {
2943 			fnIsWow64Process(GetCurrentProcess(), &val);
2944 		}
2945 		if(val) //32 bit running on 64 bit
2946 			ret = wxT("win64 win32");
2947 		else //just 32-bit only
2948 			ret = wxT("win32");
2949 	#endif
2950 
2951 #elif defined(__APPLE__) && defined(__MACH__) //osx has universal binaries
2952 	ret = wxT("osx");
2953 
2954 #elif defined(__gnu_linux__)
2955 	#if defined(__amd64__)
2956 		ret = wxT("linux64 linux32 source");
2957 	#elif defined(__i386__)
2958 		ret = wxT("linux32 source");
2959 	#else
2960 		ret = wxT("source"); //source distribution is kosher on linux
2961 	#endif
2962 #else
2963 	ret = wxT(""); //give them a homepage
2964 #endif
2965 	return ret;
2966 }
2967 
2968 // Class for encapsulating version information
2969 class revLevel {
2970  public:
2971 	explicit revLevel(long _major = 0, long _minor = 0, long _patch = 0) {
2972 		major = _major;
2973 		minor = _minor;
2974 		patch = _patch;
2975 	}
2976 	explicit revLevel(const wxString& _value) {
2977 		wxString str = _value;
2978 		str.Trim(true);
2979 		str.Trim(false);
2980 		wxStringTokenizer vers(str, wxT("."));
2981 		wxString majorVer = vers.GetNextToken();
2982 		wxString minorVer = vers.GetNextToken();
2983 		wxString patchVer = vers.GetNextToken();
2984 		if (majorVer.IsNumber()) {
2985 			majorVer.ToLong(&major);
2986 		} else {
2987 			major = -1;
2988 		}
2989 		if (minorVer.IsNumber()) {
2990 			minorVer.ToLong(&minor);
2991 		} else {
2992 			minor = -1;
2993 		}
2994 		if (patchVer != wxT("") && patchVer.IsNumber()) {
2995 			patchVer.ToLong(&patch);
2996 		} else {
2997 			patch = 0;
2998 		}
2999 	}
3000 	wxString Value(void) {
3001 		if (patch > 0)
3002 			return wxString::Format(wxT("%ld.%ld.%ld"), major, minor, patch);
3003 		else
3004 			return wxString::Format(wxT("%ld.%ld"), major, minor);
3005 	}
3006 	long major;
3007 	long minor;
3008 	long patch;
3009 	bool operator >(const revLevel& other) {
3010 		if (major > other.major) return true;
3011 		if (major == other.major) {
3012 			if (minor > other.minor) return true;
3013 			if (minor == other.minor) {
3014 				if (patch > other.patch) return true;
3015 			}
3016 		}
3017 		return false;
3018 	}
3019 	bool operator  >=(const revLevel& other) {
3020 		if (major > other.major) return true;
3021 		if (major == other.major) {
3022 			if (minor > other.minor) return true;
3023 			if (minor == other.minor) {
3024 				if (patch >= other.patch) return true;
3025 			}
3026 		}
3027 		return false;
3028 	}
3029 };
3030 
3031 class revInfo {
3032  public:
3033 	explicit revInfo(bool _noGUI = false, bool _silent = false) {
3034 		noGUI = _noGUI;
3035 		silent = _silent;
3036 		error = false;
3037 		message = false;
3038 		programRev = newProgramRev = NULL;
3039 		configRev = newConfigRev = NULL;
3040 		mutex = new wxMutex;
3041 		condition = new wxCondition(*mutex);
3042 		mutex->Lock();
3043 	}
3044 	~revInfo() {
3045 		if (programRev)
3046 			delete programRev;
3047 		if (newProgramRev)
3048 			delete newProgramRev;
3049 		if (configRev)
3050 			delete configRev;
3051 		if (newConfigRev)
3052 			delete newConfigRev;
3053 		if (condition)
3054 			delete condition;
3055 		if (mutex)
3056 			delete mutex;
3057 	}
3058 	bool error;
3059 	bool message;
3060 	bool noGUI;
3061 	bool silent;
3062 	revLevel* programRev;
3063 	revLevel* newProgramRev;
3064 	revLevel* configRev;
3065 	revLevel* newConfigRev;
3066 	bool newProgram;
3067 	bool newConfig;
3068 	wxString errorText;
3069 	wxString homepage;
3070 	wxString url;
3071 	wxMutex* mutex;
3072 	wxCondition* condition;
3073 };
3074 
3075 
3076 class UpdateDialogMsgBox: public wxDialog {
3077  public:
3078 	UpdateDialogMsgBox(wxWindow* parent, bool newProg, bool newConfig, revLevel* currentProgRev, revLevel* newProgRev,
3079 			revLevel* currentConfigRev, revLevel* newConfigRev, wxString platformURL, wxString homepage)
3080 			: wxDialog(parent, (wxWindowID)wxID_ANY, _("TQSL Update Available"), wxDefaultPosition, wxDefaultSize) {
3081 		tqslTrace("UpdateDialogMsgBox::UpdateDialogMsgBox", "parent=%lx, newProg=%d, newConfig=%d, currentProgRev %s, newProgRev %s, currentConfigRev %s, newConfigRev=%s, platformURL=%s, homepage=%s", reinterpret_cast<void *>(parent), newProg, newConfig, S(currentProgRev->Value()), S(newProgRev->Value()), S(currentConfigRev->Value()), S(newConfigRev->Value()), S(platformURL), S(homepage));
3082 		wxSizer* overall = new wxBoxSizer(wxVERTICAL);
3083 		long flags = wxOK;
3084 #ifndef _WIN32
3085 		if (newConfig)
3086 #endif
3087 			flags |= wxCANCEL;
3088 
3089 		wxSizer* buttons = CreateButtonSizer(flags);
3090 		wxString notice;
3091 		if (newProg)
3092 			notice = wxString::Format(_("A new TQSL release (V%s) is available!"), newProgRev->Value().c_str());
3093 		else if (newConfig)
3094 			notice = wxString::Format(_("An updated TrustedQSL configuration file (V%s) is available!\nThe configuration file installs definitions for entities, modes, etc."), newConfigRev->Value().c_str());
3095 
3096 		overall->Add(new wxStaticText(this, wxID_ANY, notice), 0, wxALIGN_CENTER_HORIZONTAL);
3097 
3098 		if (newProg) {
3099 #ifndef _WIN32
3100 			if (!platformURL.IsEmpty()) {
3101 				wxSizer* thisline = new wxBoxSizer(wxHORIZONTAL);
3102 				thisline->Add(new wxStaticText(this, wxID_ANY, _("Download from:")));
3103 				thisline->Add(new wxHyperlinkCtrl(this, wxID_ANY, platformURL, platformURL));
3104 
3105 				overall->AddSpacer(10);
3106 				overall->Add(thisline);
3107 			}
3108 
3109 			if (!homepage.IsEmpty()) {
3110 				wxSizer* thisline = new wxBoxSizer(wxHORIZONTAL);
3111 				thisline->Add(new wxStaticText(this, wxID_ANY, _("More details at:")));
3112 				thisline->Add(new wxHyperlinkCtrl(this, wxID_ANY, homepage, homepage));
3113 
3114 				overall->AddSpacer(10);
3115 				overall->Add(thisline);
3116 			}
3117 #else
3118 			overall->AddSpacer(10);
3119 			overall->Add(new wxStaticText(this, wxID_ANY, _("Click 'OK' to install the new version of TQSL, or Cancel to ignore it.")));
3120 #endif
3121 		}
3122 		if (newConfig) {
3123 			overall->AddSpacer(10);
3124 			overall->Add(new wxStaticText(this, wxID_ANY, _("Click 'OK' to install the new configuration file, or Cancel to ignore it.")));
3125 		}
3126 		if (buttons) { //should always be here but documentation says to check
3127 			overall->AddSpacer(10);
3128 			overall->Add(buttons, 0, wxALIGN_CENTER_HORIZONTAL);
3129 		}
3130 
3131 #if wxMAJOR_VERSION > 2 || (wxMAJOR_VERSION == 2 && wxMINOR_VERSION == 9)
3132 		SetSizerAndFit(overall);
3133 #else
3134 		wxSizer* padding = new wxBoxSizer(wxVERTICAL);
3135 		padding->Add(overall, 0, wxALL, 10);
3136 		SetSizer(padding);
3137 		Fit();
3138 #endif
3139 	}
3140 
3141  private:
3142 };
3143 
3144 static size_t file_recv(void *ptr, size_t size, size_t nmemb, void *stream) {
3145 	size_t left = nmemb * size;
3146 	size_t written;
3147 
3148 	while (left > 0) {
3149   		written = fwrite(ptr, size, nmemb, reinterpret_cast <FILE *>(stream));
3150 		if (written == 0)
3151 			return 0;
3152 		left -= (written * size);
3153 	}
3154 	return nmemb * size;
3155 }
3156 
3157 void MyFrame::UpdateConfigFile() {
3158 	tqslTrace("MyFrame::UpdateConfigFile", NULL);
3159 	wxConfig* config = reinterpret_cast<wxConfig *>(wxConfig::Get());
3160 	wxString newConfigURL = config->Read(wxT("NewConfigURL"), DEFAULT_CONFIG_FILE_URL);
3161  retry:
3162 	curlReq = tqsl_curl_init("Config File Download Log", (const char *)newConfigURL.ToUTF8(), &curlLogFile, false);
3163 
3164 	wxString filename;
3165 #ifdef _WIN32
3166 	filename.Printf(wxT("%hs\\config.tq6"), wxString::FromUTF8(tQSL_BaseDir));
3167 	wchar_t* lfn = utf8_to_wchar(filename.ToUTF8());
3168 	FILE *configFile = _wfopen(lfn, L"wb");
3169 	free_wchar(lfn);
3170 #else
3171 	filename.Printf(wxT("%hs/config.tq6"), tQSL_BaseDir);
3172 	FILE *configFile = fopen(filename.ToUTF8(), "wb");
3173 #endif
3174 	if (!configFile) {
3175 		tqslTrace("UpdateConfigFile", "Can't open new file %s: %s", static_cast<const char *>(filename.ToUTF8()), strerror(errno));
3176 		wxMessageBox(wxString::Format(_("Can't open new configuration file %s: %hs"), filename.c_str(), strerror(errno)), _("Error"), wxOK | wxICON_ERROR, this);
3177 		return;
3178 	}
3179 
3180 	curl_easy_setopt(curlReq, CURLOPT_WRITEFUNCTION, &file_recv);
3181 	curl_easy_setopt(curlReq, CURLOPT_WRITEDATA, configFile);
3182 
3183 	curl_easy_setopt(curlReq, CURLOPT_FAILONERROR, 1); //let us find out about a server issue
3184 
3185 	char errorbuf[CURL_ERROR_SIZE];
3186 	curl_easy_setopt(curlReq, CURLOPT_ERRORBUFFER, errorbuf);
3187 	int retval = curl_easy_perform(curlReq);
3188 	if (retval == CURLE_OK) {
3189 		if (fclose(configFile)) {
3190 			tqslTrace("UpdateConfigFile", "Error writing new file %s: %s", static_cast<const char *>(filename.ToUTF8()), strerror(errno));
3191 			wxMessageBox(wxString::Format(_("Error writing new configuration file %s: %hs"), filename.c_str(), strerror(errno)), _("Error"), wxOK | wxICON_ERROR, this);
3192 			return;
3193 		}
3194 		notifyData nd;
3195 		if (tqsl_importTQSLFile(filename.ToUTF8(), notifyImport, &nd)) {
3196 			wxLogError(getLocalizedErrorString());
3197 		} else {
3198 			tqslTrace("UpdateConfigFile", "Config update success");
3199 			wxMessageBox(_("Configuration file successfully updated"), _("Update Completed"), wxOK | wxICON_INFORMATION, this);
3200 		}
3201 	} else {
3202 		tqslTrace("MyFrame::UpdateConfigFile", "cURL Error during config file download: %s (%s)\n", curl_easy_strerror((CURLcode)retval), errorbuf);
3203 		if (retval == CURLE_SSL_CACERT && verifyCA) {
3204 			tqslTrace("MyFrame::UpdateConfigFile", "cURL SSL Certificate error - disabling verify and retry");
3205 			verifyCA = false;
3206 			goto retry;
3207 		}
3208 		if (curlLogFile) {
3209 			fprintf(curlLogFile, "cURL Error during config file download: %s (%s)\n", curl_easy_strerror((CURLcode)retval), errorbuf);
3210 		}
3211 		if (retval == CURLE_COULDNT_RESOLVE_HOST || retval == CURLE_COULDNT_CONNECT) {
3212 			wxLogMessage(_("Unable to update - either your Internet connection is down or LoTW is unreachable."));
3213 			wxLogMessage(_("Please try again later."));
3214 		} else if (retval == CURLE_WRITE_ERROR || retval == CURLE_SEND_ERROR || retval == CURLE_RECV_ERROR) {
3215 			wxLogMessage(_("Unable to update. The nework is down or the LoTW site is too busy."));
3216 			wxLogMessage(_("Please try again later."));
3217 		} else if (retval == CURLE_SSL_CONNECT_ERROR) {
3218 			wxLogMessage(_("Unable to connect to the update site."));
3219 			wxLogMessage(_("Please try again later."));
3220 		} else { // some other error
3221 			wxString fmt = _("Error downloading new configuration file:");
3222 			fmt += wxT("\n%hs");
3223 			wxMessageBox(wxString::Format(fmt, errorbuf), _("Update"), wxOK | wxICON_EXCLAMATION, this);
3224 		}
3225 	}
3226 }
3227 
3228 #ifdef _WIN32
3229 void MyFrame::UpdateTQSL(wxString& url) {
3230 	tqslTrace("MyFrame::UpdateTQSL", "url=%s", S(url));
3231  retry:
3232 	bool needToCleanUp = false;
3233 	if (curlReq) {
3234 		curl_easy_setopt(curlReq, CURLOPT_URL, (const char *)url.ToUTF8());
3235 	} else {
3236 		curlReq = tqsl_curl_init("TQSL Update Download Log", (const char *)url.ToUTF8(), &curlLogFile, false);
3237 		needToCleanUp = true;
3238 	}
3239 
3240 	wxString filename;
3241 #ifdef _WIN32
3242 	filename.Printf(wxT("%hs\\tqslupdate.msi"), wxString::FromUTF8(tQSL_BaseDir));
3243 	wchar_t* lfn = utf8_to_wchar(filename.ToUTF8());
3244 	FILE *updateFile = _wfopen(lfn, L"wb");
3245 	free_wchar(lfn);
3246 #else
3247 	filename.Printf(wxT("%hs/tqslupdate.msi"), tQSL_BaseDir);
3248 	FILE *updateFile = fopen(filename.ToUTF8(), "wb");
3249 #endif
3250 	if (!updateFile) {
3251 		tqslTrace("UpdateTQSL", "Can't open new file %s: %s", static_cast<const char *>(filename.ToUTF8()), strerror(errno));
3252 		wxMessageBox(wxString::Format(_("Can't open TQSL update file %s: %hs"), filename.c_str(), strerror(errno)), _("Error"), wxOK | wxICON_ERROR, this);
3253 		return;
3254 	}
3255 
3256 	curl_easy_setopt(curlReq, CURLOPT_WRITEFUNCTION, &file_recv);
3257 	curl_easy_setopt(curlReq, CURLOPT_WRITEDATA, updateFile);
3258 
3259 	curl_easy_setopt(curlReq, CURLOPT_FAILONERROR, 1); //let us find out about a server issue
3260 
3261 	char errorbuf[CURL_ERROR_SIZE];
3262 	curl_easy_setopt(curlReq, CURLOPT_ERRORBUFFER, errorbuf);
3263 	int retval = curl_easy_perform(curlReq);
3264 	if (retval == CURLE_OK) {
3265 		if (fclose(updateFile)) {
3266 			tqslTrace("UpdateTQSL", "Error writing new file %s: %s", static_cast<const char *>(filename.ToUTF8()), strerror(errno));
3267 			wxMessageBox(wxString::Format(_("Error writing new configuration file %s: %hs"), filename.c_str(), strerror(errno)), _("Error"), wxOK | wxICON_ERROR, this);
3268 			return;
3269 		}
3270 		wxExecute(wxT("msiexec /i ") + filename, wxEXEC_ASYNC);
3271 		wxExit();
3272 	} else {
3273 		tqslTrace("MyFrame::UpdateTQSL", "cURL Error during file download: %s (%s)\n", curl_easy_strerror((CURLcode)retval), errorbuf);
3274 		if (retval == CURLE_SSL_CACERT && verifyCA) {
3275 			tqslTrace("MyFrame::UpdateTQSL", "cURL SSL Certificate error - disabling verify and retry");
3276 			verifyCA = false;
3277 			goto retry;
3278 		}
3279 		if (curlLogFile) {
3280 			fprintf(curlLogFile, "cURL Error during file download: %s (%s)\n", curl_easy_strerror((CURLcode)retval), errorbuf);
3281 		}
3282 		if (retval == CURLE_COULDNT_RESOLVE_HOST || retval == CURLE_COULDNT_CONNECT) {
3283 			wxLogMessage(_("Unable to update - either your Internet connection is down or LoTW is unreachable."));
3284 			wxLogMessage(_("Please try again later."));
3285 		} else if (retval == CURLE_WRITE_ERROR || retval == CURLE_SEND_ERROR || retval == CURLE_RECV_ERROR) {
3286 			wxLogMessage(_("Unable to update. The nework is down or the LoTW site is too busy."));
3287 			wxLogMessage(_("Please try again later."));
3288 		} else if (retval == CURLE_SSL_CONNECT_ERROR) {
3289 			wxLogMessage(_("Unable to connect to the update site."));
3290 			wxLogMessage(_("Please try again later."));
3291 		} else { // some other error
3292 			wxString fmt = _("Error downloading new file:");
3293 			fmt += wxT("\n%hs");
3294 			wxMessageBox(wxString::Format(fmt, errorbuf), _("Update"), wxOK | wxICON_EXCLAMATION, this);
3295 		}
3296 	}
3297 	if (needToCleanUp) {
3298 		curl_easy_cleanup(curlReq);
3299 		curlReq = NULL;
3300 	}
3301 }
3302 #endif /* _WIN32 */
3303 
3304 // Check if a certificate is still valid and current at LoTW
3305 bool MyFrame::CheckCertStatus(long serial, wxString& result) {
3306 	tqslTrace("MyFrame::CheckCertStatus()", "Serial=%ld", serial);
3307 	wxConfig* config = reinterpret_cast<wxConfig *>(wxConfig::Get());
3308 
3309 	wxString certCheckURL = config->Read(wxT("CertCheckURL"), DEFAULT_CERT_CHECK_URL);
3310 	wxString certCheckRE = config->Read(wxT("StatusRegex"), DEFAULT_CERT_CHECK_RE);
3311 	certCheckURL = certCheckURL + wxString::Format(wxT("%ld"), serial);
3312 	bool needToCleanUp = false;
3313 
3314 	if (curlReq == NULL) {
3315 		needToCleanUp = true;
3316 		curlReq = tqsl_curl_init("checkCert", certCheckURL.ToUTF8(), &curlLogFile, false);
3317 	} else {
3318 		curl_easy_setopt(curlReq, CURLOPT_URL, (const char *)certCheckURL.ToUTF8());
3319 	}
3320 
3321 	FileUploadHandler handler;
3322 
3323 	curl_easy_setopt(curlReq, CURLOPT_WRITEFUNCTION, &FileUploadHandler::recv);
3324 	curl_easy_setopt(curlReq, CURLOPT_WRITEDATA, &handler);
3325 
3326 	curl_easy_setopt(curlReq, CURLOPT_FAILONERROR, 1); //let us find out about a server issue
3327 
3328 	char errorbuf[CURL_ERROR_SIZE];
3329 	errorbuf[0] = '\0';
3330 	curl_easy_setopt(curlReq, CURLOPT_ERRORBUFFER, errorbuf);
3331 	int retval = curl_easy_perform(curlReq);
3332 	result = wxString(wxT("Unknown"));
3333 	bool ret = false;
3334 	if (retval == CURLE_OK) {
3335 		wxString checkresult = wxString::FromAscii(handler.s.c_str());
3336 
3337 		wxRegEx checkStatusRE(certCheckRE);
3338 
3339 		if (checkStatusRE.Matches(checkresult)) { // valid response
3340 			result = checkStatusRE.GetMatch(checkresult, 1).Trim(true).Trim(false);
3341 			ret = true;
3342 		}
3343 	} else {
3344 		tqslTrace("MyFrame::CheckCertStatus", "cURL Error during cert status check: %s (%s)\n", curl_easy_strerror((CURLcode)retval), errorbuf);
3345 		if (curlLogFile) {
3346 			fprintf(curlLogFile, "cURL Error during cert status check: %s (%s)\n", curl_easy_strerror((CURLcode)retval), errorbuf);
3347 		}
3348 		if (retval == CURLE_SSL_CACERT && verifyCA) {
3349 			verifyCA = false;
3350 		}
3351 	}
3352 	if (needToCleanUp) {
3353 		curl_easy_cleanup(curlReq);
3354 		curlReq = NULL;
3355 	}
3356 	return ret;
3357 }
3358 
3359 class expInfo {
3360  public:
3361 	explicit expInfo(bool _noGUI = false) {
3362 		noGUI = _noGUI;
3363 		days = 0;
3364 		callsign = NULL;
3365 		error = false;
3366 		mutex = new wxMutex;
3367 		condition = new wxCondition(*mutex);
3368 		mutex->Lock();
3369 	}
3370 	bool noGUI;
3371 	int days;
3372 	tQSL_Cert cert;
3373 	char* callsign;
3374 	bool error;
3375 	wxString errorText;
3376 	wxMutex* mutex;
3377 	wxCondition* condition;
3378 	~expInfo() {
3379 		if (callsign) free(callsign);
3380 		if (condition) delete condition;
3381 		if (mutex) delete mutex;
3382 	}
3383 };
3384 
3385 // Report an error back to the main thread
3386 static void
3387 report_error(expInfo **eip) {
3388 	expInfo *ei = *eip;
3389 	ei->error = true;
3390 	ei->errorText = getLocalizedErrorString();
3391 	// Send the result back to the main thread
3392 	wxCommandEvent* event = new wxCommandEvent(wxEVT_COMMAND_MENU_SELECTED, bg_expiring);
3393 	event->SetClientData(ei);
3394 	wxPostEvent(frame, *event);
3395 	ei->condition->Wait();		// stalls here until the main thread resumes the thread
3396 	ei->mutex->Unlock();
3397 	delete ei;
3398 	delete event;
3399 	*eip = new expInfo;
3400 }
3401 
3402 // Check for certificates expiring in the next nn (default 60) days
3403 void
3404 MyFrame::DoCheckExpiringCerts(bool noGUI) {
3405 	tQSL_Cert *clist;
3406 	int nc;
3407 
3408 	tqsl_selectCertificates(&clist, &nc, 0, 0, 0, 0, TQSL_SELECT_CERT_WITHKEYS | TQSL_SELECT_CERT_EXPIRED);
3409 	if (nc == 0) return;
3410 
3411 	expInfo *ei = new expInfo;
3412 	ei->noGUI = noGUI;
3413 
3414 	bool needToCleanUp = false;
3415 
3416         if (curlReq == NULL) {
3417                 needToCleanUp = true;
3418 		curlReq = tqsl_curl_init("Certificate Check Log", "https://lotw.arrl.org", &curlLogFile, false);
3419         }
3420 
3421 	long expireDays = DEFAULT_CERT_WARNING;
3422 	wxConfig::Get()->Read(wxT("CertWarnDays"), &expireDays);
3423 	// Get today's date
3424 	time_t t = time(0);
3425 	struct tm *tm = gmtime(&t);
3426 	tQSL_Date d;
3427 	d.year = tm->tm_year + 1900;
3428 	d.month = tm->tm_mon + 1;
3429 	d.day = tm->tm_mday;
3430 	tQSL_Date exp;
3431 
3432 	for (int i = 0; i < nc; i ++) {
3433 		ei->cert = clist[i];
3434 		char callsign[64];
3435 		if (tqsl_getCertificateCallSign(clist[i], callsign, sizeof callsign)) {
3436 			report_error(&ei);
3437 			continue;
3438 		}
3439 		wxString county;
3440 		wxString grid;
3441 
3442 		// Get the user detail info for this callsign from the ARRL server
3443 		SaveAddressInfo(callsign);
3444 
3445 		int expired = 0;
3446 		tqsl_isCertificateExpired(clist[i], &expired);
3447 		if (expired) continue;			// Don't check expired already
3448 
3449 		int keyonly, pending;
3450 		keyonly = pending = 0;
3451 		if (tqsl_getCertificateKeyOnly(clist[i], &keyonly)) {
3452 			report_error(&ei);
3453 			continue;
3454 		}
3455 		long serial = 0;
3456 		wxString status = wxString(wxT("KeyOnly"));
3457 		if (!keyonly) {
3458 			if (tqsl_getCertificateSerial(clist[i], &serial)) {
3459 				report_error(&ei);
3460 				continue;
3461 			}
3462 			CheckCertStatus(serial, status);
3463 			if (tqsl_setCertificateStatus(serial, (const char *)status.ToUTF8())) {
3464 				report_error(&ei);
3465 				continue;
3466 			}
3467 		}
3468 		wxString reqPending = wxConfig::Get()->Read(wxT("RequestPending"));
3469 		wxStringTokenizer tkz(reqPending, wxT(","));
3470 		while (tkz.HasMoreTokens()) {
3471 			wxString pend = tkz.GetNextToken();
3472 			if (pend == wxString::FromUTF8(callsign)) {
3473 				pending = true;
3474 				break;
3475 			}
3476 		}
3477 
3478 		if (keyonly || pending)
3479 			continue;
3480 
3481 		if (0 == tqsl_getCertificateNotAfterDate(clist[i], &exp)) {
3482 			int days_left;
3483 			tqsl_subtractDates(&d, &exp, &days_left);
3484 			if (days_left < expireDays) {
3485 				ei->days = days_left;
3486 				ei->callsign = strdup(callsign);
3487 				ei->cert = clist[i];
3488 				// Send the result back to the main thread
3489 				wxCommandEvent* event = new wxCommandEvent(wxEVT_COMMAND_MENU_SELECTED, bg_expiring);
3490 				event->SetClientData(ei);
3491 				wxPostEvent(frame, *event);
3492 				ei->condition->Wait();
3493 				ei->mutex->Unlock();
3494 				delete ei;
3495 				delete event;
3496 				ei = new expInfo;
3497 				ei->noGUI = noGUI;
3498 			}
3499 		}
3500 	}
3501 	ei->mutex->Unlock();
3502 	delete ei;
3503 	if (clist) {
3504 		tqsl_freeCertificateList(clist, nc);
3505 	}
3506 	if (needToCleanUp) {
3507 		curl_easy_cleanup(curlReq);
3508 		curlReq = NULL;
3509 		if (curlLogFile) {
3510 			fclose(curlLogFile);
3511 			curlLogFile = NULL;
3512 		}
3513 	}
3514 	return;
3515 }
3516 
3517 void
3518 MyFrame::OnExpiredCertFound(wxCommandEvent& event) {
3519 	expInfo *ei = reinterpret_cast<expInfo *>(event.GetClientData());
3520 	if (ei->error) {
3521 		if (ei->noGUI) {
3522 			wxLogError(ei->errorText);
3523 		} else {
3524 			wxMessageBox(wxString(_("Error checking for expired callsign certificates:")) + ei->errorText,
3525 				_("Check Error"), wxOK | wxICON_EXCLAMATION, this);
3526 		}
3527 	} else if (ei->noGUI) {
3528 		wxLogMessage(_("The certificate for %hs expires in %d days."),
3529 			ei->callsign, ei->days);
3530 	} else {
3531 		wxString fmt = _("The certificate for %hs expires in %d days");
3532 			fmt += wxT("\n");
3533 			fmt += _("Do you want to renew it now?");
3534 		if (wxMessageBox(wxString::Format(fmt, ei->callsign, ei->days),
3535 					_("Certificate Expiring"), wxYES_NO|wxICON_QUESTION, this) == wxYES) {
3536 				// Select the certificate in the tree
3537 				cert_tree->SelectCert(ei->cert);
3538 				// Then start the renewal
3539 				wxCommandEvent dummy;
3540 				CRQWizardRenew(dummy);
3541 		}
3542 	}
3543 	// Tell the background thread that it's OK to continue
3544 	wxMutexLocker lock(*ei->mutex);
3545 	ei->condition->Signal();
3546 }
3547 
3548 void
3549 MyFrame::OnUpdateCheckDone(wxCommandEvent& event) {
3550 	revInfo *ri = reinterpret_cast<revInfo *>(event.GetClientData());
3551 	if (!ri) return;
3552 	if (ri->error) {
3553 		if (!ri->silent && !ri->noGUI)
3554 			wxLogMessage(ri->errorText);
3555 		wxMutexLocker lock(*ri->mutex);
3556 		ri->condition->Signal();
3557 		return;
3558 	}
3559 	if (ri->message) {
3560 		if (!ri->silent && !ri->noGUI)
3561 			wxMessageBox(ri->errorText, _("Update"), wxOK | wxICON_EXCLAMATION, this);
3562 		wxMutexLocker lock(*ri->mutex);
3563 		ri->condition->Signal();
3564 		return;
3565 	}
3566 	if (ri->newProgram) {
3567 		if (ri->noGUI) {
3568 			wxLogMessage(_("A new TQSL release (V%s) is available."), ri->newProgramRev->Value().c_str());
3569 		} else {
3570 			//will create ("homepage"->"") if none there, which is what we'd be checking for anyway
3571 			UpdateDialogMsgBox msg(this, true, false, ri->programRev, ri->newProgramRev,
3572 					ri->configRev, ri->newConfigRev, ri->url, ri->homepage);
3573 
3574 #ifdef _WIN32
3575 			if (msg.ShowModal() == wxID_OK) {
3576 				UpdateTQSL(ri->url);
3577 			}
3578 #else
3579 			msg.ShowModal();
3580 #endif
3581 		}
3582 	}
3583 	if (ri->newConfig) {
3584 		if (ri->noGUI) {
3585 			wxLogMessage(_("A new TrustedQSL configuration file (V%s) is available."), ri->newConfigRev->Value().c_str());
3586 		} else {
3587 			UpdateDialogMsgBox msg(this, false, true, ri->programRev, ri->newProgramRev,
3588 					ri->configRev, ri->newConfigRev, wxT(""), wxT(""));
3589 
3590 			if (msg.ShowModal() == wxID_OK) {
3591 				UpdateConfigFile();
3592 			}
3593 		}
3594 	}
3595 
3596 	if (!ri->newProgram && !ri->newConfig) {
3597 		if (!ri->silent && !ri->noGUI) {
3598 			wxString fmt = _("Your system is up to date");
3599 			fmt += wxT("\n");
3600 			fmt += _("TQSL Version %hs and Configuration Data Version %s");
3601 			fmt += wxT("\n");
3602 			fmt += _("are the newest available");
3603 			wxMessageBox(wxString::Format(fmt, VERSION, ri->configRev->Value().c_str()), _("No Updates"), wxOK | wxICON_INFORMATION, this);
3604 		}
3605 	}
3606 	// Tell the background thread that it's OK to continue
3607 	wxMutexLocker lock(*ri->mutex);
3608 	ri->condition->Signal();
3609 }
3610 
3611 // The macro for declaring a hash map defines a couple of typedefs
3612 // that it never uses. Current GCC warns about those. The pragma
3613 // below suppresses those warnings for those.
3614 #if !defined(__APPLE__) && !defined(_WIN32) && !defined(__clang__)
3615 	#pragma GCC diagnostic ignored "-Wunused-local-typedefs"
3616 #endif
3617 void
3618 MyFrame::DoCheckForUpdates(bool silent, bool noGUI) {
3619 	tqslTrace("MyFrame::DoCheckForUpdates", "silent=%d noGUI=%d", silent, noGUI);
3620 	wxConfig* config = reinterpret_cast<wxConfig *>(wxConfig::Get());
3621 
3622 	wxString lastUpdateTime = config->Read(wxT("UpdateCheckTime"));
3623 	int numdays = config->Read(wxT("UpdateCheckInterval"), 1); // in days
3624 
3625 	bool check = true;
3626 	bool networkError = false;
3627 	if (!lastUpdateTime.IsEmpty()) {
3628 		wxDateTime lastcheck; lastcheck.ParseFormat(lastUpdateTime, wxT("%Y-%m-%d"));
3629 		lastcheck+=wxDateSpan::Days(numdays); // x days from when we checked
3630 		if (lastcheck > wxDateTime::Today()) //if we checked less than x days ago
3631 			check = false;  // don't check again
3632 	} // else no stored value, means check
3633 
3634 	if (!silent) check = true; //unless the user explicitly asked
3635 
3636 	if (!check) return;	//if we really weren't supposed to check, get out of here
3637 
3638 	revInfo* ri = new revInfo;
3639 	ri->noGUI = noGUI;
3640 	ri->silent = silent;
3641 	ri->programRev = new revLevel(wxT(VERSION));
3642 
3643 	int currentConfigMajor, currentConfigMinor;
3644 	tqsl_getConfigVersion(&currentConfigMajor, &currentConfigMinor);
3645 	ri->configRev = new revLevel(currentConfigMajor, currentConfigMinor, 0);		// config files don't have patch levels
3646 
3647 	wxString updateURL = config->Read(wxT("UpdateURL"), DEFAULT_UPD_URL);
3648 
3649 	bool needToCleanUp = false;
3650  retry:
3651 	if (curlReq) {
3652 		curl_easy_setopt(curlReq, CURLOPT_URL, (const char *)updateURL.ToUTF8());
3653 	} else {
3654 		needToCleanUp = true;
3655 		curlReq = tqsl_curl_init("Version Check Log", (const char*)updateURL.ToUTF8(), &curlLogFile, false);
3656 	}
3657 
3658 	//the following allow us to analyze our file
3659 
3660 	FileUploadHandler handler;
3661 
3662 	curl_easy_setopt(curlReq, CURLOPT_WRITEFUNCTION, &FileUploadHandler::recv);
3663 	curl_easy_setopt(curlReq, CURLOPT_WRITEDATA, &handler);
3664 
3665 	if (silent) { // if there's a problem, we don't want the program to hang while we're starting it
3666 		curl_easy_setopt(curlReq, CURLOPT_CONNECTTIMEOUT, 120);
3667 	}
3668 
3669 	curl_easy_setopt(curlReq, CURLOPT_FAILONERROR, 1); //let us find out about a server issue
3670 
3671 	char errorbuf[CURL_ERROR_SIZE];
3672 	curl_easy_setopt(curlReq, CURLOPT_ERRORBUFFER, errorbuf);
3673 
3674 	tqslTrace("MyFrame::DoCheckForUpdates", "calling curl_easy_perform");
3675 	int retval = curl_easy_perform(curlReq);
3676 	tqslTrace("MyFrame::DoCheckForUpdates", "Program rev check returns %d", retval);
3677 	if (retval == CURLE_OK) {
3678 		tqslTrace("MyFrame::DoCheckForUpdates", "Program rev returns %d chars, %s", handler.s.size(), handler.s.c_str());
3679 		// Add the config.xml text to the result
3680 		wxString configURL = config->Read(wxT("ConfigFileVerURL"), DEFAULT_UPD_CONFIG_URL);
3681 		curl_easy_setopt(curlReq, CURLOPT_URL, (const char*)configURL.ToUTF8());
3682 
3683 		retval = curl_easy_perform(curlReq);
3684 		if (retval == CURLE_OK) {
3685 			tqslTrace("MyFrame::DoCheckForUpdates", "Prog + Config rev returns %d chars, %s", handler.s.size(), handler.s.c_str());
3686 			wxString result = wxString::FromAscii(handler.s.c_str());
3687 			wxString url;
3688 			WX_DECLARE_STRING_HASH_MAP(wxString, URLHashMap);
3689 			URLHashMap map;
3690 			ri->newProgramRev = NULL;
3691 			ri->newConfigRev = NULL;
3692 
3693 			wxStringTokenizer urls(result, wxT("\n"));
3694 			wxString onlinever;
3695 			while(urls.HasMoreTokens()) {
3696 				wxString header = urls.GetNextToken().Trim();
3697 				if (header.StartsWith(wxT("TQSLVERSION;"), &onlinever)) {
3698 					ri->newProgramRev = new revLevel(onlinever);
3699 				} else if (header.IsEmpty()) {
3700 					continue; //blank line
3701 				} else if (header[0] == '#') {
3702 					continue; //comments
3703 				} else if (header.StartsWith(wxT("config.xml"), &onlinever)) {
3704 					onlinever.Replace(wxT(":"), wxT(""));
3705 					onlinever.Replace(wxT("Version"), wxT(""));
3706 					onlinever.Trim(true);
3707 					onlinever.Trim(false);
3708 					ri->newConfigRev = new revLevel(onlinever);
3709 				} else {
3710 					int sep = header.Find(';'); //; is invalid in URLs
3711 					if (sep == wxNOT_FOUND) continue; //malformed string
3712 					wxString plat = header.Left(sep);
3713 					wxString url = header.Right(header.size()-sep-1);
3714 					map[plat] = url;
3715 				}
3716 			}
3717 #ifdef TQSL_TEST_BUILD
3718 			ri->newProgram = ri->newProgramRev ? (*ri->newProgramRev >= *ri->programRev) : false;
3719 #else
3720 			ri->newProgram = ri->newProgramRev ? (*ri->newProgramRev > *ri->programRev) : false;
3721 #endif
3722 			ri->newConfig = ri->newConfigRev ? (*ri->newConfigRev > *ri->configRev) : false;
3723 			if (ri->newProgram) {
3724 				wxString ourPlatURL; //empty by default (we check against this later)
3725 
3726 				wxStringTokenizer plats(GetUpdatePlatformString(), wxT(" "));
3727 				while(plats.HasMoreTokens()) {
3728 					wxString tok = plats.GetNextToken();
3729 					//see if this token is here
3730 					if (map.count(tok)) { ourPlatURL=map[tok]; break; }
3731 				}
3732 				ri->homepage = map[wxT("homepage")];
3733 				ri->url = ourPlatURL;
3734 			}
3735 		} else {
3736 			tqslTrace("MyFrame::DoCheckForUpdates", "cURL Error during config file version check: %d : %s (%s)\n", retval, curl_easy_strerror((CURLcode)retval), errorbuf);
3737 			if (curlLogFile) {
3738 				fprintf(curlLogFile, "cURL Error during config file version check: %s (%s)\n", curl_easy_strerror((CURLcode)retval), errorbuf);
3739 			}
3740 			if (retval == CURLE_COULDNT_RESOLVE_HOST || retval == CURLE_COULDNT_CONNECT) {
3741 				networkError = true;
3742 				ri->error = true;
3743 				ri->errorText = wxString(_("Unable to check for updates - either your Internet connection is down or LoTW is unreachable."));
3744 				ri->errorText += wxT("\n");
3745 				ri->errorText += _("Please try again later.");
3746 			} else if (retval == CURLE_WRITE_ERROR || retval == CURLE_SEND_ERROR || retval == CURLE_RECV_ERROR) {
3747 				networkError = true;
3748 				ri->error = true;
3749 				ri->errorText = wxString(_("Unable to check for updates. The nework is down or the LoTW site is too busy."));
3750 				ri->errorText += wxT("\n");
3751 				ri->errorText += _("Please try again later.");
3752 			} else if (retval == CURLE_SSL_CONNECT_ERROR) {
3753 				networkError = true;
3754 				ri->error = true;
3755 				ri->errorText = wxString(_("Unable to connect to the update site."));
3756 				ri->errorText += wxT("\n");
3757 				ri->errorText += _("Please try again later.");
3758 			} else { // some other error
3759 				ri->message = true;
3760 				wxString fmt = _("Error downloading new version information:");
3761 				fmt += wxT("\n%hs");
3762 				ri->errorText = wxString::Format(fmt, errorbuf);
3763 			}
3764 		}
3765 	} else {
3766 		tqslTrace("MyFrame::DoCheckForUpdates", "cURL Error during program revision check: %d: %s (%s)\n", retval, curl_easy_strerror((CURLcode)retval), errorbuf);
3767 		if (retval == CURLE_SSL_CACERT && verifyCA) {
3768 			tqslTrace("MyFrame::DoCheckForUpdates", "cURL SSL Certificate error - disabling verify and retry");
3769 			verifyCA = false;
3770 			goto retry;
3771 		}
3772 		if (curlLogFile) {
3773 			fprintf(curlLogFile, "cURL Error during program revision check: %s (%s)\n", curl_easy_strerror((CURLcode)retval), errorbuf);
3774 		}
3775 		if (retval == CURLE_COULDNT_RESOLVE_HOST || retval == CURLE_COULDNT_CONNECT) {
3776 			networkError = true;
3777 			ri->error = true;
3778 			ri->errorText = wxString(_("Unable to check for updates - either your Internet connection is down or LoTW is unreachable."));
3779 			ri->errorText += wxT("\n");
3780 			ri->errorText += _("Please try again later.");
3781 		} else if (retval == CURLE_WRITE_ERROR || retval == CURLE_SEND_ERROR || retval == CURLE_RECV_ERROR) {
3782 			networkError = true;
3783 			ri->error = true;
3784 			ri->errorText = wxString(_("Unable to check for updates. The nework is down or the LoTW site is too busy."));
3785 			ri->errorText += wxT("\n");
3786 			ri->errorText += _("Please try again later.");
3787 		} else if (retval == CURLE_SSL_CONNECT_ERROR) {
3788 			networkError = true;
3789 			ri->error = true;
3790 			ri->errorText = wxString(_("Unable to connect to the update site."));
3791 			ri->errorText += wxT("\n");
3792 			ri->errorText += _("Please try again later.");
3793 		} else { // some other error
3794 			ri->message = true;
3795 			wxString fmt = _("Error downloading update version information:");
3796 			fmt += wxT("\n%hs");
3797 			ri->errorText = wxString::Format(fmt, errorbuf);
3798 		}
3799 	}
3800 
3801 	// Send the result back to the main thread
3802 	wxCommandEvent* event = new wxCommandEvent(wxEVT_COMMAND_MENU_SELECTED, bg_updateCheck);
3803 	event->SetClientData(ri);
3804 	wxPostEvent(frame, *event);
3805 	ri->condition->Wait();
3806 	ri->mutex->Unlock();
3807 	delete ri;
3808 	delete event;
3809 	if (needToCleanUp) {
3810 		if (curlReq) curl_easy_cleanup(curlReq);
3811 		if (curlLogFile) fclose(curlLogFile);
3812 		curlReq = NULL;
3813 		curlLogFile = NULL;
3814 	}
3815 
3816 	// we checked today, and whatever the result, no need to (automatically) check again until the next interval
3817 
3818 	config->Write(wxT("UpdateCheckTime"), wxDateTime::Today().FormatISODate());
3819 
3820 	// After update check, validate user certificates
3821 	if (!networkError)
3822 		DoCheckExpiringCerts(noGUI);
3823 	return;
3824 }
3825 #if !defined(__APPLE__) && !defined(_WIN32) && !defined(__clang__)
3826 	#pragma GCC diagnostic warning "-Wunused-local-typedefs"
3827 #endif
3828 
3829 static void
3830 wx_tokens(const wxString& str, vector<wxString> &toks) {
3831 	size_t idx = 0;
3832 	size_t newidx;
3833 	wxString tok;
3834 	do {
3835 		newidx = str.find(wxT(" "), idx);
3836 		if (newidx != wxString::npos) {
3837 			toks.push_back(str.Mid(idx, newidx - idx));
3838 			idx = newidx + 1;
3839 		}
3840 	} while (newidx != wxString::npos);
3841 	if (str.Mid(idx) != wxT(""))
3842 		toks.push_back(str.Mid(idx));
3843 }
3844 
3845 int
3846 MyFrame::SaveAddressInfo(const char *callsign) {
3847 	if (callsign == NULL) {
3848 		tQSL_Error = TQSL_ARGUMENT_ERROR;
3849 		return 1;
3850 	}
3851 
3852 	bool needToCleanUp = false;
3853 	char url[512];
3854 	strncpy(url, (wxString::Format(wxT("https://lotw.arrl.org/tqsl-setup.php?callsign=%hs"), callsign)).ToUTF8(), sizeof url);
3855 	tqslTrace("MyFrame::SaveAddressInfo", "Call = %s, url = %s", callsign, url);
3856 	if (curlReq) {
3857 		curl_easy_setopt(curlReq, CURLOPT_URL, url);
3858 	} else {
3859 		curlReq = tqsl_curl_init("checkLoc", url, &curlLogFile, false);
3860 		needToCleanUp = true;
3861 	}
3862 	FileUploadHandler handler;
3863 
3864 	curl_easy_setopt(curlReq, CURLOPT_WRITEFUNCTION, &FileUploadHandler::recv);
3865 	curl_easy_setopt(curlReq, CURLOPT_WRITEDATA, &handler);
3866 
3867 	curl_easy_setopt(curlReq, CURLOPT_FAILONERROR, 1); //let us find out about a server issue
3868 
3869 	char errorbuf[CURL_ERROR_SIZE];
3870 	errorbuf[0] = '\0';
3871 	curl_easy_setopt(curlReq, CURLOPT_ERRORBUFFER, errorbuf);
3872 	int retval = curl_easy_perform(curlReq);
3873 	wxString checkresult = wxT("");
3874 
3875 	if (needToCleanUp) {
3876 		if (curlLogFile)
3877 			fclose(curlLogFile);
3878 		curl_easy_cleanup(curlReq);
3879 		curlReq = NULL;
3880 	}
3881 
3882 	if (retval == CURLE_OK) {
3883 		if (handler.s != "null") {
3884 			tqslTrace("MyFrame::SaveAddressInfo", "callsign=%s, result = %s", callsign, handler.s.c_str());
3885 			tqsl_saveCallsignLocationInfo(callsign, handler.s.c_str());
3886 		}
3887 	} else {
3888 		tqslTrace("save_address_info", "cURL Error during cert status check: %s (%s)\n", curl_easy_strerror((CURLcode)retval), errorbuf);
3889 		return 1;
3890 	}
3891 
3892 	return 0;
3893 }
3894 
3895 int
3896 get_address_field(const char *callsign, const char *field, string& result) {
3897 	typedef map<string, string>LocMap;
3898 	static LocMap locInfo;
3899 
3900 	if (callsign == NULL || field == NULL) {
3901 		tQSL_Error = TQSL_ARGUMENT_ERROR;
3902 		return 1;
3903 	}
3904 
3905 	result = "";
3906 
3907 	if (strcmp(callsign, "[None]") == 0) {
3908 		return 1;
3909 	}
3910 
3911 	if (locInfo["call"] ==  callsign) { // Already got data for this call
3912 		LocMap::iterator it;
3913 
3914 		it = locInfo.find(field);
3915 		if (it == locInfo.end()) {
3916 			return 1;
3917 		}
3918 		string temp = it->second;
3919 		if (temp.empty() || temp == "null") {
3920 			return 1;
3921 		}
3922 		result = temp;
3923 		return 0;
3924 	}
3925 
3926 	locInfo.clear();
3927 
3928 	char *buf;
3929 	if (tqsl_getCallsignLocationInfo(callsign, &buf)) {
3930 		return 1;
3931 	}
3932 	wxString checkresult = wxString::FromUTF8(buf);
3933 
3934 	wxJSONReader reader;
3935 	wxJSONValue root;
3936 
3937 	int errors = reader.Parse(checkresult, &root);
3938 	if (errors > 0)
3939 		return 1;
3940 
3941 	locInfo["call"] = callsign;
3942 
3943 	locInfo["status"] = root[wxT("Status")].AsString().ToUTF8();
3944 	if (locInfo["status"] != "OK") {
3945 		return 1;
3946 	}
3947 	locInfo["dxcc"] = root[wxT("DXCC_Entity")].AsString().ToUTF8();
3948 	locInfo["entity"] = root[wxT("DXCC_Entity_Name")].AsString().ToUTF8();
3949 	locInfo["country"] = root[wxT("Country")].AsString().ToUTF8();
3950 	locInfo["state"] = root[wxT("STATE")].AsString().ToUTF8();
3951 	wxStringTokenizer toker(root[wxT("CNTY")].AsString(), wxT(","));
3952 	wxString state = toker.GetNextToken();
3953 	wxString cty = toker.GetNextToken();
3954 	locInfo["county"] = cty.ToUTF8();
3955 	if (locInfo["state"] == "") {
3956 		locInfo["state"] = state.ToUTF8();
3957 	}
3958 	locInfo["grid"] = root[wxT("GRID")].AsString().ToUTF8();
3959 	locInfo["pas"] = root[wxT("Primary_Admnistrative_Subdivision")].AsString().ToUTF8();
3960 	locInfo["sas"] = root[wxT("Secondary_Admnistrative_Subdivision")].AsString().ToUTF8();
3961 	locInfo["address"] = root[wxT("Address_In")].AsString().ToUTF8();
3962 	string grids;
3963 	wxJSONValue gridlist = root[wxT("VUCC_Grids")];
3964 	for (int x = 0; x < gridlist.Size(); x++) {
3965 		if (!grids.empty()) {
3966 			grids = grids + "|";
3967 		}
3968 		grids = grids + ((string)gridlist[x].AsString().ToUTF8());
3969 	}
3970 	locInfo["grids"] = grids;
3971 	wxString first = root[wxT("Source_address_components")][wxT("first_name")].AsString();
3972 	wxString middle = root[wxT("Source_address_components")][wxT("middle_name")].AsString();
3973 	wxString last = root[wxT("Source_address_components")][wxT("last_name")].AsString();
3974 
3975 	wxString name;
3976 	if (middle == wxT(""))
3977 		name = first + wxT(" ") + last;
3978 	else
3979 		name = first + wxT(" ") + middle + wxT(" ") + last;
3980 	if (!name.IsEmpty())
3981 		locInfo["name"] = ((string)name.ToUTF8());
3982 
3983 	locInfo["addr1"] = root[wxT("Source_address_components")][wxT("addr1")].AsString().ToUTF8();
3984 	locInfo["addr2"] = root[wxT("Source_address_components")][wxT("addr2")].AsString().ToUTF8();
3985 	locInfo["addr3"] = root[wxT("Source_address_components")][wxT("addr3")].AsString().ToUTF8();
3986 	locInfo["city"] = root[wxT("Source_address_components")][wxT("city")].AsString().ToUTF8();
3987 	locInfo["addrState"] = root[wxT("Source_address_components")][wxT("state")].AsString().ToUTF8();
3988 	locInfo["mailCode"] = root[wxT("Source_address_components")][wxT("mail_code")].AsString().ToUTF8();
3989 	locInfo["aCountry"] = root[wxT("Source_address_components")][wxT("country")].AsString().ToUTF8();
3990 
3991 	LocMap::iterator it;
3992 
3993 	it = locInfo.find(field);
3994 	if (it == locInfo.end()) {
3995 		return 1;
3996 	}
3997 	string temp = it->second;
3998 	if (temp.empty() || temp == "null") {
3999 		return 1;
4000 	}
4001 	result = temp;
4002 	return 0;
4003 }
4004 
4005 // Common method for sign and (save, upload) a log
4006 void
4007 MyFrame::ProcessQSODataFile(bool upload, bool compressed) {
4008 	tqslTrace("MyFrame::ProcessQSODataFile", "upload=%d, compressed=%d", upload, compressed);
4009 	wxString infile;
4010 	wxString outfile;
4011 
4012 	// Does the user have any certificates?
4013 	get_certlist("", 0, false, false, true);
4014 	if (ncerts == 0) {
4015 		wxString msg = _("You have no callsign certificates to use to sign a log file.");
4016 			msg += wxT("\n");
4017 			msg += _("Please install a callsign certificate then try again.");
4018 		wxMessageBox(msg, _("No Callsign Certificates"),
4019 			   wxOK | wxICON_EXCLAMATION, this);
4020 		free_certlist();
4021 		return;
4022 	}
4023 	free_certlist();
4024 	try {
4025 		wxConfig *config = reinterpret_cast<wxConfig *>(wxConfig::Get());
4026 		// Get input file
4027 		wxString path = config->Read(wxT("ImportPath"), wxString(wxT("")));
4028 		wxString defext = config->Read(wxT("ImportExtension"), wxString(wxT("adi"))).Lower();
4029 		bool defFound = false;
4030 
4031 		// Construct filter string for file-open dialog
4032 		wxString filter = wxT("All files (*.*)|*.*");
4033 		vector<wxString> exts;
4034 		wxString file_exts = config->Read(wxT("ADIFFiles"), wxString(DEFAULT_ADIF_FILES));
4035 		wx_tokens(file_exts, exts);
4036 		wxString extList;
4037 		for (int i = 0; i < static_cast<int>(exts.size()); i++) {
4038 			extList += wxT("*.") + exts[i] + wxT(";");
4039 			if (exts[i] == defext)
4040 				defFound = true;
4041 		}
4042 		extList.RemoveLast();		// Remove the trailing semicolon
4043 		filter += _("|ADIF files (") + extList + wxT(")|") + extList;
4044 
4045 		exts.clear();
4046 		extList.Clear();
4047 		file_exts = config->Read(wxT("CabrilloFiles"), wxString(DEFAULT_CABRILLO_FILES));
4048 		wx_tokens(file_exts, exts);
4049 		for (int i = 0; i < static_cast<int>(exts.size()); i++) {
4050 			extList += wxT("*.") + exts[i] + wxT(";");
4051 			if (exts[i] == defext)
4052 				defFound = true;
4053 		}
4054 		extList.RemoveLast();
4055 		filter += _("|Cabrillo files (") + extList + wxT(")|") + extList;
4056 		if (defext.IsEmpty() || !defFound)
4057 			defext = wxString(wxT("adi"));
4058 		infile = wxFileSelector(_("Select file to Sign"), path, wxT(""), defext, filter,
4059 			wxFD_OPEN|wxFD_FILE_MUST_EXIST, this);
4060 		if (infile == wxT(""))
4061 			return;
4062 		wxString inPath;
4063 		wxString inExt;
4064 		wxFileName::SplitPath(infile.c_str(), &inPath, NULL, &inExt);
4065 		inExt.Lower();
4066 		config->Write(wxT("ImportPath"), inPath);
4067 		config->Write(wxT("ImportExtension"), inExt);
4068 		if (!upload) {
4069 			// Get output file
4070 			wxString basename;
4071 			wxFileName::SplitPath(infile.c_str(), 0, &basename, 0);
4072 			path = wxConfig::Get()->Read(wxT("ExportPath"), wxString(wxT("")));
4073 			wxString deftype = compressed ? wxT("tq8") : wxT("tq7");
4074 			filter = compressed ? _("TQSL compressed data files (*.tq8)|*.tq8")
4075 				: _("TQSL data files (*.tq7)|*.tq7");
4076 			basename += wxT(".") + deftype;
4077 			outfile = wxFileSelector(_("Select file to write to"),
4078 				path, basename, deftype, filter + _("|All files (*.*)|*.*"),
4079 				wxFD_SAVE|wxFD_OVERWRITE_PROMPT, this);
4080 			if (outfile == wxT(""))
4081 				return;
4082 			config->Write(wxT("ExportPath"), wxPathOnly(outfile));
4083 		}
4084 
4085 		// Get Station Location
4086 		int n;
4087 		tQSL_Location loc;
4088 		check_tqsl_error(tqsl_initStationLocationCapture(&loc));
4089 		check_tqsl_error(tqsl_getNumStationLocations(loc, &n));
4090 		if (n != 1) {
4091 			check_tqsl_error(tqsl_endStationLocationCapture(&loc));
4092 			loc = SelectStationLocation(_("Select Station Location for Signing"));
4093 		} else {
4094 			// There's only one station location. Use that and don't prompt.
4095 			char deflocn[512];
4096 			check_tqsl_error(tqsl_getStationLocationName(loc, 0, deflocn, sizeof deflocn));
4097 			check_tqsl_error(tqsl_endStationLocationCapture(&loc));
4098 			check_tqsl_error(tqsl_getStationLocation(&loc, deflocn));
4099 		}
4100 		if (loc == 0)
4101 			return;
4102 
4103 		if (!verify_cert(loc, false))
4104 			return;
4105 		char callsign[40];
4106 		char loc_name[256];
4107 		int dxccnum;
4108 		check_tqsl_error(tqsl_getLocationCallSign(loc, callsign, sizeof callsign));
4109 		check_tqsl_error(tqsl_getLocationDXCCEntity(loc, &dxccnum));
4110 		check_tqsl_error(tqsl_getStationLocationCaptureName(loc, loc_name, sizeof loc_name));
4111 		DXCC dxcc;
4112 		dxcc.getByEntity(dxccnum);
4113 		tqslTrace("MyFrame::ProcessQSODataFile", "file=%s location %hs, call %hs dxcc %hs",
4114 				S(infile), loc_name, callsign, dxcc.name());
4115 		if (strcmp(callsign, "[None]")) {
4116 			wxString fmt = _("The file (%s) will be signed using:");
4117 				 fmt += wxT("\n");
4118 				 fmt += _("Station Location:");
4119 				 fmt += wxT(" %hs\n");
4120 				 fmt += _("Call sign:");
4121 				 fmt += wxT(" %hs\n");
4122 				 fmt += _("DXCC:");
4123 				 fmt += wxT(" %hs\n");
4124 				 fmt += _("Is this correct?");
4125 			if (wxMessageBox(wxString::Format(fmt, infile.c_str(), loc_name,
4126 				callsign, dxcc.name()), _("TQSL - Confirm signing"), wxYES_NO | wxICON_QUESTION, this) == wxYES) {
4127 				if (upload) {
4128 					UploadLogFile(loc, infile);
4129 				} else {
4130 					ConvertLogFile(loc, infile, outfile, compressed);
4131 				}
4132 			} else {
4133 				wxLogMessage(_("Signing abandoned"));
4134 			}
4135 		} else {
4136 			if (upload) {
4137 				UploadLogFile(loc, infile);
4138 			} else {
4139 				ConvertLogFile(loc, infile, outfile, compressed);
4140 			}
4141 		}
4142 		check_tqsl_error(tqsl_endStationLocationCapture(&loc));
4143 	}
4144 	catch(TQSLException& x) {
4145 		wxString s;
4146 		wxString err = wxString::FromUTF8(x.what());
4147 		if (err.Find(infile) == wxNOT_FOUND) {
4148 			if (infile != wxT(""))
4149 				s = infile + wxT(": ");
4150 		}
4151 		s += err;
4152 		wxLogError(wxT("%s"), (const char *)s.c_str());
4153 	}
4154 	free_certlist();
4155 	return;
4156 }
4157 
4158 void
4159 MyFrame::ImportQSODataFile(wxCommandEvent& event) {
4160 	tqslTrace("MyFrame::ImportQSODataFile", NULL);
4161 
4162 	bool compressed = (event.GetId() == tm_f_import_compress || event.GetId() == tl_Save);
4163 	ProcessQSODataFile(false, compressed);
4164 	return;
4165 }
4166 
4167 void
4168 MyFrame::UploadQSODataFile(wxCommandEvent& event) {
4169 	tqslTrace("MyFrame::UploadQSODataFile", NULL);
4170 	ProcessQSODataFile(true, true);
4171 	return;
4172 }
4173 
4174 void MyFrame::OnPreferences(wxCommandEvent& WXUNUSED(event)) {
4175 	tqslTrace("MyFrame::OnPreferences", NULL);
4176 	Preferences* dial = new Preferences(this, help);
4177 	dial->Show(true);
4178 	file_menu->Enable(tm_f_preferences, false);
4179 }
4180 
4181 class TQSLConfig {
4182  public:
4183 	TQSLConfig() {
4184 		callSign = "";
4185 		serial = 0;
4186 		dxcc = 0;
4187 		dupes = 0;
4188 		elementBody = wxT("");
4189 		locstring = wxT("");
4190 		config = NULL;
4191 		outstr = NULL;
4192 		conv = NULL;
4193 	}
4194 	void SaveSettings(gzFile* out, wxString appname);
4195 	void RestoreCert(void);
4196 	void RestoreConfig(const gzFile& in);
4197 	void ParseLocations(gzFile* out, const tQSL_StationDataEnc loc);
4198 	wxConfig *config;
4199 	long serial;
4200 	int dxcc;
4201 	int dupes;
4202 	string callSign;
4203 	wxString signedCert;
4204 	wxString privateKey;
4205 	wxString elementBody;
4206 	wxString locstring;
4207 	gzFile* outstr;
4208 	tQSL_Converter conv;
4209 
4210  private:
4211 	static void xml_restore_start(void *data, const XML_Char *name, const XML_Char **atts);
4212 	static void xml_restore_end(void *data, const XML_Char *name);
4213 	static void xml_text(void *data, const XML_Char *text, int len);
4214 	static void xml_location_start(void *data, const XML_Char *name, const XML_Char **atts);
4215 	static void xml_location_end(void *data, const XML_Char *name);
4216 };
4217 
4218 // Save the user's configuration settings - appname is the
4219 // application name (tqslapp)
4220 
4221 void TQSLConfig::SaveSettings(gzFile* out, wxString appname) {
4222 	tqslTrace("TQSLConfig::SaveSettings", "appname=%s", S(appname));
4223 	config = new wxConfig(appname);
4224 	wxString name, gname;
4225 	long	context;
4226 	wxString svalue;
4227 	long	lvalue;
4228 	bool	bvalue;
4229 	double	dvalue;
4230 	wxArrayString groupNames;
4231 
4232 	tqslTrace("TQSLConfig::SaveSettings", "... init groups");
4233 	groupNames.Add(wxT("/"));
4234 	bool more = config->GetFirstGroup(gname, context);
4235 	while (more) {
4236 		tqslTrace("TQSLConfig::SaveSettings", "... add group %s", S(name));
4237 		groupNames.Add(wxT("/") + gname);
4238 		more = config->GetNextGroup(gname, context);
4239 	}
4240 	tqslTrace("TQSLConfig::SaveSettings", "... groups done.");
4241 
4242 	for (unsigned i = 0; i < groupNames.GetCount(); i++) {
4243 		tqslTrace("TQSLConfig::SaveSettings", "Group %d setting path %s", i, S(groupNames[i]));
4244 		int err;
4245 		config->SetPath(groupNames[i]);
4246 		more = config->GetFirstEntry(name, context);
4247 		while (more) {
4248 			tqslTrace("TQSLConfig::SaveSettings", "name=%s", S(name));
4249 			if (name.IsEmpty()) {
4250 				more = config->GetNextEntry(name, context);
4251 				continue;
4252 			}
4253 			if (gzprintf(*out, "<Setting name=\"%s\" group=\"%s\" ",
4254 					(const char *)name.ToUTF8(), (const char *)groupNames[i].ToUTF8()) < 0) {
4255 				throw TQSLException(gzerror(*out, &err));
4256 			}
4257 			wxConfigBase::EntryType etype = config->GetEntryType(name);
4258 			switch (etype) {
4259 				case wxConfigBase::Type_Unknown:
4260 				case wxConfigBase::Type_String:
4261 					config->Read(name, &svalue);
4262 					long testlong;
4263 					if (svalue.ToLong(&testlong)) {
4264 						if (gzprintf(*out, "Type=\"Int\" Value=\"%d\"/>\n", testlong) < 0) {
4265 							throw TQSLException(gzerror(*out, &err));
4266 						}
4267 					} else {
4268 						urlEncode(svalue);
4269 						if (gzprintf(*out, "Type=\"String\" Value=\"%s\"/>\n",
4270 								(const char *)svalue.ToUTF8()) < 0) {
4271 							throw TQSLException(gzerror(*out, &err));
4272 						}
4273 					}
4274 					break;
4275 				case wxConfigBase::Type_Boolean:
4276 					config->Read(name, &bvalue);
4277 					if (bvalue) {
4278 						if (gzprintf(*out, "Type=\"Bool\" Value=\"true\"/>\n") < 0) {
4279 							throw TQSLException(gzerror(*out, &err));
4280 						}
4281 					} else {
4282 						if (gzprintf(*out, "Type=\"Bool\" Value=\"false\"/>\n") < 0) {
4283 							throw TQSLException(gzerror(*out, &err));
4284 						}
4285 					} break;
4286 				case wxConfigBase::Type_Integer:
4287 					config->Read(name, &lvalue);
4288 					if (gzprintf(*out, "Type=\"Int\" Value=\"%d\"/>\n", lvalue) < 0)
4289 						throw TQSLException(gzerror(*out, &err));
4290 					break;
4291 				case wxConfigBase::Type_Float:
4292 					config->Read(name, &dvalue);
4293 					if (gzprintf(*out, "Type=\"Float\" Value=\"%f\"/>\n", dvalue) < 0)
4294 						throw TQSLException(gzerror(*out, &err));
4295 					break;
4296 			}
4297 			more = config->GetNextEntry(name, context);
4298 		}
4299 	}
4300 	tqslTrace("TQSLConfig::SaveSettings", "Done.");
4301 	config->SetPath(wxT("/"));
4302 
4303 	return;
4304 }
4305 
4306 void
4307 MyFrame::BackupConfig(const wxString& filename, bool quiet) {
4308 	tqslTrace("MyFrame::BackupConfig", "filename=%s, quiet=%d", S(filename), quiet);
4309 	int i;
4310 	gzFile out = 0;
4311 	int err;
4312 	wxBusyCursor wait;
4313 #ifdef _WIN32
4314 	int fd = -1;
4315 #endif
4316 	if (lock_db(false) < 0) {
4317 		if (quiet)			// If there's an active signing thread,
4318 			return;			// then exit without taking a backup.
4319 		wxSafeYield();
4320 		wxLogMessage(_("TQSL must wait for other running copies of TQSL to exit before backing up..."));
4321 		wxSafeYield();
4322 		lock_db(true);
4323 	}
4324 	try {
4325 #ifdef _WIN32
4326 		wchar_t* lfn = utf8_to_wchar(filename.ToUTF8());
4327 		fd = _wopen(lfn, _O_WRONLY |_O_CREAT|_O_BINARY, _S_IREAD|_S_IWRITE);
4328 		free_wchar(lfn);
4329 		if (fd != -1)
4330 			out = gzdopen(fd, "wb9");
4331 #else
4332 		out = gzopen(filename.ToUTF8(), "wb9");
4333 #endif
4334 		if (!out) {
4335 			wxLogError(_("Error opening save file %s: %hs"), filename.c_str(), strerror(errno));
4336 			return;
4337 		}
4338 		TQSLConfig* conf = new TQSLConfig();
4339 
4340 		if (gzprintf(out, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<TQSL_Configuration>\n") < 0)
4341 			throw TQSLException(gzerror(out, &err));
4342 		if (gzprintf(out, "<!-- Warning! If you directly edit this file, you are responsible for its content.\n") < 0)
4343 			throw TQSLException(gzerror(out, &err));
4344 		if (gzprintf(out, "The ARRL's LoTW Help Desk will be unable to assist you. -->\n") < 0)
4345 			throw TQSLException(gzerror(out, &err));
4346 		if (gzprintf(out, "<Certificates>\n") < 0)
4347 			throw TQSLException(gzerror(out, &err));
4348 
4349 		if (!quiet) {
4350 			wxLogMessage(_("Saving callsign certificates"));
4351 		} else {
4352 			tqslTrace("MyFrame::BackupConfig", "Saving callsign certificates");
4353 		}
4354 		wxSafeYield(frame);
4355 		int ncerts;
4356 		char buf[8192];
4357 		// Save root certificates
4358 		check_tqsl_error(tqsl_selectCACertificates(&certlist, &ncerts, "root"));
4359 		for (i = 0; i < ncerts; i++) {
4360 			if (gzprintf(out, "<RootCert>\n") < 0)
4361 				throw TQSLException(gzerror(out, &err));
4362 			check_tqsl_error(tqsl_getCertificateEncoded(certlist[i], buf, sizeof buf));
4363 			if (gzwrite(out, buf, strlen(buf)) < 0)
4364 				throw TQSLException(gzerror(out, &err));
4365 			if (gzprintf(out, "</RootCert>\n") < 0)
4366 				throw TQSLException(gzerror(out, &err));
4367 		}
4368 		tqsl_freeCertificateList(certlist, ncerts);
4369 		// Save CA certificates
4370 		check_tqsl_error(tqsl_selectCACertificates(&certlist, &ncerts, "authorities"));
4371 		for (i = 0; i < ncerts; i++) {
4372 			if (gzprintf(out, "<CACert>\n") < 0)
4373 				throw TQSLException(gzerror(out, &err));
4374 			check_tqsl_error(tqsl_getCertificateEncoded(certlist[i], buf, sizeof buf));
4375 			if (gzwrite(out, buf, strlen(buf)) < 0)
4376 				throw TQSLException(gzerror(out, &err));
4377 			if (gzprintf(out, "</CACert>\n") < 0)
4378 				throw TQSLException(gzerror(out, &err));
4379 		}
4380 		tqsl_freeCertificateList(certlist, ncerts);
4381 		tqsl_selectCertificates(&certlist, &ncerts, 0, 0, 0, 0, TQSL_SELECT_CERT_WITHKEYS | TQSL_SELECT_CERT_EXPIRED | TQSL_SELECT_CERT_SUPERCEDED);
4382 		for (i = 0; i < ncerts; i++) {
4383 			char callsign[64];
4384 			long serial = 0;
4385 			int dxcc = 0;
4386 			int keyonly;
4387 			check_tqsl_error(tqsl_getCertificateKeyOnly(certlist[i], &keyonly));
4388 			check_tqsl_error(tqsl_getCertificateCallSign(certlist[i], callsign, sizeof callsign));
4389 			if (!keyonly) {
4390 				check_tqsl_error(tqsl_getCertificateSerial(certlist[i], &serial));
4391 			}
4392 			check_tqsl_error(tqsl_getCertificateDXCCEntity(certlist[i], &dxcc));
4393 			if (!quiet) {
4394 				wxLogMessage(wxString(wxT("\t")) + _("Saving callsign certificate for %hs"), callsign);
4395 			}
4396 			if (gzprintf(out, "<UserCert CallSign=\"%s\" dxcc=\"%d\" serial=\"%d\">\n", callsign, dxcc, serial) < 0)
4397 				throw TQSLException(gzerror(out, &err));
4398 			if (!keyonly) {
4399 				if (gzprintf(out, "<SignedCert>\n") < 0)
4400 					throw TQSLException(gzerror(out, &err));
4401 				check_tqsl_error(tqsl_getCertificateEncoded(certlist[i], buf, sizeof buf));
4402 				if (gzwrite(out, buf, strlen(buf)) < 0)
4403 					throw TQSLException(gzerror(out, &err));
4404 				if (gzprintf(out, "</SignedCert>\n") < 0)
4405 					throw TQSLException(gzerror(out, &err));
4406 			}
4407 			// Handle case where there's no private key
4408 			if (tqsl_getKeyEncoded(certlist[i], buf, sizeof buf) == 0) {
4409 				if (gzprintf(out, "<PrivateKey>\n") < 0)
4410 					throw TQSLException(gzerror(out, &err));
4411 				if (gzwrite(out, buf, strlen(buf)) < 0)
4412 					throw TQSLException(gzerror(out, &err));
4413 				if (gzprintf(out, "</PrivateKey>\n</UserCert>\n") < 0)
4414 					throw TQSLException(gzerror(out, &err));
4415 			} else {
4416 				// No private key.
4417 				if (gzprintf(out, "</UserCert>\n") < 0)
4418 					throw TQSLException(gzerror(out, &err));
4419 			}
4420 		}
4421 		free_certlist();
4422 		if (gzprintf(out, "</Certificates>\n") < 0)
4423 			throw TQSLException(gzerror(out, &err));
4424 		if (gzprintf(out, "<Locations>\n") < 0)
4425 			throw TQSLException(gzerror(out, &err));
4426 		if (!quiet) {
4427 			wxLogMessage(_("Saving Station Locations"));
4428 		} else {
4429 			tqslTrace("MyFrame::BackupConfig", "Saving Station Locations");
4430 		}
4431 		wxSafeYield(frame);
4432 		tQSL_StationDataEnc sdbuf = NULL;
4433 		check_tqsl_error(tqsl_getStationDataEnc(&sdbuf));
4434 		TQSLConfig* parser = new TQSLConfig();
4435 		if (sdbuf)
4436 			parser->ParseLocations(&out, sdbuf);
4437 		check_tqsl_error(tqsl_freeStationDataEnc(sdbuf));
4438 		if (gzprintf(out, "</Locations>\n") < 0)
4439 			throw TQSLException(gzerror(out, &err));
4440 
4441 		if (!quiet) {
4442 			wxLogMessage(_("Saving TQSL Preferences"));
4443 		} else {
4444 			tqslTrace("MyFrame::BackupConfig", "Saving TQSL Preferences - out=0x%lx", reinterpret_cast<void *>(out));
4445 		}
4446 		wxSafeYield(frame);
4447 		if (gzprintf(out, "<TQSLSettings>\n") < 0)
4448 			throw TQSLException(gzerror(out, &err));
4449 		conf->SaveSettings(&out, wxT("tqslapp"));
4450 		tqslTrace("MyFrame::BackupConfig", "Done with settings. out=0x%lx", reinterpret_cast<void *>(out));
4451 		if (gzprintf(out, "</TQSLSettings>\n") < 0)
4452 			throw TQSLException(gzerror(out, &err));
4453 
4454 		if (!quiet) {
4455 			wxLogMessage(_("Saving QSOs"));
4456 		} else {
4457 			tqslTrace("MyFrame::BackupConfig", "Saving QSOs");
4458 		}
4459 
4460 		wxSafeYield(frame);
4461 		tQSL_Converter conv = NULL;
4462 		check_tqsl_error(tqsl_beginConverter(&conv));
4463 		tqslTrace("MyFrame::BackupConfig", "beginConverter call success");
4464 		if (gzprintf(out, "<DupeDb>\n") < 0)
4465 			throw TQSLException(gzerror(out, &err));
4466 
4467 		char dupekey[256];
4468 		char dupedata[256];
4469 		int count = 0;
4470 		while (true) {
4471 			int status = tqsl_getDuplicateRecordsV2(conv, dupekey, dupedata, sizeof(dupekey));
4472 			if (status == -1)		// End of file
4473 				break;
4474 			check_tqsl_error(status);
4475 			wxString dk = wxString::FromUTF8(dupekey);
4476 			wxString dd = wxString::FromUTF8(dupedata);
4477 			dk = urlEncode(dk);
4478 			dd = urlEncode(dd);
4479 			strncpy(dupekey, dk.ToUTF8(), sizeof dupekey);
4480 			strncpy(dupedata, dd.ToUTF8(), sizeof dupedata);
4481 			if (gzprintf(out, "<Dupe key=\"%s\" data=\"%s\" />\n", dupekey, dupedata) < 0)
4482 				throw TQSLException(gzerror(out, &err));
4483 			if ((count++ % 100000) == 0) {
4484 				wxSafeYield(frame);
4485 			}
4486 		}
4487 		if (gzprintf(out, "</DupeDb>\n") < 0)
4488 			throw TQSLException(gzerror(out, &err));
4489 		tqsl_converterCommit(conv);
4490 		tqsl_endConverter(&conv);
4491 		unlock_db();
4492 		tqslTrace("MyFrame::BackupConfig", "Dupes db saved OK");
4493 
4494 		if (gzprintf(out, "</TQSL_Configuration>\n") < 0)
4495 			throw TQSLException(gzerror(out, &err));
4496 		if (gzclose(out) != Z_OK)
4497 			throw TQSLException(gzerror(out, &err));
4498 		if (!quiet) {
4499 			wxLogMessage(_("Save operation complete."));
4500 		} else {
4501 			tqslTrace("MyFrame::BackupConfig", "Save operation complete.");
4502 		}
4503 	}
4504 	catch(TQSLException& x) {
4505 		if (out) gzclose(out);
4506 		if (quiet) {
4507 			wxString errmsg = wxString::Format(_("Error performing automatic backup: %hs"), x.what());
4508 			wxMessageBox(errmsg, _("Backup Error"), wxOK | wxICON_EXCLAMATION, this);
4509 		} else {
4510 			wxLogError(_("Backup operation failed: %hs"), x.what());
4511 		}
4512 	}
4513 }
4514 
4515 void
4516 MyFrame::OnSaveConfig(wxCommandEvent& WXUNUSED(event)) {
4517 	tqslTrace("MyFrame::OnSaveConfig", NULL);
4518 	try {
4519 		wxString file_default = wxT("tqslconfig.tbk");
4520 		wxString filename = wxFileSelector(_("Enter file to save to"), wxT(""),
4521 			file_default, wxT(".tbk"), _("Configuration files (*.tbk)|*.tbk|All files (*.*)|*.*"),
4522 			wxFD_SAVE|wxFD_OVERWRITE_PROMPT, this);
4523 		if (filename == wxT(""))
4524 			return;
4525 
4526 		BackupConfig(filename, false);
4527 	}
4528 	catch(TQSLException& x) {
4529 		wxLogError(_("Backup operation failed: %hs"), x.what());
4530 	}
4531 }
4532 
4533 
4534 void
4535 restore_user_cert(TQSLConfig* loader) {
4536 	tqslTrace("restore_user_cert", "Restoring certificate for callsign %s", loader->callSign.c_str());
4537 	get_certlist(loader->callSign.c_str(), loader->dxcc, true, true, true);
4538 	for (int i = 0; i < ncerts; i++) {
4539 		long serial;
4540 		int dxcc;
4541 		int keyonly;
4542 		check_tqsl_error(tqsl_getCertificateKeyOnly(certlist[i], &keyonly));
4543 		check_tqsl_error(tqsl_getCertificateDXCCEntity(certlist[i], &dxcc));
4544 		if (!keyonly) {
4545 			check_tqsl_error(tqsl_getCertificateSerial(certlist[i], &serial));
4546 			if (serial == loader->serial && dxcc == loader->dxcc) {
4547 				return;			// This certificate is already installed.
4548 			}
4549 		} else {
4550 			// See if the keyonly cert is the one we're trying to load
4551 			char buf[8192];
4552 			check_tqsl_error(tqsl_getKeyEncoded(certlist[i], buf, sizeof buf));
4553 			if (!strcmp(buf, loader->privateKey.ToUTF8())) {
4554 				return;			// Already installed
4555 			}
4556 		}
4557 	}
4558 
4559 	// There is no certificate matching this callsign/entity/serial.
4560 	wxLogMessage(wxString(wxT("\t")) + _("Restoring callsign certificate for %hs"), loader->callSign.c_str());
4561 	wxSafeYield(frame);
4562 	check_tqsl_error(tqsl_importKeyPairEncoded(loader->callSign.c_str(), "user", loader->privateKey.ToUTF8(), loader->signedCert.ToUTF8()));
4563 }
4564 
4565 void
4566 restore_root_cert(TQSLConfig* loader) {
4567 	int rval = tqsl_importKeyPairEncoded(NULL, "root", NULL, loader->signedCert.ToUTF8());
4568 	if (rval && tQSL_Error != TQSL_CERT_ERROR)
4569 		check_tqsl_error(rval);
4570 }
4571 
4572 void
4573 restore_ca_cert(TQSLConfig* loader) {
4574 	int rval = tqsl_importKeyPairEncoded(NULL, "authorities", NULL, loader->signedCert.ToUTF8());
4575 	if (rval && tQSL_Error != TQSL_CERT_ERROR)
4576 		check_tqsl_error(rval);
4577 }
4578 
4579 void
4580 TQSLConfig::xml_restore_start(void *data, const XML_Char *name, const XML_Char **atts) {
4581 	TQSLConfig* loader = reinterpret_cast<TQSLConfig *> (data);
4582 	int i;
4583 
4584 	loader->elementBody = wxT("");
4585 	if (strcmp(name, "UserCert") == 0) {
4586 		for (int i = 0; atts[i]; i+=2) {
4587 			if (strcmp(atts[i], "CallSign") == 0) {
4588 				loader->callSign = atts[i + 1];
4589 			} else if (strcmp(atts[i], "serial") == 0) {
4590 				if (strlen(atts[i+1]) == 0) {
4591 					loader->serial = 0;
4592 				} else {
4593 					loader->serial =  strtol(atts[i+1], NULL, 10);
4594 				}
4595 			} else if (strcmp(atts[i], "dxcc") == 0) {
4596 				if (strlen(atts[i+1]) == 0) {
4597 					loader->dxcc = 0;
4598 				} else {
4599 					loader->dxcc =  strtol(atts[i+1], NULL, 10);
4600 				}
4601 			}
4602 		}
4603 		loader->privateKey = wxT("");
4604 		loader->signedCert = wxT("");
4605 	} else if (strcmp(name, "TQSLSettings") == 0) {
4606 		wxLogMessage(_("Restoring Preferences"));
4607 		wxSafeYield(frame);
4608 		loader->config = new wxConfig(wxT("tqslapp"));
4609 	} else if (strcmp(name, "Setting") == 0) {
4610 		wxString sname;
4611 		wxString sgroup;
4612 		wxString stype;
4613 		wxString svalue;
4614 		for (i = 0; atts[i]; i+=2) {
4615 			if (strcmp(atts[i], "name") == 0) {
4616 				sname = wxString::FromUTF8(atts[i+1]);
4617 			} else if (strcmp(atts[i], "group") == 0) {
4618 				sgroup = wxString::FromUTF8(atts[i+1]);
4619 			} else if (strcmp(atts[i], "Type") == 0) {
4620 				stype = wxString::FromUTF8(atts[i+1]);
4621 			} else if (strcmp(atts[i], "Value") == 0) {
4622 				svalue = wxString::FromUTF8(atts[i+1]);
4623 			}
4624 		}
4625 		// Don't restore wxHtmlWindow as these settings are OS-specific.
4626 		if (sgroup != wxT("wxHtmlWindow")) {
4627 			loader->config->SetPath(sgroup);
4628 			if (stype == wxT("String")) {
4629 				svalue.Replace(wxT("&lt;"), wxT("<"), true);
4630 				svalue.Replace(wxT("&gt;"), wxT(">"), true);
4631 				svalue.Replace(wxT("&amp;"), wxT("&"), true);
4632 				if (sname == wxT("BackupFolder") && !svalue.IsEmpty()) {
4633 					// If it's the backup directory, don't restore it if the
4634 					// referenced directory doesn't exist.
4635 #ifdef _WIN32
4636 					struct _stat32 s;
4637 					wchar_t* lfn = utf8_to_wchar(svalue.ToUTF8());
4638 					int ret = _wstat32(lfn, &s);
4639 					free_wchar(lfn);
4640 					if (ret == 0) {
4641 #else
4642 					struct stat s;
4643 					if (lstat(svalue.ToUTF8(), &s) == 0) {		// Does it exist?
4644 #endif
4645 						if (S_ISDIR(s.st_mode)) {		// And is it a directory?
4646 							loader->config->Write(sname, svalue); // OK to use it.
4647 						}
4648 					}
4649 				} else {
4650 					loader->config->Write(sname, svalue);
4651 				}
4652 			} else if (stype == wxT("Bool")) {
4653 				bool bsw = (svalue == wxT("true"));
4654 				loader->config->Write(sname, bsw);
4655 			} else if (stype == wxT("Int")) {
4656 				long lval = strtol(svalue.ToUTF8(), NULL, 10);
4657 				loader->config->Write(sname, lval);
4658 			} else if (stype == wxT("Float")) {
4659 				double dval = strtod(svalue.ToUTF8(), NULL);
4660 				loader->config->Write(sname, dval);
4661 			}
4662 			loader->config->SetPath(wxT("/"));
4663 		}
4664 	} else if (strcmp(name, "Locations") == 0) {
4665 		wxLogMessage(_("Restoring Station Locations"));
4666 		wxSafeYield(frame);
4667 		loader->locstring = wxT("<StationDataFile>\n");
4668 	} else if (strcmp(name, "Location") == 0) {
4669 		for (i = 0; atts[i]; i+=2) {
4670 			wxString attval = wxString::FromUTF8(atts[i+1]);
4671 			if (strcmp(atts[i], "name") == 0) {
4672 				tqslTrace("TQSLConfig::xml_restore_start", "Restoring location %s", atts[i+1]);
4673 				loader->locstring += wxT("<StationData name=\"") + urlEncode(attval) + wxT("\">\n");
4674 				break;
4675 			}
4676 		}
4677 		for (i = 0; atts[i]; i+=2) {
4678 			wxString attname = wxString::FromUTF8(atts[i]);
4679 			wxString attval = wxString::FromUTF8(atts[i+1]);
4680 			if (strcmp(atts[i], "name") != 0) {
4681 				loader->locstring += wxT("<") + attname + wxT(">") +
4682 					urlEncode(attval) + wxT("</") + attname + wxT(">\n");
4683 			}
4684 		}
4685 	} else if (strcmp(name, "DupeDb") == 0) {
4686 		wxLogMessage(_("Restoring QSO records"));
4687 		wxSafeYield(frame);
4688 		check_tqsl_error(tqsl_beginConverter(&loader->conv));
4689 	} else if (strcmp(name, "Dupe") == 0) {
4690 		const char *dupekey = NULL;
4691 		const char *dupedata = NULL;
4692 		for (i = 0; atts[i]; i+=2) {
4693 			if (strcmp(atts[i], "key") == 0) {
4694 				dupekey = atts[i+1];
4695 			}
4696 			if (strcmp(atts[i], "data") == 0) {
4697 				dupedata = atts[i+1];
4698 			}
4699 		}
4700 		if (dupedata == NULL) {
4701 			dupedata = "D"; // Old school dupe record
4702 		}
4703 		int status = tqsl_putDuplicateRecord(loader->conv,  dupekey, dupedata, dupekey ? strlen(dupekey) : 0);
4704 		if (status > 0) {		// Error writing that record
4705 			check_tqsl_error(status);
4706 		}
4707 		if ((loader->dupes++ % 100000) == 0) {
4708 			wxSafeYield(frame);
4709 		}
4710 	}
4711 }
4712 
4713 void
4714 TQSLConfig::xml_restore_end(void *data, const XML_Char *name) {
4715 	TQSLConfig* loader = reinterpret_cast<TQSLConfig *> (data);
4716 	if (strcmp(name, "SignedCert") == 0) {
4717 		loader->signedCert = loader->elementBody.Trim(false);
4718 	} else if (strcmp(name, "PrivateKey") == 0) {
4719 		loader->privateKey = loader->elementBody.Trim(false);
4720 	} else if (strcmp(name, "RootCert") == 0) {
4721 		loader->signedCert = loader->elementBody.Trim(false);
4722 		restore_root_cert(loader);
4723 	} else if (strcmp(name, "CACert") == 0) {
4724 		loader->signedCert = loader->elementBody.Trim(false);
4725 		restore_ca_cert(loader);
4726 	} else if (strcmp(name, "UserCert") == 0) {
4727 		restore_user_cert(loader);
4728 	} else if (strcmp(name, "Location") == 0) {
4729 		loader->locstring += wxT("</StationData>\n");
4730 	} else if (strcmp(name, "Locations") == 0) {
4731 		loader->locstring += wxT("</StationDataFile>\n");
4732 		tqslTrace("TQSLConfig::xml_restore_end", "Merging station locations");
4733 		if (tqsl_mergeStationLocations(loader->locstring.ToUTF8()) != 0) {
4734 			char buf[500];
4735 			strncpy(buf, getLocalizedErrorString().ToUTF8(), sizeof buf);
4736 			wxLogError(wxString::Format(wxString(wxT("\t")) + _("Error importing station locations: %hs"), buf));
4737 		}
4738 		tqslTrace("TQSLConfig::xml_restore_end", "Completed merging station locations");
4739 	} else if (strcmp(name, "TQSLSettings") == 0) {
4740 		loader->config->Flush(false);
4741 	} else if (strcmp(name, "DupeDb") == 0) {
4742 		check_tqsl_error(tqsl_converterCommit(loader->conv));
4743 		check_tqsl_error(tqsl_endConverter(&loader->conv));
4744 	}
4745 	loader->elementBody = wxT("");
4746 }
4747 
4748 void
4749 TQSLConfig::xml_location_start(void *data, const XML_Char *name, const XML_Char **atts) {
4750 	TQSLConfig* parser = reinterpret_cast<TQSLConfig *> (data);
4751 	int err;
4752 
4753 	if (strcmp(name, "StationDataFile") == 0)
4754 		return;
4755 	if (strcmp(name, "StationData") == 0) {
4756 		wxString locname = wxString::FromUTF8(atts[1]);
4757 		urlEncode(locname);
4758 		if (gzprintf(*parser->outstr, "<Location name=\"%s\"", (const char *)locname.ToUTF8()) < 0)
4759 			throw TQSLException(gzerror(*parser->outstr, &err));
4760 	}
4761 }
4762 void
4763 TQSLConfig::xml_location_end(void *data, const XML_Char *name) {
4764 	TQSLConfig* parser = reinterpret_cast<TQSLConfig *> (data);
4765 	int err;
4766 	if (strcmp(name, "StationDataFile") == 0)
4767 		return;
4768 	if (strcmp(name, "StationData") == 0) {
4769 		if (gzprintf(*parser->outstr , " />\n") < 0)
4770 			throw TQSLException(gzerror(*parser->outstr, &err));
4771 		return;
4772 	}
4773 	// Anything else is a station attribute. Add it to the definition.
4774 	parser->elementBody.Trim(false);
4775 	parser->elementBody.Trim(true);
4776 	urlEncode(parser->elementBody);
4777 	if (gzprintf(*parser->outstr,  " %s=\"%s\"", name, (const char *)parser->elementBody.ToUTF8()) < 0)
4778 		throw TQSLException(gzerror(*parser->outstr, &err));
4779 	parser->elementBody = wxT("");
4780 }
4781 
4782 void
4783 TQSLConfig::xml_text(void *data, const XML_Char *text, int len) {
4784 	TQSLConfig* loader = reinterpret_cast<TQSLConfig *>(data);
4785 	char buf[512];
4786 	memcpy(buf, text, len);
4787 	buf[len] = '\0';
4788 	loader->elementBody += wxString::FromUTF8(buf);
4789 }
4790 
4791 void
4792 TQSLConfig::RestoreConfig(const gzFile& in) {
4793 	tqslTrace("TQSLConfig::RestoreConfig", NULL);
4794 	XML_Parser xp = XML_ParserCreate(0);
4795 	XML_SetUserData(xp, reinterpret_cast<void *>(this));
4796 	XML_SetStartElementHandler(xp, &TQSLConfig::xml_restore_start);
4797 	XML_SetEndElementHandler(xp, &TQSLConfig::xml_restore_end);
4798 	XML_SetCharacterDataHandler(xp, &TQSLConfig::xml_text);
4799 
4800 	char buf[4096];
4801 	wxBusyCursor wait;
4802 	wxLogMessage(_("Restoring Callsign Certificates"));
4803 	wxSafeYield(frame);
4804 	int rcount = 0;
4805 	do {
4806 		rcount = gzread(in, buf, sizeof(buf));
4807 		if (rcount > 0) {
4808 			if (XML_Parse(xp, buf, rcount, 0) == 0) {
4809 				wxLogError(_("Error parsing saved configuration file: %hs"), XML_ErrorString(XML_GetErrorCode(xp)));
4810 				XML_ParserFree(xp);
4811 				return;
4812 			}
4813 		}
4814 	} while (rcount > 0);
4815 	if (!gzeof(in)) {
4816 		int gerr;
4817 		wxLogError(_("Error parsing saved configuration file: %hs"), gzerror(in, &gerr));
4818 		XML_ParserFree(xp);
4819 		return;
4820 	}
4821 	if (XML_Parse(xp, "", 0, 1) == 0) {
4822 		wxLogError(_("Error parsing saved configuration file: %hs"), XML_ErrorString(XML_GetErrorCode(xp)));
4823 		XML_ParserFree(xp);
4824 		return;
4825 	}
4826 	wxLogMessage(_("Restore Complete."));
4827 }
4828 
4829 void
4830 TQSLConfig::ParseLocations(gzFile* out, const tQSL_StationDataEnc loc) {
4831 	tqslTrace("TQSL::ParseLocations", "loc=%s", loc);
4832 	XML_Parser xp = XML_ParserCreate(0);
4833 	XML_SetUserData(xp, reinterpret_cast<void *>(this));
4834 	XML_SetStartElementHandler(xp, &TQSLConfig::xml_location_start);
4835 	XML_SetEndElementHandler(xp, &TQSLConfig::xml_location_end);
4836 	XML_SetCharacterDataHandler(xp, &TQSLConfig::xml_text);
4837 	outstr = out;
4838 
4839 	if (XML_Parse(xp, loc, strlen(loc), 1) == 0) {
4840 		wxLogError(_("Error parsing station location file: %hs"), XML_ErrorString(XML_GetErrorCode(xp)));
4841 		XML_ParserFree(xp);
4842 		return;
4843 	}
4844 }
4845 
4846 void
4847 MyFrame::OnLoadConfig(wxCommandEvent& WXUNUSED(event)) {
4848 #ifdef _WIN32
4849 	int fd = -1;
4850 #endif
4851 	tqslTrace("MyFrame::OnLoadConfig", NULL);
4852 	wxString filename = wxFileSelector(_("Select saved configuration file"), wxT(""),
4853 					   wxT("tqslconfig.tbk"), wxT("tbk"), _("Saved configuration files (*.tbk)|*.tbk"),
4854 					   wxFD_OPEN|wxFD_FILE_MUST_EXIST);
4855 	if (filename == wxT(""))
4856 		return;
4857 
4858 	gzFile in = 0;
4859 	try {
4860 #ifdef _WIN32
4861 		wchar_t* lfn = utf8_to_wchar(filename.ToUTF8());
4862 		fd = _wopen(lfn, _O_RDONLY|_O_BINARY);
4863 		free_wchar(lfn);
4864 		if (fd != -1)
4865 			in = gzdopen(fd, "rb");
4866 #else
4867 		in = gzopen(filename.ToUTF8(), "rb");
4868 #endif
4869 		if (!in) {
4870 			wxLogError(_("Error opening save file %s: %hs"), filename.c_str(), strerror(errno));
4871 			return;
4872 		}
4873 
4874 		TQSLConfig loader;
4875 		loader.RestoreConfig(in);
4876 		cert_tree->Build(CERTLIST_FLAGS);
4877 		loc_tree->Build();
4878 		LocTreeReset();
4879 		CertTreeReset();
4880 		gzclose(in);
4881 	}
4882 	catch(TQSLException& x) {
4883 		wxLogError(_("Restore operation failed: %hs"), x.what());
4884 		gzclose(in);
4885 	}
4886 }
4887 
4888 QSLApp::QSLApp() : wxApp() {
4889 	lang = wxLANGUAGE_UNKNOWN;
4890 	locale = NULL;
4891 #ifdef __WXMAC__	// Tell wx to put these items on the proper menu
4892 	wxApp::s_macAboutMenuItemId = long(tm_h_about);
4893 	wxApp::s_macPreferencesMenuItemId = long(tm_f_preferences);
4894 	wxApp::s_macExitMenuItemId = long(tm_f_exit);
4895 #endif
4896 
4897 	wxConfigBase::Set(new wxConfig(wxT("tqslapp")));
4898 }
4899 
4900 /*
4901 wxLog *
4902 QSLApp::CreateLogTarget() {
4903 cerr << "called" << endl;
4904 	MyFrame *mf = (MyFrame *)GetTopWindow();
4905 	if (mf)
4906 		return new LogList(mf);
4907 	return 0;
4908 }
4909 */
4910 
4911 MyFrame *
4912 QSLApp::GUIinit(bool checkUpdates, bool quiet) {
4913 	tqslTrace("QSLApp::GUIinit", "checkUpdates=%d", checkUpdates);
4914 	int x, y, w, h;
4915 	bool maximized;
4916 	wxConfig *config = reinterpret_cast<wxConfig *>(wxConfig::Get());
4917 	config->Read(wxT("MainWindowX"), &x, 50);
4918 	config->Read(wxT("MainWindowY"), &y, 50);
4919 	config->Read(wxT("MainWindowWidth"), &w, 800);
4920 	config->Read(wxT("MainWindowHeight"), &h, 600);
4921 	config->Read(wxT("MainWindowMaximized"), &maximized, false);
4922 	if (w < MAIN_WINDOW_MIN_WIDTH) w = MAIN_WINDOW_MIN_WIDTH;
4923 	if (h < MAIN_WINDOW_MIN_HEIGHT) w = MAIN_WINDOW_MIN_HEIGHT;
4924 
4925 	frame = new MyFrame(wxT("TQSL"), x, y, w, h, checkUpdates, quiet, locale);
4926 	frame->SetMinSize(wxSize(MAIN_WINDOW_MIN_WIDTH, MAIN_WINDOW_MIN_HEIGHT));
4927 	if (maximized)
4928 		frame->Maximize();
4929 	if (checkUpdates)
4930 		frame->FirstTime();
4931 	frame->Show(!quiet);
4932 	if (checkUpdates && !quiet)
4933 		frame->SetFocus();
4934 	SetTopWindow(frame);
4935 
4936 	return frame;
4937 }
4938 
4939 // Override OnRun so we can have a last-chance exception handler
4940 // in case something doesn't handle an error.
4941 int
4942 QSLApp::OnRun() {
4943 	tqslTrace("QSLApp::OnRun", NULL);
4944 	try {
4945 		if (m_exitOnFrameDelete == Later)
4946 			m_exitOnFrameDelete = Yes;
4947 		return MainLoop();
4948 	}
4949 	catch(TQSLException& x) {
4950 		string msg = x.what();
4951 		tqslTrace("QSLApp::OnRun", "Last chance handler, string=%s", (const char *)msg.c_str());
4952 		cerr << "An exception has occurred! " << msg << endl;
4953 		wxLogError(wxT("%hs"), x.what());
4954 		exitNow(TQSL_EXIT_TQSL_ERROR, false);
4955 	}
4956 	return 0;
4957 }
4958 
4959 bool
4960 QSLApp::OnInit() {
4961 	frame = 0;
4962 	long lng = -1;
4963 
4964 	int major, minor;
4965 	if (tqsl_getConfigVersion(&major, &minor)) {
4966 		wxMessageBox(getLocalizedErrorString(), _("Error"), wxOK | wxICON_ERROR, frame);
4967 		exitNow(TQSL_EXIT_TQSL_ERROR, quiet);
4968 	}
4969 
4970 	wxConfig::Get()->Read(wxT("Language"), &lng, wxLANGUAGE_UNKNOWN);
4971 	lang = (wxLanguage) lng;
4972 
4973 	if (lang == wxLANGUAGE_UNKNOWN) {
4974 		lang = wxLANGUAGE_DEFAULT;
4975 	}
4976 
4977 	for (lng = 0; (unsigned) lng < sizeof (langNames); lng++) {
4978 		if (lang == langIds[lng])
4979 			break;
4980 	}
4981 
4982 #ifdef __WXGTK__
4983 	// Add locale search path for where we install language files
4984 	locale->AddCatalogLookupPathPrefix(wxT("/usr/local/share/locale"));
4985 #endif
4986 	if (wxLocale::IsAvailable(lang)) {
4987 		if (locale) delete locale;
4988 		locale = new wxLocale(lang);
4989 		if (!locale)
4990 			locale = new wxLocale(wxLANGUAGE_DEFAULT);
4991 	} else {
4992 		wxLogError(wxT("This language is not supported by the system."));
4993 		locale = new wxLocale(wxLANGUAGE_DEFAULT);
4994 	}
4995 
4996 	// Add a subdirectory for language files
4997 	locale->AddCatalogLookupPathPrefix(wxT("lang"));
4998 
4999 	// Initialize the catalogs we'll be using
5000 	locale->AddCatalog(wxT("tqslapp"));
5001 	locale->AddCatalog(wxT("wxstd"));
5002 
5003 	// this catalog is installed in standard location on Linux systems and
5004 	// shows that you may make use of the standard message catalogs as well
5005 	//
5006 	// If it's not installed on your system, it is just silently ignored
5007 #ifdef __LINUX__
5008         {
5009 		wxLogNull nolog;
5010 		locale->AddCatalog(wxT("fileutils"));
5011 	}
5012 #endif
5013 
5014 	wxFileSystem::AddHandler(new tqslInternetFSHandler());
5015 	// Allow JAWS for windows to speak the context-sensitive help.
5016 	wxHelpProvider::Set(new wxSimpleHelpProvider());
5017 
5018 	//short circuit if no arguments
5019 
5020 	if (argc <= 1) {
5021 		GUIinit(true, quiet);
5022 		return true;
5023 	}
5024 
5025 	tQSL_Location loc = 0;
5026 	wxString locname;
5027 	bool suppressdate = false;
5028 	int action = TQSL_ACTION_UNSPEC;
5029 	bool upload = false;
5030 	char *password = NULL;
5031 	char *defcall = NULL;
5032 	wxString infile(wxT(""));
5033 	wxString outfile(wxT(""));
5034 	wxString importfile(wxT(""));
5035 	wxString diagfile(wxT(""));
5036 
5037 	wxCmdLineParser parser;
5038 
5039 #if wxMAJOR_VERSION > 2 || (wxMAJOR_VERSION == 2 && wxMINOR_VERSION == 9)
5040 #define arg(x) (x)
5041 #define i18narg(x) (x)
5042 #else
5043 #define arg(x) wxT(x)
5044 #define i18narg(x) _(x)
5045 #endif
5046 	static const wxCmdLineEntryDesc cmdLineDesc[] = {
5047 		{ wxCMD_LINE_OPTION, arg("a"), arg("action"),	i18narg("Specify dialog action - abort, all, compliant or ask") },
5048 		{ wxCMD_LINE_OPTION, arg("b"), arg("begindate"), i18narg("Specify start date for QSOs to sign") },
5049 		{ wxCMD_LINE_OPTION, arg("c"), arg("callsign"),	i18narg("Specify default callsign for log signing") },
5050 		{ wxCMD_LINE_OPTION, arg("e"), arg("enddate"),	i18narg("Specify end date for QSOs to sign") },
5051 		{ wxCMD_LINE_SWITCH, arg("d"), arg("nodate"),	i18narg("Suppress date range dialog") },
5052 		{ wxCMD_LINE_OPTION, arg("i"), arg("import"),	i18narg("Import a certificate file (.p12 or .tq6)") },
5053 		{ wxCMD_LINE_OPTION, arg("l"), arg("location"),	i18narg("Selects Station Location") },
5054 		{ wxCMD_LINE_SWITCH, arg("s"), arg("editlocation"), i18narg("Edit (if used with -l) or create Station Location") },
5055 		{ wxCMD_LINE_OPTION, arg("o"), arg("output"),	i18narg("Output file name (defaults to input name minus extension plus .tq8") },
5056 		{ wxCMD_LINE_SWITCH, arg("u"), arg("upload"),	i18narg("Upload after signing instead of saving") },
5057 		{ wxCMD_LINE_SWITCH, arg("x"), arg("batch"),	i18narg("Exit after processing log (otherwise start normally)") },
5058 		{ wxCMD_LINE_OPTION, arg("p"), arg("password"),	i18narg("Password for the signing key") },
5059 		{ wxCMD_LINE_SWITCH, arg("q"), arg("quiet"),	i18narg("Quiet Mode - same behavior as -x") },
5060 		{ wxCMD_LINE_OPTION, arg("t"), arg("diagnose"),	i18narg("File name for diagnostic tracking log") },
5061 		{ wxCMD_LINE_SWITCH, arg("v"), arg("version"),  i18narg("Display the version information and exit") },
5062 		{ wxCMD_LINE_SWITCH, arg("n"), arg("updates"),	i18narg("Check for updates to tqsl and the configuration file") },
5063 		{ wxCMD_LINE_SWITCH, arg("h"), arg("help"),	i18narg("Display command line help"), wxCMD_LINE_VAL_NONE, wxCMD_LINE_OPTION_HELP },
5064 		{ wxCMD_LINE_PARAM,  NULL, NULL,		i18narg("Input ADIF or Cabrillo log file to sign"), wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
5065 		{ wxCMD_LINE_NONE }
5066 	};
5067 
5068 	// Lowercase command options
5069 	origCommandLine = argv[0];
5070 	for (int i = 1; i < argc; i++) {
5071 		origCommandLine += wxT(" ");
5072 		origCommandLine += argv[i];
5073 #ifdef _WIN32
5074 		if ((const wxChar *)argv[i])
5075 			if (argv[i][0] == wxT('-') || argv[i][0] == wxT('/'))
5076 				if (wxIsalpha(argv[i][1]) && wxIsupper(argv[i][1]))
5077 					argv[i][1] = wxTolower(argv[i][1]);
5078 #endif
5079 	}
5080 
5081 	parser.SetCmdLine(argc, argv);
5082 	parser.SetDesc(cmdLineDesc);
5083 	// only allow "-" for options, otherwise "/path/something.adif"
5084 	// is parsed as "-path"
5085 	//parser.SetSwitchChars(wxT("-")); //by default, this is '-' on Unix, or '-' or '/' on Windows. We should respect the Win32 conventions, but allow the cross-platform Unix one for cross-plat loggers
5086 	int parseStatus = parser.Parse(true);
5087 	if (parseStatus == -1) {	// said "-h"
5088 		return false;
5089 	}
5090 	// Always display TQSL version
5091 	if ((!parser.Found(wxT("n"))) || parser.Found(wxT("v"))) {
5092 		cerr << "TQSL Version " VERSION " " BUILD "\n";
5093 	}
5094 	if (parseStatus != 0)  {
5095 		exitNow(TQSL_EXIT_COMMAND_ERROR, quiet);
5096 	}
5097 
5098 	// version already displayed - just exit
5099 	if (parser.Found(wxT("v"))) {
5100 		return false;
5101 	}
5102 
5103 	if (parser.Found(wxT("x")) || parser.Found(wxT("q"))) {
5104 		quiet = true;
5105 		wxLog::SetActiveTarget(new LogStderr());
5106 	}
5107 
5108 	if (parser.Found(wxT("t"), &diagfile)) {
5109 		if (tqsl_openDiagFile(diagfile.ToUTF8())) {
5110 			cerr << "Error opening diagnostic log " << diagfile.ToUTF8() << ": " << strerror(errno) << endl;
5111 		} else {
5112 			wxString about = getAbout();
5113 			tqslTrace(NULL, "TQSL Diagnostics\r\n%s\n\n", (const char *)about.ToUTF8());
5114 			tqslTrace(NULL, "Command Line: %s\r\n", (const char *)origCommandLine.ToUTF8());
5115 			tqsl_init();
5116 			tqslTrace(NULL, "Working Directory: %s\r\n", tQSL_BaseDir);
5117 		}
5118 	}
5119 
5120 	tqsl_init();	// Init tqsllib
5121 	// check for logical command switches
5122 	if (parser.Found(wxT("o")) && parser.Found(wxT("u"))) {
5123 		cerr << "Option -o cannot be combined with -u" << endl;
5124 		exitNow(TQSL_EXIT_COMMAND_ERROR, quiet);
5125 	}
5126 	if ((parser.Found(wxT("o")) || parser.Found(wxT("u"))) && parser.Found(wxT("s"))) {
5127 		cerr << "Option -s cannot be combined with -u or -o" << endl;
5128 		exitNow(TQSL_EXIT_COMMAND_ERROR, quiet);
5129 	}
5130 	if (parser.Found(wxT("s")) && parser.GetParamCount() > 0) {
5131 		cerr << "Option -s cannot be combined with an input file" << endl;
5132 		exitNow(TQSL_EXIT_COMMAND_ERROR, quiet);
5133 	}
5134 
5135 	// Request to check for new versions of tqsl/config/certs
5136 	if (parser.Found(wxT("n"))) {
5137 		if (parser.Found(wxT("i")) || parser.Found(wxT("o")) ||
5138 		    parser.Found(wxT("s")) || parser.Found(wxT("u"))) {
5139 			cerr << "Option -n cannot be combined with any other options" << endl;
5140 			exitNow(TQSL_EXIT_COMMAND_ERROR, quiet);
5141 		}
5142 		frame = GUIinit(false, true);
5143 		frame->Show(false);
5144 		// Check for updates then bail out.
5145 		wxLog::SetActiveTarget(new LogStderr());
5146 		frame->DoUpdateCheck(false, true);
5147 		return(false);
5148 	}
5149 
5150 	frame = GUIinit(!quiet, quiet);
5151 	if (quiet) {
5152 		wxLog::SetActiveTarget(new LogStderr());
5153 		frame->Show(false);
5154 	}
5155 
5156 	if (parser.Found(wxT("l"), &locname)) {
5157 		locname.Trim(true);			// clean up whitespace
5158 		locname.Trim(false);
5159 		tqsl_endStationLocationCapture(&loc);
5160 		if (tqsl_getStationLocation(&loc, locname.ToUTF8())) {
5161 			if (quiet) {
5162 				wxLogError(getLocalizedErrorString());
5163 				exitNow(TQSL_EXIT_COMMAND_ERROR, quiet);
5164 			} else {
5165 				wxMessageBox(getLocalizedErrorString(), ErrorTitle, wxOK | wxICON_ERROR | wxCENTRE, frame);
5166 				return false;
5167 			}
5168 		}
5169 	}
5170 
5171 	wxString call;
5172 	if (parser.Found(wxT("c"), &call)) {
5173 		call.Trim(true);
5174 		call.Trim(false);
5175 		defcall = strdup(call.MakeUpper().ToUTF8());
5176 	}
5177 
5178 	wxString pwd;
5179 	if (parser.Found(wxT("p"), &pwd)) {
5180 		password = strdup(pwd.ToUTF8());
5181 		utf8_to_ucs2(password, unipwd, sizeof unipwd);
5182 	}
5183 	if (parser.Found(wxT("o"), &outfile)) {
5184 	}
5185 
5186 	if (parser.Found(wxT("d"))) {
5187 		suppressdate = true;
5188 	}
5189 	wxString start = wxT("");
5190 	wxString end = wxT("");
5191 	tQSL_Date* startdate = NULL;
5192 	tQSL_Date* enddate = NULL;
5193 	tQSL_Date s, e;
5194 	if (parser.Found(wxT("b"), &start)) {
5195 		if (start.Trim() == wxT("")) {
5196 			startdate = NULL;
5197 		} else if (tqsl_initDate(&s, start.ToUTF8()) || !tqsl_isDateValid(&s)) {
5198 			if (quiet) {
5199 				wxLogError(_("Start date of %s is invalid"), start.c_str());
5200 				exitNow(TQSL_EXIT_COMMAND_ERROR, quiet);
5201 			} else {
5202 				wxMessageBox(wxString::Format(_("Start date of %s is invalid"), start.c_str()), ErrorTitle, wxOK | wxICON_ERROR | wxCENTRE, frame);
5203 				return false;
5204 			}
5205 		}
5206 		startdate = &s;
5207 	}
5208 	if (parser.Found(wxT("e"), &end)) {
5209 		if (end.Trim() == wxT("")) {
5210 			enddate = NULL;
5211 		} else if (tqsl_initDate(&e, end.ToUTF8()) || !tqsl_isDateValid(&e)) {
5212 			if (quiet) {
5213 				wxLogError(_("End date of %s is invalid"), end.c_str());
5214 				exitNow(TQSL_EXIT_COMMAND_ERROR, quiet);
5215 			} else {
5216 				wxMessageBox(wxString::Format(_("End date of %s is invalid"), end.c_str()), ErrorTitle, wxOK | wxICON_ERROR | wxCENTRE, frame);
5217 				return false;
5218 			}
5219 		}
5220 		enddate = &e;
5221 	}
5222 
5223 	wxString act;
5224 	if (parser.Found(wxT("a"), &act)) {
5225 		if (!act.CmpNoCase(wxT("abort"))) {
5226 			action = TQSL_ACTION_ABORT;
5227 		} else if (!act.CmpNoCase(wxT("compliant"))) {
5228 			action = TQSL_ACTION_NEW;
5229 		} else if (!act.CmpNoCase(wxT("all"))) {
5230 			action = TQSL_ACTION_ALL;
5231 		} else if (!act.CmpNoCase(wxT("ask"))) {
5232 			action = TQSL_ACTION_ASK;
5233 		} else {
5234 			char tmp[100];
5235 			strncpy(tmp, (const char *)act.ToUTF8(), sizeof tmp);
5236 			tmp[sizeof tmp -1] = '\0';
5237 			if (quiet)
5238 				wxLogMessage(_("The -a parameter %hs is not recognized"), tmp);
5239 			else
5240 				cerr << "The action parameter " << tmp << " is not recognized" << endl;
5241 			exitNow(TQSL_EXIT_COMMAND_ERROR, quiet);
5242 		}
5243 	}
5244 	if (parser.Found(wxT("u"))) {
5245 		upload = true;
5246 	}
5247 	if (parser.Found(wxT("s"))) {
5248 		// Add/Edit station location
5249 		if (loc == 0) {
5250 			if (tqsl_initStationLocationCapture(&loc)) {
5251 				wxLogError(getLocalizedErrorString());
5252 			}
5253 			AddEditStationLocation(loc, true);
5254 		} else {
5255 			AddEditStationLocation(loc, false, _("Edit Station Location"));
5256 		}
5257 		tqsl_endStationLocationCapture(&loc);
5258 		return false;
5259 	}
5260 	if (parser.GetParamCount() > 0) {
5261 		infile = parser.GetParam(0);
5262 	}
5263 
5264 	wxString path, name, ext;
5265 	wxFileName::SplitPath(infile, &path, &name, &ext);
5266 
5267 	// Handle "-i" (import cert), or bare cert file on command line
5268 
5269 	bool tq6File = false;
5270 	if (!wxIsEmpty(infile)) {
5271 		if (ext.CmpNoCase(wxT("tq6")) == 0) {
5272 			tq6File = true;
5273 		}
5274 	}
5275 	if (parser.Found(wxT("i"), &infile) && (!wxIsEmpty(infile))) {
5276 		tq6File = true;
5277 	}
5278 
5279 	if (tq6File) {
5280 		infile.Trim(true).Trim(false);
5281 		notifyData nd;
5282 		if (tqsl_importTQSLFile(infile.ToUTF8(), notifyImport, &nd)) {
5283 			if (tQSL_Error != TQSL_CERT_ERROR) {
5284 				wxLogError(getLocalizedErrorString());
5285 			}
5286 		} else {
5287 			wxLogMessage(nd.Message());
5288 			if (tQSL_ImportSerial != 0) {
5289 				wxString status;
5290 				frame->CheckCertStatus(tQSL_ImportSerial, status);		// Update from LoTW's "CRL"
5291 				tqsl_setCertificateStatus(tQSL_ImportSerial, (const char *)status.ToUTF8());
5292 			}
5293 			if (tQSL_ImportCall[0] != '\0' && tQSL_ImportSerial != 0 && tqsl_getCertificateStatus(tQSL_ImportSerial) == TQSL_CERT_STATUS_OK) {
5294 				get_certlist(tQSL_ImportCall, 0, true, true, true);	// Get any expired/superceded ones for this call
5295 				for (int i = 0; i < ncerts; i++) {
5296 					long serial = 0;
5297 					int keyonly = false;
5298 					tqsl_getCertificateKeyOnly(certlist[i], &keyonly);
5299 					if (keyonly) {
5300 						if (tQSL_ImportSerial != 0) {		// A full cert for this was imported
5301 							tqsl_deleteCertificate(certlist[i]);
5302 						}
5303 						continue;
5304 					}
5305 					if (tqsl_getCertificateSerial(certlist[i], &serial)) {
5306 						continue;
5307 					}
5308 					if (serial == tQSL_ImportSerial) {
5309 						continue;
5310 					}
5311 					// This is not the one we just imported
5312 					int sup, exp;
5313 					if (tqsl_isCertificateSuperceded(certlist[i], &sup) == 0 && sup) {
5314 						tqsl_deleteCertificate(certlist[i]);
5315 					} else if (tqsl_isCertificateExpired(certlist[i], &exp) == 0 && exp) {
5316 						tqsl_deleteCertificate(certlist[i]);
5317 					}
5318 				}
5319 			}
5320 			frame->cert_tree->Build(CERTLIST_FLAGS);
5321 			wxString call = wxString::FromUTF8(tQSL_ImportCall);
5322 			wxString pending = wxConfig::Get()->Read(wxT("RequestPending"));
5323 			pending.Replace(call, wxT(""), true);
5324 			wxString rest;
5325 			while (pending.StartsWith(wxT(","), &rest))
5326 				pending = rest;
5327 			while (pending.EndsWith(wxT(","), &rest))
5328 				pending = rest;
5329 			wxConfig::Get()->Write(wxT("RequestPending"), pending);
5330 		}
5331 		return(true);
5332 	}
5333 
5334 	// We need a logfile, else there's nothing to do.
5335 	if (wxIsEmpty(infile)) {	// Nothing to sign
5336 		if (quiet) {
5337 			wxLogError(_("No logfile to sign!"));
5338 			exitNow(TQSL_EXIT_COMMAND_ERROR, quiet);
5339 			return false;
5340 		}
5341 		return true;
5342 	}
5343 
5344 	bool editAdif = DEFAULT_ADIF_EDIT;
5345 	wxConfig::Get()->Read(wxT("AdifEdit"), &editAdif, DEFAULT_ADIF_EDIT);
5346 
5347 	// If it's an ADIF file, invoke the editor if that's the only argument
5348 	// unless we're running in batch mode
5349 	if (editAdif && !quiet && !wxIsEmpty(infile) && (ext.CmpNoCase(wxT("adi")) || ext.CmpNoCase(wxT("adif")))) {
5350 		QSORecordList recs;
5351 		loadQSOfile(infile, recs);
5352 		wxMessageBox(_("Warning: The TQSL ADIF editor only processes a limited number of ADIF fields.\n\nUsing the editor on an ADIF file can cause QSO details to be lost!"), _("Warning"), wxOK | wxICON_EXCLAMATION, frame);
5353 		try {
5354 			QSODataDialog dial(frame, infile, frame->help, &recs);
5355 			dial.ShowModal();
5356 		} catch(TQSLException& x) {
5357 			wxLogError(wxT("%hs"), x.what());
5358 		}
5359 		exitNow(TQSL_EXIT_SUCCESS, quiet);
5360 	}
5361 
5362 	// Assume that it's a log to sign
5363 	if (loc == 0) {
5364 		try {
5365 			int n;
5366 			check_tqsl_error(tqsl_initStationLocationCapture(&loc));
5367 			check_tqsl_error(tqsl_getNumStationLocations(loc, &n));
5368 			if (n != 1) {
5369 				check_tqsl_error(tqsl_endStationLocationCapture(&loc));
5370 				loc = frame->SelectStationLocation(_("Select Station Location for Signing"));
5371 			} else {
5372 				// There's only one station location. Use that and don't prompt.
5373 				char deflocn[512];
5374 				check_tqsl_error(tqsl_getStationLocationName(loc, 0, deflocn, sizeof deflocn));
5375 				check_tqsl_error(tqsl_endStationLocationCapture(&loc));
5376 				check_tqsl_error(tqsl_getStationLocation(&loc, deflocn));
5377 			}
5378 		}
5379 		catch(TQSLException& x) {
5380 			wxLogError(wxT("%hs"), x.what());
5381 			if (quiet)
5382 				exitNow(TQSL_EXIT_CANCEL, quiet);
5383 		}
5384 	}
5385 	// If no location specified and not chosen, can't sign. Exit.
5386 	if (loc == 0) {
5387 		if (quiet)
5388 			exitNow(TQSL_EXIT_CANCEL, quiet);
5389 		return false;
5390 	}
5391 	if (!wxIsEmpty(outfile)) {
5392 		path = outfile;
5393 	} else {
5394 		if (!wxIsEmpty(path))
5395 			path += wxT("/");
5396 		path += name + wxT(".tq8");
5397 	}
5398 	if (upload) {
5399 		try {
5400 			int val = frame->UploadLogFile(loc, infile, true, suppressdate, startdate, enddate, action, password, defcall);
5401 			if (quiet)
5402 				exitNow(val, quiet);
5403 			else
5404 				return true;	// Run the GUI
5405 		} catch(TQSLException& x) {
5406 			wxString s;
5407 			wxString err = wxString::FromUTF8(x.what());
5408 			if (err.Find(infile) == wxNOT_FOUND) {
5409 				if (!infile.empty())
5410 					s = infile + wxT(": ");
5411 			}
5412 			s += err;
5413 			wxLogError(wxT("%s"), (const char *)s.c_str());
5414 			if (quiet)
5415 				exitNow(TQSL_EXIT_LIB_ERROR, quiet);
5416 			else
5417 				return true;
5418 		}
5419 	} else {
5420 		try {
5421 			int val = frame->ConvertLogFile(loc, infile, path, true, suppressdate, startdate, enddate, action, password, defcall);
5422 			if (quiet)
5423 				exitNow(val, quiet);
5424 			else
5425 				return true;
5426 		} catch(TQSLException& x) {
5427 			wxString s;
5428 			wxString err = wxString::FromUTF8(x.what());
5429 			if (err.Find(infile) == wxNOT_FOUND) {
5430 				if (infile != wxT(""))
5431 					s = infile + wxT(": ");
5432 			}
5433 			s += err;
5434 			wxLogError(wxT("%s"), (const char *)s.c_str());
5435 			if (quiet)
5436 				exitNow(TQSL_EXIT_LIB_ERROR, quiet);
5437 			else
5438 				return true;
5439 		}
5440 	}
5441 	check_tqsl_error(tqsl_endStationLocationCapture(&loc));
5442 	if (quiet)
5443 		exitNow(TQSL_EXIT_SUCCESS, quiet);
5444 	return true;
5445 }
5446 
5447 void MyFrame::FirstTime(void) {
5448 	tqslTrace("MyFrame::FirstTime", NULL);
5449 	if (wxConfig::Get()->Read(wxT("HasRun")) == wxT("")) {
5450 		wxConfig::Get()->Write(wxT("HasRun"), wxT("yes"));
5451 		DisplayHelp();
5452 		wxMessageBox(_("Please review the introductory documentation before using this program."),
5453 			_("Notice"), wxOK | wxICON_INFORMATION, this);
5454 	}
5455 	int ncerts = cert_tree->Build(CERTLIST_FLAGS);
5456 	CertTreeReset();
5457 	if (ncerts == 0) {
5458 		wxString msg = _("You have no callsign certificate installed on this computer with which to sign log submissions.");
5459 		msg += wxT("\n");
5460 		msg += _("Would you like to request a callsign certificate now?");
5461 		if (wxMessageBox(msg, _("Alert"), wxYES_NO | wxICON_QUESTION, this) == wxYES) {
5462 			wxCommandEvent e;
5463 			CRQWizard(e);
5464 		}
5465 	}
5466 	wxString pending = wxConfig::Get()->Read(wxT("RequestPending"));
5467 	wxStringTokenizer tkz(pending, wxT(","));
5468 	while (tkz.HasMoreTokens()) {
5469 		wxString pend = tkz.GetNextToken();
5470 		bool found = false;
5471 		tQSL_Cert *certs;
5472 		int ncerts = 0;
5473 		if (!tqsl_selectCertificates(&certs, &ncerts, pend.ToUTF8(), 0, 0, 0, TQSL_SELECT_CERT_WITHKEYS)) {
5474 			for (int i = 0; i < ncerts; i++) {
5475 				int keyonly;
5476 				if (!tqsl_getCertificateKeyOnly(certs[i], &keyonly)) {
5477 					if (!found && keyonly)
5478 						found = true;
5479 				}
5480 			}
5481 			tqsl_freeCertificateList(certs, ncerts);
5482 		}
5483 
5484 		if (!found) {
5485 			// Remove this call from the list of pending certificate requests
5486 			wxString p = wxConfig::Get()->Read(wxT("RequestPending"));
5487 			p.Replace(pend, wxT(""), true);
5488 			wxString rest;
5489 			while (p.StartsWith(wxT(","), &rest))
5490 				p = rest;
5491 			while (p.EndsWith(wxT(","), &rest))
5492 				p = rest;
5493 			wxConfig::Get()->Write(wxT("RequestPending"), p);
5494 		}
5495 	}
5496 
5497 	if (ncerts > 0) {
5498 		TQ_WXCOOKIE cookie;
5499 		wxTreeItemId it = cert_tree->GetFirstChild(cert_tree->GetRootItem(), cookie);
5500 		while (it.IsOk()) {
5501 			if (cert_tree->GetItemText(it) == wxT("Test Certificate Authority")) {
5502 				wxMessageBox(wxT("You must delete your beta-test certificates (the ones\n")
5503 					wxT("listed under \"Test Certificate Authority\") to ensure proprer\n")
5504 					wxT("operation of the TrustedQSL software."), wxT("Warning"), wxOK, this);
5505 				break;
5506 			}
5507 			it = cert_tree->GetNextChild(cert_tree->GetRootItem(), cookie);
5508 		}
5509 	}
5510 // Copy tqslcert preferences to tqsl unless already done.
5511 	if (wxConfig::Get()->Read(wxT("PrefsMigrated")) == wxT("")) {
5512 		wxConfig::Get()->Write(wxT("PrefsMigrated"), wxT("yes"));
5513 		tqslTrace("MyFrame::FirstTime", "Migrating preferences from tqslcert");
5514 		wxConfig* certconfig = new wxConfig(wxT("tqslcert"));
5515 
5516 		wxString name, gname;
5517 		long	context;
5518 		wxString svalue;
5519 		long	lvalue;
5520 		bool	bvalue;
5521 		double	dvalue;
5522 		wxArrayString groupNames;
5523 
5524 		groupNames.Add(wxT("/"));
5525 		bool more = certconfig->GetFirstGroup(gname, context);
5526 		while (more) {
5527 			groupNames.Add(wxT("/") + gname);
5528 			more = certconfig->GetNextGroup(gname, context);
5529 		}
5530 
5531 		for (unsigned i = 0; i < groupNames.GetCount(); i++) {
5532 			certconfig->SetPath(groupNames[i]);
5533 			wxConfig::Get()->SetPath(groupNames[i]);
5534 			more = certconfig->GetFirstEntry(name, context);
5535 			while (more) {
5536 				wxConfigBase::EntryType etype = certconfig->GetEntryType(name);
5537 				switch (etype) {
5538 					case wxConfigBase::Type_Unknown:
5539 					case wxConfigBase::Type_String:
5540 						certconfig->Read(name, &svalue);
5541 						wxConfig::Get()->Write(name, svalue);
5542 						break;
5543 					case wxConfigBase::Type_Boolean:
5544 						certconfig->Read(name, &bvalue);
5545 						wxConfig::Get()->Write(name, bvalue);
5546 						break;
5547 					case wxConfigBase::Type_Integer:
5548 						certconfig->Read(name, &lvalue);
5549 						wxConfig::Get()->Write(name, lvalue);
5550 						break;
5551 					case wxConfigBase::Type_Float:
5552 						certconfig->Read(name, &dvalue);
5553 						wxConfig::Get()->Write(name, dvalue);
5554 						break;
5555 				}
5556 				more = certconfig->GetNextEntry(name, context);
5557 			}
5558 		}
5559 		delete certconfig;
5560 		wxConfig::Get()->SetPath(wxT("/"));
5561 		wxConfig::Get()->Flush();
5562 	}
5563 	// Find and report conflicting mode maps
5564 	init_modes();
5565 	// Run automatic check for updates - except for wx 2.9, which breaks threading.
5566 	wxConfig *config = reinterpret_cast<wxConfig *>(wxConfig::Get());
5567 	if (config->Read(wxT("AutoUpdateCheck"), true)) {
5568 		DoUpdateCheck(true, false);
5569 	}
5570 	return;
5571 }
5572 
5573 wxMenu *
5574 makeCertificateMenu(bool enable, bool keyonly, const char *callsign) {
5575 	tqslTrace("makeCertificateMenu", "enable=%d, keyonly=%d", enable, keyonly);
5576 	wxMenu *c_menu = new wxMenu;
5577 	c_menu->Append(tc_c_Properties, _("Display Callsign Certificate &Properties"));
5578 	c_menu->Enable(tc_c_Properties, enable);
5579 	c_menu->AppendSeparator();
5580 	c_menu->Append(tc_c_Load, _("&Load Callsign Certificate from File"));
5581 	c_menu->Append(tc_c_Export, _("&Save Callsign Certificate to File..."));
5582 	c_menu->Enable(tc_c_Export, enable);
5583 	if (!keyonly) {
5584 		c_menu->AppendSeparator();
5585 		c_menu->Append(tc_c_New, _("Request &New Callsign Certificate..."));
5586 		c_menu->Append(tc_c_Renew, _("&Renew Callsign Certificate"));
5587 		c_menu->Enable(tc_c_Renew, enable);
5588 	} else {
5589 		c_menu->AppendSeparator();
5590 	}
5591 	c_menu->Append(tc_c_Delete, _("&Delete Callsign Certificate"));
5592 	c_menu->Enable(tc_c_Delete, enable);
5593 	c_menu->AppendSeparator();
5594 
5595 	int ncalls = 0;
5596 	tqsl_getDeletedCallsignCertificates(NULL, &ncalls, callsign);
5597 	c_menu->Append(tc_c_Undelete, _("Restore Deleted Callsign Certificate"));
5598 	c_menu->Enable(tc_c_Undelete, ncalls > 0);
5599 
5600 	return c_menu;
5601 }
5602 
5603 wxMenu *
5604 makeLocationMenu(bool enable) {
5605 	tqslTrace("makeLocationMenu", "enable=%d", enable);
5606 	wxMenu *loc_menu = new wxMenu;
5607 	loc_menu->Append(tl_c_Properties, _("&Properties"));
5608 	loc_menu->Enable(tl_c_Properties, enable);
5609 	stn_menu->Enable(tm_s_Properties, enable);
5610 	loc_menu->Append(tl_c_Edit, _("&Edit"));
5611 	loc_menu->Enable(tl_c_Edit, enable);
5612 	loc_menu->Append(tl_c_Delete, _("&Delete"));
5613 	loc_menu->Enable(tl_c_Delete, enable);
5614 	return loc_menu;
5615 }
5616 
5617 /////////// Frame /////////////
5618 
5619 void MyFrame::OnLoadCertificateFile(wxCommandEvent& WXUNUSED(event)) {
5620 	tqslTrace("MyFrame::OnLoadCertificateFile", NULL);
5621 	LoadCertWiz lcw(this, help, _("Load Certificate File"));
5622 	lcw.RunWizard();
5623 	if (tQSL_ImportCall[0] != '\0') {				// If a user cert was imported
5624 		if (tQSL_ImportSerial != 0) {
5625 			wxString status;
5626 			CheckCertStatus(tQSL_ImportSerial, status);		// Update from LoTW's "CRL"
5627 			tqsl_setCertificateStatus(tQSL_ImportSerial, (const char *)status.ToUTF8());
5628 		}
5629 		if (tQSL_ImportCall[0] != '\0' && tQSL_ImportSerial != 0 && tqsl_getCertificateStatus(tQSL_ImportSerial) == TQSL_CERT_STATUS_OK) {
5630 			get_certlist(tQSL_ImportCall, 0, true, true, true);	// Get any superceded ones for this call
5631 			for (int i = 0; i < ncerts; i++) {
5632 				long serial = 0;
5633 				int keyonly = false;
5634 				tqsl_getCertificateKeyOnly(certlist[i], &keyonly);
5635 				if (keyonly) {
5636 					tqsl_deleteCertificate(certlist[i]);
5637 				}
5638 				if (tqsl_getCertificateSerial(certlist[i], &serial)) {
5639 					continue;
5640 				}
5641 				if (serial == tQSL_ImportSerial) {	// Don't delete the one we just imported
5642 					continue;
5643 				}
5644 				// This is not the one we just imported
5645 				int sup, exp;
5646 				if (tqsl_isCertificateSuperceded(certlist[i], &sup) == 0 && sup) {
5647 					tqsl_deleteCertificate(certlist[i]);
5648 				} else if (tqsl_isCertificateExpired(certlist[i], &exp) == 0 && exp) {
5649 					tqsl_deleteCertificate(certlist[i]);
5650 				}
5651 			}
5652 		}
5653 	}
5654 	cert_tree->Build(CERTLIST_FLAGS);
5655 	CertTreeReset();
5656 }
5657 
5658 void MyFrame::CRQWizardRenew(wxCommandEvent& event) {
5659 	tqslTrace("MyFrame::CRQWizardRenew", NULL);
5660 	CertTreeItemData *data = reinterpret_cast<CertTreeItemData *>(cert_tree->GetItemData(cert_tree->GetSelection()));
5661 	req = 0;
5662 	tQSL_Cert cert;
5663 	wxString callSign, name, address1, address2, city, state, postalCode,
5664 		country, emailAddress;
5665 	if (data != NULL && (cert = data->getCert()) != 0) {	// Should be true
5666 		char buf[256];
5667 		req = new TQSL_CERT_REQ;
5668 		if (!tqsl_getCertificateIssuerOrganization(cert, buf, sizeof buf))
5669 			strncpy(req->providerName, buf, sizeof req->providerName);
5670 		if (!tqsl_getCertificateIssuerOrganizationalUnit(cert, buf, sizeof buf))
5671 			strncpy(req->providerUnit, buf, sizeof req->providerUnit);
5672 		if (!tqsl_getCertificateCallSign(cert, buf, sizeof buf)) {
5673 			callSign = wxString::FromUTF8(buf);
5674 			strncpy(req->callSign, callSign.ToUTF8(), sizeof req->callSign);
5675 		}
5676 		if (!tqsl_getCertificateAROName(cert, buf, sizeof buf)) {
5677 			name = wxString::FromUTF8(buf);
5678 			strncpy(req->name, name.ToUTF8(), sizeof req->name);
5679 		}
5680 		tqsl_getCertificateDXCCEntity(cert, &(req->dxccEntity));
5681 		tqsl_getCertificateQSONotBeforeDate(cert, &(req->qsoNotBefore));
5682 		tqsl_getCertificateQSONotAfterDate(cert, &(req->qsoNotAfter));
5683 		if (!tqsl_getCertificateEmailAddress(cert, buf, sizeof buf)) {
5684 			emailAddress = wxString::FromUTF8(buf);
5685 			strncpy(req->emailAddress, emailAddress.ToUTF8(), sizeof req->emailAddress);
5686 		}
5687 		if (!tqsl_getCertificateRequestAddress1(cert, buf, sizeof buf)) {
5688 			address1 = wxString::FromUTF8(buf);
5689 			strncpy(req->address1, address1.ToUTF8(), sizeof req->address1);
5690 		}
5691 		if (!tqsl_getCertificateRequestAddress2(cert, buf, sizeof buf)) {
5692 			address2 = wxString::FromUTF8(buf);
5693 			strncpy(req->address2, address2.ToUTF8(), sizeof req->address2);
5694 		}
5695 		if (!tqsl_getCertificateRequestCity(cert, buf, sizeof buf)) {
5696 			city = wxString::FromUTF8(buf);
5697 			strncpy(req->city, city.ToUTF8(), sizeof req->city);
5698 		}
5699 		if (!tqsl_getCertificateRequestState(cert, buf, sizeof buf)) {
5700 			state = wxString::FromUTF8(buf);
5701 			strncpy(req->state, state.ToUTF8(), sizeof req->state);
5702 		}
5703 		if (!tqsl_getCertificateRequestPostalCode(cert, buf, sizeof buf)) {
5704 			postalCode = wxString::FromUTF8(buf);
5705 			strncpy(req->postalCode, postalCode.ToUTF8(), sizeof req->postalCode);
5706 		}
5707 		if (!tqsl_getCertificateRequestCountry(cert, buf, sizeof buf)) {
5708 			country = wxString::FromUTF8(buf);
5709 			strncpy(req->country, country.ToUTF8(), sizeof req->country);
5710 		}
5711 	}
5712 	CRQWizard(event);
5713 	if (req)
5714 		delete req;
5715 	req = 0;
5716 }
5717 
5718 // Delete an abandoned/failed cert request
5719 static void deleteRequest(const char *callsign, int dxccEntity) {
5720 	int savedError = tQSL_Error;
5721 	free_certlist();
5722 	tqsl_selectCertificates(&certlist, &ncerts, callsign, dxccEntity, 0, 0, TQSL_SELECT_CERT_WITHKEYS);
5723 	int ko;
5724 	for (int i = 0; i < ncerts; i ++) {
5725 		if (!tqsl_getCertificateKeyOnly(certlist[i], &ko) && ko) {
5726 			if (tqsl_deleteCertificate(certlist[i])) {
5727 				wxLogError(getLocalizedErrorString());
5728 			}
5729 			certlist = NULL;		// Invalidated in deleteCertificate flow
5730 			ncerts = 0;
5731 			tQSL_Error = savedError;
5732 			return;
5733 		}
5734 	}
5735 	tQSL_Error = savedError;
5736 	return;
5737 }
5738 
5739 void MyFrame::CRQWizard(wxCommandEvent& event) {
5740 	tqslTrace("MyFrame::CRQWizard", NULL);
5741 	char renew = (req != 0) ? 1 : 0;
5742 	tQSL_Cert cert = (renew ? (reinterpret_cast<CertTreeItemData *>(cert_tree->GetItemData(cert_tree->GetSelection()))->getCert()) : 0);
5743 	CRQWiz wiz(req, cert, this, help, renew ? _("Renew a Callsign Certificate") : _("Request a new Callsign Certificate"));
5744 	int retval = 0;
5745 /*
5746 	CRQ_ProviderPage *prov = new CRQ_ProviderPage(wiz, req);
5747 	CRQ_IntroPage *intro = new CRQ_IntroPage(wiz, req);
5748 	CRQ_NamePage *name = new CRQ_NamePage(wiz, req);
5749 	CRQ_EmailPage *email = new CRQ_EmailPage(wiz, req);
5750 	wxSize size = prov->GetSize();
5751 	if (intro->GetSize().GetWidth() > size.GetWidth())
5752 		size = intro->GetSize();
5753 	if (name->GetSize().GetWidth() > size.GetWidth())
5754 		size = name->GetSize();
5755 	if (email->GetSize().GetWidth() > size.GetWidth())
5756 		size = email->GetSize();
5757 	CRQ_PasswordPage *pw = new CRQ_PasswordPage(wiz);
5758 	CRQ_SignPage *sign = new CRQ_SignPage(wiz, size, &(prov->provider));
5759 	wxWizardPageSimple::Chain(prov, intro);
5760 	wxWizardPageSimple::Chain(intro, name);
5761 	wxWizardPageSimple::Chain(name, email);
5762 	wxWizardPageSimple::Chain(email, pw);
5763 	if (renew)
5764 		sign->cert = ;
5765 	else
5766 		wxWizardPageSimple::Chain(pw, sign);
5767 
5768 	wiz.SetPageSize(size);
5769 
5770 */
5771 
5772 	if (wiz.RunWizard()) {
5773 		// Save or upload?
5774 		wxString file = flattenCallSign(wiz.callsign) + wxT(".") + wxT(TQSL_CRQ_FILE_EXT);
5775 		bool upload = false;
5776 		wxString msg = _("Do you want to upload this certificate request to LoTW now?");
5777 		if (!renew) {
5778 			msg += wxT("\n");
5779 			msg += _("You do not need an account on LoTW to do this.");
5780 		}
5781 		if (wxMessageBox(msg, _("Upload"), wxYES_NO|wxICON_QUESTION, this) == wxYES) {
5782 			upload = true;
5783 			// Save it in the working directory
5784 #ifdef _WIN32
5785 			file = wxString::FromUTF8(tQSL_BaseDir) + wxT("\\") + file;
5786 #else
5787 			file = wxString::FromUTF8(tQSL_BaseDir) + wxT("/") + file;
5788 #endif
5789 		} else {
5790 			// Where to put it?
5791 			wxString wildcard = _("tQSL Cert Request files (*.");
5792 			wildcard += wxString::FromUTF8(TQSL_CRQ_FILE_EXT ")|*." TQSL_CRQ_FILE_EXT);
5793 			wildcard += _("|All files (") + wxString::FromUTF8(ALLFILESWILD ")|" ALLFILESWILD);
5794 			file = wxFileSelector(_("Save request"), wxT(""), file, wxT(TQSL_CRQ_FILE_EXT), wildcard,
5795 				wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
5796 			if (file.IsEmpty()) {
5797 				wxLogMessage(_("Request cancelled"));
5798 				deleteRequest(wiz.callsign.ToUTF8(), wiz.dxcc);
5799 				return;
5800 			}
5801 		}
5802 		TQSL_CERT_REQ req;
5803 		strncpy(req.providerName, wiz.provider.organizationName, sizeof req.providerName);
5804 		strncpy(req.providerUnit, wiz.provider.organizationalUnitName, sizeof req.providerUnit);
5805 		strncpy(req.callSign, wiz.callsign.ToUTF8(), sizeof req.callSign);
5806 		strncpy(req.name, wiz.name.ToUTF8(), sizeof req.name);
5807 		strncpy(req.address1, wiz.addr1.ToUTF8(), sizeof req.address1);
5808 		strncpy(req.address2, wiz.addr2.ToUTF8(), sizeof req.address2);
5809 		strncpy(req.city, wiz.city.ToUTF8(), sizeof req.city);
5810 		strncpy(req.state, wiz.state.ToUTF8(), sizeof req.state);
5811 		strncpy(req.postalCode, wiz.zip.ToUTF8(), sizeof req.postalCode);
5812 		if (wiz.country.IsEmpty())
5813 			strncpy(req.country, "USA", sizeof req.country);
5814 		else
5815 			strncpy(req.country, wiz.country.ToUTF8(), sizeof req.country);
5816 		strncpy(req.emailAddress, wiz.email.ToUTF8(), sizeof req.emailAddress);
5817 		strncpy(req.password, wiz.password.ToUTF8(), sizeof req.password);
5818 		req.dxccEntity = wiz.dxcc;
5819 		req.qsoNotBefore = wiz.qsonotbefore;
5820 		req.qsoNotAfter = wiz.qsonotafter;
5821 		req.signer = wiz.cert;
5822 		if (req.signer) {
5823 			char buf[40];
5824 			void *call = 0;
5825 			if (!tqsl_getCertificateCallSign(req.signer, buf, sizeof(buf)))
5826 				call = &buf;
5827 			while (tqsl_beginSigning(req.signer, 0, getPassword, call)) {
5828 				if (tQSL_Error != TQSL_PASSWORD_ERROR) {
5829 					if (tQSL_Error == TQSL_CUSTOM_ERROR && (tQSL_Errno == ENOENT || tQSL_Errno == EPERM)) {
5830 						snprintf(tQSL_CustomError, sizeof tQSL_CustomError,
5831 							"Can't open the private key file for %s: %s", static_cast<char *>(call), strerror(tQSL_Errno));
5832 					}
5833 					wxLogError(getLocalizedErrorString());
5834 					deleteRequest(req.callSign, req.dxccEntity);
5835 					return;
5836 				}
5837 				// Try signing with the unicode version of the password
5838 				if (tqsl_beginSigning(req.signer, unipwd, NULL, call) == 0) {
5839 					// If OK, signing is ready to go.
5840 					break;
5841 				}
5842 				if (tQSL_Error != TQSL_PASSWORD_ERROR) {
5843 					deleteRequest(req.callSign, req.dxccEntity);
5844 					return;
5845 				} else {
5846 					wxLogError(getLocalizedErrorString());
5847 				}
5848 			}
5849 		}
5850 		req.renew = renew ? 1 : 0;
5851 		if (tqsl_createCertRequest(file.ToUTF8(), &req, 0, 0)) {
5852 			wxString msg = getLocalizedErrorString();
5853 			if (req.signer)
5854 				tqsl_endSigning(req.signer);
5855 			wxLogError(msg);
5856 			char m[500];
5857 			strncpy(m, msg.ToUTF8(), sizeof m);
5858 			wxMessageBox(wxString::Format(_("Error creating callsign certificate request: %hs"), m), _("Error creating Callsign Certificate Request"), wxOK | wxICON_EXCLAMATION, this);
5859 			deleteRequest(req.callSign, req.dxccEntity);
5860 			return;
5861 		}
5862 		if (upload) {
5863 #ifdef _WIN32
5864 			wchar_t* wfile = utf8_to_wchar(file.ToUTF8());
5865 			ifstream in(wfile, ios::in | ios::binary);
5866 			free_wchar(wfile);
5867 #else
5868 			ifstream in(file.ToUTF8(), ios::in | ios::binary);
5869 #endif
5870 			if (!in) {
5871 				wxLogError(_("Error opening certificate request file %s: %hs"), file.c_str(), strerror(errno));
5872 				deleteRequest(req.callSign, req.dxccEntity);
5873 			} else {
5874 				string contents;
5875 				in.seekg(0, ios::end);
5876 				contents.resize(in.tellg());
5877 				in.seekg(0, ios::beg);
5878 				in.read(&contents[0], contents.size());
5879 				in.close();
5880 
5881 				wxString fileType(_("Certificate Request"));
5882 				retval = UploadFile(file, file.ToUTF8(), 0, reinterpret_cast<void *>(const_cast<char *>(contents.c_str())),
5883 							contents.size(), fileType);
5884 				if (retval != 0) {
5885 					wxLogError(_("Your certificate request did not upload properly"));
5886 					wxLogError(_("Please try again."));
5887 					deleteRequest(req.callSign, req.dxccEntity);
5888 				}
5889 			}
5890 		} else {
5891 			wxString msg = _("You may now send your new certificate request (");
5892 			msg += file;
5893 			msg += wxT(")");
5894 			if (wiz.provider.emailAddress[0] != 0) {
5895 				msg += wxT("\n");
5896 				msg += _("to:");
5897 				msg += wxT("\n   ");
5898 				msg += wxString::FromUTF8(wiz.provider.emailAddress);
5899 			}
5900 			if (wiz.provider.url[0] != 0) {
5901 				msg += wxT("\n");
5902 				if (wiz.provider.emailAddress[0] != 0)
5903 					msg += _("or ");
5904 				msg += wxString(_("see:"));
5905 				msg += wxT("\n    ");
5906 				msg += wxString::FromUTF8(wiz.provider.url);
5907 			}
5908 			wxMessageBox(msg, wxT("TQSL"), wxOK | wxICON_ERROR, this);
5909 		}
5910 		if (retval == 0) {
5911 			wxString pending = wxConfig::Get()->Read(wxT("RequestPending"));
5912 			if (pending.IsEmpty())
5913 				pending = wiz.callsign;
5914 			else
5915 				pending += wxT(",") + wiz.callsign;
5916 			wxConfig::Get()->Write(wxT("RequestPending"), pending);
5917 		}
5918 		if (req.signer)
5919 			tqsl_endSigning(req.signer);
5920 		cert_tree->Build(CERTLIST_FLAGS);
5921 		CertTreeReset();
5922 	}
5923 }
5924 
5925 void
5926 MyFrame::CertTreeReset() {
5927 	if (!cert_save_label) return;
5928 	wxString nl = wxT("\n");
5929 	cert_save_label->SetLabel(nl + _("Save a Callsign Certificate"));
5930 	cert_save_button->SetLabel(nl + _("Save a Callsign Certificate"));
5931 	cert_renew_label->SetLabel(nl + _("Renew a Callsign Certificate"));
5932 	cert_renew_button->SetLabel(nl + _("Renew a Callsign Certificate"));
5933 	cert_prop_label->SetLabel(nl + _("Display a Callsign Certificate"));
5934 	cert_prop_button->SetLabel(nl + _("Display a Callsign Certificate"));
5935 	cert_menu->Enable(tc_c_Renew, false);
5936 	cert_renew_button->Enable(false);
5937 	cert_select_label->SetLabel(nl + _("Select a Callsign Certificate to process"));
5938 	cert_save_button->Enable(false);
5939 	cert_prop_button->Enable(false);
5940 	int ncalls = 0;
5941 	tqsl_getDeletedCallsignCertificates(NULL, &ncalls, NULL);
5942 	cert_menu->Enable(tc_c_Undelete, ncalls > 0);
5943 }
5944 
5945 void MyFrame::OnCertTreeSel(wxTreeEvent& event) {
5946 	tqslTrace("MyFrame::OnCertTreeSel", NULL);
5947 	wxTreeItemId id = event.GetItem();
5948 	CertTreeItemData *data = reinterpret_cast<CertTreeItemData *>(cert_tree->GetItemData(id));
5949 	if (data) {
5950 		int keyonly = 0;
5951 		int expired = 0;
5952 		int superseded = 0;
5953 		int deleted = 0;
5954 		char call[40];
5955 		tqsl_getCertificateCallSign(data->getCert(), call, sizeof call);
5956 		wxString callSign = wxString::FromUTF8(call);
5957 		tqsl_getCertificateKeyOnly(data->getCert(), &keyonly);
5958 		tqsl_isCertificateExpired(data->getCert(), &expired);
5959 		tqsl_isCertificateSuperceded(data->getCert(), &superseded);
5960 		tqsl_getDeletedCallsignCertificates(NULL, &deleted, call);
5961 		tqslTrace("MyFrame::OnCertTreeSel", "call=%s", call);
5962 
5963 		cert_select_label->SetLabel(wxT(""));
5964 		cert_menu->Enable(tc_c_Properties, true);
5965 		cert_menu->Enable(tc_c_Export, true);
5966 		cert_menu->Enable(tc_c_Delete, true);
5967 		cert_menu->Enable(tc_c_Renew, true);
5968 		cert_menu->Enable(tc_c_Undelete, deleted != 0);
5969 		cert_save_button->Enable(true);
5970 		cert_load_button->Enable(true);
5971 		cert_prop_button->Enable(true);
5972 
5973 		int w, h;
5974 		loc_add_label->GetSize(&w, &h);
5975 		wxString nl = wxT("\n");
5976 		cert_save_label->SetLabel(nl + _("Save the Callsign Certificate for") + wxT(" ") + callSign);
5977 		cert_save_label->Wrap(w - 10);
5978 		cert_save_button->SetLabel(nl + _("Save the Callsign Certificate for") + wxT(" ") + callSign);
5979 		cert_prop_label->SetLabel(nl + _("Display the Callsign Certificate properties for") + wxT(" ") + callSign);
5980 		cert_prop_label->Wrap(w - 10);
5981 		cert_prop_button->SetLabel(nl + _("Display the Callsign Certificate properties for") + wxT(" ") + callSign);
5982 		if (!(keyonly || expired || superseded)) {
5983 			cert_renew_label->SetLabel(nl + _("Renew the Callsign Certificate for") +wxT(" ") + callSign);
5984 			cert_renew_label->Wrap(w - 10);
5985 			cert_renew_button->SetLabel(nl + _("Renew the Callsign Certificate for") +wxT(" ") + callSign);
5986 		} else {
5987 			cert_renew_label->SetLabel(nl + _("Renew a Callsign Certificate"));
5988 			cert_renew_button->SetLabel(nl + _("Renew a Callsign Certificate"));
5989 		}
5990 		cert_menu->Enable(tc_c_Renew, !(keyonly || expired || superseded));
5991 		cert_menu->Enable(tc_c_Undelete, deleted != 0);
5992 		cert_renew_button->Enable(!(keyonly || expired || superseded));
5993 	} else {
5994 		CertTreeReset();
5995 	}
5996 }
5997 
5998 void MyFrame::OnCertProperties(wxCommandEvent& WXUNUSED(event)) {
5999 	tqslTrace("MyFrame::OnCertProperties", NULL);
6000 	CertTreeItemData *data = reinterpret_cast<CertTreeItemData *>(cert_tree->GetItemData(cert_tree->GetSelection()));
6001 	if (data != NULL)
6002 		displayCertProperties(data, this);
6003 }
6004 
6005 void MyFrame::OnCertExport(wxCommandEvent& WXUNUSED(event)) {
6006 	tqslTrace("MyFrame::OnCertExport", NULL);
6007 	CertTreeItemData *data = reinterpret_cast<CertTreeItemData *>(cert_tree->GetItemData(cert_tree->GetSelection()));
6008 	if (data == NULL)	// "Never happens"
6009 		return;
6010 
6011 	char call[40];
6012 	if (tqsl_getCertificateCallSign(data->getCert(), call, sizeof call)) {
6013 		wxLogError(getLocalizedErrorString());
6014 		return;
6015 	}
6016 	tqslTrace("MyFrame::OnCertExport", "call=%s", call);
6017 	wxString file_default = flattenCallSign(wxString::FromUTF8(call));
6018 	int ko = 0;
6019 	tqsl_getCertificateKeyOnly(data->getCert(), &ko);
6020 	if (ko)
6021 		file_default += wxT("-key-only");
6022 	file_default += wxT(".p12");
6023 	wxString path = wxConfig::Get()->Read(wxT("CertFilePath"), wxT(""));
6024 	wxString filename = wxFileSelector(_("Enter the name for the new Certificate Container file"), path,
6025 		file_default, wxT(".p12"), _("Certificate Container files (*.p12)|*.p12|All files (*.*)|*.*"),
6026 		wxFD_SAVE|wxFD_OVERWRITE_PROMPT, this);
6027 	if (filename == wxT(""))
6028 		return;
6029 	wxConfig::Get()->Write(wxT("CertFilePath"), wxPathOnly(filename));
6030 	wxString msg = _("Enter the password for the certificate container file.");
6031 		msg += wxT("\n\n");
6032 		msg += _("If you are using a computer system that is shared "
6033 			"with others, you should specify a password to "
6034 			"protect this certificate. However, if you are using "
6035 			"a computer in a private residence, no password need be specified.");
6036 		msg += wxT("\n\n");
6037 		msg += _("You will have to enter the password any time you "
6038 			"load the file into TrustedQSL.");
6039 		msg += wxT("\n\n");
6040 		msg += _("Leave the password blank and click 'OK' unless you want to "
6041 			"use a password.");
6042 		msg += wxT("\n\n");
6043 	GetNewPasswordDialog dial(this, _("Certificate Container Password"), msg, true, help, wxT("save.htm"));
6044 	if (dial.ShowModal() != wxID_OK)
6045 		return;	// Cancelled
6046 	int terr;
6047 	do {
6048 		terr = tqsl_beginSigning(data->getCert(), 0, getPassword, reinterpret_cast<void *>(&call));
6049 		if (terr) {
6050 			if (tQSL_Error == TQSL_PASSWORD_ERROR) {
6051 				terr = tqsl_beginSigning(data->getCert(), unipwd, NULL, reinterpret_cast<void *>(&call));
6052 				if (terr) {
6053 					if (tQSL_Error == TQSL_PASSWORD_ERROR)
6054 						continue;
6055 					wxLogError(getLocalizedErrorString());
6056 				}
6057 				continue;
6058 			}
6059 			if (tQSL_Error == TQSL_OPERATOR_ABORT)
6060 				return;
6061 			// Unable to open the private key
6062 			if (tQSL_Error == TQSL_CUSTOM_ERROR && (tQSL_Errno == ENOENT || tQSL_Errno == EPERM)) {
6063 				snprintf(tQSL_CustomError, sizeof tQSL_CustomError,
6064 					"Can't open the private key file for %s: %s", call, strerror(tQSL_Errno));
6065 			}
6066 			wxLogError(getLocalizedErrorString());
6067 			return;
6068 		}
6069 	} while (terr);
6070 	// When setting the password, always use UTF8.
6071 	if (tqsl_exportPKCS12File(data->getCert(), filename.ToUTF8(), dial.Password().ToUTF8())) {
6072 		char buf[500];
6073 		strncpy(buf, getLocalizedErrorString().ToUTF8(), sizeof buf);
6074 		wxLogError(wxString::Format(_("Export to %s failed: %hs"), filename.c_str(), buf));
6075 	} else {
6076 		wxLogMessage(_("Certificate saved in file %s"), filename.c_str());
6077 	}
6078 	tqsl_endSigning(data->getCert());
6079 }
6080 
6081 void MyFrame::OnCertDelete(wxCommandEvent& WXUNUSED(event)) {
6082 	tqslTrace("MyFrame::OnCertDelete", NULL);
6083 	CertTreeItemData *data = reinterpret_cast<CertTreeItemData *>(cert_tree->GetItemData(cert_tree->GetSelection()));
6084 	if (data == NULL)	// "Never happens"
6085 		return;
6086 
6087 	wxString warn = _("This will remove the selected callsign certificate from your system.");
6088 	warn += wxT("\n");
6089 	warn += _("You will NOT be able to recover it by loading a .TQ6 file.");
6090 	warn += wxT("\n");
6091 	warn += _("You WILL be able to recover it from a container (.p12) file,");
6092 	warn += wxT("\n");
6093 	warn += _("if you have created one via the Callsign Certificate menu's");
6094 	warn += wxT("\n");
6095 	warn += _("'Save Callsign Certificate' command.");
6096 	warn += wxT("\n\n");
6097 	warn += _("Are you sure you want to delete the certificate?");
6098 	if (wxMessageBox(warn, _("Warning"), wxYES_NO|wxICON_QUESTION, this) == wxYES) {
6099 		char buf[128];
6100 		if (!tqsl_getCertificateCallSign(data->getCert(), buf, sizeof buf)) {
6101 			wxString call = wxString::FromUTF8(buf);
6102 			wxString pending = wxConfig::Get()->Read(wxT("RequestPending"));
6103 			pending.Replace(call, wxT(""), true);
6104 			wxString rest;
6105 			while (pending.StartsWith(wxT(","), &rest))
6106 				pending = rest;
6107 			while (pending.EndsWith(wxT(","), &rest))
6108 				pending = rest;
6109 			wxConfig::Get()->Write(wxT("RequestPending"), pending);
6110 		}
6111 		int keyonly, sup, exp;
6112 		long serial;
6113 		tqsl_getCertificateKeyOnly(data->getCert(), &keyonly);
6114 		tqsl_getCertificateSerial(data->getCert(), &serial);
6115 		tqsl_isCertificateExpired(data->getCert(), &exp);
6116 		tqsl_isCertificateSuperceded(data->getCert(), &sup);
6117 		tqslTrace("MyFrame::OnCertDelete", "About to delete cert for callsign %s, serial %ld, keyonly %d, superceded %d, expired %d", buf, serial, keyonly, sup, exp);
6118 		if (tqsl_deleteCertificate(data->getCert()))
6119 			wxLogError(getLocalizedErrorString());
6120 		cert_tree->Build(CERTLIST_FLAGS);
6121 		CertTreeReset();
6122 	}
6123 }
6124 
6125 void MyFrame::OnCertUndelete(wxCommandEvent& WXUNUSED(event)) {
6126 	tqslTrace("MyFrame::OnCertUndelete", NULL);
6127 
6128 	int ncalls;
6129 	char **calls = NULL;
6130 
6131 	try {
6132 		check_tqsl_error(tqsl_getDeletedCallsignCertificates(&calls, &ncalls, NULL));
6133 
6134 		if (ncalls <= 0) {
6135 			wxMessageBox(_("There are no deleted Callsign Certificates to restore"), _("Undelete Error"), wxOK | wxICON_EXCLAMATION, this);
6136 			return;
6137 		}
6138 
6139 		wxArrayString choices;
6140 		choices.clear();
6141 		for (int i = 0; i < ncalls; i++) {
6142 			choices.Add(wxString::FromUTF8(calls[i]));
6143 		}
6144 		choices.Sort();
6145 
6146 		wxString selected = wxGetSingleChoice(_("Choose a Callsign Certificate to restore"),
6147 					 	_("Callsign Certificates"),
6148 						choices);
6149 		if (selected.IsEmpty())
6150 			return;			// Cancelled
6151 
6152 		check_tqsl_error(tqsl_restoreCallsignCertificate(selected.ToUTF8()));
6153 		tqsl_freeDeletedCertificateList(calls, ncalls);
6154 		cert_tree->Build(CERTLIST_FLAGS);
6155 		CertTreeReset();
6156 	} catch(TQSLException& x) {
6157 		wxLogError(wxT("%hs"), x.what());
6158 	}
6159 }
6160 
6161 void
6162 MyFrame::LocTreeReset() {
6163 	if (!loc_edit_button) return;
6164 	loc_edit_button->Disable();
6165 	loc_delete_button->Disable();
6166 	loc_prop_button->Disable();
6167 	stn_menu->Enable(tm_s_Properties, false);
6168 	wxString nl = wxT("\n");
6169 	loc_edit_label->SetLabel(nl + _("Edit a Station Location"));
6170 	loc_edit_button->SetLabel(nl + _("Edit a Station Location"));
6171 	loc_delete_label->SetLabel(nl + _("Delete a Station Location"));
6172 	loc_delete_button->SetLabel(nl + _("Delete a Station Location"));
6173 	loc_prop_label->SetLabel(nl + _("Display Station Location Properties"));
6174 	loc_prop_button->SetLabel(nl + _("Display Station Location Properties"));
6175 	loc_select_label->SetLabel(nl + _("Select a Station Location to process"));
6176 }
6177 
6178 void MyFrame::OnLocTreeSel(wxTreeEvent& event) {
6179 	tqslTrace("MyFrame::OnLocTreeSel", NULL);
6180 	wxTreeItemId id = event.GetItem();
6181 	LocTreeItemData *data = reinterpret_cast<LocTreeItemData *>(loc_tree->GetItemData(id));
6182 	if (data) {
6183 		int w, h;
6184 		wxString lname = data->getLocname();
6185 		wxString call = data->getCallSign();
6186 		tqslTrace("MyFrame::OnLocTreeSel", "lname=%s, call=%s", S(lname), S(call));
6187 
6188 		loc_add_label->GetSize(&w, &h);
6189 
6190 		loc_edit_button->Enable();
6191 		loc_delete_button->Enable();
6192 		loc_prop_button->Enable();
6193 		stn_menu->Enable(tm_s_Properties, true);
6194 		loc_edit_label->SetLabel(_("Edit Station Location ") + call + wxT(": ") + lname);
6195 		loc_edit_label->Wrap(w - 10);
6196 		loc_edit_button->SetLabel(_("Edit Station Location ") + call + wxT(": ") + lname);
6197 		loc_delete_label->SetLabel(_("Delete Station Location ") + call + wxT(": ") + lname);
6198 		loc_delete_label->Wrap(w - 10);
6199 		loc_delete_button->SetLabel(_("Delete Station Location ") + call + wxT(": ") + lname);
6200 		loc_prop_label->SetLabel(_("Display Station Location Properties for ") + call + wxT(": ") + lname);
6201 		loc_prop_label->Wrap(w - 10);
6202 		loc_prop_button->SetLabel(_("Display Station Location Properties for ") + call + wxT(": ") + lname);
6203 		loc_select_label->SetLabel(wxT(""));
6204 	} else {
6205 		LocTreeReset();
6206 	}
6207 }
6208 
6209 void MyFrame::OnLocProperties(wxCommandEvent& WXUNUSED(event)) {
6210 	tqslTrace("MyFrame::OnLocProperties", NULL);
6211 	LocTreeItemData *data = reinterpret_cast<LocTreeItemData *>(loc_tree->GetItemData(loc_tree->GetSelection()));
6212 	if (data != NULL)
6213 		displayLocProperties(data, this);
6214 }
6215 
6216 void MyFrame::OnLocDelete(wxCommandEvent& WXUNUSED(event)) {
6217 	tqslTrace("MyFrame::OnLocDelete", NULL);
6218 	LocTreeItemData *data = reinterpret_cast<LocTreeItemData *>(loc_tree->GetItemData(loc_tree->GetSelection()));
6219 	if (data == NULL)	// "Never happens"
6220 		return;
6221 
6222 	wxString warn = _("This will remove this station location from your system.");
6223 	warn += wxT("\n");
6224 	warn += _("Are you sure you want to delete this station location?");
6225 	if (wxMessageBox(warn, _("Warning"), wxYES_NO|wxICON_QUESTION, this) == wxYES) {
6226 		if (tqsl_deleteStationLocation(data->getLocname().ToUTF8()))
6227 			wxLogError(getLocalizedErrorString());
6228 		loc_tree->Build();
6229 		LocTreeReset();
6230 	}
6231 }
6232 
6233 void MyFrame::OnLocUndelete(wxCommandEvent& WXUNUSED(event)) {
6234 	tqslTrace("MyFrame::OnLocUndelete", NULL);
6235 
6236 	int nloc;
6237 	char **locp = NULL;
6238 
6239 	try {
6240 		check_tqsl_error(tqsl_getDeletedStationLocations(&locp, &nloc));
6241 
6242 		if (nloc <= 0) {
6243 			wxMessageBox(_("There are no deleted Station Locations to restore"), _("Undelete Error"), wxOK | wxICON_EXCLAMATION, this);
6244 			return;
6245 		}
6246 
6247 		wxArrayString choices;
6248 		choices.clear();
6249 		for (int i = 0; i < nloc; i++) {
6250 			choices.Add(wxString::FromUTF8(locp[i]));
6251 		}
6252 		choices.Sort();
6253 
6254 		wxString selected = wxGetSingleChoice(_("Choose a Station Location to restore"),
6255 					 	_("Station Locations"),
6256 						choices);
6257 		if (selected.IsEmpty()) {
6258 			tqsl_freeDeletedLocationList(locp, nloc);
6259 			return;			// Cancelled
6260 		}
6261 
6262 		check_tqsl_error(tqsl_restoreStationLocation(selected.ToUTF8()));
6263 		tqsl_freeDeletedLocationList(locp, nloc);
6264 	} catch(TQSLException& x) {
6265 		wxLogError(wxT("%hs"), x.what());
6266 	}
6267 	loc_tree->Build();
6268 	LocTreeReset();
6269 }
6270 
6271 void MyFrame::OnLocEdit(wxCommandEvent& WXUNUSED(event)) {
6272 	tqslTrace("MyFrame::OnLocEdit", NULL);
6273 	LocTreeItemData *data = reinterpret_cast<LocTreeItemData *>(loc_tree->GetItemData(loc_tree->GetSelection()));
6274 	if (data == NULL)	// "Never happens"
6275 		return;
6276 
6277 	tQSL_Location loc;
6278 	wxString selname;
6279 	char errbuf[512];
6280 
6281 	try {
6282 		check_tqsl_error(tqsl_getStationLocation(&loc, data->getLocname().ToUTF8()));
6283 		if (verify_cert(loc, true)) {	// Check if there is a certificate before editing
6284 			check_tqsl_error(tqsl_getStationLocationErrors(loc, errbuf, sizeof(errbuf)));
6285 			if (strlen(errbuf) > 0) {
6286 				wxString fmt = wxT("%hs\n");
6287 				fmt += _("The invalid data was ignored.");
6288 				wxMessageBox(wxString::Format(fmt, errbuf), _("Station Location data error"), wxOK | wxICON_EXCLAMATION, this);
6289 			}
6290 			char loccall[512];
6291 			check_tqsl_error(tqsl_getLocationCallSign(loc, loccall, sizeof loccall));
6292 			selname = run_station_wizard(this, loc, help, true, wxString::Format(_("Edit Station Location : %hs - %s"), loccall, data->getLocname().c_str()));
6293 			check_tqsl_error(tqsl_endStationLocationCapture(&loc));
6294 		}
6295 	}
6296 	catch(TQSLException& x) {
6297 		wxLogError(wxT("%hs"), x.what());
6298 	}
6299 	loc_tree->Build();
6300 	LocTreeReset();
6301 }
6302 
6303 void MyFrame::OnLoginToLogbook(wxCommandEvent& WXUNUSED(event)) {
6304 	tqslTrace("MyFrame::OnLoginToLogbook", NULL);
6305 	wxString url = wxConfig::Get()->Read(wxT("LogbookURL"), DEFAULT_LOTW_LOGIN_URL);
6306 	if (!url.IsEmpty())
6307 		wxLaunchDefaultBrowser(url);
6308 	return;
6309 }
6310 
6311 void MyFrame::OnChooseLanguage(wxCommandEvent& WXUNUSED(event)) {
6312 	tqslTrace("MyFrame::OnChooseLanguage", "Language choice dialog");
6313 
6314 	wxLanguage lang = wxGetApp().GetLang();
6315 	long lng = wxGetSingleChoiceIndex(_("Please choose language:"),
6316 					 _("Language"),
6317 					WXSIZEOF(langNames), langNames, this);
6318 	tqslTrace("MyFrame::OnChooseLanguage", "Language chosen: %d", lng);
6319 	if (lng == -1 || langIds[lng] == lang)		// Cancel or No change
6320 		return;
6321 
6322 	wxConfig::Get()->Write(wxT("Language"), static_cast<int>(langIds[lng]));
6323 	wxConfig::Get()->Flush();
6324 
6325 	if (wxLocale::IsAvailable(langIds[lng])) {
6326 		if (locale) delete locale;
6327 		locale = new wxLocale(langIds[lng]);
6328 		if (!locale) locale = new wxLocale(wxLANGUAGE_DEFAULT);
6329 	} else {
6330 		wxLogError(wxT("This language is not supported by the system."));
6331 		locale = new wxLocale(wxLANGUAGE_DEFAULT);
6332 	}
6333 	// Add a subdirectory for language files
6334 	locale->AddCatalogLookupPathPrefix(wxT("lang"));
6335 
6336 	// Initialize the catalogs we'll be using
6337 	locale->AddCatalog(wxT("tqslapp"));
6338 	locale->AddCatalog(wxT("wxstd"));
6339 
6340 	// this catalog is installed in standard location on Linux systems and
6341 	// shows that you may make use of the standard message catalogs as well
6342 	//
6343 	// If it's not installed on your system, it is just silently ignored
6344 #ifdef __LINUX__
6345         {
6346 		wxLogNull nolog;
6347 		locale->AddCatalog(wxT("fileutils"));
6348 	}
6349 #endif
6350 	SaveWindowLayout();
6351 	tqslTrace("MyFrame::OnChooseLanguage", "Destroying GUI");
6352 	Destroy();
6353 	tqslTrace("MyFrame::OnChooseLanguage", "Recreating GUI");
6354 	(reinterpret_cast<QSLApp*>(wxTheApp))->OnInit();
6355 }
6356 
6357 class CertPropDial : public wxDialog {
6358  public:
6359 	explicit CertPropDial(tQSL_Cert cert, wxWindow *parent = 0);
6360 	void closeMe(wxCommandEvent&) { EndModal(wxID_OK); }
6361 	DECLARE_EVENT_TABLE()
6362 };
6363 
6364 BEGIN_EVENT_TABLE(CertPropDial, wxDialog)
6365 	EVT_BUTTON(wxID_OK, CertPropDial::closeMe)
6366 END_EVENT_TABLE()
6367 
6368 CertPropDial::CertPropDial(tQSL_Cert cert, wxWindow *parent)
6369 		: wxDialog(parent, -1, _("Certificate Properties"), wxDefaultPosition, wxSize(400, 15 * LABEL_HEIGHT)) {
6370 	tqslTrace("CertPropDial::CertPropDial", "cert=%lx", static_cast<void *>(cert));
6371 	const char *labels[] = {
6372 		__("Begins: "),
6373 		__("Expires: "),
6374 		__("Organization: "),
6375 		"",
6376 		__("Serial: "),
6377 		__("Operator: "),
6378 		__("Call sign: "),
6379 		__("DXCC Entity: "),
6380 		__("QSO Start Date: "),
6381 		__("QSO End Date: "),
6382 		__("Password: ")
6383 	};
6384 
6385 	wxBoxSizer *topsizer = new wxBoxSizer(wxVERTICAL);
6386 
6387 	int label_width = 0;
6388 
6389 	wxStaticText* mst = new wxStaticText(this, -1, wxT("M"));
6390 	int char_width = mst->GetSize().GetWidth();
6391 	// Measure the widest label
6392 	for (int i = 0; i < static_cast<int>(sizeof labels / sizeof labels[0]); i++) {
6393 		int em_w;
6394 		wxString lab = wxGetTranslation(wxString::FromUTF8(labels[i]));
6395 		mst->SetLabel(lab);
6396 		em_w = mst->GetSize().GetWidth();
6397 		if (em_w > label_width) label_width = em_w;
6398 	}
6399 
6400 	int keyonly;
6401 	char callsign[40];
6402 
6403 	tqsl_getCertificateKeyOnly(cert, &keyonly);
6404 	wxString blob = wxT("");
6405 	for (int i = 0; i < static_cast<int>(sizeof labels / sizeof labels[0]); i++) {
6406 		wxString lbl = wxGetTranslation(wxString::FromUTF8(labels[i]));
6407 		while (1) {
6408 			mst->SetLabel(lbl);
6409 			int cur_size = mst->GetSize().GetWidth();
6410 			int delta = label_width - cur_size;
6411 			if (delta < char_width) break;
6412 			lbl += wxT(" ");
6413 		}
6414 
6415 		char buf[128] = "";
6416 		tQSL_Date date;
6417 		DXCC DXCC;
6418 		int dxcc;
6419 		long serial;
6420 		switch (i) {
6421 			case 0:
6422 				if (keyonly)
6423 					strncpy(buf, "N/A", sizeof buf);
6424 				else if (!tqsl_getCertificateNotBeforeDate(cert, &date))
6425 					tqsl_convertDateToText(&date, buf, sizeof buf);
6426 				break;
6427 			case 1:
6428 				if (keyonly)
6429 					strncpy(buf, "N/A", sizeof buf);
6430 				else if (!tqsl_getCertificateNotAfterDate(cert, &date))
6431 					tqsl_convertDateToText(&date, buf, sizeof buf);
6432 				break;
6433 			case 2:
6434 				tqsl_getCertificateIssuerOrganization(cert, buf, sizeof buf);
6435 				break;
6436 			case 3:
6437 				tqsl_getCertificateIssuerOrganizationalUnit(cert, buf, sizeof buf);
6438 				break;
6439 			case 4:
6440 				if (keyonly) {
6441 					strncpy(buf, "N/A", sizeof buf);
6442 				} else {
6443 					tqsl_getCertificateSerial(cert, &serial);
6444 					snprintf(buf, sizeof buf, "%ld", serial);
6445 				}
6446 				break;
6447 			case 5:
6448 				if (keyonly)
6449 					strncpy(buf, "N/A", sizeof buf);
6450 				else
6451 					tqsl_getCertificateAROName(cert, buf, sizeof buf);
6452 				break;
6453 			case 6:
6454 				tqsl_getCertificateCallSign(cert, buf, sizeof buf);
6455 				strncpy(callsign, buf, sizeof callsign);
6456 				break;
6457 			case 7:
6458 				tqsl_getCertificateDXCCEntity(cert, &dxcc);
6459 				DXCC.getByEntity(dxcc);
6460 				strncpy(buf, DXCC.name(), sizeof buf);
6461 				break;
6462 			case 8:
6463 				if (!tqsl_getCertificateQSONotBeforeDate(cert, &date))
6464 					tqsl_convertDateToText(&date, buf, sizeof buf);
6465 				break;
6466 			case 9:
6467 				if (!tqsl_getCertificateQSONotAfterDate(cert, &date))
6468 					tqsl_convertDateToText(&date, buf, sizeof buf);
6469 				break;
6470 			case 10:
6471 				switch (tqsl_getCertificatePrivateKeyType(cert)) {
6472 					case TQSL_PK_TYPE_ERR:
6473 						if (tQSL_Error == TQSL_CUSTOM_ERROR && (tQSL_Errno == ENOENT || tQSL_Errno == EPERM)) {
6474 							snprintf(tQSL_CustomError, sizeof tQSL_CustomError,
6475 								"Can't open the private key file for %s: %s", callsign, strerror(tQSL_Errno));
6476 						}
6477 						wxMessageBox(getLocalizedErrorString(), _("Error"), wxOK | wxICON_WARNING, this);
6478 						strncpy(buf, __("<ERROR>"), sizeof buf);
6479 						break;
6480 					case TQSL_PK_TYPE_NONE:
6481 						strncpy(buf, __("None"), sizeof buf);
6482 						break;
6483 					case TQSL_PK_TYPE_UNENC:
6484 						strncpy(buf, __("None"), sizeof buf);
6485 						break;
6486 					case TQSL_PK_TYPE_ENC:
6487 						strncpy(buf, __("Password protected"), sizeof buf);
6488 						break;
6489 				}
6490 				break;
6491 		}
6492 		if (keyonly && i == 0) {
6493 			blob += _("Certificate Request:");
6494 			blob += wxT("\t");
6495 			blob += _("Awaiting response from ARRL");
6496 			blob += wxT("\n");
6497 		}
6498 		if (!keyonly || i > 1) {
6499 			blob += lbl;
6500 			blob += wxT("\t");
6501 			blob += wxGetTranslation(wxString::FromUTF8(buf));
6502 			blob += wxT("\n");
6503 		}
6504 	}
6505 	delete mst;
6506 
6507 	int sup, exp;
6508 	if (tqsl_isCertificateSuperceded(cert, &sup) == 0 && sup) {
6509 		blob += wxT("\t\t\t\t");
6510 		blob += _("Replaced");
6511 		blob += wxT("\n");
6512 	}
6513 	if (tqsl_isCertificateExpired(cert, &exp) == 0 && exp) {
6514 		blob += wxT("\t\t\t\t");
6515 		blob += _("Expired");
6516 		blob += wxT("\n");
6517 	}
6518 
6519 	topsizer->Add(new wxStaticText(this, -1, blob));
6520 	topsizer->Add(
6521 		new wxButton(this, wxID_OK, _("Close")),
6522 		0, wxALIGN_CENTER | wxALL, 10
6523 	);
6524 	SetAutoLayout(TRUE);
6525 	SetSizer(topsizer);
6526 	topsizer->Fit(this);
6527 	topsizer->SetSizeHints(this);
6528 	CenterOnParent();
6529 }
6530 
6531 void
6532 displayCertProperties(CertTreeItemData *item, wxWindow *parent) {
6533 	tqslTrace("displayCertProperties", "item=%lx", static_cast<void *>(item));
6534 	if (item != NULL) {
6535 		CertPropDial dial(item->getCert(), parent);
6536 		dial.ShowModal();
6537 	}
6538 }
6539 
6540 
6541 class LocPropDial : public wxDialog {
6542  public:
6543 	explicit LocPropDial(wxString locname, wxWindow *parent = 0);
6544 	void closeMe(wxCommandEvent&) { EndModal(wxID_OK); }
6545 	DECLARE_EVENT_TABLE()
6546 };
6547 
6548 BEGIN_EVENT_TABLE(LocPropDial, wxDialog)
6549 	EVT_BUTTON(wxID_OK, LocPropDial::closeMe)
6550 END_EVENT_TABLE()
6551 
6552 LocPropDial::LocPropDial(wxString locname, wxWindow *parent)
6553 		: wxDialog(parent, -1, _("Station Location Properties"), wxDefaultPosition, wxSize(1000, 15 * LABEL_HEIGHT)) {
6554 	tqslTrace("LocPropDial", "locname=%s", S(locname));
6555 
6556 	const char *fields[] = { "CALL", __("Call sign: "),
6557 				 "DXCC", __("DXCC Entity: "),
6558 				 "GRIDSQUARE", __("Grid Square: "),
6559 				 "ITUZ", __("ITU Zone: "),
6560 				 "CQZ", __("CQ Zone: "),
6561 				 "IOTA", __("IOTA Locator: "),
6562 				 "US_STATE", __("State: "),
6563 				 "US_COUNTY", __("County: "),
6564 				 "US_PARK", __("Park: "),
6565 				 "CA_PROVINCE", __("Province: "),
6566 				 "CA_US_PARK", __("Park: "),
6567 				 "RU_OBLAST", __("Oblast: "),
6568 				 "CN_PROVINCE", __("Province: "),
6569 				 "AU_STATE", __("State: "),
6570 				 "DX_US_PARK", __("Park: ") };
6571 
6572 	tQSL_Location loc;
6573 	try {
6574 		check_tqsl_error(tqsl_getStationLocation(&loc, locname.ToUTF8()));
6575 	}
6576 	catch(TQSLException& x) {
6577 		wxLogError(wxT("%hs"), x.what());
6578 		return;
6579 	}
6580 
6581 	wxBoxSizer *topsizer = new wxBoxSizer(wxVERTICAL);
6582 
6583 	int label_width = 0;
6584 
6585 	wxStaticText* mst = new wxStaticText(this, -1, wxT("M"));
6586 	int char_width = mst->GetSize().GetWidth();
6587 	// Measure the widest label
6588 	for (int i = 0; i < static_cast<int>(sizeof fields / sizeof fields[0]); i++) {
6589 		int em_w;
6590 		mst->SetLabel(wxGetTranslation(wxString::FromUTF8(fields[i])));
6591 		em_w = mst->GetSize().GetWidth();
6592 		if (em_w > label_width) label_width = em_w;
6593 	}
6594 
6595 	wxString blob = wxT("");
6596 	char fieldbuf[512];
6597 	for (int i = 0; i < static_cast<int>(sizeof fields / sizeof fields[0]); i+=2) {
6598 		if (tqsl_getStationLocationField(loc, fields[i], fieldbuf, sizeof fieldbuf) == 0) {
6599 			if (strlen(fieldbuf) > 0 && strcmp(fieldbuf, "[None]") != 0) {
6600 				wxString lbl = wxGetTranslation(wxString::FromUTF8(fields[i+1]));
6601 				while(1) {
6602 					mst->SetLabel(lbl);
6603 					int cur_size = mst->GetSize().GetWidth();
6604 					int delta = label_width - cur_size;
6605 					if (delta < char_width) break;
6606 					lbl += wxT(" ");
6607 				}
6608 				blob += lbl;
6609 				blob += wxT("\t");
6610 				if (!strcmp(fields[i], "DXCC")) {
6611 					int dxcc = strtol(fieldbuf, NULL, 10);
6612 					const char *dxccname = NULL;
6613 					if (tqsl_getDXCCEntityName(dxcc, &dxccname))
6614 						strncpy(fieldbuf, "Unknown", sizeof fieldbuf);
6615 					else
6616 						strncpy(fieldbuf, dxccname, sizeof fieldbuf);
6617 				}
6618 				blob += wxString::FromUTF8(fieldbuf);
6619 				blob += wxT("\n");
6620 			}
6621 		}
6622 	}
6623 	delete mst;
6624 
6625 	topsizer->Add(new wxStaticText(this, -1, blob));
6626 	topsizer->Add(
6627 		new wxButton(this, wxID_OK, _("Close")),
6628 				0, wxALIGN_CENTER | wxALL, 10
6629 	);
6630 	SetAutoLayout(TRUE);
6631 	SetSizer(topsizer);
6632 	topsizer->Fit(this);
6633 	topsizer->SetSizeHints(this);
6634 	CenterOnParent();
6635 }
6636 
6637 void
6638 displayLocProperties(LocTreeItemData *item, wxWindow *parent) {
6639 	tqslTrace("displayLocProperties", "item=%lx", item);
6640 	if (item != NULL) {
6641 		LocPropDial dial(item->getLocname(), parent);
6642 		dial.ShowModal();
6643 	}
6644 }
6645 
6646 int
6647 getPassword(char *buf, int bufsiz, void *callsign) {
6648 	tqslTrace("getPassword", "buf=%lx, bufsiz=%d, callsign=%s", buf, bufsiz, callsign ? callsign : "NULL");
6649 	wxString prompt(_("Enter the password to unlock the callsign certificate"));
6650 
6651 	if (callsign)
6652 		prompt = wxString::Format(_T("Enter the password for your active %hs Callsign Certificate"),  reinterpret_cast<char *>(callsign));
6653 
6654 	tqslTrace("getPassword", "Probing for top window");
6655 	wxWindow* top = wxGetApp().GetTopWindow();
6656 	tqslTrace("getPassword", "Top window = 0x%lx", reinterpret_cast<void *>(top));
6657 	top->SetFocus();
6658 	tqslTrace("getPassword", "Focus grabbed. About to pop up password dialog");
6659 	GetPasswordDialog dial(top, _("Enter password"), prompt);
6660 	if (dial.ShowModal() != wxID_OK) {
6661 		tqslTrace("getPassword", "Password entry cancelled");
6662 		return 1;
6663 	}
6664 	tqslTrace("getPassword", "Password entered OK");
6665 	strncpy(buf, dial.Password().ToUTF8(), bufsiz);
6666 	utf8_to_ucs2(buf, unipwd, sizeof unipwd);
6667 	buf[bufsiz-1] = 0;
6668 	return 0;
6669 }
6670 
6671 void
6672 displayTQSLError(const char *pre) {
6673 	tqslTrace("displayTQSLError", "pre=%s", pre);
6674 	wxString s = wxGetTranslation(wxString::FromUTF8(pre));
6675 	s += wxT(":\n");
6676 	s += getLocalizedErrorString();
6677 	wxMessageBox(s, _("Error"), wxOK | wxICON_WARNING, frame);
6678 }
6679 
6680 
6681 static wxString
6682 flattenCallSign(const wxString& call) {
6683 	tqslTrace("flattenCallSign", "call=%s", S(call));
6684 	wxString flat = call;
6685 	size_t idx;
6686 	while ((idx = flat.find_first_not_of(wxT("ABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890_"))) != wxString::npos)
6687 		flat[idx] = '_';
6688 	return flat;
6689 }
6690 
6691 static int lockfileFD = -1;
6692 
6693 #ifndef _WIN32
6694 static int
6695 lock_db(bool wait) {
6696 	struct flock fl;
6697 	fl.l_type = F_WRLCK;
6698 	fl.l_whence = SEEK_SET;
6699 	fl.l_start = 0;
6700 	fl.l_len = 1;
6701 
6702 	wxString lfname = wxString::FromUTF8(tQSL_BaseDir) + wxT("/dblock");
6703 
6704 	if (lockfileFD < 0) {
6705 		lockfileFD = open(lfname.ToUTF8(), O_RDWR| O_CREAT, 0644);
6706 		if (lockfileFD < 0)
6707 			return 1;
6708 	}
6709 	if (wait) {
6710 		fcntl(lockfileFD, F_SETLKW, &fl);
6711 		return 0;
6712 	}
6713 	int ret = fcntl(lockfileFD, F_SETLK, &fl);
6714 	if (ret < 0 && (errno == EACCES || errno == EAGAIN)) {
6715 		return -1;
6716 	}
6717 	return 0;
6718 }
6719 
6720 static void
6721 unlock_db(void) {
6722 	if (lockfileFD < 0) return;
6723 	close(lockfileFD);
6724 	lockfileFD = -1;
6725 	return;
6726 }
6727 #else /* _WIN32 */
6728 
6729 static OVERLAPPED ov;
6730 static HANDLE hFile = 0;
6731 
6732 static int
6733 lock_db(bool wait) {
6734 	BOOL ret = FALSE;
6735 	DWORD locktype = LOCKFILE_EXCLUSIVE_LOCK;
6736 
6737 	wxString lfname = wxString::FromUTF8(tQSL_BaseDir) + wxT("\\dblock");
6738 
6739 	if (lockfileFD < 0) {
6740 		wchar_t* wlfname = utf8_to_wchar(lfname.ToUTF8());
6741 		lockfileFD = _wopen(wlfname, O_RDWR| O_CREAT, 0644);
6742 		free_wchar(wlfname);
6743 		if (lockfileFD < 0)
6744 			return 1;
6745 		ZeroMemory(&ov, sizeof(ov));
6746 		ov.hEvent = NULL;
6747 		ov.Offset = 0;
6748 		ov.OffsetHigh = 0x80000000;
6749 	}
6750 
6751 	hFile = (HANDLE) _get_osfhandle(lockfileFD);
6752 
6753 	if (!wait) {
6754 		locktype |= LOCKFILE_FAIL_IMMEDIATELY;
6755 	}
6756 	ret = LockFileEx(hFile, locktype, 0, 0, 0x80000000, &ov);
6757 	if (!ret) {
6758 		switch (GetLastError()) {
6759 			case ERROR_SHARING_VIOLATION:
6760 			case ERROR_LOCK_VIOLATION:
6761 			case ERROR_IO_PENDING:
6762 				return -1;
6763 			default:
6764 				return 0;
6765 		}
6766 	}
6767 	return 0;
6768 }
6769 
6770 static void
6771 unlock_db(void) {
6772 	if (hFile)
6773 		UnlockFileEx(hFile, 0, 0, 0x80000000, &ov);
6774 	if (lockfileFD != -1)
6775 		_close(lockfileFD);
6776 	lockfileFD = -1;
6777 	hFile = 0;
6778 }
6779 #endif /* _WIN32 */
6780