| 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 | } |