1package gui
2
3import (
4	"crypto/rand"
5	"fmt"
6	"log"
7	"math/big"
8	"regexp"
9
10	"github.com/coyim/coyim/i18n"
11	"github.com/coyim/coyim/session/access"
12	"github.com/coyim/coyim/xmpp/jid"
13	"github.com/coyim/gotk3adapter/gtki"
14)
15
16type verifier struct {
17	parentWindow        gtki.Window
18	session             access.Session
19	notifier            *notifier
20	pinWindow           *pinWindow
21	answerSMPWindow     *answerSMPWindow
22	smpFailed           *smpFailedNotification
23	waitingForPeer      *waitingForPeerNotification
24	peerRequestsSMP     *peerRequestsSMPNotification
25	unverifiedWarning   *unverifiedWarning
26	verificationSuccess *verificationSuccessNotification
27	peerName            func() string
28	peerToSendTo        func() jid.WithResource
29}
30
31type notifier struct {
32	notificationArea gtki.Box
33}
34
35func (n *notifier) notify(i gtki.InfoBar) {
36	n.notificationArea.Add(i)
37}
38
39// TODO: unify repeated stuff
40func newVerifier(u *gtkUI, conv *conversationPane) *verifier {
41	v := &verifier{
42		parentWindow: conv.transientParent,
43		session:      conv.account.session,
44		notifier:     &notifier{conv.notificationArea},
45		peerName: func() string {
46			p, ok := conv.currentPeer()
47			if !ok {
48				return ""
49			}
50			return p.NameForPresentation()
51		},
52		peerToSendTo: func() jid.WithResource {
53			return conv.currentPeerForSending().(jid.WithResource)
54		},
55	}
56
57	v.buildPinWindow()
58	v.buildAnswerSMPDialog()
59	// A function is used below because we cannot check whether a contact is verified
60	// when newVerifier is called.
61	v.buildUnverifiedWarning(func() bool {
62		return conv.isEncrypted() && !conv.hasVerifiedKey()
63	})
64	v.buildWaitingForPeerNotification()
65	v.buildPeerRequestsSMPNotification()
66	v.buildSMPFailedDialog()
67
68	return v
69}
70
71type pinWindow struct {
72	b             *builder
73	dialog        gtki.Dialog
74	prompt        gtki.Label
75	pin           gtki.Label
76	smpImage      gtki.Image
77	padlockImage1 gtki.Image
78	padlockImage2 gtki.Image
79	alertImage    gtki.Image
80}
81
82func (v *verifier) buildPinWindow() {
83	v.pinWindow = &pinWindow{
84		b: newBuilder("GeneratePIN"),
85	}
86
87	v.pinWindow.b.getItems(
88		"dialog", &v.pinWindow.dialog,
89		"prompt", &v.pinWindow.prompt,
90		"pin", &v.pinWindow.pin,
91		"smp_image", &v.pinWindow.smpImage,
92		"padlock_image1", &v.pinWindow.padlockImage1,
93		"padlock_image2", &v.pinWindow.padlockImage2,
94		"alert_image", &v.pinWindow.alertImage,
95	)
96
97	v.pinWindow.dialog.HideOnDelete()
98	v.pinWindow.dialog.SetTransientFor(v.parentWindow)
99	addBoldHeaderStyle(v.pinWindow.pin)
100
101	v.pinWindow.b.ConnectSignals(map[string]interface{}{
102		"close_share_pin": func() {
103			v.showWaitingForPeerToCompleteSMPDialog()
104			v.pinWindow.dialog.Hide()
105		},
106	})
107
108	setImageFromFile(v.pinWindow.smpImage, "smp.svg")
109	setImageFromFile(v.pinWindow.padlockImage1, "padlock.svg")
110	setImageFromFile(v.pinWindow.padlockImage2, "padlock.svg")
111	setImageFromFile(v.pinWindow.alertImage, "alert.svg")
112}
113
114func (v *verifier) updateUnverifiedWarning() {
115	v.unverifiedWarning.update()
116}
117
118func (v *verifier) hideUnverifiedWarning() {
119	v.unverifiedWarning.infobar.Hide()
120}
121
122// TODO: check on linux
123type unverifiedWarning struct {
124	b                         *builder
125	infobar                   gtki.Box
126	closeInfobar              gtki.Box
127	notification              gtki.Box
128	label                     gtki.Label
129	image                     gtki.Image
130	button                    gtki.Button
131	shouldShowVerificationBar func() bool
132}
133
134// TODO: how will this work with i18n?
135var question = "Please enter the PIN that I shared with you."
136var coyIMQuestion = regexp.MustCompile("Please enter the PIN that I shared with you.")
137
138func (u *unverifiedWarning) update() {
139	if u.shouldShowVerificationBar() {
140		u.infobar.Show()
141		u.label.Show()
142		u.image.ShowAll()
143	} else {
144		u.infobar.Hide()
145	}
146}
147
148func (v *verifier) buildUnverifiedWarning(shouldShowVerificationBar func() bool) {
149	v.unverifiedWarning = &unverifiedWarning{
150		b: newBuilder("UnverifiedWarning"),
151	}
152
153	v.unverifiedWarning.shouldShowVerificationBar = shouldShowVerificationBar
154
155	v.unverifiedWarning.b.getItems(
156		"verify-infobar", &v.unverifiedWarning.infobar,
157		"verify-close-infobar", &v.unverifiedWarning.closeInfobar,
158		"verify-notification", &v.unverifiedWarning.notification,
159		"verify-message", &v.unverifiedWarning.label,
160		"verify-image", &v.unverifiedWarning.image,
161		"verify-button", &v.unverifiedWarning.button,
162	)
163
164	v.unverifiedWarning.b.ConnectSignals(map[string]interface{}{
165		"on_press_image": v.hideUnverifiedWarning,
166	})
167
168	prov := providerWithCSS("box { background-color: #fff3f3; color: #000000; border: 2px; }")
169	updateWithStyle(v.unverifiedWarning.infobar, prov)
170
171	prov = providerWithCSS("box { background-color: #e5d7d6; }")
172	updateWithStyle(v.unverifiedWarning.closeInfobar, prov)
173
174	setImageFromFile(v.unverifiedWarning.image, "warning.svg")
175	v.unverifiedWarning.label.SetLabel(i18n.Local("Make sure no one else\nis reading your messages"))
176	v.unverifiedWarning.button.Connect("clicked", v.showPINDialog)
177
178	v.notifier.notify(v.unverifiedWarning.infobar)
179}
180
181func (v *verifier) smpError(err error) {
182	v.hideUnverifiedWarning()
183	v.showCannotGeneratePINDialog(err)
184}
185
186func (v *verifier) showPINDialog() {
187	v.unverifiedWarning.infobar.Hide()
188
189	pin, err := createPIN()
190	if err != nil {
191		v.pinWindow.dialog.Hide()
192		v.smpError(err)
193		return
194	}
195	v.pinWindow.pin.SetText(pin)
196	v.pinWindow.prompt.SetMarkup(i18n.Localf("Share this one-time PIN with <b>%s</b>", v.peerName()))
197
198	v.session.StartSMP(v.peerToSendTo(), question, pin)
199	v.pinWindow.dialog.ShowAll()
200
201	v.waitingForPeer.label.SetLabel(i18n.Localf("Waiting for peer to finish \nsecuring the channel..."))
202	v.waitingForPeer.infobar.ShowAll()
203}
204
205func createPIN() (string, error) {
206	val, err := rand.Int(rand.Reader, big.NewInt(int64(1000000)))
207	if err != nil {
208		log.Printf("Error encountered when creating a new PIN: %v", err)
209		return "", err
210	}
211
212	return fmt.Sprintf("%06d", val), err
213}
214
215type waitingForPeerNotification struct {
216	b       *builder
217	infobar gtki.InfoBar
218	label   gtki.Label
219	image   gtki.Image
220	button  gtki.Button
221}
222
223func (v *verifier) buildWaitingForPeerNotification() {
224	v.waitingForPeer = &waitingForPeerNotification{
225		b: newBuilder("WaitingSMPComplete"),
226	}
227
228	v.waitingForPeer.b.getItems(
229		"smp-waiting-infobar", &v.waitingForPeer.infobar,
230		"smp-waiting-label", &v.waitingForPeer.label,
231		"smp-waiting-image", &v.waitingForPeer.image,
232		"smp-waiting-button", &v.waitingForPeer.button,
233	)
234
235	prov := providerWithCSS("box { background-color: #fff3f3; color: #000000; border: 2px; }")
236	updateWithStyle(v.waitingForPeer.infobar, prov)
237
238	v.waitingForPeer.label.SetText(i18n.Localf("Waiting for peer to finish \nsecuring the channel..."))
239	setImageFromFile(v.waitingForPeer.image, "waiting.svg")
240
241	v.waitingForPeer.button.Connect("clicked", func() {
242		v.cancelSMP()
243	})
244
245	v.notifier.notify(v.waitingForPeer.infobar)
246}
247
248func (v *verifier) showWaitingForPeerToCompleteSMPDialog() {
249	v.waitingForPeer.label.SetLabel(i18n.Localf("Waiting for peer to finish \nsecuring the channel..."))
250	v.hideUnverifiedWarning()
251	v.waitingForPeer.infobar.ShowAll()
252}
253
254func (v *verifier) showCannotGeneratePINDialog(err error) {
255	b := newBuilder("CannotVerifyWithSMP")
256
257	infobar := b.getObj("smp-error-infobar").(gtki.InfoBar)
258	label := b.getObj("smp-error-label").(gtki.Label)
259	image := b.getObj("smp-error-image").(gtki.Image)
260	button := b.getObj("smp-error-button").(gtki.Button)
261
262	prov := providerWithCSS("box { background-color: #fff3f3; color: #000000; border: 2px; }")
263	updateWithStyle(infobar, prov)
264
265	label.SetText(i18n.Local("Unable to verify at this time."))
266	log.Printf("Cannot recover from error: %v. Quitting SMP verification.", err)
267	setImageFromFile(image, "failure.svg")
268	button.Connect("clicked", infobar.Destroy)
269
270	infobar.ShowAll()
271
272	v.notifier.notify(infobar)
273}
274
275type answerSMPWindow struct {
276	b             *builder
277	dialog        gtki.Dialog
278	question      gtki.Label
279	answer        gtki.Entry
280	submitButton  gtki.Button
281	smpImage      gtki.Image
282	padlockImage1 gtki.Image
283	padlockImage2 gtki.Image
284	alertImage    gtki.Image
285}
286
287func (v *verifier) buildAnswerSMPDialog() {
288	v.answerSMPWindow = &answerSMPWindow{
289		b: newBuilder("AnswerSMPQuestion"),
290	}
291
292	v.answerSMPWindow.b.getItems(
293		"dialog", &v.answerSMPWindow.dialog,
294		"question_from_peer", &v.answerSMPWindow.question,
295		"button_submit", &v.answerSMPWindow.submitButton,
296		"answer", &v.answerSMPWindow.answer,
297		"smp_image", &v.answerSMPWindow.smpImage,
298		"padlock_image1", &v.answerSMPWindow.padlockImage1,
299		"padlock_image2", &v.answerSMPWindow.padlockImage2,
300		"alert_image", &v.answerSMPWindow.alertImage,
301	)
302
303	v.answerSMPWindow.dialog.HideOnDelete()
304	v.answerSMPWindow.dialog.SetTransientFor(v.parentWindow)
305	v.answerSMPWindow.submitButton.SetSensitive(false)
306
307	setImageFromFile(v.answerSMPWindow.smpImage, "smp.svg")
308	setImageFromFile(v.answerSMPWindow.padlockImage1, "padlock.svg")
309	setImageFromFile(v.answerSMPWindow.padlockImage2, "padlock.svg")
310	setImageFromFile(v.answerSMPWindow.alertImage, "alert.svg")
311
312	v.answerSMPWindow.b.ConnectSignals(map[string]interface{}{
313		"text_changing": func() {
314			answer, _ := v.answerSMPWindow.answer.GetText()
315			v.answerSMPWindow.submitButton.SetSensitive(len(answer) > 0)
316		},
317		"close_share_pin": func() {
318			answer, _ := v.answerSMPWindow.answer.GetText()
319			v.removeInProgressDialogs()
320			v.session.FinishSMP(v.peerToSendTo(), answer)
321			v.showWaitingForPeerToCompleteSMPDialog()
322		},
323	})
324
325}
326
327func (v *verifier) showAnswerSMPDialog(question string) {
328	if "" == question {
329		v.answerSMPWindow.question.SetMarkup(i18n.Localf("Enter the secret that <b>%s</b> shared with you", v.peerName()))
330	} else if coyIMQuestion.MatchString(question) {
331		v.answerSMPWindow.question.SetMarkup(i18n.Localf("Type the PIN that <b>%s</b> sent you. It can be used only once.", v.peerName()))
332	} else {
333		v.answerSMPWindow.question.SetMarkup(i18n.Localf("Enter the answer to\n<b>%s</b>", question))
334	}
335
336	v.answerSMPWindow.answer.SetText("")
337	v.answerSMPWindow.dialog.ShowAll()
338}
339
340type peerRequestsSMPNotification struct {
341	b            *builder
342	infobar      gtki.Box
343	closeInfobar gtki.Box
344	notification gtki.Box
345	label        gtki.Label
346	image        gtki.Image
347	button       gtki.Button
348}
349
350func (p *peerRequestsSMPNotification) show() {
351	p.infobar.Show()
352	p.closeInfobar.Show()
353	p.label.Show()
354}
355
356func (v *verifier) buildPeerRequestsSMPNotification() {
357	v.peerRequestsSMP = &peerRequestsSMPNotification{
358		b: newBuilder("PeerRequestsSMP"),
359	}
360
361	v.peerRequestsSMP.b.getItems(
362		"smp-requested-infobar", &v.peerRequestsSMP.infobar,
363		"smp-requested-close-infobar", &v.peerRequestsSMP.closeInfobar,
364		"smp-requested-notification", &v.peerRequestsSMP.notification,
365		"smp-requested-message", &v.peerRequestsSMP.label,
366		"smp-requested-image", &v.peerRequestsSMP.image,
367		"smp-requested-button", &v.peerRequestsSMP.button,
368	)
369
370	prov := providerWithCSS("box { background-color: #fff3f3; color: #000000; border: 2px; }")
371	updateWithStyle(v.peerRequestsSMP.infobar, prov)
372
373	prov = providerWithCSS("box { background-color: #e5d7d6; }")
374	updateWithStyle(v.peerRequestsSMP.closeInfobar, prov)
375
376	v.peerRequestsSMP.b.ConnectSignals(map[string]interface{}{
377		"on_press_close_image": v.cancelSMP,
378	})
379
380	setImageFromFile(v.peerRequestsSMP.image, "waiting.svg")
381
382	v.notifier.notify(v.peerRequestsSMP.infobar)
383}
384
385func (v *verifier) displayRequestForSecret(question string) {
386	v.hideUnverifiedWarning()
387
388	v.peerRequestsSMP.label.SetLabel(i18n.Localf("Finish verifying the \nsecurity of this channel..."))
389	v.peerRequestsSMP.button.Connect("clicked", func() {
390		v.showAnswerSMPDialog(question)
391	})
392
393	v.peerRequestsSMP.show()
394}
395
396type verificationSuccessNotification struct {
397	b      *builder
398	dialog gtki.Dialog
399	label  gtki.Label
400	image  gtki.Image
401	button gtki.Button
402}
403
404func (v *verifier) displayVerificationSuccess() {
405	v.verificationSuccess = &verificationSuccessNotification{
406		b: newBuilder("VerificationSucceeded"),
407	}
408
409	v.verificationSuccess.b.getItems(
410		"verif-success-dialog", &v.verificationSuccess.dialog,
411		"verif-success-label", &v.verificationSuccess.label,
412		"verif-success-image", &v.verificationSuccess.image,
413		"verif-success-button", &v.verificationSuccess.button,
414	)
415
416	v.verificationSuccess.button.Connect("clicked", v.verificationSuccess.dialog.Destroy)
417
418	v.verificationSuccess.label.SetMarkup(i18n.Localf("Hooray! No one is listening to your conversations with <b>%s</b>", v.peerName()))
419	setImageFromFile(v.verificationSuccess.image, "smpsuccess.svg")
420
421	v.hideUnverifiedWarning()
422	v.verificationSuccess.dialog.SetTransientFor(v.parentWindow)
423	v.verificationSuccess.dialog.ShowAll()
424}
425
426type smpFailedNotification struct {
427	dialog gtki.Dialog
428	label  gtki.Label
429	button gtki.Button
430}
431
432// TODO: make this consistent
433func (v *verifier) buildSMPFailedDialog() {
434	builder := newBuilder("VerificationFailed")
435	v.smpFailed = &smpFailedNotification{
436		dialog: builder.getObj("verif-failure-dialog").(gtki.Dialog),
437		label:  builder.getObj("verif-failure-label").(gtki.Label),
438		button: builder.getObj("verif-failure-button").(gtki.Button),
439	}
440
441	v.smpFailed.dialog.SetTransientFor(v.parentWindow)
442	v.smpFailed.dialog.HideOnDelete()
443
444	v.smpFailed.dialog.Connect("response", func() {
445		v.updateUnverifiedWarning()
446		v.smpFailed.dialog.Hide()
447	})
448	v.smpFailed.button.Connect("clicked", func() {
449		v.updateUnverifiedWarning()
450		v.smpFailed.dialog.Hide()
451	})
452}
453
454func (v *verifier) displayVerificationFailure() {
455	v.smpFailed.label.SetMarkup(i18n.Localf("We could not verify this channel with <b>%s</b>.", v.peerName()))
456	v.smpFailed.dialog.ShowAll()
457}
458
459func (v *verifier) updateInProgressDialogs(encrypted bool) {
460	if !encrypted {
461		v.removeInProgressDialogs()
462	}
463}
464
465func (v *verifier) removeInProgressDialogs() {
466	v.peerRequestsSMP.infobar.Hide()
467	v.waitingForPeer.infobar.Hide()
468	v.pinWindow.dialog.Hide()
469	v.answerSMPWindow.dialog.Hide()
470}
471
472func (v *verifier) cancelSMP() {
473	v.removeInProgressDialogs()
474	v.session.AbortSMP(v.peerToSendTo())
475	v.updateUnverifiedWarning()
476}
477