forked from godbus/dbus
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
auth: Optionally not send UID with external auth
Due to mismatch between UID in a user-namespace and out-of-band credential acquired by server on another user-namespace refrain from sending UID with external authentication by default to keep compatibility still fallback to sending UID if it fails godbus#345
- Loading branch information
😎Mostafa Emami
committed
Jan 2, 2023
1 parent
a852926
commit e026873
Showing
3 changed files
with
315 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,258 @@ | ||
package dbus | ||
|
||
import ( | ||
"bufio" | ||
"bytes" | ||
"errors" | ||
"io" | ||
"os" | ||
"strconv" | ||
) | ||
|
||
// AuthStatus represents the Status of an authentication mechanism. | ||
type AuthStatus byte | ||
|
||
const ( | ||
// AuthOk signals that authentication is finished; the next command | ||
// from the server should be an OK. | ||
AuthOk AuthStatus = iota | ||
|
||
// AuthContinue signals that additional data is needed; the next command | ||
// from the server should be a DATA. | ||
AuthContinue | ||
|
||
// AuthError signals an error; the server sent invalid data or some | ||
// other unexpected thing happened and the current authentication | ||
// process should be aborted. | ||
AuthError | ||
) | ||
|
||
type authState byte | ||
|
||
const ( | ||
waitingForData authState = iota | ||
waitingForOk | ||
waitingForReject | ||
) | ||
|
||
// Auth defines the behaviour of an authentication mechanism. | ||
type Auth interface { | ||
// Return the name of the mechanism, the argument to the first AUTH command | ||
// and the next status. | ||
FirstData() (name, resp []byte, status AuthStatus) | ||
|
||
// Process the given DATA command, and return the argument to the DATA | ||
// command and the next status. If len(resp) == 0, no DATA command is sent. | ||
HandleData(data []byte) (resp []byte, status AuthStatus) | ||
} | ||
|
||
// Auth authenticates the connection, trying the given list of authentication | ||
// mechanisms (in that order). If nil is passed, the EXTERNAL with empty authorization identity | ||
// (authentication done with what-ever uid server can derive from out-of-band credentials), | ||
// EXTERNAL with uid and DBUS_COOKIE_SHA1 mechanisms are tried for the current user. | ||
// For private connections, this method must be called before sending any messages to the | ||
// bus. Auth must not be called on shared connections. | ||
func (conn *Conn) Auth(methods []Auth) error { | ||
if methods == nil { | ||
uid := strconv.Itoa(os.Geteuid()) | ||
methods = []Auth{AuthExternal(""), AuthExternal(uid), AuthCookieSha1(uid, getHomeDir())} | ||
} | ||
in := bufio.NewReader(conn.transport) | ||
err := conn.transport.SendNullByte() | ||
if err != nil { | ||
return err | ||
} | ||
err = authWriteLine(conn.transport, []byte("AUTH")) | ||
if err != nil { | ||
return err | ||
} | ||
s, err := authReadLine(in) | ||
if err != nil { | ||
return err | ||
} | ||
if len(s) < 2 || !bytes.Equal(s[0], []byte("REJECTED")) { | ||
return errors.New("dbus: authentication protocol error") | ||
} | ||
s = s[1:] | ||
for _, v := range s { | ||
for _, m := range methods { | ||
if name, _, status := m.FirstData(); bytes.Equal(v, name) { | ||
var ok bool | ||
err = authWriteLine(conn.transport, []byte("AUTH"), v) | ||
if err != nil { | ||
return err | ||
} | ||
switch status { | ||
case AuthOk: | ||
ok, err = conn.tryAuth(m, waitingForOk, in) | ||
case AuthContinue: | ||
ok, err = conn.tryAuth(m, waitingForData, in) | ||
default: | ||
panic("dbus: invalid authentication status") | ||
} | ||
if err != nil { | ||
return err | ||
} | ||
if ok { | ||
if conn.transport.SupportsUnixFDs() { | ||
err = authWriteLine(conn, []byte("NEGOTIATE_UNIX_FD")) | ||
if err != nil { | ||
return err | ||
} | ||
line, err := authReadLine(in) | ||
if err != nil { | ||
return err | ||
} | ||
switch { | ||
case bytes.Equal(line[0], []byte("AGREE_UNIX_FD")): | ||
conn.EnableUnixFDs() | ||
conn.unixFD = true | ||
case bytes.Equal(line[0], []byte("ERROR")): | ||
default: | ||
return errors.New("dbus: authentication protocol error") | ||
} | ||
} | ||
err = authWriteLine(conn.transport, []byte("BEGIN")) | ||
if err != nil { | ||
return err | ||
} | ||
go conn.inWorker() | ||
return nil | ||
} | ||
} | ||
} | ||
} | ||
return errors.New("dbus: authentication failed") | ||
} | ||
|
||
// tryAuth tries to authenticate with m as the mechanism, using state as the | ||
// initial authState and in for reading input. It returns (true, nil) on | ||
// success, (false, nil) on a REJECTED and (false, someErr) if some other | ||
// error occurred. | ||
func (conn *Conn) tryAuth(m Auth, state authState, in *bufio.Reader) (bool, error) { | ||
for { | ||
s, err := authReadLine(in) | ||
if err != nil { | ||
return false, err | ||
} | ||
switch { | ||
case state == waitingForData && string(s[0]) == "DATA": | ||
if len(s) != 2 { | ||
err = authWriteLine(conn.transport, []byte("ERROR")) | ||
if err != nil { | ||
return false, err | ||
} | ||
continue | ||
} | ||
data, status := m.HandleData(s[1]) | ||
switch status { | ||
case AuthOk, AuthContinue: | ||
if len(data) != 0 { | ||
err = authWriteLine(conn.transport, []byte("DATA"), data) | ||
if err != nil { | ||
return false, err | ||
} | ||
} | ||
if status == AuthOk { | ||
state = waitingForOk | ||
} | ||
case AuthError: | ||
err = authWriteLine(conn.transport, []byte("ERROR")) | ||
if err != nil { | ||
return false, err | ||
} | ||
} | ||
case state == waitingForData && string(s[0]) == "REJECTED": | ||
return false, nil | ||
case state == waitingForData && string(s[0]) == "ERROR": | ||
err = authWriteLine(conn.transport, []byte("CANCEL")) | ||
if err != nil { | ||
return false, err | ||
} | ||
state = waitingForReject | ||
case state == waitingForData && string(s[0]) == "OK": | ||
if len(s) != 2 { | ||
err = authWriteLine(conn.transport, []byte("CANCEL")) | ||
if err != nil { | ||
return false, err | ||
} | ||
state = waitingForReject | ||
} else { | ||
conn.uuid = string(s[1]) | ||
return true, nil | ||
} | ||
case state == waitingForData: | ||
err = authWriteLine(conn.transport, []byte("ERROR")) | ||
if err != nil { | ||
return false, err | ||
} | ||
case state == waitingForOk && string(s[0]) == "OK": | ||
if len(s) != 2 { | ||
err = authWriteLine(conn.transport, []byte("CANCEL")) | ||
if err != nil { | ||
return false, err | ||
} | ||
state = waitingForReject | ||
} else { | ||
conn.uuid = string(s[1]) | ||
return true, nil | ||
} | ||
case state == waitingForOk && string(s[0]) == "DATA": | ||
err = authWriteLine(conn.transport, []byte("DATA")) | ||
if err != nil { | ||
return false, nil | ||
} | ||
case state == waitingForOk && string(s[0]) == "REJECTED": | ||
return false, nil | ||
case state == waitingForOk && string(s[0]) == "ERROR": | ||
err = authWriteLine(conn.transport, []byte("CANCEL")) | ||
if err != nil { | ||
return false, err | ||
} | ||
state = waitingForReject | ||
case state == waitingForOk: | ||
err = authWriteLine(conn.transport, []byte("ERROR")) | ||
if err != nil { | ||
return false, err | ||
} | ||
case state == waitingForReject && string(s[0]) == "REJECTED": | ||
return false, nil | ||
case state == waitingForReject: | ||
return false, errors.New("dbus: authentication protocol error") | ||
default: | ||
panic("dbus: invalid auth state") | ||
} | ||
} | ||
} | ||
|
||
// authReadLine reads a line and separates it into its fields. | ||
func authReadLine(in *bufio.Reader) ([][]byte, error) { | ||
data, err := in.ReadBytes('\n') | ||
if err != nil { | ||
return nil, err | ||
} | ||
data = bytes.TrimSuffix(data, []byte("\r\n")) | ||
return bytes.Split(data, []byte{' '}), nil | ||
} | ||
|
||
// authWriteLine writes the given line in the authentication protocol format | ||
// (elements of data separated by a " " and terminated by "\r\n"). | ||
func authWriteLine(out io.Writer, data ...[]byte) error { | ||
buf := make([]byte, 0) | ||
for i, v := range data { | ||
buf = append(buf, v...) | ||
if i != len(data)-1 { | ||
buf = append(buf, ' ') | ||
} | ||
} | ||
buf = append(buf, '\r') | ||
buf = append(buf, '\n') | ||
n, err := out.Write(buf) | ||
if err != nil { | ||
return err | ||
} | ||
if n != len(buf) { | ||
return io.ErrUnexpectedEOF | ||
} | ||
return nil | ||
} |