1package main
2
3import (
4	"bytes"
5	"crypto/tls"
6	"crypto/x509"
7	"encoding/hex"
8	"encoding/xml"
9	"errors"
10	"flag"
11	"fmt"
12	"io"
13	"io/ioutil"
14	"net/url"
15	"os"
16	"os/exec"
17	"os/signal"
18	"path/filepath"
19	"sort"
20	"strconv"
21	"strings"
22	"sync"
23	"syscall"
24	"time"
25
26	"github.com/agl/xmpp-client/xmpp"
27	"golang.org/x/crypto/otr"
28	"golang.org/x/crypto/ssh/terminal"
29	"golang.org/x/net/html"
30	"golang.org/x/net/proxy"
31)
32
33var configFile *string = flag.String("config-file", "", "Location of the config file")
34var createAccount *bool = flag.Bool("create", false, "If true, attempt to create account")
35
36// OTRWhitespaceTagStart may be appended to plaintext messages to signal to the
37// remote client that we support OTR. It should be followed by one of the
38// version specific tags, below. See "Tagged plaintext messages" in
39// http://www.cypherpunks.ca/otr/Protocol-v3-4.0.0.html.
40var OTRWhitespaceTagStart = []byte("\x20\x09\x20\x20\x09\x09\x09\x09\x20\x09\x20\x09\x20\x09\x20\x20")
41
42var OTRWhiteSpaceTagV1 = []byte("\x20\x09\x20\x09\x20\x20\x09\x20")
43var OTRWhiteSpaceTagV2 = []byte("\x20\x20\x09\x09\x20\x20\x09\x20")
44var OTRWhiteSpaceTagV3 = []byte("\x20\x20\x09\x09\x20\x20\x09\x09")
45
46var OTRWhitespaceTag = append(OTRWhitespaceTagStart, OTRWhiteSpaceTagV2...)
47
48// appendTerminalEscaped acts like append(), but breaks terminal escape
49// sequences that may be in msg.
50func appendTerminalEscaped(out, msg []byte) []byte {
51	for _, c := range msg {
52		if c == 127 || (c < 32 && c != '\t') {
53			out = append(out, '?')
54		} else {
55			out = append(out, c)
56		}
57	}
58	return out
59}
60
61func stripHTML(msg []byte) (out []byte) {
62	z := html.NewTokenizer(bytes.NewReader(msg))
63
64loop:
65	for {
66		tt := z.Next()
67		switch tt {
68		case html.TextToken:
69			out = append(out, z.Text()...)
70		case html.ErrorToken:
71			if err := z.Err(); err != nil && err != io.EOF {
72				out = msg
73				return
74			}
75			break loop
76		}
77	}
78
79	return
80}
81
82func terminalMessage(term *terminal.Terminal, color []byte, msg string, critical bool) {
83	line := make([]byte, 0, len(msg)+16)
84
85	line = append(line, ' ')
86	line = append(line, color...)
87	line = append(line, '*')
88	line = append(line, term.Escape.Reset...)
89	line = append(line, []byte(fmt.Sprintf(" (%s) ", time.Now().Format(time.Kitchen)))...)
90	if critical {
91		line = append(line, term.Escape.Red...)
92	}
93	line = appendTerminalEscaped(line, []byte(msg))
94	if critical {
95		line = append(line, term.Escape.Reset...)
96	}
97	line = append(line, '\n')
98	term.Write(line)
99}
100
101func info(term *terminal.Terminal, msg string) {
102	terminalMessage(term, term.Escape.Blue, msg, false)
103}
104
105func warn(term *terminal.Terminal, msg string) {
106	terminalMessage(term, term.Escape.Magenta, msg, false)
107}
108
109func alert(term *terminal.Terminal, msg string) {
110	terminalMessage(term, term.Escape.Red, msg, false)
111}
112
113func critical(term *terminal.Terminal, msg string) {
114	terminalMessage(term, term.Escape.Red, msg, true)
115}
116
117type Session struct {
118	account string
119	conn    *xmpp.Conn
120	term    *terminal.Terminal
121	roster  []xmpp.RosterEntry
122	input   Input
123	// conversations maps from a JID (without the resource) to an OTR
124	// conversation. (Note that unencrypted conversations also pass through
125	// OTR.)
126	conversations map[string]*otr.Conversation
127	// knownStates maps from a JID (without the resource) to the last known
128	// presence state of that contact. It's used to deduping presence
129	// notifications.
130	knownStates map[string]string
131	privateKey  *otr.PrivateKey
132	config      *Config
133	// lastMessageFrom is the JID (without the resource) of the contact
134	// that we last received a message from.
135	lastMessageFrom string
136	// timeouts maps from Cookies (from outstanding requests) to the
137	// absolute time when that request should timeout.
138	timeouts map[xmpp.Cookie]time.Time
139	// pendingRosterEdit, if non-nil, contains information about a pending
140	// roster edit operation.
141	pendingRosterEdit *rosterEdit
142	// pendingRosterChan is the channel over which roster edit information
143	// is received.
144	pendingRosterChan chan *rosterEdit
145	// pendingSubscribes maps JID with pending subscription requests to the
146	// ID if the iq for the reply.
147	pendingSubscribes map[string]string
148	// lastActionTime is the time at which the user last entered a command,
149	// or was last notified.
150	lastActionTime time.Time
151	// ignored is a list of users from whom messages are currently being
152	// ignored, e.g. due to doing `/ignore soandso@jabber.foo`
153	ignored map[string]struct{}
154}
155
156// rosterEdit contains information about a pending roster edit. Roster edits
157// occur by writing the roster to a file and inviting the user to edit the
158// file.
159type rosterEdit struct {
160	// fileName is the name of the file containing the roster information.
161	fileName string
162	// roster contains the state of the roster at the time of writing the
163	// file. It's what we diff against when reading the file.
164	roster []xmpp.RosterEntry
165	// isComplete is true if this is the result of reading an edited
166	// roster, rather than a report that the file has been written.
167	isComplete bool
168	// contents contains the edited roster, if isComplete is true.
169	contents []byte
170}
171
172func (s *Session) readMessages(stanzaChan chan<- xmpp.Stanza) {
173	defer close(stanzaChan)
174
175	for {
176		stanza, err := s.conn.Next()
177		if err != nil {
178			alert(s.term, err.Error())
179			return
180		}
181		stanzaChan <- stanza
182	}
183}
184
185func updateTerminalSize(term *terminal.Terminal) {
186	width, height, err := terminal.GetSize(0)
187	if err != nil {
188		return
189	}
190	term.SetSize(width, height)
191}
192
193func main() {
194	flag.Parse()
195
196	oldState, err := terminal.MakeRaw(0)
197	if err != nil {
198		panic(err.Error())
199	}
200	defer terminal.Restore(0, oldState)
201	term := terminal.NewTerminal(os.Stdin, "")
202	updateTerminalSize(term)
203	term.SetBracketedPasteMode(true)
204	defer term.SetBracketedPasteMode(false)
205
206	resizeChan := make(chan os.Signal)
207	go func() {
208		for _ = range resizeChan {
209			updateTerminalSize(term)
210		}
211	}()
212	signal.Notify(resizeChan, syscall.SIGWINCH)
213
214	if len(*configFile) == 0 {
215		homeDir := os.Getenv("HOME")
216		if len(homeDir) == 0 {
217			alert(term, "$HOME not set. Please either export $HOME or use the -config-file option.\n")
218			return
219		}
220		persistentDir := filepath.Join(homeDir, "Persistent")
221		if stat, err := os.Lstat(persistentDir); err == nil && stat.IsDir() {
222			// Looks like Tails.
223			homeDir = persistentDir
224		}
225		*configFile = filepath.Join(homeDir, ".xmpp-client")
226	}
227
228	config, err := ParseConfig(*configFile)
229	if err != nil {
230		alert(term, "Failed to parse config file: "+err.Error())
231		config = new(Config)
232		if !enroll(config, term) {
233			return
234		}
235		config.filename = *configFile
236		config.Save()
237	}
238
239	password := config.Password
240	if len(password) == 0 {
241		if password, err = term.ReadPassword(fmt.Sprintf("Password for %s (will not be saved to disk): ", config.Account)); err != nil {
242			alert(term, "Failed to read password: "+err.Error())
243			return
244		}
245	}
246	term.SetPrompt("> ")
247
248	parts := strings.SplitN(config.Account, "@", 2)
249	if len(parts) != 2 {
250		alert(term, "invalid username (want user@domain): "+config.Account)
251		return
252	}
253	user := parts[0]
254	domain := parts[1]
255
256	var addr string
257	addrTrusted := false
258
259	if len(config.Server) > 0 && config.Port > 0 {
260		addr = fmt.Sprintf("%s:%d", config.Server, config.Port)
261		addrTrusted = true
262	} else {
263		if len(config.Proxies) > 0 {
264			alert(term, "Cannot connect via a proxy without Server and Port being set in the config file as an SRV lookup would leak information.")
265			return
266		}
267		host, port, err := xmpp.Resolve(domain)
268		if err != nil {
269			alert(term, "Failed to resolve XMPP server: "+err.Error())
270			return
271		}
272		addr = fmt.Sprintf("%s:%d", host, port)
273	}
274
275	var dialer proxy.Dialer
276	for i := len(config.Proxies) - 1; i >= 0; i-- {
277		u, err := url.Parse(config.Proxies[i])
278		if err != nil {
279			alert(term, "Failed to parse "+config.Proxies[i]+" as a URL: "+err.Error())
280			return
281		}
282		if dialer == nil {
283			dialer = proxy.Direct
284		}
285		if dialer, err = proxy.FromURL(u, dialer); err != nil {
286			alert(term, "Failed to parse "+config.Proxies[i]+" as a proxy: "+err.Error())
287			return
288		}
289	}
290
291	var certSHA256 []byte
292	if len(config.ServerCertificateSHA256) > 0 {
293		certSHA256, err = hex.DecodeString(config.ServerCertificateSHA256)
294		if err != nil {
295			alert(term, "Failed to parse ServerCertificateSHA256 (should be hex string): "+err.Error())
296			return
297		}
298		if len(certSHA256) != 32 {
299			alert(term, "ServerCertificateSHA256 is not 32 bytes long")
300			return
301		}
302	}
303
304	var createCallback xmpp.FormCallback
305	if *createAccount {
306		createCallback = func(title, instructions string, fields []interface{}) error {
307			return promptForForm(term, user, password, title, instructions, fields)
308		}
309	}
310
311	xmppConfig := &xmpp.Config{
312		Log:                     &lineLogger{term, nil},
313		CreateCallback:          createCallback,
314		TrustedAddress:          addrTrusted,
315		Archive:                 false,
316		ServerCertificateSHA256: certSHA256,
317		TLSConfig: &tls.Config{
318			MinVersion: tls.VersionTLS10,
319			CipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
320				tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
321				tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
322				tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
323				tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
324				tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
325			},
326		},
327	}
328
329	if domain == "jabber.ccc.de" {
330		// jabber.ccc.de uses CACert but distros are removing that root
331		// certificate.
332		roots := x509.NewCertPool()
333		caCertRoot, err := x509.ParseCertificate(caCertRootDER)
334		if err == nil {
335			alert(term, "Temporarily trusting only CACert root for CCC Jabber server")
336			roots.AddCert(caCertRoot)
337			xmppConfig.TLSConfig.RootCAs = roots
338		} else {
339			alert(term, "Tried to add CACert root for jabber.ccc.de but failed: "+err.Error())
340		}
341	}
342
343	if len(config.RawLogFile) > 0 {
344		rawLog, err := os.OpenFile(config.RawLogFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
345		if err != nil {
346			alert(term, "Failed to open raw log file: "+err.Error())
347			return
348		}
349
350		lock := new(sync.Mutex)
351		in := rawLogger{
352			out:    rawLog,
353			prefix: []byte("<- "),
354			lock:   lock,
355		}
356		out := rawLogger{
357			out:    rawLog,
358			prefix: []byte("-> "),
359			lock:   lock,
360		}
361		in.other, out.other = &out, &in
362
363		xmppConfig.InLog = &in
364		xmppConfig.OutLog = &out
365
366		defer in.flush()
367		defer out.flush()
368	}
369
370	if dialer != nil {
371		info(term, "Making connection to "+addr+" via proxy")
372		if xmppConfig.Conn, err = dialer.Dial("tcp", addr); err != nil {
373			alert(term, "Failed to connect via proxy: "+err.Error())
374			return
375		}
376	}
377
378	conn, err := xmpp.Dial(addr, user, domain, config.Resource, password, xmppConfig)
379	if err != nil {
380		alert(term, "Failed to connect to XMPP server: "+err.Error())
381		return
382	}
383
384	s := Session{
385		account:           config.Account,
386		conn:              conn,
387		term:              term,
388		conversations:     make(map[string]*otr.Conversation),
389		knownStates:       make(map[string]string),
390		privateKey:        new(otr.PrivateKey),
391		config:            config,
392		pendingRosterChan: make(chan *rosterEdit),
393		pendingSubscribes: make(map[string]string),
394		lastActionTime:    time.Now(),
395		// ignored contains UIDs that are currently being ignored.
396		ignored: make(map[string]struct{}),
397	}
398	info(term, "Fetching roster")
399
400	//var rosterReply chan xmpp.Stanza
401	rosterReply, _, err := s.conn.RequestRoster()
402	if err != nil {
403		alert(term, "Failed to request roster: "+err.Error())
404		return
405	}
406
407	conn.SignalPresence("")
408
409	s.input = Input{
410		term:        term,
411		uidComplete: new(priorityList),
412	}
413	commandChan := make(chan interface{})
414	go s.input.ProcessCommands(commandChan)
415
416	stanzaChan := make(chan xmpp.Stanza)
417	go s.readMessages(stanzaChan)
418
419	if _, ok := s.privateKey.Parse(config.PrivateKey); !ok {
420		alert(term, "Failed to parse private key from config")
421		return
422	}
423	s.timeouts = make(map[xmpp.Cookie]time.Time)
424
425	info(term, fmt.Sprintf("Your fingerprint is %x", s.privateKey.Fingerprint()))
426
427	ticker := time.NewTicker(1 * time.Second)
428	pingTicker := time.NewTicker(60 * time.Second)
429
430MainLoop:
431	for {
432		select {
433		case <-pingTicker.C:
434			// Send periodic pings so that we can detect connection timeouts.
435			s.conn.Ping()
436		case now := <-ticker.C:
437			haveExpired := false
438			for _, expiry := range s.timeouts {
439				if now.After(expiry) {
440					haveExpired = true
441					break
442				}
443			}
444			if !haveExpired {
445				continue
446			}
447
448			newTimeouts := make(map[xmpp.Cookie]time.Time)
449			for cookie, expiry := range s.timeouts {
450				if now.After(expiry) {
451					s.conn.Cancel(cookie)
452				} else {
453					newTimeouts[cookie] = expiry
454				}
455			}
456			s.timeouts = newTimeouts
457
458		case edit := <-s.pendingRosterChan:
459			if !edit.isComplete {
460				info(s.term, "Please edit "+edit.fileName+" and run /rostereditdone when complete")
461				s.pendingRosterEdit = edit
462				continue
463			}
464			if s.processEditedRoster(edit) {
465				s.pendingRosterEdit = nil
466			} else {
467				alert(s.term, "Please reedit file and run /rostereditdone again")
468			}
469
470		case rosterStanza, ok := <-rosterReply:
471			if !ok {
472				alert(s.term, "Failed to read roster: "+err.Error())
473				return
474			}
475			if s.roster, err = xmpp.ParseRoster(rosterStanza); err != nil {
476				alert(s.term, "Failed to parse roster: "+err.Error())
477				return
478			}
479			for _, entry := range s.roster {
480				s.input.AddUser(entry.Jid)
481			}
482			info(s.term, "Roster received")
483
484		case cmd, ok := <-commandChan:
485			if !ok {
486				warn(term, "Exiting because command channel closed")
487				break MainLoop
488			}
489			s.lastActionTime = time.Now()
490			switch cmd := cmd.(type) {
491			case quitCommand:
492				for to, conversation := range s.conversations {
493					msgs := conversation.End()
494					for _, msg := range msgs {
495						s.conn.Send(to, string(msg))
496					}
497				}
498				break MainLoop
499			case versionCommand:
500				replyChan, cookie, err := s.conn.SendIQ(cmd.User, "get", xmpp.VersionQuery{})
501				if err != nil {
502					alert(s.term, "Error sending version request: "+err.Error())
503					continue
504				}
505				s.timeouts[cookie] = time.Now().Add(5 * time.Second)
506				go s.awaitVersionReply(replyChan, cmd.User)
507			case rosterCommand:
508				info(s.term, "Current roster:")
509				maxLen := 0
510				for _, item := range s.roster {
511					if maxLen < len(item.Jid) {
512						maxLen = len(item.Jid)
513					}
514				}
515
516				for _, item := range s.roster {
517					state, ok := s.knownStates[item.Jid]
518
519					line := ""
520					if ok {
521						line += "[*] "
522					} else if cmd.OnlineOnly {
523						continue
524					} else {
525						line += "[ ] "
526					}
527
528					line += item.Jid
529					numSpaces := 1 + (maxLen - len(item.Jid))
530					for i := 0; i < numSpaces; i++ {
531						line += " "
532					}
533					line += item.Subscription + "\t" + item.Name
534					if ok {
535						line += "\t" + state
536					}
537					info(s.term, line)
538				}
539			case rosterEditCommand:
540				if s.pendingRosterEdit != nil {
541					warn(s.term, "Aborting previous roster edit")
542					s.pendingRosterEdit = nil
543				}
544				rosterCopy := make([]xmpp.RosterEntry, len(s.roster))
545				copy(rosterCopy, s.roster)
546				go s.editRoster(rosterCopy)
547			case rosterEditDoneCommand:
548				if s.pendingRosterEdit == nil {
549					warn(s.term, "No roster edit in progress. Use /rosteredit to start one")
550					continue
551				}
552				go s.loadEditedRoster(*s.pendingRosterEdit)
553			case toggleStatusUpdatesCommand:
554				s.config.HideStatusUpdates = !s.config.HideStatusUpdates
555				s.config.Save()
556				// Tell the user the current state of the statuses
557				if s.config.HideStatusUpdates {
558					info(s.term, "Status updates disabled")
559				} else {
560					info(s.term, "Status updates enabled")
561				}
562			case confirmCommand:
563				s.handleConfirmOrDeny(cmd.User, true /* confirm */)
564			case denyCommand:
565				s.handleConfirmOrDeny(cmd.User, false /* deny */)
566			case addCommand:
567				s.conn.SendPresence(cmd.User, "subscribe", "" /* generate id */)
568			case msgCommand:
569				conversation, ok := s.conversations[cmd.to]
570				isEncrypted := ok && conversation.IsEncrypted()
571				if cmd.setPromptIsEncrypted != nil {
572					cmd.setPromptIsEncrypted <- isEncrypted
573				}
574				if !isEncrypted && config.ShouldEncryptTo(cmd.to) {
575					warn(s.term, fmt.Sprintf("Did not send: no encryption established with %s", cmd.to))
576					continue
577				}
578				var msgs [][]byte
579				message := []byte(cmd.msg)
580				// Automatically tag all outgoing plaintext
581				// messages with a whitespace tag that
582				// indicates that we support OTR.
583				if config.OTRAutoAppendTag &&
584					!bytes.Contains(message, []byte("?OTR")) &&
585					(!ok || !conversation.IsEncrypted()) {
586					message = append(message, OTRWhitespaceTag...)
587				}
588				if ok {
589					var err error
590					msgs, err = conversation.Send(message)
591					if err != nil {
592						alert(s.term, err.Error())
593						break
594					}
595				} else {
596					msgs = [][]byte{[]byte(message)}
597				}
598				for _, message := range msgs {
599					s.conn.Send(cmd.to, string(message))
600				}
601			case otrCommand:
602				s.conn.Send(string(cmd.User), otr.QueryMessage)
603			case otrInfoCommand:
604				info(term, fmt.Sprintf("Your OTR fingerprint is %x", s.privateKey.Fingerprint()))
605				for to, conversation := range s.conversations {
606					if conversation.IsEncrypted() {
607						info(s.term, fmt.Sprintf("Secure session with %s underway:", to))
608						printConversationInfo(&s, to, conversation)
609					}
610				}
611			case endOTRCommand:
612				to := string(cmd.User)
613				conversation, ok := s.conversations[to]
614				if !ok {
615					alert(s.term, "No secure session established")
616					break
617				}
618				msgs := conversation.End()
619				for _, msg := range msgs {
620					s.conn.Send(to, string(msg))
621				}
622				s.input.SetPromptForTarget(cmd.User, false)
623				warn(s.term, "OTR conversation ended with "+cmd.User)
624			case authQACommand:
625				to := string(cmd.User)
626				conversation, ok := s.conversations[to]
627				if !ok {
628					alert(s.term, "Can't authenticate without a secure conversation established")
629					break
630				}
631				msgs, err := conversation.Authenticate(cmd.Question, []byte(cmd.Secret))
632				if err != nil {
633					alert(s.term, "Error while starting authentication with "+to+": "+err.Error())
634				}
635				for _, msg := range msgs {
636					s.conn.Send(to, string(msg))
637				}
638			case authOobCommand:
639				fpr, err := hex.DecodeString(cmd.Fingerprint)
640				if err != nil {
641					alert(s.term, fmt.Sprintf("Invalid fingerprint %s - not authenticated", cmd.Fingerprint))
642					break
643				}
644				existing := s.config.UserIdForFingerprint(fpr)
645				if len(existing) != 0 {
646					alert(s.term, fmt.Sprintf("Fingerprint %s already belongs to %s", cmd.Fingerprint, existing))
647					break
648				}
649				s.config.KnownFingerprints = append(s.config.KnownFingerprints, KnownFingerprint{fingerprint: fpr, UserId: cmd.User})
650				s.config.Save()
651				info(s.term, fmt.Sprintf("Saved manually verified fingerprint %s for %s", cmd.Fingerprint, cmd.User))
652			case awayCommand:
653				s.conn.SignalPresence("away")
654			case chatCommand:
655				s.conn.SignalPresence("chat")
656			case dndCommand:
657				s.conn.SignalPresence("dnd")
658			case xaCommand:
659				s.conn.SignalPresence("xa")
660			case onlineCommand:
661				s.conn.SignalPresence("")
662			case ignoreCommand:
663				s.ignoreUser(cmd.User)
664			case unignoreCommand:
665				s.unignoreUser(cmd.User)
666			case ignoreListCommand:
667				s.ignoreList()
668			}
669		case rawStanza, ok := <-stanzaChan:
670			if !ok {
671				warn(term, "Exiting because channel to server closed")
672				break MainLoop
673			}
674			switch stanza := rawStanza.Value.(type) {
675			case *xmpp.ClientMessage:
676				s.processClientMessage(stanza)
677			case *xmpp.ClientPresence:
678				s.processPresence(stanza)
679			case *xmpp.ClientIQ:
680				if stanza.Type != "get" && stanza.Type != "set" {
681					continue
682				}
683				reply := s.processIQ(stanza)
684				if reply == nil {
685					reply = xmpp.ErrorReply{
686						Type:  "cancel",
687						Error: xmpp.ErrorBadRequest{},
688					}
689				}
690				if err := s.conn.SendIQReply(stanza.From, "result", stanza.Id, reply); err != nil {
691					alert(term, "Failed to send IQ message: "+err.Error())
692				}
693			case *xmpp.StreamError:
694				var text string
695				if len(stanza.Text) > 0 {
696					text = stanza.Text
697				} else {
698					text = fmt.Sprintf("%s", stanza.Any)
699				}
700				alert(term, "Exiting in response to fatal error from server: "+text)
701				break MainLoop
702			default:
703				info(term, fmt.Sprintf("%s %s", rawStanza.Name, rawStanza.Value))
704			}
705		}
706	}
707
708	os.Stdout.Write([]byte("\n"))
709}
710
711func (s *Session) processIQ(stanza *xmpp.ClientIQ) interface{} {
712	buf := bytes.NewBuffer(stanza.Query)
713	parser := xml.NewDecoder(buf)
714	token, _ := parser.Token()
715	if token == nil {
716		return nil
717	}
718	startElem, ok := token.(xml.StartElement)
719	if !ok {
720		return nil
721	}
722	switch startElem.Name.Space + " " + startElem.Name.Local {
723	case "http://jabber.org/protocol/disco#info query":
724		return xmpp.DiscoveryReply{
725			Identities: []xmpp.DiscoveryIdentity{
726				{
727					Category: "client",
728					Type:     "pc",
729					Name:     s.config.Account,
730				},
731			},
732		}
733	case "jabber:iq:version query":
734		return xmpp.VersionReply{
735			Name:    "testing",
736			Version: "version",
737			OS:      "none",
738		}
739	case "jabber:iq:roster query":
740		if len(stanza.From) > 0 && stanza.From != s.account {
741			warn(s.term, "Ignoring roster IQ from bad address: "+stanza.From)
742			return nil
743		}
744		var roster xmpp.Roster
745		if err := xml.NewDecoder(bytes.NewBuffer(stanza.Query)).Decode(&roster); err != nil || len(roster.Item) == 0 {
746			warn(s.term, "Failed to parse roster push IQ")
747			return nil
748		}
749		entry := roster.Item[0]
750
751		if entry.Subscription == "remove" {
752			for i, rosterEntry := range s.roster {
753				if rosterEntry.Jid == entry.Jid {
754					copy(s.roster[i:], s.roster[i+1:])
755					s.roster = s.roster[:len(s.roster)-1]
756				}
757			}
758			return xmpp.EmptyReply{}
759		}
760
761		found := false
762		for i, rosterEntry := range s.roster {
763			if rosterEntry.Jid == entry.Jid {
764				s.roster[i] = entry
765				found = true
766				break
767			}
768		}
769		if !found {
770			s.roster = append(s.roster, entry)
771			s.input.AddUser(entry.Jid)
772		}
773		return xmpp.EmptyReply{}
774	default:
775		info(s.term, "Unknown IQ: "+startElem.Name.Space+" "+startElem.Name.Local)
776	}
777
778	return nil
779}
780
781func (s *Session) handleConfirmOrDeny(jid string, isConfirm bool) {
782	id, ok := s.pendingSubscribes[jid]
783	if !ok {
784		warn(s.term, "No pending subscription from "+jid)
785		return
786	}
787	delete(s.pendingSubscribes, id)
788	typ := "unsubscribed"
789	if isConfirm {
790		typ = "subscribed"
791	}
792	if err := s.conn.SendPresence(jid, typ, id); err != nil {
793		alert(s.term, "Error sending presence stanza: "+err.Error())
794	}
795}
796
797func (s *Session) ignoreUser(uid string) {
798	if _, ok := s.ignored[uid]; ok {
799		info(s.input.term, "Already ignoring "+uid)
800		return
801	}
802
803	s.input.lock.Lock()
804	defer s.input.lock.Unlock()
805
806	hasContact := false
807
808	for _, existingUid := range s.input.uids {
809		if existingUid == uid {
810			hasContact = true
811		}
812	}
813
814	if hasContact {
815		info(s.input.term, fmt.Sprintf("Ignoring messages from %s for the duration of this session", uid))
816	} else {
817		warn(s.input.term, fmt.Sprintf("%s isn't in your contact list... ignoring anyway for the duration of this session!", uid))
818	}
819
820	s.ignored[uid] = struct{}{}
821	info(s.input.term, fmt.Sprintf("Use '/unignore %s' to continue receiving messages from them.", uid))
822}
823
824func (s *Session) unignoreUser(uid string) {
825	if _, ok := s.ignored[uid]; !ok {
826		info(s.input.term, "No ignore registered for "+uid)
827		return
828	}
829
830	info(s.input.term, "No longer ignoring messages from "+uid)
831	delete(s.ignored, uid)
832}
833
834func (s *Session) ignoreList() {
835	var ignored []string
836
837	for ignoredUser, _ := range s.ignored {
838		ignored = append(ignored, ignoredUser)
839	}
840	sort.Strings(ignored)
841
842	info(s.input.term, "Ignoring messages from these users for the duration of the session:")
843	for _, ignoredUser := range ignored {
844		info(s.term, "  "+ignoredUser)
845	}
846}
847
848func (s *Session) processClientMessage(stanza *xmpp.ClientMessage) {
849	from := xmpp.RemoveResourceFromJid(stanza.From)
850
851	if _, ok := s.ignored[from]; ok {
852		return
853	}
854
855	if stanza.Type == "error" {
856		alert(s.term, "Error reported from "+from+": "+stanza.Body)
857		return
858	}
859
860	conversation, ok := s.conversations[from]
861	if !ok {
862		conversation = new(otr.Conversation)
863		conversation.PrivateKey = s.privateKey
864		s.conversations[from] = conversation
865	}
866
867	out, encrypted, change, toSend, err := conversation.Receive([]byte(stanza.Body))
868	if err != nil {
869		alert(s.term, "While processing message from "+from+": "+err.Error())
870		s.conn.Send(stanza.From, otr.ErrorPrefix+"Error processing message")
871	}
872	for _, msg := range toSend {
873		s.conn.Send(stanza.From, string(msg))
874	}
875	switch change {
876	case otr.NewKeys:
877		s.input.SetPromptForTarget(from, true)
878		info(s.term, fmt.Sprintf("New OTR session with %s established", from))
879		printConversationInfo(s, from, conversation)
880	case otr.ConversationEnded:
881		s.input.SetPromptForTarget(from, false)
882		// This is probably unsafe without a policy that _forces_ crypto to
883		// _everyone_ by default and refuses plaintext. Users might not notice
884		// their buddy has ended a session, which they have also ended, and they
885		// might send a plain text message. So we should ensure they _want_ this
886		// feature and have set it as an explicit preference.
887		if s.config.OTRAutoTearDown {
888			if s.conversations[from] == nil {
889				alert(s.term, fmt.Sprintf("No secure session established; unable to automatically tear down OTR conversation with %s.", from))
890				break
891			} else {
892				info(s.term, fmt.Sprintf("%s has ended the secure conversation.", from))
893				msgs := conversation.End()
894				for _, msg := range msgs {
895					s.conn.Send(from, string(msg))
896				}
897				info(s.term, fmt.Sprintf("Secure session with %s has been automatically ended. Messages will be sent in the clear until another OTR session is established.", from))
898			}
899		} else {
900			info(s.term, fmt.Sprintf("%s has ended the secure conversation. You should do likewise with /otr-end %s", from, from))
901		}
902	case otr.SMPSecretNeeded:
903		info(s.term, fmt.Sprintf("%s is attempting to authenticate. Please supply mutual shared secret with /otr-auth user secret", from))
904		if question := conversation.SMPQuestion(); len(question) > 0 {
905			info(s.term, fmt.Sprintf("%s asks: %s", from, question))
906		}
907	case otr.SMPComplete:
908		info(s.term, fmt.Sprintf("Authentication with %s successful", from))
909		fpr := conversation.TheirPublicKey.Fingerprint()
910		if len(s.config.UserIdForFingerprint(fpr)) == 0 {
911			s.config.KnownFingerprints = append(s.config.KnownFingerprints, KnownFingerprint{fingerprint: fpr, UserId: from})
912		}
913		s.config.Save()
914	case otr.SMPFailed:
915		alert(s.term, fmt.Sprintf("Authentication with %s failed", from))
916	}
917
918	if len(out) == 0 {
919		return
920	}
921
922	detectedOTRVersion := 0
923	// We don't need to alert about tags encoded inside of messages that are
924	// already encrypted with OTR
925	whitespaceTagLength := len(OTRWhitespaceTagStart) + len(OTRWhiteSpaceTagV1)
926	if !encrypted && len(out) >= whitespaceTagLength {
927		whitespaceTag := out[len(out)-whitespaceTagLength:]
928		if bytes.Equal(whitespaceTag[:len(OTRWhitespaceTagStart)], OTRWhitespaceTagStart) {
929			if bytes.HasSuffix(whitespaceTag, OTRWhiteSpaceTagV1) {
930				info(s.term, fmt.Sprintf("%s appears to support OTRv1. You should encourage them to upgrade their OTR client!", from))
931				detectedOTRVersion = 1
932			}
933			if bytes.HasSuffix(whitespaceTag, OTRWhiteSpaceTagV2) {
934				detectedOTRVersion = 2
935			}
936			if bytes.HasSuffix(whitespaceTag, OTRWhiteSpaceTagV3) {
937				detectedOTRVersion = 3
938			}
939		}
940	}
941
942	if s.config.OTRAutoStartSession && detectedOTRVersion >= 2 {
943		info(s.term, fmt.Sprintf("%s appears to support OTRv%d. We are attempting to start an OTR session with them.", from, detectedOTRVersion))
944		s.conn.Send(from, otr.QueryMessage)
945	} else if s.config.OTRAutoStartSession && detectedOTRVersion == 1 {
946		info(s.term, fmt.Sprintf("%s appears to support OTRv%d. You should encourage them to upgrade their OTR client!", from, detectedOTRVersion))
947	}
948
949	var line []byte
950	if encrypted {
951		line = append(line, s.term.Escape.Green...)
952	} else {
953		line = append(line, s.term.Escape.Red...)
954	}
955
956	var timestamp string
957	var messageTime time.Time
958	if stanza.Delay != nil && len(stanza.Delay.Stamp) > 0 {
959		// An XEP-0203 Delayed Delivery <delay/> element exists for
960		// this message, meaning that someone sent it while we were
961		// offline. Let's show the timestamp for when the message was
962		// sent, rather than time.Now().
963		messageTime, err = time.Parse(time.RFC3339, stanza.Delay.Stamp)
964		if err != nil {
965			alert(s.term, "Can not parse Delayed Delivery timestamp, using quoted string instead.")
966			timestamp = fmt.Sprintf("%q", stanza.Delay.Stamp)
967		}
968	} else {
969		messageTime = time.Now()
970	}
971	if len(timestamp) == 0 {
972		timestamp = messageTime.Format(time.Stamp)
973	}
974
975	t := fmt.Sprintf("(%s) %s: ", timestamp, from)
976	line = append(line, []byte(t)...)
977	line = append(line, s.term.Escape.Reset...)
978	line = appendTerminalEscaped(line, stripHTML(out))
979	line = append(line, '\n')
980	if s.config.Bell {
981		line = append(line, '\a')
982	}
983	s.term.Write(line)
984	s.maybeNotify()
985}
986
987func (s *Session) maybeNotify() {
988	now := time.Now()
989	idleThreshold := s.config.IdleSecondsBeforeNotification
990	if idleThreshold == 0 {
991		idleThreshold = 60
992	}
993	notifyTime := s.lastActionTime.Add(time.Duration(idleThreshold) * time.Second)
994	if now.Before(notifyTime) {
995		return
996	}
997
998	s.lastActionTime = now
999	if len(s.config.NotifyCommand) == 0 {
1000		return
1001	}
1002
1003	cmd := exec.Command(s.config.NotifyCommand[0], s.config.NotifyCommand[1:]...)
1004	go func() {
1005		if err := cmd.Run(); err != nil {
1006			alert(s.term, "Failed to run notify command: "+err.Error())
1007		}
1008	}()
1009}
1010
1011func isAwayStatus(status string) bool {
1012	switch status {
1013	case "xa", "away":
1014		return true
1015	}
1016	return false
1017}
1018
1019func (s *Session) processPresence(stanza *xmpp.ClientPresence) {
1020	gone := false
1021
1022	switch stanza.Type {
1023	case "subscribe":
1024		// This is a subscription request
1025		jid := xmpp.RemoveResourceFromJid(stanza.From)
1026		info(s.term, jid+" wishes to see when you're online. Use '/confirm "+jid+"' to confirm (or likewise with /deny to decline)")
1027		s.pendingSubscribes[jid] = stanza.Id
1028		s.input.AddUser(jid)
1029		return
1030	case "unavailable":
1031		gone = true
1032	case "":
1033		break
1034	default:
1035		return
1036	}
1037
1038	from := xmpp.RemoveResourceFromJid(stanza.From)
1039
1040	if gone {
1041		if _, ok := s.knownStates[from]; !ok {
1042			// They've gone, but we never knew they were online.
1043			return
1044		}
1045		delete(s.knownStates, from)
1046	} else {
1047		if _, ok := s.knownStates[from]; !ok && isAwayStatus(stanza.Show) {
1048			// Skip people who are initially away.
1049			return
1050		}
1051
1052		if lastState, ok := s.knownStates[from]; ok && lastState == stanza.Show {
1053			// No change. Ignore.
1054			return
1055		}
1056		s.knownStates[from] = stanza.Show
1057	}
1058
1059	if !s.config.HideStatusUpdates {
1060		var line []byte
1061		line = append(line, []byte(fmt.Sprintf("   (%s) ", time.Now().Format(time.Kitchen)))...)
1062		line = append(line, s.term.Escape.Magenta...)
1063		line = append(line, []byte(from)...)
1064		line = append(line, ':')
1065		line = append(line, s.term.Escape.Reset...)
1066		line = append(line, ' ')
1067		if gone {
1068			line = append(line, []byte("offline")...)
1069		} else if len(stanza.Show) > 0 {
1070			line = append(line, []byte(stanza.Show)...)
1071		} else {
1072			line = append(line, []byte("online")...)
1073		}
1074		line = append(line, ' ')
1075		line = append(line, []byte(stanza.Status)...)
1076		line = append(line, '\n')
1077		s.term.Write(line)
1078	}
1079}
1080
1081func (s *Session) awaitVersionReply(ch <-chan xmpp.Stanza, user string) {
1082	stanza, ok := <-ch
1083	if !ok {
1084		warn(s.term, "Version request to "+user+" timed out")
1085		return
1086	}
1087	reply, ok := stanza.Value.(*xmpp.ClientIQ)
1088	if !ok {
1089		warn(s.term, "Version request to "+user+" resulted in bad reply type")
1090		return
1091	}
1092
1093	if reply.Type == "error" {
1094		warn(s.term, "Version request to "+user+" resulted in XMPP error")
1095		return
1096	} else if reply.Type != "result" {
1097		warn(s.term, "Version request to "+user+" resulted in response with unknown type: "+reply.Type)
1098		return
1099	}
1100
1101	buf := bytes.NewBuffer(reply.Query)
1102	var versionReply xmpp.VersionReply
1103	if err := xml.NewDecoder(buf).Decode(&versionReply); err != nil {
1104		warn(s.term, "Failed to parse version reply from "+user+": "+err.Error())
1105		return
1106	}
1107
1108	info(s.term, fmt.Sprintf("Version reply from %s: %#v", user, versionReply))
1109}
1110
1111// editRoster runs in a goroutine and writes the roster to a file that the user
1112// can edit.
1113func (s *Session) editRoster(roster []xmpp.RosterEntry) {
1114	// In case the editor rewrites the file, we work inside a temp
1115	// directory.
1116	dir, err := ioutil.TempDir("" /* system default temp dir */, "xmpp-client")
1117	if err != nil {
1118		alert(s.term, "Failed to create temp dir to edit roster: "+err.Error())
1119		return
1120	}
1121
1122	mode, err := os.Stat(dir)
1123	if err != nil || mode.Mode()&os.ModePerm != 0700 {
1124		panic("broken system libraries gave us an insecure temp dir")
1125	}
1126
1127	fileName := filepath.Join(dir, "roster")
1128	f, err := os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
1129	if err != nil {
1130		alert(s.term, "Failed to create temp file: "+err.Error())
1131		return
1132	}
1133
1134	io.WriteString(f, `# Use this file to edit your roster.
1135# The file is tab deliminated and you need to preserve that. Otherwise you
1136# can delete lines to remove roster entries, add lines to subscribe (only
1137# the account is needed when adding a line) and change lines to change the
1138# corresponding entry.
1139
1140# Once you are done, use the /rostereditdone command to process the result.
1141
1142# Since there are multiple levels of unspecified character encoding, we give up
1143# and hex escape anything outside of printable ASCII in "\x01" form.
1144
1145`)
1146
1147	// Calculate the number of tabs which covers the longest escaped JID.
1148	maxLen := 0
1149	escapedJids := make([]string, len(roster))
1150	for i, item := range roster {
1151		escapedJids[i] = escapeNonASCII(item.Jid)
1152		if l := len(escapedJids[i]); l > maxLen {
1153			maxLen = l
1154		}
1155	}
1156	tabs := (maxLen + 7) / 8
1157
1158	for i, item := range s.roster {
1159		line := escapedJids[i]
1160		tabsUsed := len(escapedJids[i]) / 8
1161
1162		if len(item.Name) > 0 || len(item.Group) > 0 {
1163			// We're going to put something else on the line to tab
1164			// across to the next column.
1165			for i := 0; i < tabs-tabsUsed; i++ {
1166				line += "\t"
1167			}
1168		}
1169
1170		if len(item.Name) > 0 {
1171			line += "name:" + escapeNonASCII(item.Name)
1172			if len(item.Group) > 0 {
1173				line += "\t"
1174			}
1175		}
1176
1177		for j, group := range item.Group {
1178			if j > 0 {
1179				line += "\t"
1180			}
1181			line += "group:" + escapeNonASCII(group)
1182		}
1183		line += "\n"
1184		io.WriteString(f, line)
1185	}
1186	f.Close()
1187
1188	s.pendingRosterChan <- &rosterEdit{
1189		fileName: fileName,
1190		roster:   roster,
1191	}
1192}
1193
1194var hexTable = "0123456789abcdef"
1195
1196// escapeNonASCII replaces tabs and other non-printable characters with a
1197// "\x01" form of hex escaping. It works on a byte-by-byte basis.
1198func escapeNonASCII(in string) string {
1199	escapes := 0
1200	for i := 0; i < len(in); i++ {
1201		if in[i] < 32 || in[i] > 126 || in[i] == '\\' {
1202			escapes++
1203		}
1204	}
1205
1206	if escapes == 0 {
1207		return in
1208	}
1209
1210	out := make([]byte, 0, len(in)+3*escapes)
1211	for i := 0; i < len(in); i++ {
1212		if in[i] < 32 || in[i] > 126 || in[i] == '\\' {
1213			out = append(out, '\\', 'x', hexTable[in[i]>>4], hexTable[in[i]&15])
1214		} else {
1215			out = append(out, in[i])
1216		}
1217	}
1218
1219	return string(out)
1220}
1221
1222// unescapeNonASCII undoes the transformation of escapeNonASCII.
1223func unescapeNonASCII(in string) (string, error) {
1224	needsUnescaping := false
1225	for i := 0; i < len(in); i++ {
1226		if in[i] == '\\' {
1227			needsUnescaping = true
1228			break
1229		}
1230	}
1231
1232	if !needsUnescaping {
1233		return in, nil
1234	}
1235
1236	out := make([]byte, 0, len(in))
1237	for i := 0; i < len(in); i++ {
1238		if in[i] == '\\' {
1239			if len(in) <= i+3 {
1240				return "", errors.New("truncated escape sequence at end: " + in)
1241			}
1242			if in[i+1] != 'x' {
1243				return "", errors.New("escape sequence didn't start with \\x in: " + in)
1244			}
1245			v, err := strconv.ParseUint(in[i+2:i+4], 16, 8)
1246			if err != nil {
1247				return "", errors.New("failed to parse value in '" + in + "': " + err.Error())
1248			}
1249			out = append(out, byte(v))
1250			i += 3
1251		} else {
1252			out = append(out, in[i])
1253		}
1254	}
1255
1256	return string(out), nil
1257}
1258
1259func (s *Session) loadEditedRoster(edit rosterEdit) {
1260	contents, err := ioutil.ReadFile(edit.fileName)
1261	if err != nil {
1262		alert(s.term, "Failed to load edited roster: "+err.Error())
1263		return
1264	}
1265	os.Remove(edit.fileName)
1266	os.Remove(filepath.Dir(edit.fileName))
1267
1268	edit.isComplete = true
1269	edit.contents = contents
1270	s.pendingRosterChan <- &edit
1271}
1272
1273func (s *Session) processEditedRoster(edit *rosterEdit) bool {
1274	parsedRoster := make(map[string]xmpp.RosterEntry)
1275	lines := bytes.Split(edit.contents, newLine)
1276	tab := []byte{'\t'}
1277
1278	// Parse roster entries from the file.
1279	for i, line := range lines {
1280		if len(line) == 0 || line[0] == '#' {
1281			continue
1282		}
1283		parts := bytes.Split(line, tab)
1284
1285		var entry xmpp.RosterEntry
1286		var err error
1287
1288		if entry.Jid, err = unescapeNonASCII(string(string(parts[0]))); err != nil {
1289			alert(s.term, fmt.Sprintf("Failed to parse JID on line %d: %s", i+1, err))
1290			return false
1291		}
1292		for _, part := range parts[1:] {
1293			if len(part) == 0 {
1294				continue
1295			}
1296
1297			pos := bytes.IndexByte(part, ':')
1298			if pos == -1 {
1299				alert(s.term, fmt.Sprintf("Failed to find colon in item on line %d", i+1))
1300				return false
1301			}
1302
1303			typ := string(part[:pos])
1304			value, err := unescapeNonASCII(string(part[pos+1:]))
1305			if err != nil {
1306				alert(s.term, fmt.Sprintf("Failed to unescape item on line %d: %s", i+1, err))
1307				return false
1308			}
1309
1310			switch typ {
1311			case "name":
1312				if len(entry.Name) > 0 {
1313					alert(s.term, fmt.Sprintf("Multiple names given for contact on line %d", i+1))
1314					return false
1315				}
1316				entry.Name = value
1317			case "group":
1318				if len(value) > 0 {
1319					entry.Group = append(entry.Group, value)
1320				}
1321			default:
1322				alert(s.term, fmt.Sprintf("Unknown item tag '%s' on line %d", typ, i+1))
1323				return false
1324			}
1325		}
1326
1327		parsedRoster[entry.Jid] = entry
1328	}
1329
1330	// Now diff them from the original roster
1331	var toDelete []string
1332	var toEdit []xmpp.RosterEntry
1333	var toAdd []xmpp.RosterEntry
1334
1335	for _, entry := range edit.roster {
1336		newEntry, ok := parsedRoster[entry.Jid]
1337		if !ok {
1338			toDelete = append(toDelete, entry.Jid)
1339			continue
1340		}
1341		if newEntry.Name != entry.Name || !setEqual(newEntry.Group, entry.Group) {
1342			toEdit = append(toEdit, newEntry)
1343		}
1344	}
1345
1346NextAdd:
1347	for jid, newEntry := range parsedRoster {
1348		for _, entry := range edit.roster {
1349			if entry.Jid == jid {
1350				continue NextAdd
1351			}
1352		}
1353		toAdd = append(toAdd, newEntry)
1354	}
1355
1356	for _, jid := range toDelete {
1357		info(s.term, "Deleting roster entry for "+jid)
1358		_, _, err := s.conn.SendIQ("" /* to the server */, "set", xmpp.RosterRequest{
1359			Item: xmpp.RosterRequestItem{
1360				Jid:          jid,
1361				Subscription: "remove",
1362			},
1363		})
1364		if err != nil {
1365			alert(s.term, "Failed to remove roster entry: "+err.Error())
1366		}
1367
1368		// Filter out any known fingerprints.
1369		newKnownFingerprints := make([]KnownFingerprint, 0, len(s.config.KnownFingerprints))
1370		for _, fpr := range s.config.KnownFingerprints {
1371			if fpr.UserId == jid {
1372				continue
1373			}
1374			newKnownFingerprints = append(newKnownFingerprints, fpr)
1375		}
1376		s.config.KnownFingerprints = newKnownFingerprints
1377		s.config.Save()
1378	}
1379
1380	for _, entry := range toEdit {
1381		info(s.term, "Updating roster entry for "+entry.Jid)
1382		_, _, err := s.conn.SendIQ("" /* to the server */, "set", xmpp.RosterRequest{
1383			Item: xmpp.RosterRequestItem{
1384				Jid:   entry.Jid,
1385				Name:  entry.Name,
1386				Group: entry.Group,
1387			},
1388		})
1389		if err != nil {
1390			alert(s.term, "Failed to update roster entry: "+err.Error())
1391		}
1392	}
1393
1394	for _, entry := range toAdd {
1395		info(s.term, "Adding roster entry for "+entry.Jid)
1396		_, _, err := s.conn.SendIQ("" /* to the server */, "set", xmpp.RosterRequest{
1397			Item: xmpp.RosterRequestItem{
1398				Jid:   entry.Jid,
1399				Name:  entry.Name,
1400				Group: entry.Group,
1401			},
1402		})
1403		if err != nil {
1404			alert(s.term, "Failed to add roster entry: "+err.Error())
1405		}
1406	}
1407
1408	return true
1409}
1410
1411func setEqual(a, b []string) bool {
1412	if len(a) != len(b) {
1413		return false
1414	}
1415
1416EachValue:
1417	for _, v := range a {
1418		for _, v2 := range b {
1419			if v == v2 {
1420				continue EachValue
1421			}
1422		}
1423		return false
1424	}
1425
1426	return true
1427}
1428
1429type rawLogger struct {
1430	out    io.Writer
1431	prefix []byte
1432	lock   *sync.Mutex
1433	other  *rawLogger
1434	buf    []byte
1435}
1436
1437func (r *rawLogger) Write(data []byte) (int, error) {
1438	r.lock.Lock()
1439	defer r.lock.Unlock()
1440
1441	if err := r.other.flush(); err != nil {
1442		return 0, nil
1443	}
1444
1445	origLen := len(data)
1446	for len(data) > 0 {
1447		if newLine := bytes.IndexByte(data, '\n'); newLine >= 0 {
1448			r.buf = append(r.buf, data[:newLine]...)
1449			data = data[newLine+1:]
1450		} else {
1451			r.buf = append(r.buf, data...)
1452			data = nil
1453		}
1454	}
1455
1456	return origLen, nil
1457}
1458
1459var newLine = []byte{'\n'}
1460
1461func (r *rawLogger) flush() error {
1462	if len(r.buf) == 0 {
1463		return nil
1464	}
1465
1466	if _, err := r.out.Write(r.prefix); err != nil {
1467		return err
1468	}
1469	if _, err := r.out.Write(r.buf); err != nil {
1470		return err
1471	}
1472	if _, err := r.out.Write(newLine); err != nil {
1473		return err
1474	}
1475	r.buf = r.buf[:0]
1476	return nil
1477}
1478
1479type lineLogger struct {
1480	term *terminal.Terminal
1481	buf  []byte
1482}
1483
1484func (l *lineLogger) logLines(in []byte) []byte {
1485	for len(in) > 0 {
1486		if newLine := bytes.IndexByte(in, '\n'); newLine >= 0 {
1487			info(l.term, string(in[:newLine]))
1488			in = in[newLine+1:]
1489		} else {
1490			break
1491		}
1492	}
1493	return in
1494}
1495
1496func (l *lineLogger) Write(data []byte) (int, error) {
1497	origLen := len(data)
1498
1499	if len(l.buf) == 0 {
1500		data = l.logLines(data)
1501	}
1502
1503	if len(data) > 0 {
1504		l.buf = append(l.buf, data...)
1505	}
1506
1507	l.buf = l.logLines(l.buf)
1508	return origLen, nil
1509}
1510
1511func printConversationInfo(s *Session, uid string, conversation *otr.Conversation) {
1512	fpr := conversation.TheirPublicKey.Fingerprint()
1513	fprUid := s.config.UserIdForFingerprint(fpr)
1514	info(s.term, fmt.Sprintf("  Fingerprint  for %s: %x", uid, fpr))
1515	info(s.term, fmt.Sprintf("  Session  ID  for %s: %x", uid, conversation.SSID))
1516	if fprUid == uid {
1517		info(s.term, fmt.Sprintf("  Identity key for %s is verified", uid))
1518	} else if len(fprUid) > 1 {
1519		alert(s.term, fmt.Sprintf("  Warning: %s is using an identity key which was verified for %s", uid, fprUid))
1520	} else if s.config.HasFingerprint(uid) {
1521		critical(s.term, fmt.Sprintf("  Identity key for %s is incorrect", uid))
1522	} else {
1523		alert(s.term, fmt.Sprintf("  Identity key for %s is not verified. You should use /otr-auth or /otr-authqa or /otr-authoob to verify their identity", uid))
1524	}
1525}
1526
1527// promptForForm runs an XEP-0004 form and collects responses from the user.
1528func promptForForm(term *terminal.Terminal, user, password, title, instructions string, fields []interface{}) error {
1529	info(term, "The server has requested the following information. Text that has come from the server will be shown in red.")
1530
1531	// formStringForPrinting takes a string form the form and returns an
1532	// escaped version with codes to make it show as red.
1533	formStringForPrinting := func(s string) string {
1534		var line []byte
1535
1536		line = append(line, term.Escape.Red...)
1537		line = appendTerminalEscaped(line, []byte(s))
1538		line = append(line, term.Escape.Reset...)
1539		return string(line)
1540	}
1541
1542	write := func(s string) {
1543		term.Write([]byte(s))
1544	}
1545
1546	var tmpDir string
1547
1548	showMediaEntries := func(questionNumber int, medias [][]xmpp.Media) {
1549		if len(medias) == 0 {
1550			return
1551		}
1552
1553		write("The following media blobs have been provided by the server with this question:\n")
1554		for i, media := range medias {
1555			for j, rep := range media {
1556				if j == 0 {
1557					write(fmt.Sprintf("  %d. ", i+1))
1558				} else {
1559					write("     ")
1560				}
1561				write(fmt.Sprintf("Data of type %s", formStringForPrinting(rep.MIMEType)))
1562				if len(rep.URI) > 0 {
1563					write(fmt.Sprintf(" at %s\n", formStringForPrinting(rep.URI)))
1564					continue
1565				}
1566
1567				var fileExt string
1568				switch rep.MIMEType {
1569				case "image/png":
1570					fileExt = "png"
1571				case "image/jpeg":
1572					fileExt = "jpeg"
1573				}
1574
1575				if len(tmpDir) == 0 {
1576					var err error
1577					if tmpDir, err = ioutil.TempDir("", "xmppclient"); err != nil {
1578						write(", but failed to create temporary directory in which to save it: " + err.Error() + "\n")
1579						continue
1580					}
1581				}
1582
1583				filename := filepath.Join(tmpDir, fmt.Sprintf("%d-%d-%d", questionNumber, i, j))
1584				if len(fileExt) > 0 {
1585					filename = filename + "." + fileExt
1586				}
1587				out, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
1588				if err != nil {
1589					write(", but failed to create file in which to save it: " + err.Error() + "\n")
1590					continue
1591				}
1592				out.Write(rep.Data)
1593				out.Close()
1594
1595				write(", saved in " + filename + "\n")
1596			}
1597		}
1598
1599		write("\n")
1600	}
1601
1602	var err error
1603	if len(title) > 0 {
1604		write(fmt.Sprintf("Title: %s\n", formStringForPrinting(title)))
1605	}
1606	if len(instructions) > 0 {
1607		write(fmt.Sprintf("Instructions: %s\n", formStringForPrinting(instructions)))
1608	}
1609
1610	questionNumber := 0
1611	for _, field := range fields {
1612		questionNumber++
1613		write("\n")
1614
1615		switch field := field.(type) {
1616		case *xmpp.FixedFormField:
1617			write(formStringForPrinting(field.Text))
1618			write("\n")
1619			questionNumber--
1620
1621		case *xmpp.BooleanFormField:
1622			write(fmt.Sprintf("%d. %s\n\n", questionNumber, formStringForPrinting(field.Label)))
1623			showMediaEntries(questionNumber, field.Media)
1624			term.SetPrompt("Please enter yes, y, no or n: ")
1625
1626		TryAgain:
1627			for {
1628				answer, err := term.ReadLine()
1629				if err != nil {
1630					return err
1631				}
1632				switch answer {
1633				case "yes", "y":
1634					field.Result = true
1635				case "no", "n":
1636					field.Result = false
1637				default:
1638					continue TryAgain
1639				}
1640				break
1641			}
1642
1643		case *xmpp.TextFormField:
1644			switch field.Label {
1645			case "CAPTCHA web page":
1646				if strings.HasPrefix(field.Default, "http") {
1647					// This is a oddity of jabber.ccc.de and maybe
1648					// others. The URL for the capture is provided
1649					// as the default answer to a question. Perhaps
1650					// that was needed with some clients. However,
1651					// we support embedded media and it's confusing
1652					// to ask the question, so we just print the
1653					// URL.
1654					write(fmt.Sprintf("CAPTCHA web page (only if not provided below): %s\n", formStringForPrinting(field.Default)))
1655					questionNumber--
1656					continue
1657				}
1658
1659			case "User":
1660				field.Result = user
1661				questionNumber--
1662				continue
1663
1664			case "Password":
1665				field.Result = password
1666				questionNumber--
1667				continue
1668			}
1669
1670			write(fmt.Sprintf("%d. %s\n\n", questionNumber, formStringForPrinting(field.Label)))
1671			showMediaEntries(questionNumber, field.Media)
1672
1673			if len(field.Default) > 0 {
1674				write(fmt.Sprintf("Please enter response or leave blank for the default, which is '%s'\n", formStringForPrinting(field.Default)))
1675			} else {
1676				write("Please enter response")
1677			}
1678			term.SetPrompt("> ")
1679			if field.Private {
1680				field.Result, err = term.ReadPassword("> ")
1681			} else {
1682				field.Result, err = term.ReadLine()
1683			}
1684			if err != nil {
1685				return err
1686			}
1687			if len(field.Result) == 0 {
1688				field.Result = field.Default
1689			}
1690
1691		case *xmpp.MultiTextFormField:
1692			write(fmt.Sprintf("%d. %s\n\n", questionNumber, formStringForPrinting(field.Label)))
1693			showMediaEntries(questionNumber, field.Media)
1694
1695			write("Please enter one or more responses, terminated by an empty line\n")
1696			term.SetPrompt("> ")
1697
1698			for {
1699				line, err := term.ReadLine()
1700				if err != nil {
1701					return err
1702				}
1703				if len(line) == 0 {
1704					break
1705				}
1706				field.Results = append(field.Results, line)
1707			}
1708
1709		case *xmpp.SelectionFormField:
1710			write(fmt.Sprintf("%d. %s\n\n", questionNumber, formStringForPrinting(field.Label)))
1711			showMediaEntries(questionNumber, field.Media)
1712
1713			for i, opt := range field.Values {
1714				write(fmt.Sprintf("  %d. %s\n\n", i+1, formStringForPrinting(opt)))
1715			}
1716			term.SetPrompt("Please enter the number of your selection: ")
1717
1718		TryAgain2:
1719			for {
1720				answer, err := term.ReadLine()
1721				if err != nil {
1722					return err
1723				}
1724				answerNum, err := strconv.Atoi(answer)
1725				answerNum--
1726				if err != nil || answerNum < 0 || answerNum >= len(field.Values) {
1727					write("Cannot parse that reply. Try again.")
1728					continue TryAgain2
1729				}
1730
1731				field.Result = answerNum
1732				break
1733			}
1734
1735		case *xmpp.MultiSelectionFormField:
1736			write(fmt.Sprintf("%d. %s\n\n", questionNumber, formStringForPrinting(field.Label)))
1737			showMediaEntries(questionNumber, field.Media)
1738
1739			for i, opt := range field.Values {
1740				write(fmt.Sprintf("  %d. %s\n\n", i+1, formStringForPrinting(opt)))
1741			}
1742			term.SetPrompt("Please enter the numbers of zero or more of the above, separated by spaces: ")
1743
1744		TryAgain3:
1745			for {
1746				answer, err := term.ReadLine()
1747				if err != nil {
1748					return err
1749				}
1750
1751				var candidateResults []int
1752				answers := strings.Fields(answer)
1753				for _, answerStr := range answers {
1754					answerNum, err := strconv.Atoi(answerStr)
1755					answerNum--
1756					if err != nil || answerNum < 0 || answerNum >= len(field.Values) {
1757						write("Cannot parse that reply. Please try again.")
1758						continue TryAgain3
1759					}
1760					for _, other := range candidateResults {
1761						if answerNum == other {
1762							write("Cannot have duplicates. Please try again.")
1763							continue TryAgain3
1764						}
1765					}
1766					candidateResults = append(candidateResults, answerNum)
1767				}
1768
1769				field.Results = candidateResults
1770				break
1771			}
1772		}
1773	}
1774
1775	if len(tmpDir) > 0 {
1776		os.RemoveAll(tmpDir)
1777	}
1778
1779	return nil
1780}
1781
1782// caCertRootDER is the DER-format, root certificate for CACert. Downloaded
1783// from http://www.cacert.org/certs/root.der.
1784var caCertRootDER = []byte{
1785	0x30, 0x82, 0x07, 0x3d, 0x30, 0x82, 0x05, 0x25, 0xa0, 0x03, 0x02, 0x01,
1786	0x02, 0x02, 0x01, 0x00, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
1787	0xf7, 0x0d, 0x01, 0x01, 0x04, 0x05, 0x00, 0x30, 0x79, 0x31, 0x10, 0x30,
1788	0x0e, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x07, 0x52, 0x6f, 0x6f, 0x74,
1789	0x20, 0x43, 0x41, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04, 0x0b,
1790	0x13, 0x15, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77,
1791	0x2e, 0x63, 0x61, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x6f, 0x72, 0x67, 0x31,
1792	0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x19, 0x43, 0x41,
1793	0x20, 0x43, 0x65, 0x72, 0x74, 0x20, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e,
1794	0x67, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x31,
1795	0x21, 0x30, 0x1f, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
1796	0x09, 0x01, 0x16, 0x12, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x40,
1797	0x63, 0x61, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x6f, 0x72, 0x67, 0x30, 0x1e,
1798	0x17, 0x0d, 0x30, 0x33, 0x30, 0x33, 0x33, 0x30, 0x31, 0x32, 0x32, 0x39,
1799	0x34, 0x39, 0x5a, 0x17, 0x0d, 0x33, 0x33, 0x30, 0x33, 0x32, 0x39, 0x31,
1800	0x32, 0x32, 0x39, 0x34, 0x39, 0x5a, 0x30, 0x79, 0x31, 0x10, 0x30, 0x0e,
1801	0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x07, 0x52, 0x6f, 0x6f, 0x74, 0x20,
1802	0x43, 0x41, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13,
1803	0x15, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e,
1804	0x63, 0x61, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x6f, 0x72, 0x67, 0x31, 0x22,
1805	0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x19, 0x43, 0x41, 0x20,
1806	0x43, 0x65, 0x72, 0x74, 0x20, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67,
1807	0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x31, 0x21,
1808	0x30, 0x1f, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09,
1809	0x01, 0x16, 0x12, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x40, 0x63,
1810	0x61, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x6f, 0x72, 0x67, 0x30, 0x82, 0x02,
1811	0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
1812	0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x02, 0x0f, 0x00, 0x30, 0x82, 0x02,
1813	0x0a, 0x02, 0x82, 0x02, 0x01, 0x00, 0xce, 0x22, 0xc0, 0xe2, 0x46, 0x7d,
1814	0xec, 0x36, 0x28, 0x07, 0x50, 0x96, 0xf2, 0xa0, 0x33, 0x40, 0x8c, 0x4b,
1815	0xf1, 0x3b, 0x66, 0x3f, 0x31, 0xe5, 0x6b, 0x02, 0x36, 0xdb, 0xd6, 0x7c,
1816	0xf6, 0xf1, 0x88, 0x8f, 0x4e, 0x77, 0x36, 0x05, 0x41, 0x95, 0xf9, 0x09,
1817	0xf0, 0x12, 0xcf, 0x46, 0x86, 0x73, 0x60, 0xb7, 0x6e, 0x7e, 0xe8, 0xc0,
1818	0x58, 0x64, 0xae, 0xcd, 0xb0, 0xad, 0x45, 0x17, 0x0c, 0x63, 0xfa, 0x67,
1819	0x0a, 0xe8, 0xd6, 0xd2, 0xbf, 0x3e, 0xe7, 0x98, 0xc4, 0xf0, 0x4c, 0xfa,
1820	0xe0, 0x03, 0xbb, 0x35, 0x5d, 0x6c, 0x21, 0xde, 0x9e, 0x20, 0xd9, 0xba,
1821	0xcd, 0x66, 0x32, 0x37, 0x72, 0xfa, 0xf7, 0x08, 0xf5, 0xc7, 0xcd, 0x58,
1822	0xc9, 0x8e, 0xe7, 0x0e, 0x5e, 0xea, 0x3e, 0xfe, 0x1c, 0xa1, 0x14, 0x0a,
1823	0x15, 0x6c, 0x86, 0x84, 0x5b, 0x64, 0x66, 0x2a, 0x7a, 0xa9, 0x4b, 0x53,
1824	0x79, 0xf5, 0x88, 0xa2, 0x7b, 0xee, 0x2f, 0x0a, 0x61, 0x2b, 0x8d, 0xb2,
1825	0x7e, 0x4d, 0x56, 0xa5, 0x13, 0xec, 0xea, 0xda, 0x92, 0x9e, 0xac, 0x44,
1826	0x41, 0x1e, 0x58, 0x60, 0x65, 0x05, 0x66, 0xf8, 0xc0, 0x44, 0xbd, 0xcb,
1827	0x94, 0xf7, 0x42, 0x7e, 0x0b, 0xf7, 0x65, 0x68, 0x98, 0x51, 0x05, 0xf0,
1828	0xf3, 0x05, 0x91, 0x04, 0x1d, 0x1b, 0x17, 0x82, 0xec, 0xc8, 0x57, 0xbb,
1829	0xc3, 0x6b, 0x7a, 0x88, 0xf1, 0xb0, 0x72, 0xcc, 0x25, 0x5b, 0x20, 0x91,
1830	0xec, 0x16, 0x02, 0x12, 0x8f, 0x32, 0xe9, 0x17, 0x18, 0x48, 0xd0, 0xc7,
1831	0x05, 0x2e, 0x02, 0x30, 0x42, 0xb8, 0x25, 0x9c, 0x05, 0x6b, 0x3f, 0xaa,
1832	0x3a, 0xa7, 0xeb, 0x53, 0x48, 0xf7, 0xe8, 0xd2, 0xb6, 0x07, 0x98, 0xdc,
1833	0x1b, 0xc6, 0x34, 0x7f, 0x7f, 0xc9, 0x1c, 0x82, 0x7a, 0x05, 0x58, 0x2b,
1834	0x08, 0x5b, 0xf3, 0x38, 0xa2, 0xab, 0x17, 0x5d, 0x66, 0xc9, 0x98, 0xd7,
1835	0x9e, 0x10, 0x8b, 0xa2, 0xd2, 0xdd, 0x74, 0x9a, 0xf7, 0x71, 0x0c, 0x72,
1836	0x60, 0xdf, 0xcd, 0x6f, 0x98, 0x33, 0x9d, 0x96, 0x34, 0x76, 0x3e, 0x24,
1837	0x7a, 0x92, 0xb0, 0x0e, 0x95, 0x1e, 0x6f, 0xe6, 0xa0, 0x45, 0x38, 0x47,
1838	0xaa, 0xd7, 0x41, 0xed, 0x4a, 0xb7, 0x12, 0xf6, 0xd7, 0x1b, 0x83, 0x8a,
1839	0x0f, 0x2e, 0xd8, 0x09, 0xb6, 0x59, 0xd7, 0xaa, 0x04, 0xff, 0xd2, 0x93,
1840	0x7d, 0x68, 0x2e, 0xdd, 0x8b, 0x4b, 0xab, 0x58, 0xba, 0x2f, 0x8d, 0xea,
1841	0x95, 0xa7, 0xa0, 0xc3, 0x54, 0x89, 0xa5, 0xfb, 0xdb, 0x8b, 0x51, 0x22,
1842	0x9d, 0xb2, 0xc3, 0xbe, 0x11, 0xbe, 0x2c, 0x91, 0x86, 0x8b, 0x96, 0x78,
1843	0xad, 0x20, 0xd3, 0x8a, 0x2f, 0x1a, 0x3f, 0xc6, 0xd0, 0x51, 0x65, 0x87,
1844	0x21, 0xb1, 0x19, 0x01, 0x65, 0x7f, 0x45, 0x1c, 0x87, 0xf5, 0x7c, 0xd0,
1845	0x41, 0x4c, 0x4f, 0x29, 0x98, 0x21, 0xfd, 0x33, 0x1f, 0x75, 0x0c, 0x04,
1846	0x51, 0xfa, 0x19, 0x77, 0xdb, 0xd4, 0x14, 0x1c, 0xee, 0x81, 0xc3, 0x1d,
1847	0xf5, 0x98, 0xb7, 0x69, 0x06, 0x91, 0x22, 0xdd, 0x00, 0x50, 0xcc, 0x81,
1848	0x31, 0xac, 0x12, 0x07, 0x7b, 0x38, 0xda, 0x68, 0x5b, 0xe6, 0x2b, 0xd4,
1849	0x7e, 0xc9, 0x5f, 0xad, 0xe8, 0xeb, 0x72, 0x4c, 0xf3, 0x01, 0xe5, 0x4b,
1850	0x20, 0xbf, 0x9a, 0xa6, 0x57, 0xca, 0x91, 0x00, 0x01, 0x8b, 0xa1, 0x75,
1851	0x21, 0x37, 0xb5, 0x63, 0x0d, 0x67, 0x3e, 0x46, 0x4f, 0x70, 0x20, 0x67,
1852	0xce, 0xc5, 0xd6, 0x59, 0xdb, 0x02, 0xe0, 0xf0, 0xd2, 0xcb, 0xcd, 0xba,
1853	0x62, 0xb7, 0x90, 0x41, 0xe8, 0xdd, 0x20, 0xe4, 0x29, 0xbc, 0x64, 0x29,
1854	0x42, 0xc8, 0x22, 0xdc, 0x78, 0x9a, 0xff, 0x43, 0xec, 0x98, 0x1b, 0x09,
1855	0x51, 0x4b, 0x5a, 0x5a, 0xc2, 0x71, 0xf1, 0xc4, 0xcb, 0x73, 0xa9, 0xe5,
1856	0xa1, 0x0b, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0xce, 0x30,
1857	0x82, 0x01, 0xca, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16,
1858	0x04, 0x14, 0x16, 0xb5, 0x32, 0x1b, 0xd4, 0xc7, 0xf3, 0xe0, 0xe6, 0x8e,
1859	0xf3, 0xbd, 0xd2, 0xb0, 0x3a, 0xee, 0xb2, 0x39, 0x18, 0xd1, 0x30, 0x81,
1860	0xa3, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x81, 0x9b, 0x30, 0x81, 0x98,
1861	0x80, 0x14, 0x16, 0xb5, 0x32, 0x1b, 0xd4, 0xc7, 0xf3, 0xe0, 0xe6, 0x8e,
1862	0xf3, 0xbd, 0xd2, 0xb0, 0x3a, 0xee, 0xb2, 0x39, 0x18, 0xd1, 0xa1, 0x7d,
1863	0xa4, 0x7b, 0x30, 0x79, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04,
1864	0x0a, 0x13, 0x07, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x31, 0x1e,
1865	0x30, 0x1c, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x15, 0x68, 0x74, 0x74,
1866	0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x63, 0x61, 0x63, 0x65,
1867	0x72, 0x74, 0x2e, 0x6f, 0x72, 0x67, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03,
1868	0x55, 0x04, 0x03, 0x13, 0x19, 0x43, 0x41, 0x20, 0x43, 0x65, 0x72, 0x74,
1869	0x20, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x20, 0x41, 0x75, 0x74,
1870	0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x09,
1871	0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01, 0x16, 0x12, 0x73,
1872	0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x40, 0x63, 0x61, 0x63, 0x65, 0x72,
1873	0x74, 0x2e, 0x6f, 0x72, 0x67, 0x82, 0x01, 0x00, 0x30, 0x0f, 0x06, 0x03,
1874	0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01,
1875	0xff, 0x30, 0x32, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2b, 0x30, 0x29,
1876	0x30, 0x27, 0xa0, 0x25, 0xa0, 0x23, 0x86, 0x21, 0x68, 0x74, 0x74, 0x70,
1877	0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x63, 0x61, 0x63, 0x65,
1878	0x72, 0x74, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x72, 0x65, 0x76, 0x6f, 0x6b,
1879	0x65, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x30, 0x06, 0x09, 0x60, 0x86, 0x48,
1880	0x01, 0x86, 0xf8, 0x42, 0x01, 0x04, 0x04, 0x23, 0x16, 0x21, 0x68, 0x74,
1881	0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x63, 0x61,
1882	0x63, 0x65, 0x72, 0x74, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x72, 0x65, 0x76,
1883	0x6f, 0x6b, 0x65, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x34, 0x06, 0x09, 0x60,
1884	0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x08, 0x04, 0x27, 0x16, 0x25,
1885	0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x63,
1886	0x61, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x69, 0x6e,
1887	0x64, 0x65, 0x78, 0x2e, 0x70, 0x68, 0x70, 0x3f, 0x69, 0x64, 0x3d, 0x31,
1888	0x30, 0x30, 0x56, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42,
1889	0x01, 0x0d, 0x04, 0x49, 0x16, 0x47, 0x54, 0x6f, 0x20, 0x67, 0x65, 0x74,
1890	0x20, 0x79, 0x6f, 0x75, 0x72, 0x20, 0x6f, 0x77, 0x6e, 0x20, 0x63, 0x65,
1891	0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x66, 0x6f,
1892	0x72, 0x20, 0x46, 0x52, 0x45, 0x45, 0x20, 0x68, 0x65, 0x61, 0x64, 0x20,
1893	0x6f, 0x76, 0x65, 0x72, 0x20, 0x74, 0x6f, 0x20, 0x68, 0x74, 0x74, 0x70,
1894	0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x63, 0x61, 0x63, 0x65, 0x72,
1895	0x74, 0x2e, 0x6f, 0x72, 0x67, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48,
1896	0x86, 0xf7, 0x0d, 0x01, 0x01, 0x04, 0x05, 0x00, 0x03, 0x82, 0x02, 0x01,
1897	0x00, 0x28, 0xc7, 0xee, 0x9c, 0x82, 0x02, 0xba, 0x5c, 0x80, 0x12, 0xca,
1898	0x35, 0x0a, 0x1d, 0x81, 0x6f, 0x89, 0x6a, 0x99, 0xcc, 0xf2, 0x68, 0x0f,
1899	0x7f, 0xa7, 0xe1, 0x8d, 0x58, 0x95, 0x3e, 0xbd, 0xf2, 0x06, 0xc3, 0x90,
1900	0x5a, 0xac, 0xb5, 0x60, 0xf6, 0x99, 0x43, 0x01, 0xa3, 0x88, 0x70, 0x9c,
1901	0x9d, 0x62, 0x9d, 0xa4, 0x87, 0xaf, 0x67, 0x58, 0x0d, 0x30, 0x36, 0x3b,
1902	0xe6, 0xad, 0x48, 0xd3, 0xcb, 0x74, 0x02, 0x86, 0x71, 0x3e, 0xe2, 0x2b,
1903	0x03, 0x68, 0xf1, 0x34, 0x62, 0x40, 0x46, 0x3b, 0x53, 0xea, 0x28, 0xf4,
1904	0xac, 0xfb, 0x66, 0x95, 0x53, 0x8a, 0x4d, 0x5d, 0xfd, 0x3b, 0xd9, 0x60,
1905	0xd7, 0xca, 0x79, 0x69, 0x3b, 0xb1, 0x65, 0x92, 0xa6, 0xc6, 0x81, 0x82,
1906	0x5c, 0x9c, 0xcd, 0xeb, 0x4d, 0x01, 0x8a, 0xa5, 0xdf, 0x11, 0x55, 0xaa,
1907	0x15, 0xca, 0x1f, 0x37, 0xc0, 0x82, 0x98, 0x70, 0x61, 0xdb, 0x6a, 0x7c,
1908	0x96, 0xa3, 0x8e, 0x2e, 0x54, 0x3e, 0x4f, 0x21, 0xa9, 0x90, 0xef, 0xdc,
1909	0x82, 0xbf, 0xdc, 0xe8, 0x45, 0xad, 0x4d, 0x90, 0x73, 0x08, 0x3c, 0x94,
1910	0x65, 0xb0, 0x04, 0x99, 0x76, 0x7f, 0xe2, 0xbc, 0xc2, 0x6a, 0x15, 0xaa,
1911	0x97, 0x04, 0x37, 0x24, 0xd8, 0x1e, 0x94, 0x4e, 0x6d, 0x0e, 0x51, 0xbe,
1912	0xd6, 0xc4, 0x8f, 0xca, 0x96, 0x6d, 0xf7, 0x43, 0xdf, 0xe8, 0x30, 0x65,
1913	0x27, 0x3b, 0x7b, 0xbb, 0x43, 0x43, 0x63, 0xc4, 0x43, 0xf7, 0xb2, 0xec,
1914	0x68, 0xcc, 0xe1, 0x19, 0x8e, 0x22, 0xfb, 0x98, 0xe1, 0x7b, 0x5a, 0x3e,
1915	0x01, 0x37, 0x3b, 0x8b, 0x08, 0xb0, 0xa2, 0xf3, 0x95, 0x4e, 0x1a, 0xcb,
1916	0x9b, 0xcd, 0x9a, 0xb1, 0xdb, 0xb2, 0x70, 0xf0, 0x2d, 0x4a, 0xdb, 0xd8,
1917	0xb0, 0xe3, 0x6f, 0x45, 0x48, 0x33, 0x12, 0xff, 0xfe, 0x3c, 0x32, 0x2a,
1918	0x54, 0xf7, 0xc4, 0xf7, 0x8a, 0xf0, 0x88, 0x23, 0xc2, 0x47, 0xfe, 0x64,
1919	0x7a, 0x71, 0xc0, 0xd1, 0x1e, 0xa6, 0x63, 0xb0, 0x07, 0x7e, 0xa4, 0x2f,
1920	0xd3, 0x01, 0x8f, 0xdc, 0x9f, 0x2b, 0xb6, 0xc6, 0x08, 0xa9, 0x0f, 0x93,
1921	0x48, 0x25, 0xfc, 0x12, 0xfd, 0x9f, 0x42, 0xdc, 0xf3, 0xc4, 0x3e, 0xf6,
1922	0x57, 0xb0, 0xd7, 0xdd, 0x69, 0xd1, 0x06, 0x77, 0x34, 0x0a, 0x4b, 0xd2,
1923	0xca, 0xa0, 0xff, 0x1c, 0xc6, 0x8c, 0xc9, 0x16, 0xbe, 0xc4, 0xcc, 0x32,
1924	0x37, 0x68, 0x73, 0x5f, 0x08, 0xfb, 0x51, 0xf7, 0x49, 0x53, 0x36, 0x05,
1925	0x0a, 0x95, 0x02, 0x4c, 0xf2, 0x79, 0x1a, 0x10, 0xf6, 0xd8, 0x3a, 0x75,
1926	0x9c, 0xf3, 0x1d, 0xf1, 0xa2, 0x0d, 0x70, 0x67, 0x86, 0x1b, 0xb3, 0x16,
1927	0xf5, 0x2f, 0xe5, 0xa4, 0xeb, 0x79, 0x86, 0xf9, 0x3d, 0x0b, 0xc2, 0x73,
1928	0x0b, 0xa5, 0x99, 0xac, 0x6f, 0xfc, 0x67, 0xb8, 0xe5, 0x2f, 0x0b, 0xa6,
1929	0x18, 0x24, 0x8d, 0x7b, 0xd1, 0x48, 0x35, 0x29, 0x18, 0x40, 0xac, 0x93,
1930	0x60, 0xe1, 0x96, 0x86, 0x50, 0xb4, 0x7a, 0x59, 0xd8, 0x8f, 0x21, 0x0b,
1931	0x9f, 0xcf, 0x82, 0x91, 0xc6, 0x3b, 0xbf, 0x6b, 0xdc, 0x07, 0x91, 0xb9,
1932	0x97, 0x56, 0x23, 0xaa, 0xb6, 0x6c, 0x94, 0xc6, 0x48, 0x06, 0x3c, 0xe4,
1933	0xce, 0x4e, 0xaa, 0xe4, 0xf6, 0x2f, 0x09, 0xdc, 0x53, 0x6f, 0x2e, 0xfc,
1934	0x74, 0xeb, 0x3a, 0x63, 0x99, 0xc2, 0xa6, 0xac, 0x89, 0xbc, 0xa7, 0xb2,
1935	0x44, 0xa0, 0x0d, 0x8a, 0x10, 0xe3, 0x6c, 0xf2, 0x24, 0xcb, 0xfa, 0x9b,
1936	0x9f, 0x70, 0x47, 0x2e, 0xde, 0x14, 0x8b, 0xd4, 0xb2, 0x20, 0x09, 0x96,
1937	0xa2, 0x64, 0xf1, 0x24, 0x1c, 0xdc, 0xa1, 0x35, 0x9c, 0x15, 0xb2, 0xd4,
1938	0xbc, 0x55, 0x2e, 0x7d, 0x06, 0xf5, 0x9c, 0x0e, 0x55, 0xf4, 0x5a, 0xd6,
1939	0x93, 0xda, 0x76, 0xad, 0x25, 0x73, 0x4c, 0xc5, 0x43,
1940}
1941