blob: 5eab431d7e98e469a8d1042870af719958d96ff2 [file] [log] [blame]
Serge Bazanskid4438d62021-05-23 13:37:30 +02001package ident
2
3import (
4 "fmt"
5 "regexp"
6 "strconv"
7 "strings"
8)
9
10var (
11 // reErrorReply matches error-reply from RFC1413, but also allows extra
12 // whitespace between significant tokens. It does not ensure that the
13 // error-type is one of the standardized values.
14 reErrorReply = regexp.MustCompile(`^\s*(\d{1,5})\s*,\s*(\d{1,5})\s*:\s*ERROR\s*:\s*(.+)$`)
15 // reIdentReply matches ident-reply from RFC1413, but also allows extra
16 // whitespace between significant tokens. It does not ensure that that
17 // opsys-field and user-id parts are RFC compliant.
18 reIdentReply = regexp.MustCompile(`^\s*(\d{1,5})\s*,\s*(\d{1,5})\s*:\s*USERID\s*:\s*([^:,]+)(,([^:]+))?\s*:(.+)$`)
19)
20
21// Response is an ident protocol response, as seen by the client or server.
22type Response struct {
23 // ClientPort is the port number on the client side of the indent protocol,
24 // ie. the port local to the ident client.
25 ClientPort uint16
26 // ServerPort is the port number on the server side of the ident protocol,
27 // ie. the port local to the ident server.
28 ServerPort uint16
29
30 // Exactly one of {Error, Ident} must be non-zero.
31
32 // Error is either NoError (the zero value) or one of the ErrorResponse
33 // types if this response represents an ident protocol error reply.
34 Error ErrorResponse
35 // Ident is either nil or a IdentResponse if this response represents an
36 // ident protocol ident reply.
37 Ident *IdentResponse
38}
39
40// ErrorResponse is error-type from RFC1413, indicating one of the possible
41// errors returned by the ident protocol server.
42type ErrorResponse string
43
44const (
45 // NoError is an ErrorResponse that indicates a lack of error.
46 NoError ErrorResponse = ""
47 // InvalidPort indicates that either the local or foreign port was
48 // improperly specified.
49 InvalidPort ErrorResponse = "INVALID-PORT"
50 // NoUser indicates that the port pair is not currently in use or currently
51 // not owned by an identifiable entity.
52 NoUser ErrorResponse = "NO-USER"
53 // HiddenUser indicates that the server was able to identify the user of
54 // this port, but the information was not returned at the request of the
55 // user.
56 HiddenUser ErrorResponse = "HIDDEN-USER"
57 // UnknownError indicates that the server could not determine the
58 // connection owner for an unknown reason.
59 UnknownError ErrorResponse = "UNKNOWN-ERROR"
60)
61
62// IsStandardError returns whether ErrorResponse represents a standard error.
63func (e ErrorResponse) IsStandardError() bool {
64 switch e {
65 case InvalidPort, NoUser, HiddenUser, UnknownError:
66 return true
67 default:
68 return false
69 }
70}
71
72// IsNonStandardError returns ehther the ErrorResponse represents a
73// non-standard error.
74func (e ErrorResponse) IsNonStandardError() bool {
75 return len(e) > 0 && e[0] == 'X'
76}
77
78func (e ErrorResponse) IsError() bool {
79 if e.IsStandardError() {
80 return true
81 }
82 if e.IsNonStandardError() {
83 return true
84 }
85 return false
86}
87
88// IdentResponse is the combined opsys, charset and user-id fields from
89// RFC1413. It represents a non-error response from the ident protocol server.
90type IdentResponse struct {
91 // OperatingSystem is an operating system identifier as per RFC1340. This
92 // is usually UNIX. OTHER has a special meaning, see RFC1413 for more
93 // information.
94 OperatingSystem string
95 // CharacterSet a character set as per RFC1340, defaulting to US-ASCII.
96 CharacterSet string
97 // UserID is the 'normal' user identification of the owner of the
98 // connection, unless the operating system is set to OTHER. See RFC1413 for
99 // more information.
100 UserID string
101}
102
103// decodeResponse parses the given bytes as an ident response. The data must be
104// stripped of the trailing \r\n.
105func decodeResponse(data []byte) (*Response, error) {
106 if match := reErrorReply.FindStringSubmatch(string(data)); match != nil {
107 serverPort, err := strconv.ParseUint(match[1], 10, 16)
108 if err != nil {
109 return nil, fmt.Errorf("invalid server port: %w", err)
110 }
111 clientPort, err := strconv.ParseUint(match[2], 10, 16)
112 if err != nil {
113 return nil, fmt.Errorf("invalid client port: %w", err)
114 }
115 errResp := ErrorResponse(strings.TrimSpace(match[3]))
116 if !errResp.IsError() {
117 // The RFC doesn't tell us what we should do in this case. For
118 // reliability, we downcast any unknown error to UNKNOWN-ERROR.
119 errResp = UnknownError
120 }
121 return &Response{
122 ClientPort: uint16(clientPort),
123 ServerPort: uint16(serverPort),
124 Error: errResp,
125 }, nil
126 }
127 if match := reIdentReply.FindStringSubmatch(string(data)); match != nil {
128 serverPort, err := strconv.ParseUint(match[1], 10, 16)
129 if err != nil {
130 return nil, fmt.Errorf("invalid server port: %w", err)
131 }
132 clientPort, err := strconv.ParseUint(match[2], 10, 16)
133 if err != nil {
134 return nil, fmt.Errorf("invalid client port: %w", err)
135 }
136 os := strings.TrimSpace(match[3])
137 charset := strings.TrimSpace(match[5])
138 if charset == "" {
139 charset = "US-ASCII"
140 }
141 userid := strings.TrimSpace(match[6])
142 return &Response{
143 ClientPort: uint16(clientPort),
144 ServerPort: uint16(serverPort),
145 Ident: &IdentResponse{
146 OperatingSystem: os,
147 CharacterSet: charset,
148 UserID: userid,
149 },
150 }, nil
151 }
152 return nil, fmt.Errorf("unparseable response")
153}