~ubuntu-branches/ubuntu/vivid/juju-core/vivid-updates

« back to all changes in this revision

Viewing changes to src/github.com/godbus/dbus/auth.go

  • Committer: Package Import Robot
  • Author(s): Curtis C. Hovey
  • Date: 2015-09-29 19:43:29 UTC
  • mfrom: (47.1.4 wily-proposed)
  • Revision ID: package-import@ubuntu.com-20150929194329-9y496tbic30hc7vp
Tags: 1.24.6-0ubuntu1~15.04.1
Backport of 1.24.6 from wily. (LP: #1500916, #1497087)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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 mechnism, 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"), []byte(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
                                        go conn.outWorker()
 
120
                                        return nil
 
121
                                }
 
122
                        }
 
123
                }
 
124
        }
 
125
        return errors.New("dbus: authentication failed")
 
126
}
 
127
 
 
128
// tryAuth tries to authenticate with m as the mechanism, using state as the
 
129
// initial authState and in for reading input. It returns (nil, true) on
 
130
// success, (nil, false) on a REJECTED and (someErr, false) if some other
 
131
// error occured.
 
132
func (conn *Conn) tryAuth(m Auth, state authState, in *bufio.Reader) (error, bool) {
 
133
        for {
 
134
                s, err := authReadLine(in)
 
135
                if err != nil {
 
136
                        return err, false
 
137
                }
 
138
                switch {
 
139
                case state == waitingForData && string(s[0]) == "DATA":
 
140
                        if len(s) != 2 {
 
141
                                err = authWriteLine(conn.transport, []byte("ERROR"))
 
142
                                if err != nil {
 
143
                                        return err, false
 
144
                                }
 
145
                                continue
 
146
                        }
 
147
                        data, status := m.HandleData(s[1])
 
148
                        switch status {
 
149
                        case AuthOk, AuthContinue:
 
150
                                if len(data) != 0 {
 
151
                                        err = authWriteLine(conn.transport, []byte("DATA"), data)
 
152
                                        if err != nil {
 
153
                                                return err, false
 
154
                                        }
 
155
                                }
 
156
                                if status == AuthOk {
 
157
                                        state = waitingForOk
 
158
                                }
 
159
                        case AuthError:
 
160
                                err = authWriteLine(conn.transport, []byte("ERROR"))
 
161
                                if err != nil {
 
162
                                        return err, false
 
163
                                }
 
164
                        }
 
165
                case state == waitingForData && string(s[0]) == "REJECTED":
 
166
                        return nil, false
 
167
                case state == waitingForData && string(s[0]) == "ERROR":
 
168
                        err = authWriteLine(conn.transport, []byte("CANCEL"))
 
169
                        if err != nil {
 
170
                                return err, false
 
171
                        }
 
172
                        state = waitingForReject
 
173
                case state == waitingForData && string(s[0]) == "OK":
 
174
                        if len(s) != 2 {
 
175
                                err = authWriteLine(conn.transport, []byte("CANCEL"))
 
176
                                if err != nil {
 
177
                                        return err, false
 
178
                                }
 
179
                                state = waitingForReject
 
180
                        }
 
181
                        conn.uuid = string(s[1])
 
182
                        return nil, true
 
183
                case state == waitingForData:
 
184
                        err = authWriteLine(conn.transport, []byte("ERROR"))
 
185
                        if err != nil {
 
186
                                return err, false
 
187
                        }
 
188
                case state == waitingForOk && string(s[0]) == "OK":
 
189
                        if len(s) != 2 {
 
190
                                err = authWriteLine(conn.transport, []byte("CANCEL"))
 
191
                                if err != nil {
 
192
                                        return err, false
 
193
                                }
 
194
                                state = waitingForReject
 
195
                        }
 
196
                        conn.uuid = string(s[1])
 
197
                        return nil, true
 
198
                case state == waitingForOk && string(s[0]) == "REJECTED":
 
199
                        return nil, false
 
200
                case state == waitingForOk && (string(s[0]) == "DATA" ||
 
201
                        string(s[0]) == "ERROR"):
 
202
 
 
203
                        err = authWriteLine(conn.transport, []byte("CANCEL"))
 
204
                        if err != nil {
 
205
                                return err, false
 
206
                        }
 
207
                        state = waitingForReject
 
208
                case state == waitingForOk:
 
209
                        err = authWriteLine(conn.transport, []byte("ERROR"))
 
210
                        if err != nil {
 
211
                                return err, false
 
212
                        }
 
213
                case state == waitingForReject && string(s[0]) == "REJECTED":
 
214
                        return nil, false
 
215
                case state == waitingForReject:
 
216
                        return errors.New("dbus: authentication protocol error"), false
 
217
                default:
 
218
                        panic("dbus: invalid auth state")
 
219
                }
 
220
        }
 
221
}
 
222
 
 
223
// authReadLine reads a line and separates it into its fields.
 
224
func authReadLine(in *bufio.Reader) ([][]byte, error) {
 
225
        data, err := in.ReadBytes('\n')
 
226
        if err != nil {
 
227
                return nil, err
 
228
        }
 
229
        data = bytes.TrimSuffix(data, []byte("\r\n"))
 
230
        return bytes.Split(data, []byte{' '}), nil
 
231
}
 
232
 
 
233
// authWriteLine writes the given line in the authentication protocol format
 
234
// (elements of data separated by a " " and terminated by "\r\n").
 
235
func authWriteLine(out io.Writer, data ...[]byte) error {
 
236
        buf := make([]byte, 0)
 
237
        for i, v := range data {
 
238
                buf = append(buf, v...)
 
239
                if i != len(data)-1 {
 
240
                        buf = append(buf, ' ')
 
241
                }
 
242
        }
 
243
        buf = append(buf, '\r')
 
244
        buf = append(buf, '\n')
 
245
        n, err := out.Write(buf)
 
246
        if err != nil {
 
247
                return err
 
248
        }
 
249
        if n != len(buf) {
 
250
                return io.ErrUnexpectedEOF
 
251
        }
 
252
        return nil
 
253
}