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 }