2021-05-23 11:37:30 +00:00
|
|
|
package ident
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"regexp"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Response is an ident protocol response, as seen by the client or server.
|
|
|
|
type Response struct {
|
|
|
|
// ClientPort is the port number on the client side of the indent protocol,
|
|
|
|
// ie. the port local to the ident client.
|
|
|
|
ClientPort uint16
|
|
|
|
// ServerPort is the port number on the server side of the ident protocol,
|
|
|
|
// ie. the port local to the ident server.
|
|
|
|
ServerPort uint16
|
|
|
|
|
|
|
|
// Exactly one of {Error, Ident} must be non-zero.
|
|
|
|
|
|
|
|
// Error is either NoError (the zero value) or one of the ErrorResponse
|
|
|
|
// types if this response represents an ident protocol error reply.
|
|
|
|
Error ErrorResponse
|
|
|
|
// Ident is either nil or a IdentResponse if this response represents an
|
|
|
|
// ident protocol ident reply.
|
|
|
|
Ident *IdentResponse
|
|
|
|
}
|
|
|
|
|
|
|
|
// ErrorResponse is error-type from RFC1413, indicating one of the possible
|
|
|
|
// errors returned by the ident protocol server.
|
|
|
|
type ErrorResponse string
|
|
|
|
|
|
|
|
const (
|
|
|
|
// NoError is an ErrorResponse that indicates a lack of error.
|
|
|
|
NoError ErrorResponse = ""
|
|
|
|
// InvalidPort indicates that either the local or foreign port was
|
|
|
|
// improperly specified.
|
|
|
|
InvalidPort ErrorResponse = "INVALID-PORT"
|
|
|
|
// NoUser indicates that the port pair is not currently in use or currently
|
|
|
|
// not owned by an identifiable entity.
|
|
|
|
NoUser ErrorResponse = "NO-USER"
|
|
|
|
// HiddenUser indicates that the server was able to identify the user of
|
|
|
|
// this port, but the information was not returned at the request of the
|
|
|
|
// user.
|
|
|
|
HiddenUser ErrorResponse = "HIDDEN-USER"
|
|
|
|
// UnknownError indicates that the server could not determine the
|
|
|
|
// connection owner for an unknown reason.
|
|
|
|
UnknownError ErrorResponse = "UNKNOWN-ERROR"
|
|
|
|
)
|
|
|
|
|
|
|
|
// IsStandardError returns whether ErrorResponse represents a standard error.
|
|
|
|
func (e ErrorResponse) IsStandardError() bool {
|
|
|
|
switch e {
|
|
|
|
case InvalidPort, NoUser, HiddenUser, UnknownError:
|
|
|
|
return true
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsNonStandardError returns ehther the ErrorResponse represents a
|
|
|
|
// non-standard error.
|
|
|
|
func (e ErrorResponse) IsNonStandardError() bool {
|
|
|
|
return len(e) > 0 && e[0] == 'X'
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e ErrorResponse) IsError() bool {
|
|
|
|
if e.IsStandardError() {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if e.IsNonStandardError() {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// IdentResponse is the combined opsys, charset and user-id fields from
|
|
|
|
// RFC1413. It represents a non-error response from the ident protocol server.
|
|
|
|
type IdentResponse struct {
|
|
|
|
// OperatingSystem is an operating system identifier as per RFC1340. This
|
|
|
|
// is usually UNIX. OTHER has a special meaning, see RFC1413 for more
|
|
|
|
// information.
|
|
|
|
OperatingSystem string
|
|
|
|
// CharacterSet a character set as per RFC1340, defaulting to US-ASCII.
|
|
|
|
CharacterSet string
|
|
|
|
// UserID is the 'normal' user identification of the owner of the
|
|
|
|
// connection, unless the operating system is set to OTHER. See RFC1413 for
|
|
|
|
// more information.
|
|
|
|
UserID string
|
|
|
|
}
|
|
|
|
|
2021-05-23 15:15:29 +00:00
|
|
|
// encode encodes the given Response. If the Response is unencodable/malformed,
|
|
|
|
// nil is returned.
|
|
|
|
func (r *Response) encode() []byte {
|
|
|
|
// Both Error and Ident cannot be set at once.
|
|
|
|
if r.Error != "" && r.Ident != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if r.Error != "" {
|
|
|
|
if !r.Error.IsError() {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return []byte(fmt.Sprintf("%d,%d:ERROR:%s\r\n", r.ServerPort, r.ClientPort, r.Error))
|
|
|
|
}
|
|
|
|
if r.Ident != nil {
|
|
|
|
id := r.Ident
|
|
|
|
os := id.OperatingSystem
|
|
|
|
if os == "" {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
// For compatibility, do not set US-ASCII explicitly.
|
|
|
|
if id.CharacterSet != "" && id.CharacterSet != "US-ASCII" {
|
|
|
|
os += "," + id.CharacterSet
|
|
|
|
}
|
|
|
|
return []byte(fmt.Sprintf("%d,%d:USERID:%s:%s\r\n", r.ServerPort, r.ClientPort, os, id.UserID))
|
|
|
|
}
|
|
|
|
// Malformed response, return nil.
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
// reErrorReply matches error-reply from RFC1413, but also allows extra
|
|
|
|
// whitespace between significant tokens. It does not ensure that the
|
|
|
|
// error-type is one of the standardized values.
|
|
|
|
reErrorReply = regexp.MustCompile(`^\s*(\d{1,5})\s*,\s*(\d{1,5})\s*:\s*ERROR\s*:\s*(.+)$`)
|
|
|
|
// reIdentReply matches ident-reply from RFC1413, but also allows extra
|
|
|
|
// whitespace between significant tokens. It does not ensure that that
|
|
|
|
// opsys-field and user-id parts are RFC compliant.
|
|
|
|
reIdentReply = regexp.MustCompile(`^\s*(\d{1,5})\s*,\s*(\d{1,5})\s*:\s*USERID\s*:\s*([^:,]+)(,([^:]+))?\s*:(.+)$`)
|
|
|
|
)
|
|
|
|
|
2021-05-23 11:37:30 +00:00
|
|
|
// decodeResponse parses the given bytes as an ident response. The data must be
|
|
|
|
// stripped of the trailing \r\n.
|
|
|
|
func decodeResponse(data []byte) (*Response, error) {
|
|
|
|
if match := reErrorReply.FindStringSubmatch(string(data)); match != nil {
|
|
|
|
serverPort, err := strconv.ParseUint(match[1], 10, 16)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("invalid server port: %w", err)
|
|
|
|
}
|
|
|
|
clientPort, err := strconv.ParseUint(match[2], 10, 16)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("invalid client port: %w", err)
|
|
|
|
}
|
|
|
|
errResp := ErrorResponse(strings.TrimSpace(match[3]))
|
|
|
|
if !errResp.IsError() {
|
|
|
|
// The RFC doesn't tell us what we should do in this case. For
|
|
|
|
// reliability, we downcast any unknown error to UNKNOWN-ERROR.
|
|
|
|
errResp = UnknownError
|
|
|
|
}
|
|
|
|
return &Response{
|
|
|
|
ClientPort: uint16(clientPort),
|
|
|
|
ServerPort: uint16(serverPort),
|
|
|
|
Error: errResp,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
if match := reIdentReply.FindStringSubmatch(string(data)); match != nil {
|
|
|
|
serverPort, err := strconv.ParseUint(match[1], 10, 16)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("invalid server port: %w", err)
|
|
|
|
}
|
|
|
|
clientPort, err := strconv.ParseUint(match[2], 10, 16)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("invalid client port: %w", err)
|
|
|
|
}
|
|
|
|
os := strings.TrimSpace(match[3])
|
|
|
|
charset := strings.TrimSpace(match[5])
|
|
|
|
if charset == "" {
|
|
|
|
charset = "US-ASCII"
|
|
|
|
}
|
|
|
|
userid := strings.TrimSpace(match[6])
|
|
|
|
return &Response{
|
|
|
|
ClientPort: uint16(clientPort),
|
|
|
|
ServerPort: uint16(serverPort),
|
|
|
|
Ident: &IdentResponse{
|
|
|
|
OperatingSystem: os,
|
|
|
|
CharacterSet: charset,
|
|
|
|
UserID: userid,
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("unparseable response")
|
|
|
|
}
|