]> git.ipfire.org Git - thirdparty/squid.git/blob - src/ident/Ident.cc
Source Format Enforcement (#763)
[thirdparty/squid.git] / src / ident / Ident.cc
1 /*
2 * Copyright (C) 1996-2021 The Squid Software Foundation and contributors
3 *
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
7 */
8
9 /* DEBUG: section 30 Ident (RFC 931) */
10
11 #include "squid.h"
12
13 #if USE_IDENT
14 #include "comm.h"
15 #include "comm/Connection.h"
16 #include "comm/ConnOpener.h"
17 #include "comm/Read.h"
18 #include "comm/Write.h"
19 #include "CommCalls.h"
20 #include "globals.h"
21 #include "ident/Config.h"
22 #include "ident/Ident.h"
23 #include "MemBuf.h"
24
25 namespace Ident
26 {
27
28 #define IDENT_PORT 113
29 #define IDENT_KEY_SZ 50
30 #define IDENT_BUFSIZE 4096
31
32 typedef struct _IdentClient {
33 IDCB *callback;
34 void *callback_data;
35
36 struct _IdentClient *next;
37 } IdentClient;
38
39 class IdentStateData
40 {
41 public:
42 hash_link hash; /* must be first */
43 private:
44 CBDATA_CLASS(IdentStateData);
45
46 public:
47 /* AsyncJob API emulated */
48 void deleteThis(const char *aReason);
49 void swanSong();
50
51 /// notify all waiting IdentClient callbacks
52 void notify(const char *result);
53
54 Comm::ConnectionPointer conn;
55 MemBuf queryMsg; ///< the lookup message sent to IDENT server
56 IdentClient *clients;
57 char buf[IDENT_BUFSIZE];
58 };
59
60 CBDATA_CLASS_INIT(IdentStateData);
61
62 // TODO: make these all a series of Async job calls. They are self-contained callbacks now.
63 static IOCB ReadReply;
64 static IOCB WriteFeedback;
65 static CLCB Close;
66 static CTCB Timeout;
67 static CNCB ConnectDone;
68 static hash_table *ident_hash = NULL;
69 static void ClientAdd(IdentStateData * state, IDCB * callback, void *callback_data);
70
71 } // namespace Ident
72
73 Ident::IdentConfig Ident::TheConfig;
74
75 void
76 Ident::IdentStateData::deleteThis(const char *)
77 {
78 swanSong();
79 delete this;
80 }
81
82 void
83 Ident::IdentStateData::swanSong()
84 {
85 if (clients != NULL)
86 notify(NULL);
87
88 if (Comm::IsConnOpen(conn)) {
89 comm_remove_close_handler(conn->fd, Ident::Close, this);
90 conn->close();
91 }
92
93 hash_remove_link(ident_hash, (hash_link *) this);
94 xfree(hash.key);
95 }
96
97 void
98 Ident::IdentStateData::notify(const char *result)
99 {
100 while (IdentClient *client = clients) {
101 void *cbdata;
102 clients = client->next;
103
104 if (cbdataReferenceValidDone(client->callback_data, &cbdata))
105 client->callback(result, cbdata);
106
107 xfree(client);
108 }
109 }
110
111 void
112 Ident::Close(const CommCloseCbParams &params)
113 {
114 IdentStateData *state = (IdentStateData *)params.data;
115 state->deleteThis("connection closed");
116 }
117
118 void
119 Ident::Timeout(const CommTimeoutCbParams &io)
120 {
121 debugs(30, 3, HERE << io.conn);
122 IdentStateData *state = (IdentStateData *)io.data;
123 state->deleteThis("timeout");
124 }
125
126 void
127 Ident::ConnectDone(const Comm::ConnectionPointer &conn, Comm::Flag status, int, void *data)
128 {
129 IdentStateData *state = (IdentStateData *)data;
130
131 if (status != Comm::OK) {
132 if (status == Comm::TIMEOUT)
133 debugs(30, 3, "IDENT connection timeout to " << state->conn->remote);
134 state->deleteThis(status == Comm::TIMEOUT ? "connect timeout" : "connect error");
135 return;
136 }
137
138 /*
139 * see if any of our clients still care
140 */
141 IdentClient *c;
142 for (c = state->clients; c; c = c->next) {
143 if (cbdataReferenceValid(c->callback_data))
144 break;
145 }
146
147 if (c == NULL) {
148 state->deleteThis("client(s) aborted");
149 return;
150 }
151
152 assert(conn != NULL && conn == state->conn);
153 comm_add_close_handler(conn->fd, Ident::Close, state);
154
155 AsyncCall::Pointer writeCall = commCbCall(5,4, "Ident::WriteFeedback",
156 CommIoCbPtrFun(Ident::WriteFeedback, state));
157 Comm::Write(conn, &state->queryMsg, writeCall);
158 AsyncCall::Pointer readCall = commCbCall(5,4, "Ident::ReadReply",
159 CommIoCbPtrFun(Ident::ReadReply, state));
160 comm_read(conn, state->buf, IDENT_BUFSIZE, readCall);
161 AsyncCall::Pointer timeoutCall = commCbCall(5,4, "Ident::Timeout",
162 CommTimeoutCbPtrFun(Ident::Timeout, state));
163 commSetConnTimeout(conn, Ident::TheConfig.timeout, timeoutCall);
164 }
165
166 void
167 Ident::WriteFeedback(const Comm::ConnectionPointer &conn, char *, size_t len, Comm::Flag flag, int xerrno, void *data)
168 {
169 debugs(30, 5, HERE << conn << ": Wrote IDENT request " << len << " bytes.");
170
171 // TODO handle write errors better. retry or abort?
172 if (flag != Comm::OK) {
173 debugs(30, 2, HERE << conn << " err-flags=" << flag << " IDENT write error: " << xstrerr(xerrno));
174 IdentStateData *state = (IdentStateData *)data;
175 state->deleteThis("write error");
176 }
177 }
178
179 void
180 Ident::ReadReply(const Comm::ConnectionPointer &conn, char *buf, size_t len, Comm::Flag flag, int, void *data)
181 {
182 IdentStateData *state = (IdentStateData *)data;
183 char *ident = NULL;
184 char *t = NULL;
185
186 assert(buf == state->buf);
187 assert(conn->fd == state->conn->fd);
188
189 if (flag != Comm::OK || len <= 0) {
190 state->deleteThis("read error");
191 return;
192 }
193
194 /*
195 * XXX This isn't really very tolerant. It should read until EOL
196 * or EOF and then decode the answer... If the reply is fragmented
197 * then this will fail
198 */
199 buf[len] = '\0';
200
201 if ((t = strchr(buf, '\r')))
202 *t = '\0';
203
204 if ((t = strchr(buf, '\n')))
205 *t = '\0';
206
207 debugs(30, 5, HERE << conn << ": Read '" << buf << "'");
208
209 if (strstr(buf, "USERID")) {
210 if ((ident = strrchr(buf, ':'))) {
211 while (xisspace(*++ident));
212 if (ident && *ident == '\0')
213 ident = NULL;
214 state->notify(ident);
215 }
216 }
217
218 state->deleteThis("completed");
219 }
220
221 void
222 Ident::ClientAdd(IdentStateData * state, IDCB * callback, void *callback_data)
223 {
224 IdentClient *c = (IdentClient *)xcalloc(1, sizeof(*c));
225 IdentClient **C;
226 c->callback = callback;
227 c->callback_data = cbdataReference(callback_data);
228
229 for (C = &state->clients; *C; C = &(*C)->next);
230 *C = c;
231 }
232
233 /*
234 * start a TCP connection to the peer host on port 113
235 */
236 void
237 Ident::Start(const Comm::ConnectionPointer &conn, IDCB * callback, void *data)
238 {
239 IdentStateData *state;
240 char key1[IDENT_KEY_SZ];
241 char key2[IDENT_KEY_SZ];
242 char key[IDENT_KEY_SZ*2+2]; // key1 + ',' + key2 + terminator
243
244 conn->local.toUrl(key1, IDENT_KEY_SZ);
245 conn->remote.toUrl(key2, IDENT_KEY_SZ);
246 const auto res = snprintf(key, sizeof(key), "%s,%s", key1, key2);
247 assert(res > 0);
248 assert(static_cast<std::make_unsigned<decltype(res)>::type>(res) < sizeof(key));
249
250 if (!ident_hash) {
251 Init();
252 }
253 if ((state = (IdentStateData *)hash_lookup(ident_hash, key)) != NULL) {
254 ClientAdd(state, callback, data);
255 return;
256 }
257
258 state = new IdentStateData;
259 state->hash.key = xstrdup(key);
260
261 // copy the conn details. We do not want the original FD to be re-used by IDENT.
262 state->conn = conn->cloneIdentDetails();
263 // NP: use random port for secure outbound to IDENT_PORT
264 state->conn->local.port(0);
265 state->conn->remote.port(IDENT_PORT);
266
267 // build our query from the original connection details
268 state->queryMsg.init();
269 state->queryMsg.appendf("%d, %d\r\n", conn->remote.port(), conn->local.port());
270
271 ClientAdd(state, callback, data);
272 hash_join(ident_hash, &state->hash);
273
274 AsyncCall::Pointer call = commCbCall(30,3, "Ident::ConnectDone", CommConnectCbPtrFun(Ident::ConnectDone, state));
275 AsyncJob::Start(new Comm::ConnOpener(state->conn, call, Ident::TheConfig.timeout));
276 }
277
278 void
279 Ident::Init(void)
280 {
281 if (ident_hash) {
282 debugs(30, DBG_CRITICAL, "WARNING: Ident already initialized.");
283 return;
284 }
285
286 ident_hash = hash_create((HASHCMP *) strcmp,
287 hashPrime(Squid_MaxFD / 8),
288 hash4);
289 }
290
291 #endif /* USE_IDENT */
292