aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--telegram/commands.go52
-rw-r--r--telegram/connect.go40
-rw-r--r--xmpp/extensions/extensions.go36
-rw-r--r--xmpp/gateway/gateway.go6
-rw-r--r--xmpp/handlers.go204
5 files changed, 303 insertions, 35 deletions
diff --git a/telegram/commands.go b/telegram/commands.go
index 0c83945..b4920d4 100644
--- a/telegram/commands.go
+++ b/telegram/commands.go
@@ -15,7 +15,8 @@ import (
)
const notEnoughArguments string = "Not enough arguments"
-const telegramNotInitialized string = "Telegram connection is not initialized yet"
+const TelegramNotInitialized string = "Telegram connection is not initialized yet"
+const TelegramAuthDone string = "Authorization is done already"
const notOnline string = "Not online"
var permissionsAdmin = client.ChatAdministratorRights{
@@ -244,40 +245,29 @@ func (c *Client) ProcessTransportCommand(cmdline string, resource string) string
}
if cmd == "login" {
- wasSessionLoginEmpty := c.Session.Login == ""
- c.Session.Login = args[0]
-
- if wasSessionLoginEmpty && c.authorizer == nil {
- go func() {
- err := c.Connect(resource)
- if err != nil {
- log.Error(errors.Wrap(err, "TDlib connection failure"))
- }
- }()
- // a quirk for authorizer to become ready. If it's still not,
- // nothing bad: the command just needs to be resent again
- time.Sleep(1e5)
+ err := c.TryLogin(resource, args[0])
+ if err != nil {
+ return err.Error()
}
- }
- if c.authorizer == nil {
- return telegramNotInitialized
- }
+ c.authorizer.PhoneNumber <- args[0]
+ } else {
+ if c.authorizer == nil {
+ return TelegramNotInitialized
+ }
- if c.authorizer.isClosed {
- return "Authorization is done already"
- }
+ if c.authorizer.isClosed {
+ return TelegramAuthDone
+ }
- switch cmd {
- // sign in
- case "login":
- c.authorizer.PhoneNumber <- args[0]
- // check auth code
- case "code":
- c.authorizer.Code <- args[0]
- // check auth password
- case "password":
- c.authorizer.Password <- args[0]
+ switch cmd {
+ // check auth code
+ case "code":
+ c.authorizer.Code <- args[0]
+ // check auth password
+ case "password":
+ c.authorizer.Password <- args[0]
+ }
}
// sign out
case "logout":
diff --git a/telegram/connect.go b/telegram/connect.go
index ef03428..ab9c19c 100644
--- a/telegram/connect.go
+++ b/telegram/connect.go
@@ -3,6 +3,7 @@ package telegram
import (
"github.com/pkg/errors"
"strconv"
+ "time"
"dev.narayana.im/narayana/telegabber/xmpp/gateway"
@@ -154,14 +155,49 @@ func (c *Client) Connect(resource string) error {
log.Errorf("Could not retrieve chats: %v", err)
}
- gateway.SendPresence(c.xmpp, c.jid, gateway.SPType("subscribe"))
- gateway.SendPresence(c.xmpp, c.jid, gateway.SPType("subscribed"))
+ gateway.SubscribeToTransport(c.xmpp, c.jid)
gateway.SendPresence(c.xmpp, c.jid, gateway.SPStatus("Logged in as: "+c.Session.Login))
}()
return nil
}
+func (c *Client) TryLogin(resource string, login string) error {
+ wasSessionLoginEmpty := c.Session.Login == ""
+ c.Session.Login = login
+
+ if wasSessionLoginEmpty && c.authorizer == nil {
+ go func() {
+ err := c.Connect(resource)
+ if err != nil {
+ log.Error(errors.Wrap(err, "TDlib connection failure"))
+ }
+ }()
+ // a quirk for authorizer to become ready. If it's still not,
+ // nothing bad: just re-login again
+ time.Sleep(1e5)
+ }
+
+ if c.authorizer == nil {
+ return errors.New(TelegramNotInitialized)
+ }
+
+ if c.authorizer.isClosed {
+ return errors.New(TelegramAuthDone)
+ }
+
+ return nil
+}
+
+func (c *Client) SetPhoneNumber(login string) error {
+ if c.authorizer == nil || c.authorizer.isClosed {
+ return errors.New("Authorization not needed")
+ }
+
+ c.authorizer.PhoneNumber <- login
+ return nil
+}
+
// Disconnect drops TDlib connection and
// returns the flag indicating if disconnecting is permitted
func (c *Client) Disconnect(resource string, quit bool) bool {
diff --git a/xmpp/extensions/extensions.go b/xmpp/extensions/extensions.go
index 192b630..8e2f743 100644
--- a/xmpp/extensions/extensions.go
+++ b/xmpp/extensions/extensions.go
@@ -193,6 +193,26 @@ type Replace struct {
Id string `xml:"id,attr"`
}
+// QueryRegister is from XEP-0077
+type QueryRegister struct {
+ XMLName xml.Name `xml:"jabber:iq:register query"`
+ Instructions string `xml:"instructions"`
+ Username string `xml:"username"`
+ Registered *QueryRegisterRegistered `xml:"registered"`
+ Remove *QueryRegisterRemove `xml:"remove"`
+ ResultSet *stanza.ResultSet `xml:"set,omitempty"`
+}
+
+// QueryRegisterRegistered is a child element from XEP-0077
+type QueryRegisterRegistered struct {
+ XMLName xml.Name `xml:"registered"`
+}
+
+// QueryRegisterRemove is a child element from XEP-0077
+type QueryRegisterRemove struct {
+ XMLName xml.Name `xml:"remove"`
+}
+
// Namespace is a namespace!
func (c PresenceNickExtension) Namespace() string {
return c.XMLName.Space
@@ -248,6 +268,16 @@ func (c Replace) Namespace() string {
return c.XMLName.Space
}
+// Namespace is a namespace!
+func (c QueryRegister) Namespace() string {
+ return c.XMLName.Space
+}
+
+// GetSet getsets!
+func (c QueryRegister) GetSet() *stanza.ResultSet {
+ return c.ResultSet
+}
+
// Name is a packet name
func (ClientMessage) Name() string {
return "message"
@@ -326,4 +356,10 @@ func init() {
"urn:xmpp:message-correct:0",
"replace",
}, Replace{})
+
+ // register query
+ stanza.TypeRegistry.MapExtension(stanza.PKTIQ, xml.Name{
+ "jabber:iq:register",
+ "query",
+ }, QueryRegister{})
}
diff --git a/xmpp/gateway/gateway.go b/xmpp/gateway/gateway.go
index 7a2500e..dfe2ebf 100644
--- a/xmpp/gateway/gateway.go
+++ b/xmpp/gateway/gateway.go
@@ -360,6 +360,12 @@ func ResumableSend(component *xmpp.Component, packet stanza.Packet) error {
return err
}
+// SubscribeToTransport ensures a two-way subscription to the transport
+func SubscribeToTransport(component *xmpp.Component, jid string) {
+ SendPresence(component, jid, SPType("subscribe"))
+ SendPresence(component, jid, SPType("subscribed"))
+}
+
// SplitJID tokenizes a JID string to bare JID and resource
func SplitJID(from string) (string, string, bool) {
fromJid, err := stanza.NewJid(from)
diff --git a/xmpp/handlers.go b/xmpp/handlers.go
index 36f9cf9..fdcf647 100644
--- a/xmpp/handlers.go
+++ b/xmpp/handlers.go
@@ -4,6 +4,7 @@ import (
"bytes"
"encoding/base64"
"encoding/xml"
+ "fmt"
"github.com/pkg/errors"
"io"
"strconv"
@@ -57,6 +58,22 @@ func HandleIq(s xmpp.Sender, p stanza.Packet) {
go handleGetDiscoInfo(s, iq)
return
}
+ _, ok = iq.Payload.(*stanza.DiscoItems)
+ if ok {
+ go handleGetDiscoItems(s, iq)
+ return
+ }
+ _, ok = iq.Payload.(*extensions.QueryRegister)
+ if ok {
+ go handleGetQueryRegister(s, iq)
+ return
+ }
+ } else if iq.Type == "set" {
+ query, ok := iq.Payload.(*extensions.QueryRegister)
+ if ok {
+ go handleSetQueryRegister(s, iq, query)
+ return
+ }
}
}
@@ -91,8 +108,7 @@ func HandleMessage(s xmpp.Sender, p stanza.Packet) {
session, ok := sessions[bare]
if !ok {
if msg.To == gatewayJid {
- gateway.SendPresence(component, msg.From, gateway.SPType("subscribe"))
- gateway.SendPresence(component, msg.From, gateway.SPType("subscribed"))
+ gateway.SubscribeToTransport(component, msg.From)
} else {
log.Error("Message from stranger")
}
@@ -444,6 +460,7 @@ func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ) {
disco.AddIdentity("", "account", "registered")
} else {
disco.AddIdentity("Telegram Gateway", "gateway", "telegram")
+ disco.AddFeatures("jabber:iq:register")
}
answer.Payload = disco
@@ -458,6 +475,189 @@ func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ) {
_ = gateway.ResumableSend(component, answer)
}
+func handleGetDiscoItems(s xmpp.Sender, iq *stanza.IQ) {
+ answer, err := stanza.NewIQ(stanza.Attrs{
+ Type: stanza.IQTypeResult,
+ From: iq.To,
+ To: iq.From,
+ Id: iq.Id,
+ Lang: "en",
+ })
+ if err != nil {
+ log.Errorf("Failed to create answer IQ: %v", err)
+ return
+ }
+
+ answer.Payload = answer.DiscoItems()
+
+ component, ok := s.(*xmpp.Component)
+ if !ok {
+ log.Error("Not a component")
+ return
+ }
+
+ _ = gateway.ResumableSend(component, answer)
+}
+
+func handleGetQueryRegister(s xmpp.Sender, iq *stanza.IQ) {
+ component, ok := s.(*xmpp.Component)
+ if !ok {
+ log.Error("Not a component")
+ return
+ }
+
+ answer, err := stanza.NewIQ(stanza.Attrs{
+ Type: stanza.IQTypeResult,
+ From: iq.To,
+ To: iq.From,
+ Id: iq.Id,
+ Lang: "en",
+ })
+ if err != nil {
+ log.Errorf("Failed to create answer IQ: %v", err)
+ return
+ }
+
+ var login string
+ bare, _, ok := gateway.SplitJID(iq.From)
+ if ok {
+ session, ok := sessions[bare]
+ if ok {
+ login = session.Session.Login
+ }
+ }
+
+ var query stanza.IQPayload
+ if login == "" {
+ query = extensions.QueryRegister{
+ Instructions: fmt.Sprintf("Authorization in Telegram is a multi-step process, so please accept %v to your contacts and follow further instructions (provide the authentication code there, etc.).\nFor now, please provide your login.", iq.To),
+ }
+ } else {
+ query = extensions.QueryRegister{
+ Instructions: "Already logged in",
+ Username: login,
+ Registered: &extensions.QueryRegisterRegistered{},
+ }
+ }
+ answer.Payload = query
+
+ log.Debugf("%#v", query)
+
+ _ = gateway.ResumableSend(component, answer)
+
+ if login == "" {
+ gateway.SubscribeToTransport(component, iq.From)
+ }
+}
+
+func handleSetQueryRegister(s xmpp.Sender, iq *stanza.IQ, query *extensions.QueryRegister) {
+ component, ok := s.(*xmpp.Component)
+ if !ok {
+ log.Error("Not a component")
+ return
+ }
+
+ answer, err := stanza.NewIQ(stanza.Attrs{
+ Type: stanza.IQTypeResult,
+ From: iq.To,
+ To: iq.From,
+ Id: iq.Id,
+ Lang: "en",
+ })
+ if err != nil {
+ log.Errorf("Failed to create answer IQ: %v", err)
+ return
+ }
+
+ defer gateway.ResumableSend(component, answer)
+
+ if query.Remove != nil {
+ iqAnswerSetError(answer, query, 405)
+ return
+ }
+
+ var login string
+ var session *telegram.Client
+ bare, resource, ok := gateway.SplitJID(iq.From)
+ if ok {
+ session, ok = sessions[bare]
+ if ok {
+ login = session.Session.Login
+ }
+ }
+
+ if login == "" {
+ if !ok {
+ session, ok = getTelegramInstance(bare, &persistence.Session{}, component)
+ if !ok {
+ iqAnswerSetError(answer, query, 500)
+ return
+ }
+ }
+
+ err := session.TryLogin(resource, query.Username)
+ if err != nil {
+ if err.Error() == telegram.TelegramAuthDone {
+ iqAnswerSetError(answer, query, 406)
+ } else {
+ iqAnswerSetError(answer, query, 500)
+ }
+ return
+ }
+
+ err = session.SetPhoneNumber(query.Username)
+ if err != nil {
+ iqAnswerSetError(answer, query, 500)
+ return
+ }
+
+ // everything okay, the response should be empty with no payload/error at this point
+ gateway.SubscribeToTransport(component, iq.From)
+ } else {
+ iqAnswerSetError(answer, query, 406)
+ }
+}
+
+func iqAnswerSetError(answer *stanza.IQ, payload *extensions.QueryRegister, code int) {
+ answer.Type = stanza.IQTypeError
+ answer.Payload = *payload
+ switch code {
+ case 400:
+ answer.Error = &stanza.Err{
+ Code: code,
+ Type: stanza.ErrorTypeModify,
+ Reason: "bad-request",
+ }
+ case 405:
+ answer.Error = &stanza.Err{
+ Code: code,
+ Type: stanza.ErrorTypeCancel,
+ Reason: "not-allowed",
+ Text: "Logging out is dangerous. If you are sure you would be able to receive the authentication code again, issue the /logout command to the transport",
+ }
+ case 406:
+ answer.Error = &stanza.Err{
+ Code: code,
+ Type: stanza.ErrorTypeModify,
+ Reason: "not-acceptable",
+ Text: "Phone number already provided, chat with the transport for further instruction",
+ }
+ case 500:
+ answer.Error = &stanza.Err{
+ Code: code,
+ Type: stanza.ErrorTypeWait,
+ Reason: "internal-server-error",
+ }
+ default:
+ log.Error("Unknown error code, falling back with empty reason")
+ answer.Error = &stanza.Err{
+ Code: code,
+ Type: stanza.ErrorTypeCancel,
+ Reason: "undefined-condition",
+ }
+ }
+}
+
func toToID(to string) (int64, bool) {
toParts := strings.Split(to, "@")
if len(toParts) < 2 {