go-tg-screenshot-bot
257 строк · 6.8 Кб
1package dbus2
3import (4"bufio"5"bytes"6"errors"7"io"8"os"9"strconv"10)
11
12// AuthStatus represents the Status of an authentication mechanism.
13type AuthStatus byte14
15const (16// AuthOk signals that authentication is finished; the next command17// from the server should be an OK.18AuthOk AuthStatus = iota19
20// AuthContinue signals that additional data is needed; the next command21// from the server should be a DATA.22AuthContinue
23
24// AuthError signals an error; the server sent invalid data or some25// other unexpected thing happened and the current authentication26// process should be aborted.27AuthError
28)
29
30type authState byte31
32const (33waitingForData authState = iota34waitingForOk
35waitingForReject
36)
37
38// Auth defines the behaviour of an authentication mechanism.
39type Auth interface {40// Return the name of the mechanism, the argument to the first AUTH command41// and the next status.42FirstData() (name, resp []byte, status AuthStatus)43
44// Process the given DATA command, and return the argument to the DATA45// command and the next status. If len(resp) == 0, no DATA command is sent.46HandleData(data []byte) (resp []byte, status AuthStatus)47}
48
49// Auth authenticates the connection, trying the given list of authentication
50// mechanisms (in that order). If nil is passed, the EXTERNAL and
51// DBUS_COOKIE_SHA1 mechanisms are tried for the current user. For private
52// connections, this method must be called before sending any messages to the
53// bus. Auth must not be called on shared connections.
54func (conn *Conn) Auth(methods []Auth) error {55if methods == nil {56uid := strconv.Itoa(os.Geteuid())57methods = []Auth{AuthExternal(uid), AuthCookieSha1(uid, getHomeDir())}58}59in := bufio.NewReader(conn.transport)60err := conn.transport.SendNullByte()61if err != nil {62return err63}64err = authWriteLine(conn.transport, []byte("AUTH"))65if err != nil {66return err67}68s, err := authReadLine(in)69if err != nil {70return err71}72if len(s) < 2 || !bytes.Equal(s[0], []byte("REJECTED")) {73return errors.New("dbus: authentication protocol error")74}75s = s[1:]76for _, v := range s {77for _, m := range methods {78if name, _, status := m.FirstData(); bytes.Equal(v, name) {79var ok bool80err = authWriteLine(conn.transport, []byte("AUTH"), v)81if err != nil {82return err83}84switch status {85case AuthOk:86err, ok = conn.tryAuth(m, waitingForOk, in)87case AuthContinue:88err, ok = conn.tryAuth(m, waitingForData, in)89default:90panic("dbus: invalid authentication status")91}92if err != nil {93return err94}95if ok {96if conn.transport.SupportsUnixFDs() {97err = authWriteLine(conn, []byte("NEGOTIATE_UNIX_FD"))98if err != nil {99return err100}101line, err := authReadLine(in)102if err != nil {103return err104}105switch {106case bytes.Equal(line[0], []byte("AGREE_UNIX_FD")):107conn.EnableUnixFDs()108conn.unixFD = true109case bytes.Equal(line[0], []byte("ERROR")):110default:111return errors.New("dbus: authentication protocol error")112}113}114err = authWriteLine(conn.transport, []byte("BEGIN"))115if err != nil {116return err117}118go conn.inWorker()119return nil120}121}122}123}124return errors.New("dbus: authentication failed")125}
126
127// tryAuth tries to authenticate with m as the mechanism, using state as the
128// initial authState and in for reading input. It returns (nil, true) on
129// success, (nil, false) on a REJECTED and (someErr, false) if some other
130// error occurred.
131func (conn *Conn) tryAuth(m Auth, state authState, in *bufio.Reader) (error, bool) {132for {133s, err := authReadLine(in)134if err != nil {135return err, false136}137switch {138case state == waitingForData && string(s[0]) == "DATA":139if len(s) != 2 {140err = authWriteLine(conn.transport, []byte("ERROR"))141if err != nil {142return err, false143}144continue145}146data, status := m.HandleData(s[1])147switch status {148case AuthOk, AuthContinue:149if len(data) != 0 {150err = authWriteLine(conn.transport, []byte("DATA"), data)151if err != nil {152return err, false153}154}155if status == AuthOk {156state = waitingForOk157}158case AuthError:159err = authWriteLine(conn.transport, []byte("ERROR"))160if err != nil {161return err, false162}163}164case state == waitingForData && string(s[0]) == "REJECTED":165return nil, false166case state == waitingForData && string(s[0]) == "ERROR":167err = authWriteLine(conn.transport, []byte("CANCEL"))168if err != nil {169return err, false170}171state = waitingForReject172case state == waitingForData && string(s[0]) == "OK":173if len(s) != 2 {174err = authWriteLine(conn.transport, []byte("CANCEL"))175if err != nil {176return err, false177}178state = waitingForReject179} else {180conn.uuid = string(s[1])181return nil, true182}183case state == waitingForData:184err = authWriteLine(conn.transport, []byte("ERROR"))185if err != nil {186return err, false187}188case state == waitingForOk && string(s[0]) == "OK":189if len(s) != 2 {190err = authWriteLine(conn.transport, []byte("CANCEL"))191if err != nil {192return err, false193}194state = waitingForReject195} else {196conn.uuid = string(s[1])197return nil, true198}199case state == waitingForOk && string(s[0]) == "DATA":200err = authWriteLine(conn.transport, []byte("DATA"))201if err != nil {202return err, false203}204case state == waitingForOk && string(s[0]) == "REJECTED":205return nil, false206case state == waitingForOk && string(s[0]) == "ERROR":207err = authWriteLine(conn.transport, []byte("CANCEL"))208if err != nil {209return err, false210}211state = waitingForReject212case state == waitingForOk:213err = authWriteLine(conn.transport, []byte("ERROR"))214if err != nil {215return err, false216}217case state == waitingForReject && string(s[0]) == "REJECTED":218return nil, false219case state == waitingForReject:220return errors.New("dbus: authentication protocol error"), false221default:222panic("dbus: invalid auth state")223}224}225}
226
227// authReadLine reads a line and separates it into its fields.
228func authReadLine(in *bufio.Reader) ([][]byte, error) {229data, err := in.ReadBytes('\n')230if err != nil {231return nil, err232}233data = bytes.TrimSuffix(data, []byte("\r\n"))234return bytes.Split(data, []byte{' '}), nil235}
236
237// authWriteLine writes the given line in the authentication protocol format
238// (elements of data separated by a " " and terminated by "\r\n").
239func authWriteLine(out io.Writer, data ...[]byte) error {240buf := make([]byte, 0)241for i, v := range data {242buf = append(buf, v...)243if i != len(data)-1 {244buf = append(buf, ' ')245}246}247buf = append(buf, '\r')248buf = append(buf, '\n')249n, err := out.Write(buf)250if err != nil {251return err252}253if n != len(buf) {254return io.ErrUnexpectedEOF255}256return nil257}
258