Serge Bazanski | d4438d6 | 2021-05-23 13:37:30 +0200 | [diff] [blame^] | 1 | package ident |
| 2 | |
| 3 | import ( |
| 4 | "fmt" |
| 5 | "regexp" |
| 6 | "strconv" |
| 7 | "strings" |
| 8 | ) |
| 9 | |
| 10 | var ( |
| 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. |
| 22 | type 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. |
| 42 | type ErrorResponse string |
| 43 | |
| 44 | const ( |
| 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. |
| 63 | func (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. |
| 74 | func (e ErrorResponse) IsNonStandardError() bool { |
| 75 | return len(e) > 0 && e[0] == 'X' |
| 76 | } |
| 77 | |
| 78 | func (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. |
| 90 | type 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. |
| 105 | func 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 | } |