1package dbus
2
3import (
4	"bufio"
5	"bytes"
6	"crypto/rand"
7	"crypto/sha1"
8	"encoding/hex"
9	"os"
10)
11
12// AuthCookieSha1 returns an Auth that authenticates as the given user with the
13// DBUS_COOKIE_SHA1 mechanism. The home parameter should specify the home
14// directory of the user.
15func AuthCookieSha1(user, home string) Auth {
16	return authCookieSha1{user, home}
17}
18
19type authCookieSha1 struct {
20	user, home string
21}
22
23func (a authCookieSha1) FirstData() ([]byte, []byte, AuthStatus) {
24	b := make([]byte, 2*len(a.user))
25	hex.Encode(b, []byte(a.user))
26	return []byte("DBUS_COOKIE_SHA1"), b, AuthContinue
27}
28
29func (a authCookieSha1) HandleData(data []byte) ([]byte, AuthStatus) {
30	challenge := make([]byte, len(data)/2)
31	_, err := hex.Decode(challenge, data)
32	if err != nil {
33		return nil, AuthError
34	}
35	b := bytes.Split(challenge, []byte{' '})
36	if len(b) != 3 {
37		return nil, AuthError
38	}
39	context := b[0]
40	id := b[1]
41	svchallenge := b[2]
42	cookie := a.getCookie(context, id)
43	if cookie == nil {
44		return nil, AuthError
45	}
46	clchallenge := a.generateChallenge()
47	if clchallenge == nil {
48		return nil, AuthError
49	}
50	hash := sha1.New()
51	hash.Write(bytes.Join([][]byte{svchallenge, clchallenge, cookie}, []byte{':'}))
52	hexhash := make([]byte, 2*hash.Size())
53	hex.Encode(hexhash, hash.Sum(nil))
54	data = append(clchallenge, ' ')
55	data = append(data, hexhash...)
56	resp := make([]byte, 2*len(data))
57	hex.Encode(resp, data)
58	return resp, AuthOk
59}
60
61// getCookie searches for the cookie identified by id in context and returns
62// the cookie content or nil. (Since HandleData can't return a specific error,
63// but only whether an error occured, this function also doesn't bother to
64// return an error.)
65func (a authCookieSha1) getCookie(context, id []byte) []byte {
66	file, err := os.Open(a.home + "/.dbus-keyrings/" + string(context))
67	if err != nil {
68		return nil
69	}
70	defer file.Close()
71	rd := bufio.NewReader(file)
72	for {
73		line, err := rd.ReadBytes('\n')
74		if err != nil {
75			return nil
76		}
77		line = line[:len(line)-1]
78		b := bytes.Split(line, []byte{' '})
79		if len(b) != 3 {
80			return nil
81		}
82		if bytes.Equal(b[0], id) {
83			return b[2]
84		}
85	}
86}
87
88// generateChallenge returns a random, hex-encoded challenge, or nil on error
89// (see above).
90func (a authCookieSha1) generateChallenge() []byte {
91	b := make([]byte, 16)
92	n, err := rand.Read(b)
93	if err != nil {
94		return nil
95	}
96	if n != 16 {
97		return nil
98	}
99	enc := make([]byte, 32)
100	hex.Encode(enc, b)
101	return enc
102}
103