mirror of https://github.com/containers/podman.git
				
				
				
			
		
			
				
	
	
		
			258 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			258 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Go
		
	
	
	
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 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(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
 | 
						|
}
 |