]>
Commit | Line | Data |
---|---|---|
189935b1 | 1 | /* |
2 | * IRC - Internet Relay Chat, ircd/s_bsd.c | |
3 | * Copyright (C) 1990 Jarkko Oikarinen and | |
4 | * University of Oulu, Computing Center | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation; either version 1, or (at your option) | |
9 | * any later version. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | * GNU General Public License for more details. | |
15 | * | |
16 | * You should have received a copy of the GNU General Public License | |
17 | * along with this program; if not, write to the Free Software | |
18 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
19 | */ | |
20 | /** @file | |
21 | * @brief Functions that now (or in the past) relied on BSD APIs. | |
22 | * @version $Id: s_bsd.c,v 1.80 2005/07/12 02:13:10 entrope Exp $ | |
23 | */ | |
24 | #include "config.h" | |
25 | ||
26 | #include "s_bsd.h" | |
27 | #include "client.h" | |
28 | #include "IPcheck.h" | |
29 | #include "channel.h" | |
30 | #include "class.h" | |
31 | #include "hash.h" | |
32 | #include "ircd_alloc.h" | |
33 | #include "ircd_log.h" | |
34 | #include "ircd_features.h" | |
35 | #include "ircd_osdep.h" | |
36 | #include "ircd_reply.h" | |
37 | #include "ircd_snprintf.h" | |
38 | #include "ircd_string.h" | |
39 | #include "ircd.h" | |
40 | #include "list.h" | |
41 | #include "listener.h" | |
42 | #include "msg.h" | |
43 | #include "msgq.h" | |
44 | #include "numeric.h" | |
45 | #include "numnicks.h" | |
46 | #include "packet.h" | |
47 | #include "parse.h" | |
48 | #include "querycmds.h" | |
49 | #include "res.h" | |
50 | #include "s_auth.h" | |
51 | #include "s_conf.h" | |
52 | #include "s_debug.h" | |
53 | #include "s_misc.h" | |
54 | #include "s_user.h" | |
55 | #include "send.h" | |
56 | #include "struct.h" | |
57 | #include "sys.h" | |
58 | #include "uping.h" | |
59 | #include "version.h" | |
60 | ||
61 | /* #include <assert.h> -- Now using assert in ircd_log.h */ | |
62 | #include <errno.h> | |
63 | #include <fcntl.h> | |
64 | #include <netdb.h> | |
65 | #include <stdio.h> | |
66 | #include <stdlib.h> | |
67 | #include <string.h> | |
68 | #include <sys/ioctl.h> | |
69 | #include <sys/socket.h> | |
70 | #include <sys/time.h> | |
71 | #include <sys/utsname.h> | |
72 | #include <unistd.h> | |
73 | ||
74 | /** Array of my own clients, indexed by file descriptor. */ | |
75 | struct Client* LocalClientArray[MAXCONNECTIONS]; | |
76 | /** Maximum file descriptor in current use. */ | |
77 | int HighestFd = -1; | |
78 | /** Default local address for outbound IPv4 connections. */ | |
79 | struct irc_sockaddr VirtualHost_v4; | |
80 | /** Default local address for outbound IPv6 connections. */ | |
81 | struct irc_sockaddr VirtualHost_v6; | |
82 | /** Temporary buffer for reading data from a peer. */ | |
83 | static char readbuf[SERVER_TCP_WINDOW]; | |
84 | ||
85 | /* | |
86 | * report_error text constants | |
87 | */ | |
88 | const char* const ACCEPT_ERROR_MSG = "error accepting connection for %s: %s"; | |
89 | const char* const BIND_ERROR_MSG = "bind error for %s: %s"; | |
90 | const char* const CONNECT_ERROR_MSG = "connect to host %s failed: %s"; | |
91 | const char* const CONNLIMIT_ERROR_MSG = "connect limit exceeded for %s: %s"; | |
92 | const char* const LISTEN_ERROR_MSG = "listen error for %s: %s"; | |
93 | const char* const NONB_ERROR_MSG = "error setting non-blocking for %s: %s"; | |
94 | const char* const PEERNAME_ERROR_MSG = "getpeername failed for %s: %s"; | |
95 | const char* const POLL_ERROR_MSG = "poll error for %s: %s"; | |
96 | const char* const REGISTER_ERROR_MSG = "registering %s: %s"; | |
97 | const char* const REUSEADDR_ERROR_MSG = "error setting SO_REUSEADDR for %s: %s"; | |
98 | const char* const SELECT_ERROR_MSG = "select error for %s: %s"; | |
99 | const char* const SETBUFS_ERROR_MSG = "error setting buffer size for %s: %s"; | |
100 | const char* const SOCKET_ERROR_MSG = "error creating socket for %s: %s"; | |
101 | const char* const TOS_ERROR_MSG = "error setting TOS for %s: %s"; | |
102 | ||
103 | ||
104 | static void client_sock_callback(struct Event* ev); | |
105 | static void client_timer_callback(struct Event* ev); | |
106 | ||
107 | ||
108 | /* | |
109 | * Cannot use perror() within daemon. stderr is closed in | |
110 | * ircd and cannot be used. And, worse yet, it might have | |
111 | * been reassigned to a normal connection... | |
112 | */ | |
113 | ||
114 | /** Replacement for perror(). Record error to log. Send a copy to all | |
115 | * *LOCAL* opers, but only if no errors were sent to them in the last | |
116 | * 20 seconds. | |
117 | * @param text A *format* string for outputting error. It must contain | |
118 | * only two '%s', the first will be replaced by the sockhost from the | |
119 | * cptr, and the latter will be taken from sys_errlist[errno]. | |
120 | * @param who The client associated with the error. | |
121 | * @param err The errno value to display. | |
122 | */ | |
123 | void report_error(const char* text, const char* who, int err) | |
124 | { | |
125 | static time_t last_notice = 0; | |
126 | int errtmp = errno; /* debug may change 'errno' */ | |
127 | const char* errmsg = (err) ? strerror(err) : ""; | |
128 | ||
129 | if (!errmsg) | |
130 | errmsg = "Unknown error"; | |
131 | ||
132 | if (EmptyString(who)) | |
133 | who = "unknown"; | |
134 | ||
135 | if (last_notice + 20 < CurrentTime) { | |
136 | /* | |
137 | * pace error messages so opers don't get flooded by transients | |
138 | */ | |
139 | sendto_opmask_butone(0, SNO_OLDSNO, text, who, errmsg); | |
140 | last_notice = CurrentTime; | |
141 | } | |
142 | log_write(LS_SOCKET, L_ERROR, 0, text, who, errmsg); | |
143 | errno = errtmp; | |
144 | } | |
145 | ||
146 | ||
147 | /** Called when resolver query finishes. If the DNS lookup was | |
148 | * successful, start the connection; otherwise notify opers of the | |
149 | * failure. | |
150 | * @param vptr The struct ConfItem representing the Connect block. | |
151 | * @param hp A pointer to the DNS lookup results (NULL on failure). | |
152 | */ | |
153 | static void connect_dns_callback(void* vptr, const struct irc_in_addr *addr, const char *h_name) | |
154 | { | |
155 | struct ConfItem* aconf = (struct ConfItem*) vptr; | |
156 | assert(aconf); | |
157 | aconf->dns_pending = 0; | |
158 | if (addr) { | |
159 | memcpy(&aconf->address, addr, sizeof(aconf->address)); | |
160 | connect_server(aconf, 0); | |
161 | } | |
162 | else | |
163 | sendto_opmask_butone(0, SNO_OLDSNO, "Connect to %s failed: host lookup", | |
164 | aconf->name); | |
165 | } | |
166 | ||
167 | /** Closes all file descriptors. | |
168 | * @param close_stderr If non-zero, also close stderr. | |
169 | */ | |
170 | void close_connections(int close_stderr) | |
171 | { | |
172 | int i; | |
173 | if (close_stderr) | |
174 | { | |
175 | close(0); | |
176 | close(1); | |
177 | close(2); | |
178 | } | |
179 | for (i = 3; i < MAXCONNECTIONS; ++i) | |
180 | close(i); | |
181 | } | |
182 | ||
183 | /** Initialize process fd limit to MAXCONNECTIONS. | |
184 | */ | |
185 | int init_connection_limits(void) | |
186 | { | |
187 | int limit = os_set_fdlimit(MAXCONNECTIONS); | |
188 | if (0 == limit) | |
189 | return 1; | |
190 | if (limit < 0) { | |
191 | fprintf(stderr, "error setting max fd's to %d\n", limit); | |
192 | } | |
193 | else if (limit > 0) { | |
194 | fprintf(stderr, "ircd fd table too big\nHard Limit: %d IRC max: %d\n", | |
195 | limit, MAXCONNECTIONS); | |
196 | fprintf(stderr, "set MAXCONNECTIONS to a smaller value"); | |
197 | } | |
198 | return 0; | |
199 | } | |
200 | ||
201 | /** Set up address and port and make a connection. | |
202 | * @param aconf Provides the connection information. | |
203 | * @param cptr Client structure for the peer. | |
204 | * @return Non-zero on success; zero on failure. | |
205 | */ | |
206 | static int connect_inet(struct ConfItem* aconf, struct Client* cptr) | |
207 | { | |
208 | const struct irc_sockaddr *local; | |
209 | IOResult result; | |
210 | assert(0 != aconf); | |
211 | assert(0 != cptr); | |
212 | /* | |
213 | * Might as well get sockhost from here, the connection is attempted | |
214 | * with it so if it fails its useless. | |
215 | */ | |
216 | if (irc_in_addr_valid(&aconf->origin.addr)) | |
217 | local = &aconf->origin; | |
218 | else if (irc_in_addr_is_ipv4(&aconf->address.addr)) | |
219 | local = &VirtualHost_v4; | |
220 | else | |
221 | local = &VirtualHost_v6; | |
222 | cli_fd(cptr) = os_socket(local, SOCK_STREAM, cli_name(cptr)); | |
223 | if (cli_fd(cptr) < 0) | |
224 | return 0; | |
225 | ||
226 | /* | |
227 | * save connection info in client | |
228 | */ | |
229 | memcpy(&cli_ip(cptr), &aconf->address.addr, sizeof(cli_ip(cptr))); | |
230 | ircd_ntoa_r(cli_sock_ip(cptr), &cli_ip(cptr)); | |
231 | /* | |
232 | * we want a big buffer for server connections | |
233 | */ | |
234 | if (!os_set_sockbufs(cli_fd(cptr), feature_int(FEAT_SOCKSENDBUF), feature_int(FEAT_SOCKRECVBUF))) { | |
235 | cli_error(cptr) = errno; | |
236 | report_error(SETBUFS_ERROR_MSG, cli_name(cptr), errno); | |
237 | close(cli_fd(cptr)); | |
238 | cli_fd(cptr) = -1; | |
239 | return 0; | |
240 | } | |
241 | /* | |
242 | * Set the TOS bits - this is nonfatal if it doesn't stick. | |
243 | */ | |
244 | if (!os_set_tos(cli_fd(cptr), FEAT_TOS_SERVER)) { | |
245 | report_error(TOS_ERROR_MSG, cli_name(cptr), errno); | |
246 | } | |
247 | if ((result = os_connect_nonb(cli_fd(cptr), &aconf->address)) == IO_FAILURE) { | |
248 | cli_error(cptr) = errno; | |
249 | report_error(CONNECT_ERROR_MSG, cli_name(cptr), errno); | |
250 | close(cli_fd(cptr)); | |
251 | cli_fd(cptr) = -1; | |
252 | return 0; | |
253 | } | |
254 | if (!socket_add(&(cli_socket(cptr)), client_sock_callback, | |
255 | (void*) cli_connect(cptr), | |
256 | (result == IO_SUCCESS) ? SS_CONNECTED : SS_CONNECTING, | |
257 | SOCK_EVENT_READABLE, cli_fd(cptr))) { | |
258 | cli_error(cptr) = ENFILE; | |
259 | report_error(REGISTER_ERROR_MSG, cli_name(cptr), ENFILE); | |
260 | close(cli_fd(cptr)); | |
261 | cli_fd(cptr) = -1; | |
262 | return 0; | |
263 | } | |
264 | cli_freeflag(cptr) |= FREEFLAG_SOCKET; | |
265 | return 1; | |
266 | } | |
267 | ||
268 | /** Attempt to send a sequence of bytes to the connection. | |
269 | * As a side effect, updates \a cptr's FLAG_BLOCKED setting | |
270 | * and sendB/sendK fields. | |
271 | * @param cptr Client that should receive data. | |
272 | * @param buf Message buffer to send to client. | |
273 | * @return Negative on connection-fatal error; otherwise | |
274 | * number of bytes sent. | |
275 | */ | |
276 | unsigned int deliver_it(struct Client *cptr, struct MsgQ *buf) | |
277 | { | |
278 | unsigned int bytes_written = 0; | |
279 | unsigned int bytes_count = 0; | |
280 | assert(0 != cptr); | |
281 | ||
282 | switch (os_sendv_nonb(cli_fd(cptr), buf, &bytes_count, &bytes_written)) { | |
283 | case IO_SUCCESS: | |
284 | ClrFlag(cptr, FLAG_BLOCKED); | |
285 | ||
286 | cli_sendB(cptr) += bytes_written; | |
287 | cli_sendB(&me) += bytes_written; | |
288 | /* A partial write implies that future writes will block. */ | |
289 | if (bytes_written < bytes_count) | |
290 | SetFlag(cptr, FLAG_BLOCKED); | |
291 | break; | |
292 | case IO_BLOCKED: | |
293 | SetFlag(cptr, FLAG_BLOCKED); | |
294 | break; | |
295 | case IO_FAILURE: | |
296 | cli_error(cptr) = errno; | |
297 | SetFlag(cptr, FLAG_DEADSOCKET); | |
298 | break; | |
299 | } | |
300 | return bytes_written; | |
301 | } | |
302 | ||
303 | /** Complete non-blocking connect()-sequence. Check access and | |
304 | * terminate connection, if trouble detected. | |
305 | * @param cptr Client to which we have connected, with all ConfItem structs attached. | |
306 | * @return Zero on failure (caller should exit_client()), non-zero on success. | |
307 | */ | |
308 | static int completed_connection(struct Client* cptr) | |
309 | { | |
310 | struct ConfItem *aconf; | |
311 | time_t newts; | |
312 | struct Client *acptr; | |
313 | int i; | |
314 | ||
315 | assert(0 != cptr); | |
316 | ||
317 | /* | |
318 | * get the socket status from the fd first to check if | |
319 | * connection actually succeeded | |
320 | */ | |
321 | if ((cli_error(cptr) = os_get_sockerr(cli_fd(cptr)))) { | |
322 | const char* msg = strerror(cli_error(cptr)); | |
323 | if (!msg) | |
324 | msg = "Unknown error"; | |
325 | sendto_opmask_butone(0, SNO_OLDSNO, "Connection failed to %s: %s", | |
326 | cli_name(cptr), msg); | |
327 | return 0; | |
328 | } | |
329 | if (!(aconf = find_conf_byname(cli_confs(cptr), cli_name(cptr), CONF_SERVER))) { | |
330 | sendto_opmask_butone(0, SNO_OLDSNO, "Lost Server Line for %s", cli_name(cptr)); | |
331 | return 0; | |
332 | } | |
333 | if (s_state(&(cli_socket(cptr))) == SS_CONNECTING) | |
334 | socket_state(&(cli_socket(cptr)), SS_CONNECTED); | |
335 | ||
336 | if (!EmptyString(aconf->passwd)) | |
337 | sendrawto_one(cptr, MSG_PASS " :%s", aconf->passwd); | |
338 | ||
339 | /* | |
340 | * Create a unique timestamp | |
341 | */ | |
342 | newts = TStime(); | |
343 | for (i = HighestFd; i > -1; --i) { | |
344 | if ((acptr = LocalClientArray[i]) && | |
345 | (IsServer(acptr) || IsHandshake(acptr))) { | |
346 | if (cli_serv(acptr)->timestamp >= newts) | |
347 | newts = cli_serv(acptr)->timestamp + 1; | |
348 | } | |
349 | } | |
350 | assert(0 != cli_serv(cptr)); | |
351 | ||
352 | cli_serv(cptr)->timestamp = newts; | |
353 | SetHandshake(cptr); | |
354 | /* | |
355 | * Make us timeout after twice the timeout for DNS look ups | |
356 | */ | |
357 | cli_lasttime(cptr) = CurrentTime; | |
358 | SetFlag(cptr, FLAG_PINGSENT); | |
359 | ||
360 | sendrawto_one(cptr, MSG_SERVER " %s 1 %Tu %Tu J%s %s%s +%s6 :%s", | |
361 | cli_name(&me), cli_serv(&me)->timestamp, newts, | |
362 | MAJOR_PROTOCOL, NumServCap(&me), | |
363 | feature_bool(FEAT_HUB) ? "h" : "", cli_info(&me)); | |
364 | ||
365 | return (IsDead(cptr)) ? 0 : 1; | |
366 | } | |
367 | ||
368 | /** Close the physical connection. Side effects: MyConnect(cptr) | |
369 | * becomes false and cptr->from becomes NULL. | |
370 | * @param cptr Client to disconnect. | |
371 | */ | |
372 | void close_connection(struct Client *cptr) | |
373 | { | |
374 | struct ConfItem* aconf; | |
375 | ||
376 | if (IsServer(cptr)) { | |
377 | ServerStats->is_sv++; | |
378 | ServerStats->is_sbs += cli_sendB(cptr); | |
379 | ServerStats->is_sbr += cli_receiveB(cptr); | |
380 | ServerStats->is_sti += CurrentTime - cli_firsttime(cptr); | |
381 | /* | |
382 | * If the connection has been up for a long amount of time, schedule | |
383 | * a 'quick' reconnect, else reset the next-connect cycle. | |
384 | */ | |
385 | if ((aconf = find_conf_exact(cli_name(cptr), cptr, CONF_SERVER))) { | |
386 | /* | |
387 | * Reschedule a faster reconnect, if this was a automatically | |
388 | * connected configuration entry. (Note that if we have had | |
389 | * a rehash in between, the status has been changed to | |
390 | * CONF_ILLEGAL). But only do this if it was a "good" link. | |
391 | */ | |
392 | aconf->hold = CurrentTime; | |
393 | aconf->hold += ((aconf->hold - cli_since(cptr) > | |
394 | feature_int(FEAT_HANGONGOODLINK)) ? | |
395 | feature_int(FEAT_HANGONRETRYDELAY) : ConfConFreq(aconf)); | |
396 | /* if (nextconnect > aconf->hold) */ | |
397 | /* nextconnect = aconf->hold; */ | |
398 | } | |
399 | } | |
400 | else if (IsUser(cptr)) { | |
401 | ServerStats->is_cl++; | |
402 | ServerStats->is_cbs += cli_sendB(cptr); | |
403 | ServerStats->is_cbr += cli_receiveB(cptr); | |
404 | ServerStats->is_cti += CurrentTime - cli_firsttime(cptr); | |
405 | } | |
406 | else | |
407 | ServerStats->is_ni++; | |
408 | ||
409 | if (-1 < cli_fd(cptr)) { | |
410 | flush_connections(cptr); | |
411 | LocalClientArray[cli_fd(cptr)] = 0; | |
412 | close(cli_fd(cptr)); | |
413 | socket_del(&(cli_socket(cptr))); /* queue a socket delete */ | |
414 | cli_fd(cptr) = -1; | |
415 | } | |
416 | SetFlag(cptr, FLAG_DEADSOCKET); | |
417 | ||
418 | MsgQClear(&(cli_sendQ(cptr))); | |
419 | client_drop_sendq(cli_connect(cptr)); | |
420 | DBufClear(&(cli_recvQ(cptr))); | |
421 | memset(cli_passwd(cptr), 0, sizeof(cli_passwd(cptr))); | |
422 | set_snomask(cptr, 0, SNO_SET); | |
423 | ||
424 | det_confs_butmask(cptr, 0); | |
425 | ||
426 | if (cli_listener(cptr)) { | |
427 | release_listener(cli_listener(cptr)); | |
428 | cli_listener(cptr) = 0; | |
429 | } | |
430 | ||
431 | for ( ; HighestFd > 0; --HighestFd) { | |
432 | if (LocalClientArray[HighestFd]) | |
433 | break; | |
434 | } | |
435 | } | |
436 | ||
437 | /** Close all unregistered connections. | |
438 | * @param source Oper who requested the close. | |
439 | * @return Number of closed connections. | |
440 | */ | |
441 | int net_close_unregistered_connections(struct Client* source) | |
442 | { | |
443 | int i; | |
444 | struct Client* cptr; | |
445 | int count = 0; | |
446 | assert(0 != source); | |
447 | ||
448 | for (i = HighestFd; i > 0; --i) { | |
449 | if ((cptr = LocalClientArray[i]) && !IsRegistered(cptr)) { | |
450 | send_reply(source, RPL_CLOSING, get_client_name(source, HIDE_IP)); | |
451 | exit_client(source, cptr, &me, "Oper Closing"); | |
452 | ++count; | |
453 | } | |
454 | } | |
455 | return count; | |
456 | } | |
457 | ||
458 | /** Creates a client which has just connected to us on the given fd. | |
459 | * The sockhost field is initialized with the ip# of the host. | |
460 | * The client is not added to the linked list of clients, it is | |
461 | * passed off to the auth handler for dns and ident queries. | |
462 | * @param listener Listening socket that received the connection. | |
463 | * @param fd File descriptor of new connection. | |
464 | */ | |
465 | void add_connection(struct Listener* listener, int fd) { | |
466 | struct irc_sockaddr addr; | |
467 | struct Client *new_client; | |
468 | time_t next_target = 0; | |
469 | ||
470 | const char* const throttle_message = | |
471 | "ERROR :Your host is trying to (re)connect too fast -- throttled\r\n"; | |
472 | /* 12345678901234567890123456789012345679012345678901234567890123456 */ | |
473 | const char* const register_message = | |
474 | "ERROR :Unable to complete your registration\r\n"; | |
475 | ||
476 | assert(0 != listener); | |
477 | ||
478 | /* | |
479 | * Removed preliminary access check. Full check is performed in m_server and | |
480 | * m_user instead. Also connection time out help to get rid of unwanted | |
481 | * connections. | |
482 | */ | |
483 | if (!os_get_peername(fd, &addr) || !os_set_nonblocking(fd)) { | |
484 | ++ServerStats->is_ref; | |
485 | close(fd); | |
486 | return; | |
487 | } | |
488 | /* | |
489 | * Disable IP (*not* TCP) options. In particular, this makes it impossible | |
490 | * to use source routing to connect to the server. If we didn't do this | |
491 | * (and if intermediate networks didn't drop source-routed packets), an | |
492 | * attacker could successfully IP spoof us...and even return the anti-spoof | |
493 | * ping, because the options would cause the packet to be routed back to | |
494 | * the spoofer's machine. When we disable the IP options, we delete the | |
495 | * source route, and the normal routing takes over. | |
496 | */ | |
497 | os_disable_options(fd); | |
498 | ||
499 | if (listener->server) | |
500 | { | |
501 | new_client = make_client(0, STAT_UNKNOWN_SERVER); | |
502 | } | |
503 | else | |
504 | { | |
505 | /* | |
506 | * Add this local client to the IPcheck registry. | |
507 | * | |
508 | * If they're throttled, murder them, but tell them why first. | |
509 | */ | |
510 | if (!IPcheck_local_connect(&addr.addr, &next_target)) | |
511 | { | |
512 | ++ServerStats->is_ref; | |
513 | write(fd, throttle_message, strlen(throttle_message)); | |
514 | close(fd); | |
515 | return; | |
516 | } | |
517 | new_client = make_client(0, STAT_UNKNOWN_USER); | |
518 | SetIPChecked(new_client); | |
519 | } | |
520 | ||
521 | /* | |
522 | * Copy ascii address to 'sockhost' just in case. Then we have something | |
523 | * valid to put into error messages... | |
524 | */ | |
525 | ircd_ntoa_r(cli_sock_ip(new_client), &addr.addr); | |
526 | strcpy(cli_sockhost(new_client), cli_sock_ip(new_client)); | |
527 | memcpy(&cli_ip(new_client), &addr.addr, sizeof(cli_ip(new_client))); | |
528 | ||
529 | if (next_target) | |
530 | cli_nexttarget(new_client) = next_target; | |
531 | ||
532 | cli_fd(new_client) = fd; | |
533 | if (!socket_add(&(cli_socket(new_client)), client_sock_callback, | |
534 | (void*) cli_connect(new_client), SS_CONNECTED, 0, fd)) { | |
535 | ++ServerStats->is_ref; | |
536 | write(fd, register_message, strlen(register_message)); | |
537 | close(fd); | |
538 | cli_fd(new_client) = -1; | |
539 | return; | |
540 | } | |
541 | cli_freeflag(new_client) |= FREEFLAG_SOCKET; | |
542 | cli_listener(new_client) = listener; | |
543 | ++listener->ref_count; | |
544 | ||
545 | Count_newunknown(UserStats); | |
546 | /* if we've made it this far we can put the client on the auth query pile */ | |
547 | start_auth(new_client); | |
548 | } | |
549 | ||
550 | /** Determines whether to tell the events engine we're interested in | |
551 | * writable events. | |
552 | * @param cptr Client for which to decide this. | |
553 | */ | |
554 | void update_write(struct Client* cptr) | |
555 | { | |
556 | /* If there are messages that need to be sent along, or if the client | |
557 | * is in the middle of a /list, then we need to tell the engine that | |
558 | * we're interested in writable events--otherwise, we need to drop | |
559 | * that interest. | |
560 | */ | |
561 | socket_events(&(cli_socket(cptr)), | |
562 | ((MsgQLength(&cli_sendQ(cptr)) || cli_listing(cptr)) ? | |
563 | SOCK_ACTION_ADD : SOCK_ACTION_DEL) | SOCK_EVENT_WRITABLE); | |
564 | } | |
565 | ||
566 | /** Read a 'packet' of data from a connection and process it. Read in | |
567 | * 8k chunks to give a better performance rating (for server | |
568 | * connections). Do some tricky stuff for client connections to make | |
569 | * sure they don't do any flooding >:-) -avalon | |
570 | * @param cptr Client from which to read data. | |
571 | * @param socket_ready If non-zero, more data can be read from the client's socket. | |
572 | * @return Positive number on success, zero on connection-fatal failure, negative | |
573 | * if user is killed. | |
574 | */ | |
575 | static int read_packet(struct Client *cptr, int socket_ready) | |
576 | { | |
577 | unsigned int dolen = 0; | |
578 | unsigned int length = 0; | |
579 | ||
580 | if (socket_ready && | |
581 | !(IsUser(cptr) && | |
582 | DBufLength(&(cli_recvQ(cptr))) > feature_int(FEAT_CLIENT_FLOOD))) { | |
583 | switch (os_recv_nonb(cli_fd(cptr), readbuf, sizeof(readbuf), &length)) { | |
584 | case IO_SUCCESS: | |
585 | if (length) | |
586 | { | |
587 | if (!IsServer(cptr)) | |
588 | cli_lasttime(cptr) = CurrentTime; | |
589 | if (cli_lasttime(cptr) > cli_since(cptr)) | |
590 | cli_since(cptr) = cli_lasttime(cptr); | |
591 | ClrFlag(cptr, FLAG_PINGSENT); | |
592 | ClrFlag(cptr, FLAG_NONL); | |
593 | } | |
594 | break; | |
595 | case IO_BLOCKED: | |
596 | break; | |
597 | case IO_FAILURE: | |
598 | cli_error(cptr) = errno; | |
599 | /* SetFlag(cptr, FLAG_DEADSOCKET); */ | |
600 | return 0; | |
601 | } | |
602 | } | |
603 | ||
604 | /* | |
605 | * For server connections, we process as many as we can without | |
606 | * worrying about the time of day or anything :) | |
607 | */ | |
608 | if (length > 0 && IsServer(cptr)) | |
609 | return server_dopacket(cptr, readbuf, length); | |
610 | else if (length > 0 && (IsHandshake(cptr) || IsConnecting(cptr))) | |
611 | return connect_dopacket(cptr, readbuf, length); | |
612 | else | |
613 | { | |
614 | /* | |
615 | * Before we even think of parsing what we just read, stick | |
616 | * it on the end of the receive queue and do it when its | |
617 | * turn comes around. | |
618 | */ | |
619 | if (length > 0 && dbuf_put(&(cli_recvQ(cptr)), readbuf, length) == 0) | |
620 | return exit_client(cptr, cptr, &me, "dbuf_put fail"); | |
621 | ||
622 | if (DBufLength(&(cli_recvQ(cptr))) > feature_int(FEAT_CLIENT_FLOOD)) | |
623 | return exit_client(cptr, cptr, &me, "Excess Flood"); | |
624 | ||
625 | while (DBufLength(&(cli_recvQ(cptr))) && !NoNewLine(cptr) && | |
626 | (IsTrusted(cptr) || cli_since(cptr) - CurrentTime < 10)) | |
627 | { | |
628 | dolen = dbuf_getmsg(&(cli_recvQ(cptr)), cli_buffer(cptr), BUFSIZE); | |
629 | /* | |
630 | * Devious looking...whats it do ? well..if a client | |
631 | * sends a *long* message without any CR or LF, then | |
632 | * dbuf_getmsg fails and we pull it out using this | |
633 | * loop which just gets the next 512 bytes and then | |
634 | * deletes the rest of the buffer contents. | |
635 | * -avalon | |
636 | */ | |
637 | if (dolen == 0) | |
638 | { | |
639 | if (DBufLength(&(cli_recvQ(cptr))) < 510) | |
640 | SetFlag(cptr, FLAG_NONL); | |
641 | else | |
642 | DBufClear(&(cli_recvQ(cptr))); | |
643 | } | |
644 | else if (client_dopacket(cptr, dolen) == CPTR_KILLED) | |
645 | return CPTR_KILLED; | |
646 | /* | |
647 | * If it has become registered as a Server | |
648 | * then skip the per-message parsing below. | |
649 | */ | |
650 | if (IsHandshake(cptr) || IsServer(cptr)) | |
651 | { | |
652 | while (-1) | |
653 | { | |
654 | dolen = dbuf_get(&(cli_recvQ(cptr)), readbuf, sizeof(readbuf)); | |
655 | if (dolen <= 0) | |
656 | return 1; | |
657 | else if (dolen == 0) | |
658 | { | |
659 | if (DBufLength(&(cli_recvQ(cptr))) < 510) | |
660 | SetFlag(cptr, FLAG_NONL); | |
661 | else | |
662 | DBufClear(&(cli_recvQ(cptr))); | |
663 | } | |
664 | else if ((IsServer(cptr) && | |
665 | server_dopacket(cptr, readbuf, dolen) == CPTR_KILLED) || | |
666 | (!IsServer(cptr) && | |
667 | connect_dopacket(cptr, readbuf, dolen) == CPTR_KILLED)) | |
668 | return CPTR_KILLED; | |
669 | } | |
670 | } | |
671 | } | |
672 | ||
673 | /* If there's still data to process, wait 2 seconds first */ | |
674 | if (DBufLength(&(cli_recvQ(cptr))) && !NoNewLine(cptr) && | |
675 | !t_onqueue(&(cli_proc(cptr)))) | |
676 | { | |
677 | Debug((DEBUG_LIST, "Adding client process timer for %C", cptr)); | |
678 | cli_freeflag(cptr) |= FREEFLAG_TIMER; | |
679 | timer_add(&(cli_proc(cptr)), client_timer_callback, cli_connect(cptr), | |
680 | TT_RELATIVE, 2); | |
681 | } | |
682 | } | |
683 | return 1; | |
684 | } | |
685 | ||
686 | /** Start a connection to another server. | |
687 | * @param aconf Connect block data for target server. | |
688 | * @param by Client who requested the connection (if any). | |
689 | * @return Non-zero on success; zero on failure. | |
690 | */ | |
691 | int connect_server(struct ConfItem* aconf, struct Client* by) | |
692 | { | |
693 | struct Client* cptr = 0; | |
694 | assert(0 != aconf); | |
695 | ||
696 | if (aconf->dns_pending) { | |
697 | sendto_opmask_butone(0, SNO_OLDSNO, "Server %s connect DNS pending", | |
698 | aconf->name); | |
699 | return 0; | |
700 | } | |
701 | Debug((DEBUG_NOTICE, "Connect to %s[@%s]", aconf->name, | |
702 | ircd_ntoa(&aconf->address.addr))); | |
703 | ||
704 | if ((cptr = FindClient(aconf->name))) { | |
705 | if (IsServer(cptr) || IsMe(cptr)) { | |
706 | sendto_opmask_butone(0, SNO_OLDSNO, "Server %s already present from %s", | |
707 | aconf->name, cli_name(cli_from(cptr))); | |
708 | if (by && IsUser(by) && !MyUser(by)) { | |
709 | sendcmdto_one(&me, CMD_NOTICE, by, "%C :Server %s already present " | |
710 | "from %s", by, aconf->name, cli_name(cli_from(cptr))); | |
711 | } | |
712 | return 0; | |
713 | } | |
714 | else if (IsHandshake(cptr) || IsConnecting(cptr)) { | |
715 | if (by && IsUser(by)) { | |
716 | sendcmdto_one(&me, CMD_NOTICE, by, "%C :Connection to %s already in " | |
717 | "progress", by, cli_name(cptr)); | |
718 | } | |
719 | return 0; | |
720 | } | |
721 | } | |
722 | /* | |
723 | * If we don't know the IP# for this host and it is a hostname and | |
724 | * not a ip# string, then try and find the appropriate host record. | |
725 | */ | |
726 | if (!irc_in_addr_valid(&aconf->address.addr) | |
727 | && !ircd_aton(&aconf->address.addr, aconf->host)) { | |
728 | char buf[HOSTLEN + 1]; | |
729 | ||
730 | host_from_uh(buf, aconf->host, HOSTLEN); | |
731 | gethost_byname(buf, connect_dns_callback, aconf); | |
732 | aconf->dns_pending = 1; | |
733 | return 0; | |
734 | } | |
735 | cptr = make_client(NULL, STAT_UNKNOWN_SERVER); | |
736 | ||
737 | /* | |
738 | * Copy these in so we have something for error detection. | |
739 | */ | |
740 | ircd_strncpy(cli_name(cptr), aconf->name, HOSTLEN); | |
741 | ircd_strncpy(cli_sockhost(cptr), aconf->host, HOSTLEN); | |
742 | ||
743 | /* | |
744 | * Attach config entries to client here rather than in | |
745 | * completed_connection. This to avoid null pointer references | |
746 | */ | |
747 | attach_confs_byhost(cptr, aconf->host, CONF_SERVER); | |
748 | ||
749 | if (!find_conf_byhost(cli_confs(cptr), aconf->host, CONF_SERVER)) { | |
750 | sendto_opmask_butone(0, SNO_OLDSNO, "Host %s is not enabled for " | |
751 | "connecting: no Connect block", aconf->name); | |
752 | if (by && IsUser(by) && !MyUser(by)) { | |
753 | sendcmdto_one(&me, CMD_NOTICE, by, "%C :Connect to host %s failed: no " | |
754 | "Connect block", by, aconf->name); | |
755 | } | |
756 | det_confs_butmask(cptr, 0); | |
757 | free_client(cptr); | |
758 | return 0; | |
759 | } | |
760 | /* | |
761 | * attempt to connect to the server in the conf line | |
762 | */ | |
763 | if (!connect_inet(aconf, cptr)) { | |
764 | if (by && IsUser(by) && !MyUser(by)) { | |
765 | sendcmdto_one(&me, CMD_NOTICE, by, "%C :Couldn't connect to %s", by, | |
766 | cli_name(cptr)); | |
767 | } | |
768 | det_confs_butmask(cptr, 0); | |
769 | free_client(cptr); | |
770 | return 0; | |
771 | } | |
772 | /* | |
773 | * NOTE: if we're here we have a valid C:Line and the client should | |
774 | * have started the connection and stored the remote address/port and | |
775 | * ip address name in itself | |
776 | * | |
777 | * The socket has been connected or connect is in progress. | |
778 | */ | |
779 | make_server(cptr); | |
780 | if (by && IsUser(by)) { | |
781 | ircd_snprintf(0, cli_serv(cptr)->by, sizeof(cli_serv(cptr)->by), "%s%s", | |
782 | NumNick(by)); | |
783 | assert(0 == cli_serv(cptr)->user); | |
784 | cli_serv(cptr)->user = cli_user(by); | |
785 | cli_user(by)->refcnt++; | |
786 | } | |
787 | else { | |
788 | *(cli_serv(cptr))->by = '\0'; | |
789 | /* strcpy(cptr->serv->by, "Auto"); */ | |
790 | } | |
791 | cli_serv(cptr)->up = &me; | |
792 | SetConnecting(cptr); | |
793 | ||
794 | if (cli_fd(cptr) > HighestFd) | |
795 | HighestFd = cli_fd(cptr); | |
796 | ||
797 | LocalClientArray[cli_fd(cptr)] = cptr; | |
798 | ||
799 | Count_newunknown(UserStats); | |
800 | /* Actually we lie, the connect hasn't succeeded yet, but we have a valid | |
801 | * cptr, so we register it now. | |
802 | * Maybe these two calls should be merged. | |
803 | */ | |
804 | add_client_to_list(cptr); | |
805 | hAddClient(cptr); | |
806 | /* nextping = CurrentTime; */ | |
807 | ||
808 | return (s_state(&cli_socket(cptr)) == SS_CONNECTED) ? | |
809 | completed_connection(cptr) : 1; | |
810 | } | |
811 | ||
812 | /** Find the real hostname for the host running the server (or one which | |
813 | * matches the server's name) and its primary IP#. Hostname is stored | |
814 | * in the client structure passed as a pointer. | |
815 | */ | |
816 | void init_server_identity(void) | |
817 | { | |
818 | const struct LocalConf* conf = conf_get_local(); | |
819 | assert(0 != conf); | |
820 | ||
821 | ircd_strncpy(cli_name(&me), conf->name, HOSTLEN); | |
822 | SetYXXServerName(&me, conf->numeric); | |
823 | } | |
824 | ||
825 | /** Process events on a client socket. | |
826 | * @param ev Socket event structure that has a struct Connection as | |
827 | * its associated data. | |
828 | */ | |
829 | static void client_sock_callback(struct Event* ev) | |
830 | { | |
831 | struct Client* cptr; | |
832 | struct Connection* con; | |
833 | char *fmt = "%s"; | |
834 | char *fallback = 0; | |
835 | ||
836 | assert(0 != ev_socket(ev)); | |
837 | assert(0 != s_data(ev_socket(ev))); | |
838 | ||
839 | con = (struct Connection*) s_data(ev_socket(ev)); | |
840 | ||
841 | assert(0 != con_client(con) || ev_type(ev) == ET_DESTROY); | |
842 | ||
843 | cptr = con_client(con); | |
844 | ||
845 | assert(0 == cptr || con == cli_connect(cptr)); | |
846 | ||
847 | switch (ev_type(ev)) { | |
848 | case ET_DESTROY: | |
849 | con_freeflag(con) &= ~FREEFLAG_SOCKET; | |
850 | ||
851 | if (!con_freeflag(con) && !cptr) | |
852 | free_connection(con); | |
853 | break; | |
854 | ||
855 | case ET_CONNECT: /* socket connection completed */ | |
856 | if (!completed_connection(cptr) || IsDead(cptr)) | |
857 | fallback = cli_info(cptr); | |
858 | break; | |
859 | ||
860 | case ET_ERROR: /* an error occurred */ | |
861 | fallback = cli_info(cptr); | |
862 | cli_error(cptr) = ev_data(ev); | |
863 | if (s_state(&(con_socket(con))) == SS_CONNECTING) { | |
864 | completed_connection(cptr); | |
865 | /* for some reason, the os_get_sockerr() in completed_connect() | |
866 | * can return 0 even when ev_data(ev) indicates a real error, so | |
867 | * re-assign the client error here. | |
868 | */ | |
869 | cli_error(cptr) = ev_data(ev); | |
870 | break; | |
871 | } | |
872 | /*FALLTHROUGH*/ | |
873 | case ET_EOF: /* end of file on socket */ | |
874 | Debug((DEBUG_ERROR, "READ ERROR: fd = %d %d", cli_fd(cptr), | |
875 | cli_error(cptr))); | |
876 | SetFlag(cptr, FLAG_DEADSOCKET); | |
877 | if ((IsServer(cptr) || IsHandshake(cptr)) && cli_error(cptr) == 0) { | |
878 | exit_client_msg(cptr, cptr, &me, "Server %s closed the connection (%s)", | |
879 | cli_name(cptr), cli_serv(cptr)->last_error_msg); | |
880 | return; | |
881 | } else { | |
882 | fmt = "Read error: %s"; | |
883 | fallback = "EOF from client"; | |
884 | } | |
885 | break; | |
886 | ||
887 | case ET_WRITE: /* socket is writable */ | |
888 | ClrFlag(cptr, FLAG_BLOCKED); | |
889 | if (cli_listing(cptr) && MsgQLength(&(cli_sendQ(cptr))) < 2048) | |
890 | list_next_channels(cptr); | |
891 | Debug((DEBUG_SEND, "Sending queued data to %C", cptr)); | |
892 | send_queued(cptr); | |
893 | break; | |
894 | ||
895 | case ET_READ: /* socket is readable */ | |
896 | if (!IsDead(cptr)) { | |
897 | Debug((DEBUG_DEBUG, "Reading data from %C", cptr)); | |
898 | if (read_packet(cptr, 1) == 0) /* error while reading packet */ | |
899 | fallback = "EOF from client"; | |
900 | } | |
901 | break; | |
902 | ||
903 | default: | |
904 | assert(0 && "Unrecognized socket event in client_sock_callback()"); | |
905 | break; | |
906 | } | |
907 | ||
908 | assert(0 == cptr || 0 == cli_connect(cptr) || con == cli_connect(cptr)); | |
909 | ||
910 | if (fallback) { | |
911 | const char* msg = (cli_error(cptr)) ? strerror(cli_error(cptr)) : fallback; | |
912 | if (!msg) | |
913 | msg = "Unknown error"; | |
914 | exit_client_msg(cptr, cptr, &me, fmt, msg); | |
915 | } | |
916 | } | |
917 | ||
918 | /** Process a timer on client socket. | |
919 | * @param ev Timer event that has a struct Connection as its | |
920 | * associated data. | |
921 | */ | |
922 | static void client_timer_callback(struct Event* ev) | |
923 | { | |
924 | struct Client* cptr; | |
925 | struct Connection* con; | |
926 | ||
927 | assert(0 != ev_timer(ev)); | |
928 | assert(0 != t_data(ev_timer(ev))); | |
929 | assert(ET_DESTROY == ev_type(ev) || ET_EXPIRE == ev_type(ev)); | |
930 | ||
931 | con = (struct Connection*) t_data(ev_timer(ev)); | |
932 | ||
933 | assert(0 != con_client(con) || ev_type(ev) == ET_DESTROY); | |
934 | ||
935 | cptr = con_client(con); | |
936 | ||
937 | assert(0 == cptr || con == cli_connect(cptr)); | |
938 | ||
939 | if (ev_type(ev)== ET_DESTROY) { | |
940 | con_freeflag(con) &= ~FREEFLAG_TIMER; /* timer has expired... */ | |
941 | ||
942 | if (!con_freeflag(con) && !cptr) | |
943 | free_connection(con); /* client is being destroyed */ | |
944 | } else { | |
945 | Debug((DEBUG_LIST, "Client process timer for %C expired; processing", | |
946 | cptr)); | |
947 | read_packet(cptr, 0); /* read_packet will re-add timer if needed */ | |
948 | } | |
949 | ||
950 | assert(0 == cptr || 0 == cli_connect(cptr) || con == cli_connect(cptr)); | |
951 | } |