gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

auth.go (6774B)


      1 package dbus
      2 
      3 import (
      4 	"bufio"
      5 	"bytes"
      6 	"errors"
      7 	"io"
      8 	"os"
      9 	"strconv"
     10 )
     11 
     12 // AuthStatus represents the Status of an authentication mechanism.
     13 type AuthStatus byte
     14 
     15 const (
     16 	// AuthOk signals that authentication is finished; the next command
     17 	// from the server should be an OK.
     18 	AuthOk AuthStatus = iota
     19 
     20 	// AuthContinue signals that additional data is needed; the next command
     21 	// from the server should be a DATA.
     22 	AuthContinue
     23 
     24 	// AuthError signals an error; the server sent invalid data or some
     25 	// other unexpected thing happened and the current authentication
     26 	// process should be aborted.
     27 	AuthError
     28 )
     29 
     30 type authState byte
     31 
     32 const (
     33 	waitingForData authState = iota
     34 	waitingForOk
     35 	waitingForReject
     36 )
     37 
     38 // Auth defines the behaviour of an authentication mechanism.
     39 type Auth interface {
     40 	// Return the name of the mechanism, the argument to the first AUTH command
     41 	// and the next status.
     42 	FirstData() (name, resp []byte, status AuthStatus)
     43 
     44 	// Process the given DATA command, and return the argument to the DATA
     45 	// command and the next status. If len(resp) == 0, no DATA command is sent.
     46 	HandleData(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.
     54 func (conn *Conn) Auth(methods []Auth) error {
     55 	if methods == nil {
     56 		uid := strconv.Itoa(os.Getuid())
     57 		methods = []Auth{AuthExternal(uid), AuthCookieSha1(uid, getHomeDir())}
     58 	}
     59 	in := bufio.NewReader(conn.transport)
     60 	err := conn.transport.SendNullByte()
     61 	if err != nil {
     62 		return err
     63 	}
     64 	err = authWriteLine(conn.transport, []byte("AUTH"))
     65 	if err != nil {
     66 		return err
     67 	}
     68 	s, err := authReadLine(in)
     69 	if err != nil {
     70 		return err
     71 	}
     72 	if len(s) < 2 || !bytes.Equal(s[0], []byte("REJECTED")) {
     73 		return errors.New("dbus: authentication protocol error")
     74 	}
     75 	s = s[1:]
     76 	for _, v := range s {
     77 		for _, m := range methods {
     78 			if name, data, status := m.FirstData(); bytes.Equal(v, name) {
     79 				var ok bool
     80 				err = authWriteLine(conn.transport, []byte("AUTH"), v, data)
     81 				if err != nil {
     82 					return err
     83 				}
     84 				switch status {
     85 				case AuthOk:
     86 					err, ok = conn.tryAuth(m, waitingForOk, in)
     87 				case AuthContinue:
     88 					err, ok = conn.tryAuth(m, waitingForData, in)
     89 				default:
     90 					panic("dbus: invalid authentication status")
     91 				}
     92 				if err != nil {
     93 					return err
     94 				}
     95 				if ok {
     96 					if conn.transport.SupportsUnixFDs() {
     97 						err = authWriteLine(conn, []byte("NEGOTIATE_UNIX_FD"))
     98 						if err != nil {
     99 							return err
    100 						}
    101 						line, err := authReadLine(in)
    102 						if err != nil {
    103 							return err
    104 						}
    105 						switch {
    106 						case bytes.Equal(line[0], []byte("AGREE_UNIX_FD")):
    107 							conn.EnableUnixFDs()
    108 							conn.unixFD = true
    109 						case bytes.Equal(line[0], []byte("ERROR")):
    110 						default:
    111 							return errors.New("dbus: authentication protocol error")
    112 						}
    113 					}
    114 					err = authWriteLine(conn.transport, []byte("BEGIN"))
    115 					if err != nil {
    116 						return err
    117 					}
    118 					go conn.inWorker()
    119 					return nil
    120 				}
    121 			}
    122 		}
    123 	}
    124 	return 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.
    131 func (conn *Conn) tryAuth(m Auth, state authState, in *bufio.Reader) (error, bool) {
    132 	for {
    133 		s, err := authReadLine(in)
    134 		if err != nil {
    135 			return err, false
    136 		}
    137 		switch {
    138 		case state == waitingForData && string(s[0]) == "DATA":
    139 			if len(s) != 2 {
    140 				err = authWriteLine(conn.transport, []byte("ERROR"))
    141 				if err != nil {
    142 					return err, false
    143 				}
    144 				continue
    145 			}
    146 			data, status := m.HandleData(s[1])
    147 			switch status {
    148 			case AuthOk, AuthContinue:
    149 				if len(data) != 0 {
    150 					err = authWriteLine(conn.transport, []byte("DATA"), data)
    151 					if err != nil {
    152 						return err, false
    153 					}
    154 				}
    155 				if status == AuthOk {
    156 					state = waitingForOk
    157 				}
    158 			case AuthError:
    159 				err = authWriteLine(conn.transport, []byte("ERROR"))
    160 				if err != nil {
    161 					return err, false
    162 				}
    163 			}
    164 		case state == waitingForData && string(s[0]) == "REJECTED":
    165 			return nil, false
    166 		case state == waitingForData && string(s[0]) == "ERROR":
    167 			err = authWriteLine(conn.transport, []byte("CANCEL"))
    168 			if err != nil {
    169 				return err, false
    170 			}
    171 			state = waitingForReject
    172 		case state == waitingForData && string(s[0]) == "OK":
    173 			if len(s) != 2 {
    174 				err = authWriteLine(conn.transport, []byte("CANCEL"))
    175 				if err != nil {
    176 					return err, false
    177 				}
    178 				state = waitingForReject
    179 			}
    180 			conn.uuid = string(s[1])
    181 			return nil, true
    182 		case state == waitingForData:
    183 			err = authWriteLine(conn.transport, []byte("ERROR"))
    184 			if err != nil {
    185 				return err, false
    186 			}
    187 		case state == waitingForOk && string(s[0]) == "OK":
    188 			if len(s) != 2 {
    189 				err = authWriteLine(conn.transport, []byte("CANCEL"))
    190 				if err != nil {
    191 					return err, false
    192 				}
    193 				state = waitingForReject
    194 			}
    195 			conn.uuid = string(s[1])
    196 			return nil, true
    197 		case state == waitingForOk && string(s[0]) == "REJECTED":
    198 			return nil, false
    199 		case state == waitingForOk && (string(s[0]) == "DATA" ||
    200 			string(s[0]) == "ERROR"):
    201 
    202 			err = authWriteLine(conn.transport, []byte("CANCEL"))
    203 			if err != nil {
    204 				return err, false
    205 			}
    206 			state = waitingForReject
    207 		case state == waitingForOk:
    208 			err = authWriteLine(conn.transport, []byte("ERROR"))
    209 			if err != nil {
    210 				return err, false
    211 			}
    212 		case state == waitingForReject && string(s[0]) == "REJECTED":
    213 			return nil, false
    214 		case state == waitingForReject:
    215 			return errors.New("dbus: authentication protocol error"), false
    216 		default:
    217 			panic("dbus: invalid auth state")
    218 		}
    219 	}
    220 }
    221 
    222 // authReadLine reads a line and separates it into its fields.
    223 func authReadLine(in *bufio.Reader) ([][]byte, error) {
    224 	data, err := in.ReadBytes('\n')
    225 	if err != nil {
    226 		return nil, err
    227 	}
    228 	data = bytes.TrimSuffix(data, []byte("\r\n"))
    229 	return bytes.Split(data, []byte{' '}), nil
    230 }
    231 
    232 // authWriteLine writes the given line in the authentication protocol format
    233 // (elements of data separated by a " " and terminated by "\r\n").
    234 func authWriteLine(out io.Writer, data ...[]byte) error {
    235 	buf := make([]byte, 0)
    236 	for i, v := range data {
    237 		buf = append(buf, v...)
    238 		if i != len(data)-1 {
    239 			buf = append(buf, ' ')
    240 		}
    241 	}
    242 	buf = append(buf, '\r')
    243 	buf = append(buf, '\n')
    244 	n, err := out.Write(buf)
    245 	if err != nil {
    246 		return err
    247 	}
    248 	if n != len(buf) {
    249 		return io.ErrUnexpectedEOF
    250 	}
    251 	return nil
    252 }