]> git.ipfire.org Git - thirdparty/squid.git/blob - src/ident/Ident.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / ident / Ident.cc
1 /*
2 * Copyright (C) 1996-2015 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];
243
244 conn->local.toUrl(key1, IDENT_KEY_SZ);
245 conn->remote.toUrl(key2, IDENT_KEY_SZ);
246 snprintf(key, IDENT_KEY_SZ, "%s,%s", key1, key2);
247
248 if (!ident_hash) {
249 Init();
250 }
251 if ((state = (IdentStateData *)hash_lookup(ident_hash, key)) != NULL) {
252 ClientAdd(state, callback, data);
253 return;
254 }
255
256 state = new IdentStateData;
257 state->hash.key = xstrdup(key);
258
259 // copy the conn details. We dont want the original FD to be re-used by IDENT.
260 state->conn = conn->copyDetails();
261 // NP: use random port for secure outbound to IDENT_PORT
262 state->conn->local.port(0);
263 state->conn->remote.port(IDENT_PORT);
264
265 // build our query from the original connection details
266 state->queryMsg.init();
267 state->queryMsg.Printf("%d, %d\r\n", conn->remote.port(), conn->local.port());
268
269 ClientAdd(state, callback, data);
270 hash_join(ident_hash, &state->hash);
271
272 AsyncCall::Pointer call = commCbCall(30,3, "Ident::ConnectDone", CommConnectCbPtrFun(Ident::ConnectDone, state));
273 AsyncJob::Start(new Comm::ConnOpener(state->conn, call, Ident::TheConfig.timeout));
274 }
275
276 void
277 Ident::Init(void)
278 {
279 if (ident_hash) {
280 debugs(30, DBG_CRITICAL, "WARNING: Ident already initialized.");
281 return;
282 }
283
284 ident_hash = hash_create((HASHCMP *) strcmp,
285 hashPrime(Squid_MaxFD / 8),
286 hash4);
287 }
288
289 #endif /* USE_IDENT */
290