master
go 174 lines 7.17 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package api
4
5 import (
6 "context"
7 "crypto/hmac"
8 "crypto/md5"
9 "fmt"
10 "net"
11 "strconv"
12 "time"
13
14 "layeh.com/radius"
15 "layeh.com/radius/rfc2869"
16 )
17
18 type Status struct {
19 AccessRequests int64 `stm:"access-requests"`
20 AccessAccepts int64 `stm:"access-accepts"`
21 AccessRejects int64 `stm:"access-rejects"`
22 AccessChallenges int64 `stm:"access-challenges"`
23 AuthResponses int64 `stm:"auth-responses"`
24 AuthDuplicateRequests int64 `stm:"auth-duplicate-requests"`
25 AuthMalformedRequests int64 `stm:"auth-malformed-requests"`
26 AuthInvalidRequests int64 `stm:"auth-invalid-requests"`
27 AuthDroppedRequests int64 `stm:"auth-dropped-requests"`
28 AuthUnknownTypes int64 `stm:"auth-unknown-types"`
29
30 AccountingRequests int64 `stm:"accounting-requests"`
31 AccountingResponses int64 `stm:"accounting-responses"`
32 AcctDuplicateRequests int64 `stm:"acct-duplicate-requests"`
33 AcctMalformedRequests int64 `stm:"acct-malformed-requests"`
34 AcctInvalidRequests int64 `stm:"acct-invalid-requests"`
35 AcctDroppedRequests int64 `stm:"acct-dropped-requests"`
36 AcctUnknownTypes int64 `stm:"acct-unknown-types"`
37
38 ProxyAccessRequests int64 `stm:"proxy-access-requests"`
39 ProxyAccessAccepts int64 `stm:"proxy-access-accepts"`
40 ProxyAccessRejects int64 `stm:"proxy-access-rejects"`
41 ProxyAccessChallenges int64 `stm:"proxy-access-challenges"`
42 ProxyAuthResponses int64 `stm:"proxy-auth-responses"`
43 ProxyAuthDuplicateRequests int64 `stm:"proxy-auth-duplicate-requests"`
44 ProxyAuthMalformedRequests int64 `stm:"proxy-auth-malformed-requests"`
45 ProxyAuthInvalidRequests int64 `stm:"proxy-auth-invalid-requests"`
46 ProxyAuthDroppedRequests int64 `stm:"proxy-auth-dropped-requests"`
47 ProxyAuthUnknownTypes int64 `stm:"proxy-auth-unknown-types"`
48
49 ProxyAccountingRequests int64 `stm:"proxy-accounting-requests"`
50 ProxyAccountingResponses int64 `stm:"proxy-accounting-responses"`
51 ProxyAcctDuplicateRequests int64 `stm:"proxy-acct-duplicate-requests"`
52 ProxyAcctMalformedRequests int64 `stm:"proxy-acct-malformed-requests"`
53 ProxyAcctInvalidRequests int64 `stm:"proxy-acct-invalid-requests"`
54 ProxyAcctDroppedRequests int64 `stm:"proxy-acct-dropped-requests"`
55 ProxyAcctUnknownTypes int64 `stm:"proxy-acct-unknown-types"`
56 }
57
58 type (
59 radiusClient interface {
60 Exchange(ctx context.Context, packet *radius.Packet, address string) (*radius.Packet, error)
61 }
62 Config struct {
63 Address string
64 Port int
65 Secret string
66 Timeout time.Duration
67 }
68 Client struct {
69 address string
70 secret string
71 timeout time.Duration
72 radiusClient
73 }
74 )
75
76 func New(conf Config) *Client {
77 return &Client{
78 address: net.JoinHostPort(conf.Address, strconv.Itoa(conf.Port)),
79 secret: conf.Secret,
80 timeout: conf.Timeout,
81 radiusClient: &radius.Client{Retry: time.Second, MaxPacketErrors: 10},
82 }
83 }
84
85 func (c Client) Status() (*Status, error) {
86 packet, err := newStatusServerPacket(c.secret)
87 if err != nil {
88 return nil, fmt.Errorf("error on creating StatusServer packet: %v", err)
89 }
90
91 resp, err := c.queryServer(packet)
92 if err != nil {
93 return nil, fmt.Errorf("error on request to '%s': %v", c.address, err)
94 }
95
96 return decodeResponse(resp), nil
97 }
98
99 func (c Client) queryServer(packet *radius.Packet) (*radius.Packet, error) {
100 ctx, cancel := context.WithTimeout(context.Background(), c.timeout)
101 defer cancel()
102
103 resp, err := c.Exchange(ctx, packet, c.address)
104 if err != nil {
105 return nil, err
106 }
107
108 if resp.Code != radius.CodeAccessAccept {
109 return nil, fmt.Errorf("'%s' returned response code %d", c.address, resp.Code)
110 }
111 return resp, nil
112 }
113
114 func newStatusServerPacket(secret string) (*radius.Packet, error) {
115 // https://wiki.freeradius.org/config/Status#status-of-freeradius-server
116 packet := radius.New(radius.CodeStatusServer, []byte(secret))
117 if err := FreeRADIUSStatisticsType_Set(packet, FreeRADIUSStatisticsType_Value_All); err != nil {
118 return nil, err
119 }
120 if err := rfc2869.MessageAuthenticator_Set(packet, make([]byte, 16)); err != nil {
121 return nil, err
122 }
123 hash := hmac.New(md5.New, packet.Secret)
124 encode, err := packet.Encode()
125 if err != nil {
126 return nil, err
127 }
128 if _, err := hash.Write(encode); err != nil {
129 return nil, err
130 }
131 if err := rfc2869.MessageAuthenticator_Set(packet, hash.Sum(nil)); err != nil {
132 return nil, err
133 }
134 return packet, nil
135 }
136
137 func decodeResponse(resp *radius.Packet) *Status {
138 return &Status{
139 AccessRequests: int64(FreeRADIUSTotalAccessRequests_Get(resp)),
140 AccessAccepts: int64(FreeRADIUSTotalAccessAccepts_Get(resp)),
141 AccessRejects: int64(FreeRADIUSTotalAccessRejects_Get(resp)),
142 AccessChallenges: int64(FreeRADIUSTotalAccessChallenges_Get(resp)),
143 AuthResponses: int64(FreeRADIUSTotalAuthResponses_Get(resp)),
144 AuthDuplicateRequests: int64(FreeRADIUSTotalAuthDuplicateRequests_Get(resp)),
145 AuthMalformedRequests: int64(FreeRADIUSTotalAuthMalformedRequests_Get(resp)),
146 AuthInvalidRequests: int64(FreeRADIUSTotalAuthInvalidRequests_Get(resp)),
147 AuthDroppedRequests: int64(FreeRADIUSTotalAuthDroppedRequests_Get(resp)),
148 AuthUnknownTypes: int64(FreeRADIUSTotalAuthUnknownTypes_Get(resp)),
149 AccountingRequests: int64(FreeRADIUSTotalAccountingRequests_Get(resp)),
150 AccountingResponses: int64(FreeRADIUSTotalAccountingResponses_Get(resp)),
151 AcctDuplicateRequests: int64(FreeRADIUSTotalAcctDuplicateRequests_Get(resp)),
152 AcctMalformedRequests: int64(FreeRADIUSTotalAcctMalformedRequests_Get(resp)),
153 AcctInvalidRequests: int64(FreeRADIUSTotalAcctInvalidRequests_Get(resp)),
154 AcctDroppedRequests: int64(FreeRADIUSTotalAcctDroppedRequests_Get(resp)),
155 AcctUnknownTypes: int64(FreeRADIUSTotalAcctUnknownTypes_Get(resp)),
156 ProxyAccessRequests: int64(FreeRADIUSTotalProxyAccessRequests_Get(resp)),
157 ProxyAccessAccepts: int64(FreeRADIUSTotalProxyAccessAccepts_Get(resp)),
158 ProxyAccessRejects: int64(FreeRADIUSTotalProxyAccessRejects_Get(resp)),
159 ProxyAccessChallenges: int64(FreeRADIUSTotalProxyAccessChallenges_Get(resp)),
160 ProxyAuthResponses: int64(FreeRADIUSTotalProxyAuthResponses_Get(resp)),
161 ProxyAuthDuplicateRequests: int64(FreeRADIUSTotalProxyAuthDuplicateRequests_Get(resp)),
162 ProxyAuthMalformedRequests: int64(FreeRADIUSTotalProxyAuthMalformedRequests_Get(resp)),
163 ProxyAuthInvalidRequests: int64(FreeRADIUSTotalProxyAuthInvalidRequests_Get(resp)),
164 ProxyAuthDroppedRequests: int64(FreeRADIUSTotalProxyAuthDroppedRequests_Get(resp)),
165 ProxyAuthUnknownTypes: int64(FreeRADIUSTotalProxyAuthUnknownTypes_Get(resp)),
166 ProxyAccountingRequests: int64(FreeRADIUSTotalProxyAccountingRequests_Get(resp)),
167 ProxyAccountingResponses: int64(FreeRADIUSTotalProxyAccountingResponses_Get(resp)),
168 ProxyAcctDuplicateRequests: int64(FreeRADIUSTotalProxyAcctDuplicateRequests_Get(resp)),
169 ProxyAcctMalformedRequests: int64(FreeRADIUSTotalProxyAcctMalformedRequests_Get(resp)),
170 ProxyAcctInvalidRequests: int64(FreeRADIUSTotalProxyAcctInvalidRequests_Get(resp)),
171 ProxyAcctDroppedRequests: int64(FreeRADIUSTotalProxyAcctDroppedRequests_Get(resp)),
172 ProxyAcctUnknownTypes: int64(FreeRADIUSTotalProxyAcctUnknownTypes_Get(resp)),
173 }
174 }