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