-
Notifications
You must be signed in to change notification settings - Fork 17
/
Copy pathheader.go
193 lines (171 loc) · 4.96 KB
/
header.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
/*
Copyright (c) Facebook, Inc. and its affiliates.
This source code is licensed under the MIT license found in the
LICENSE file in the root directory of this source tree.
*/
package tacquito
import (
"encoding/binary"
"fmt"
"math/rand"
"time"
)
func init() {
rand.Seed(time.Now().UnixNano())
}
//
// tacplus header
// https://datatracker.ietf.org/doc/html/rfc8907#section-4.1
//
// HeaderOption used to modify existing headers that were decoded and reuse
// them in a response, or to create a new Header with options
type HeaderOption func(*Header)
// SetHeaderVersion sets Version
func SetHeaderVersion(v Version) HeaderOption {
return func(h *Header) {
h.Version = v
}
}
// SetHeaderType sets packet type
func SetHeaderType(v HeaderType) HeaderOption {
return func(h *Header) {
h.Type = v
}
}
// SetHeaderSeqNo sets SequenceNumber to a specific value
func SetHeaderSeqNo(v int) HeaderOption {
return func(h *Header) {
h.SeqNo = SequenceNumber(v)
}
}
// SetHeaderFlag sets HeaderFlag to a specific value
// This field contains various bitmapped flags.
func SetHeaderFlag(v HeaderFlag) HeaderOption {
return func(h *Header) {
h.Flags = v
}
}
// SetHeaderSessionID sets SessionID to a specific value.
// This number MUST be generated by a
// cryptographically strong random number generation method.
func SetHeaderSessionID(v SessionID) HeaderOption {
return func(h *Header) {
h.SessionID = v
}
}
// SetHeaderLen sets the length of the header. This is automatically done for you
// but if you wish to set a length explictly for tests...
func SetHeaderLen(v int) HeaderOption {
return func(h *Header) {
h.Length = uint32(v)
}
}
// SetHeaderRandomSessionID sets a weaker math/rand session id. To meet the requirements
// of the rfc, you should use SetHeaderSessionID with a cryptographically strong random number.
// this setter should only be used in examples and tests
func SetHeaderRandomSessionID() HeaderOption {
return func(h *Header) {
h.SessionID = SessionID(rand.Uint32())
}
}
// MaxHeaderLength defines a fixed size for a tacacs header
const MaxHeaderLength = 0x0c
// NewHeader will create a new Header based on the provided options, starting with
// common defaults. the defaults will be overwritten, if provided in the options
func NewHeader(opts ...HeaderOption) *Header {
h := &Header{}
var f HeaderFlag
defaults := []HeaderOption{
SetHeaderSeqNo(1),
SetHeaderFlag(f),
}
opts = append(defaults, opts...)
for _, opt := range opts {
opt(h)
}
return h
}
// Header holds the tacacs header fields found in all tacacs packet types.
type Header struct {
Version Version
Type HeaderType
SeqNo SequenceNumber
SessionID SessionID
Flags HeaderFlag
Length uint32
}
// Validate all fields on this type
func (h *Header) Validate() error {
// validate
for _, t := range []Field{h.Version, h.Type, h.SeqNo} {
if err := t.Validate(nil); err != nil {
return err
}
}
// manually validate Length since it's not a Field interface
if h.Length > MaxBodyLength {
return fmt.Errorf("length field is too large, max size is 2^(16)")
}
return nil
}
// MarshalBinary encodes Header into tacacs bytes
func (h *Header) MarshalBinary() ([]byte, error) {
// validate
if err := h.Validate(); err != nil {
return nil, err
}
buf := make([]byte, MaxHeaderLength)
version, err := h.Version.MarshalBinary()
if err != nil {
return nil, err
}
buf[0] = version[0]
buf[1] = uint8(h.Type)
buf[2] = uint8(h.SeqNo)
buf[3] = uint8(h.Flags)
binary.BigEndian.PutUint32(buf[4:], uint32(h.SessionID))
binary.BigEndian.PutUint32(buf[8:], h.Length)
return buf, nil
}
// UnmarshalBinary decodes tacacs bytes into Header
func (h *Header) UnmarshalBinary(data []byte) error {
if len(data) < MaxHeaderLength {
return fmt.Errorf("Header size [%v] is not matched to expected size [%v]", len(data), MaxHeaderLength)
}
var version Version
err := version.UnmarshalBinary(data)
if err != nil {
return err
}
h.Version = version
h.Type = HeaderType(data[1])
h.SeqNo = SequenceNumber(data[2])
h.Flags = HeaderFlag(data[3])
h.SessionID = SessionID(binary.BigEndian.Uint32(data[4:]))
h.Length = binary.BigEndian.Uint32(data[8:])
// set SingleConnect. Ignored on all other sequence numbers
// see https://datatracker.ietf.org/doc/html/rfc8907#section-4.3
// if this is our first response, we always set this. its up to the
// client to persist it beyond from seq 2
if h.SeqNo == 2 {
h.Flags.Set(SingleConnect)
}
// validate
if err := h.Validate(); err != nil {
return err
}
return nil
}
// Fields returns fields from this packet compatible with a structured logger
func (h Header) Fields() map[string]string {
// ensure fields don't collide with packet body values
// prefix with header-
return map[string]string{
"header-version": h.Version.String(),
"header-type": h.Type.String(),
"header-seq-no": h.SeqNo.String(),
"header-session-id": h.SessionID.String(),
"header-flags": h.Flags.String(),
"header-length": fmt.Sprint(h.Length),
}
}