source: code/trunk/ident.go@ 780

Last change on this file since 780 was 766, checked in by contact, 3 years ago

Retry on temporary net.Listener failure

Instead of stopping to listen, retry on temporary failure. This
can happen when running out of FDs.

Closes: https://todo.sr.ht/~emersion/soju/183

File size: 2.9 KB
RevLine 
[388]1package soju
2
3import (
4 "bufio"
5 "fmt"
6 "net"
7 "strconv"
8 "strings"
9 "sync"
10 "time"
11)
12
13var identdTimeout = 10 * time.Second
14
15type identKey struct {
16 remoteHost string
17 remotePort int
18 localPort int
19}
20
21func newIdentKey(remoteAddr, localAddr string) (*identKey, error) {
22 remoteHost, remotePort, err := splitHostPort(remoteAddr)
23 if err != nil {
24 return nil, err
25 }
26 _, localPort, err := splitHostPort(localAddr)
27 if err != nil {
28 return nil, err
29 }
30 return &identKey{
31 remoteHost: remoteHost,
32 remotePort: remotePort,
33 localPort: localPort,
34 }, nil
35}
36
37func splitHostPort(addr string) (host string, port int, err error) {
38 host, portStr, err := net.SplitHostPort(addr)
39 if err != nil {
40 return "", 0, err
41 }
42 port, err = strconv.Atoi(portStr)
43 return host, port, err
44}
45
46// Identd implements an ident server, as described in RFC 1413.
47type Identd struct {
48 entries map[identKey]string
49 lock sync.RWMutex
50}
51
52func NewIdentd() *Identd {
53 return &Identd{entries: make(map[identKey]string)}
54}
55
56func (s *Identd) Store(remoteAddr, localAddr, ident string) {
57 k, err := newIdentKey(remoteAddr, localAddr)
58 if err != nil {
59 return
60 }
61 s.lock.Lock()
62 s.entries[*k] = ident
63 s.lock.Unlock()
64}
65
66func (s *Identd) Delete(remoteAddr, localAddr string) {
67 k, err := newIdentKey(remoteAddr, localAddr)
68 if err != nil {
69 return
70 }
71 s.lock.Lock()
72 delete(s.entries, *k)
73 s.lock.Unlock()
74}
75
76func (s *Identd) Serve(ln net.Listener) error {
[766]77 ln = &retryListener{Listener: ln}
78
[388]79 for {
80 conn, err := ln.Accept()
81 if err != nil {
82 return fmt.Errorf("failed to accept connection: %v", err)
83 }
84
85 go s.handle(conn)
86 }
87}
88
89func (s *Identd) handle(c net.Conn) {
90 defer c.Close()
91
92 remoteHost, _, err := net.SplitHostPort(c.RemoteAddr().String())
93 if err != nil {
94 return
95 }
96
97 scanner := bufio.NewScanner(c)
98
99 // We only read to read lines with two port numbers
100 var buf [512]byte
101 scanner.Buffer(buf[:], len(buf))
102
103 for {
104 c.SetDeadline(time.Now().Add(identdTimeout))
105 if !scanner.Scan() {
106 break
107 }
108 l := scanner.Text()
109
110 localPort, remotePort, err := parseIdentQuery(l)
111 if err != nil {
112 fmt.Fprintf(c, "%s : ERROR : INVALID-PORT\r\n", l)
113 break
114 }
115
116 k := identKey{
117 remoteHost: remoteHost,
118 remotePort: remotePort,
119 localPort: localPort,
120 }
121
122 s.lock.RLock()
123 ident := s.entries[k]
124 s.lock.RUnlock()
125
126 if ident == "" {
127 fmt.Fprintf(c, "%s : ERROR : NO-USER\r\n", l)
128 break
129 }
130
131 fmt.Fprintf(c, "%s : USERID : OTHER : %s\r\n", l, ident)
132 }
133}
134
135func parseIdentQuery(l string) (localPort, remotePort int, err error) {
136 parts := strings.SplitN(l, ",", 2)
137 if len(parts) != 2 {
138 return 0, 0, fmt.Errorf("expected two ports")
139 }
140 localStr, remoteStr := strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])
141 if localPort, err = strconv.Atoi(localStr); err != nil {
142 return 0, 0, err
143 }
144 if remotePort, err = strconv.Atoi(remoteStr); err != nil {
145 return 0, 0, err
146 }
147 if localPort <= 0 || remotePort <= 0 {
148 return 0, 0, fmt.Errorf("invalid port")
149 }
150 return localPort, remotePort, nil
151}
Note: See TracBrowser for help on using the repository browser.