]>
Commit | Line | Data |
---|---|---|
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.2.1 2006/03/14 03:45:52 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 | int family = 0; | |
211 | ||
212 | assert(0 != aconf); | |
213 | assert(0 != cptr); | |
214 | /* | |
215 | * Might as well get sockhost from here, the connection is attempted | |
216 | * with it so if it fails its useless. | |
217 | */ | |
218 | if (irc_in_addr_valid(&aconf->origin.addr)) | |
219 | local = &aconf->origin; | |
220 | else if (irc_in_addr_is_ipv4(&aconf->address.addr)) { | |
221 | local = &VirtualHost_v4; | |
222 | family = AF_INET; | |
223 | } else | |
224 | local = &VirtualHost_v6; | |
225 | cli_fd(cptr) = os_socket(local, SOCK_STREAM, cli_name(cptr), family); | |
226 | if (cli_fd(cptr) < 0) | |
227 | return 0; | |
228 | ||
229 | /* | |
230 | * save connection info in client | |
231 | */ | |
232 | memcpy(&cli_ip(cptr), &aconf->address.addr, sizeof(cli_ip(cptr))); | |
233 | ircd_ntoa_r(cli_sock_ip(cptr), &cli_ip(cptr)); | |
234 | /* | |
235 | * we want a big buffer for server connections | |
236 | */ | |
237 | if (!os_set_sockbufs(cli_fd(cptr), feature_int(FEAT_SOCKSENDBUF), feature_int(FEAT_SOCKRECVBUF))) { | |
238 | cli_error(cptr) = errno; | |
239 | report_error(SETBUFS_ERROR_MSG, cli_name(cptr), errno); | |
240 | close(cli_fd(cptr)); | |
241 | cli_fd(cptr) = -1; | |
242 | return 0; | |
243 | } | |
244 | /* | |
245 | * Set the TOS bits - this is nonfatal if it doesn't stick. | |
246 | */ | |
247 | if (!os_set_tos(cli_fd(cptr), FEAT_TOS_SERVER)) { | |
248 | report_error(TOS_ERROR_MSG, cli_name(cptr), errno); | |
249 | } | |
250 | if ((result = os_connect_nonb(cli_fd(cptr), &aconf->address)) == IO_FAILURE) { | |
251 | cli_error(cptr) = errno; | |
252 | report_error(CONNECT_ERROR_MSG, cli_name(cptr), errno); | |
253 | close(cli_fd(cptr)); | |
254 | cli_fd(cptr) = -1; | |
255 | return 0; | |
256 | } | |
257 | if (!socket_add(&(cli_socket(cptr)), client_sock_callback, | |
258 | (void*) cli_connect(cptr), | |
259 | (result == IO_SUCCESS) ? SS_CONNECTED : SS_CONNECTING, | |
260 | SOCK_EVENT_READABLE, cli_fd(cptr))) { | |
261 | cli_error(cptr) = ENFILE; | |
262 | report_error(REGISTER_ERROR_MSG, cli_name(cptr), ENFILE); | |
263 | close(cli_fd(cptr)); | |
264 | cli_fd(cptr) = -1; | |
265 | return 0; | |
266 | } | |
267 | cli_freeflag(cptr) |= FREEFLAG_SOCKET; | |
268 | return 1; | |
269 | } | |
270 | ||
271 | /** Attempt to send a sequence of bytes to the connection. | |
272 | * As a side effect, updates \a cptr's FLAG_BLOCKED setting | |
273 | * and sendB/sendK fields. | |
274 | * @param cptr Client that should receive data. | |
275 | * @param buf Message buffer to send to client. | |
276 | * @return Negative on connection-fatal error; otherwise | |
277 | * number of bytes sent. | |
278 | */ | |
279 | unsigned int deliver_it(struct Client *cptr, struct MsgQ *buf) | |
280 | { | |
281 | unsigned int bytes_written = 0; | |
282 | unsigned int bytes_count = 0; | |
283 | assert(0 != cptr); | |
284 | ||
285 | switch (os_sendv_nonb(cli_fd(cptr), buf, &bytes_count, &bytes_written)) { | |
286 | case IO_SUCCESS: | |
287 | ClrFlag(cptr, FLAG_BLOCKED); | |
288 | ||
289 | cli_sendB(cptr) += bytes_written; | |
290 | cli_sendB(&me) += bytes_written; | |
291 | /* A partial write implies that future writes will block. */ | |
292 | if (bytes_written < bytes_count) | |
293 | SetFlag(cptr, FLAG_BLOCKED); | |
294 | break; | |
295 | case IO_BLOCKED: | |
296 | SetFlag(cptr, FLAG_BLOCKED); | |
297 | break; | |
298 | case IO_FAILURE: | |
299 | cli_error(cptr) = errno; | |
300 | SetFlag(cptr, FLAG_DEADSOCKET); | |
301 | break; | |
302 | } | |
303 | return bytes_written; | |
304 | } | |
305 | ||
306 | /** Complete non-blocking connect()-sequence. Check access and | |
307 | * terminate connection, if trouble detected. | |
308 | * @param cptr Client to which we have connected, with all ConfItem structs attached. | |
309 | * @return Zero on failure (caller should exit_client()), non-zero on success. | |
310 | */ | |
311 | static int completed_connection(struct Client* cptr) | |
312 | { | |
313 | struct ConfItem *aconf; | |
314 | time_t newts; | |
315 | struct Client *acptr; | |
316 | int i; | |
317 | ||
318 | assert(0 != cptr); | |
319 | ||
320 | /* | |
321 | * get the socket status from the fd first to check if | |
322 | * connection actually succeeded | |
323 | */ | |
324 | if ((cli_error(cptr) = os_get_sockerr(cli_fd(cptr)))) { | |
325 | const char* msg = strerror(cli_error(cptr)); | |
326 | if (!msg) | |
327 | msg = "Unknown error"; | |
328 | sendto_opmask_butone(0, SNO_OLDSNO, "Connection failed to %s: %s", | |
329 | cli_name(cptr), msg); | |
330 | return 0; | |
331 | } | |
332 | if (!(aconf = find_conf_byname(cli_confs(cptr), cli_name(cptr), CONF_SERVER))) { | |
333 | sendto_opmask_butone(0, SNO_OLDSNO, "Lost Server Line for %s", cli_name(cptr)); | |
334 | return 0; | |
335 | } | |
336 | if (s_state(&(cli_socket(cptr))) == SS_CONNECTING) | |
337 | socket_state(&(cli_socket(cptr)), SS_CONNECTED); | |
338 | ||
339 | if (!EmptyString(aconf->passwd)) | |
340 | sendrawto_one(cptr, MSG_PASS " :%s", aconf->passwd); | |
341 | ||
342 | /* | |
343 | * Create a unique timestamp | |
344 | */ | |
345 | newts = TStime(); | |
346 | for (i = HighestFd; i > -1; --i) { | |
347 | if ((acptr = LocalClientArray[i]) && | |
348 | (IsServer(acptr) || IsHandshake(acptr))) { | |
349 | if (cli_serv(acptr)->timestamp >= newts) | |
350 | newts = cli_serv(acptr)->timestamp + 1; | |
351 | } | |
352 | } | |
353 | assert(0 != cli_serv(cptr)); | |
354 | ||
355 | cli_serv(cptr)->timestamp = newts; | |
356 | SetHandshake(cptr); | |
357 | /* | |
358 | * Make us timeout after twice the timeout for DNS look ups | |
359 | */ | |
360 | cli_lasttime(cptr) = CurrentTime; | |
361 | SetFlag(cptr, FLAG_PINGSENT); | |
362 | ||
363 | sendrawto_one(cptr, MSG_SERVER " %s 1 %Tu %Tu J%s %s%s +%s6 :%s", | |
364 | cli_name(&me), cli_serv(&me)->timestamp, newts, | |
365 | MAJOR_PROTOCOL, NumServCap(&me), | |
366 | feature_bool(FEAT_HUB) ? "h" : "", cli_info(&me)); | |
367 | ||
368 | return (IsDead(cptr)) ? 0 : 1; | |
369 | } | |
370 | ||
371 | /** Close the physical connection. Side effects: MyConnect(cptr) | |
372 | * becomes false and cptr->from becomes NULL. | |
373 | * @param cptr Client to disconnect. | |
374 | */ | |
375 | void close_connection(struct Client *cptr) | |
376 | { | |
377 | struct ConfItem* aconf; | |
378 | ||
379 | if (IsServer(cptr)) { | |
380 | ServerStats->is_sv++; | |
381 | ServerStats->is_sbs += cli_sendB(cptr); | |
382 | ServerStats->is_sbr += cli_receiveB(cptr); | |
383 | ServerStats->is_sti += CurrentTime - cli_firsttime(cptr); | |
384 | /* | |
385 | * If the connection has been up for a long amount of time, schedule | |
386 | * a 'quick' reconnect, else reset the next-connect cycle. | |
387 | */ | |
388 | if ((aconf = find_conf_exact(cli_name(cptr), cptr, CONF_SERVER))) { | |
389 | /* | |
390 | * Reschedule a faster reconnect, if this was a automatically | |
391 | * connected configuration entry. (Note that if we have had | |
392 | * a rehash in between, the status has been changed to | |
393 | * CONF_ILLEGAL). But only do this if it was a "good" link. | |
394 | */ | |
395 | aconf->hold = CurrentTime; | |
396 | aconf->hold += ((aconf->hold - cli_since(cptr) > | |
397 | feature_int(FEAT_HANGONGOODLINK)) ? | |
398 | feature_int(FEAT_HANGONRETRYDELAY) : ConfConFreq(aconf)); | |
399 | /* if (nextconnect > aconf->hold) */ | |
400 | /* nextconnect = aconf->hold; */ | |
401 | } | |
402 | } | |
403 | else if (IsUser(cptr)) { | |
404 | ServerStats->is_cl++; | |
405 | ServerStats->is_cbs += cli_sendB(cptr); | |
406 | ServerStats->is_cbr += cli_receiveB(cptr); | |
407 | ServerStats->is_cti += CurrentTime - cli_firsttime(cptr); | |
408 | } | |
409 | else | |
410 | ServerStats->is_ni++; | |
411 | ||
412 | if (-1 < cli_fd(cptr)) { | |
413 | flush_connections(cptr); | |
414 | LocalClientArray[cli_fd(cptr)] = 0; | |
415 | close(cli_fd(cptr)); | |
416 | socket_del(&(cli_socket(cptr))); /* queue a socket delete */ | |
417 | cli_fd(cptr) = -1; | |
418 | } | |
419 | SetFlag(cptr, FLAG_DEADSOCKET); | |
420 | ||
421 | MsgQClear(&(cli_sendQ(cptr))); | |
422 | client_drop_sendq(cli_connect(cptr)); | |
423 | DBufClear(&(cli_recvQ(cptr))); | |
424 | memset(cli_passwd(cptr), 0, sizeof(cli_passwd(cptr))); | |
425 | set_snomask(cptr, 0, SNO_SET); | |
426 | ||
427 | det_confs_butmask(cptr, 0); | |
428 | ||
429 | if (cli_listener(cptr)) { | |
430 | release_listener(cli_listener(cptr)); | |
431 | cli_listener(cptr) = 0; | |
432 | } | |
433 | ||
434 | for ( ; HighestFd > 0; --HighestFd) { | |
435 | if (LocalClientArray[HighestFd]) | |
436 | break; | |
437 | } | |
438 | } | |
439 | ||
440 | /** Close all unregistered connections. | |
441 | * @param source Oper who requested the close. | |
442 | * @return Number of closed connections. | |
443 | */ | |
444 | int net_close_unregistered_connections(struct Client* source) | |
445 | { | |
446 | int i; | |
447 | struct Client* cptr; | |
448 | int count = 0; | |
449 | assert(0 != source); | |
450 | ||
451 | for (i = HighestFd; i > 0; --i) { | |
452 | if ((cptr = LocalClientArray[i]) && !IsRegistered(cptr)) { | |
453 | send_reply(source, RPL_CLOSING, get_client_name(source, HIDE_IP)); | |
454 | exit_client(source, cptr, &me, "Oper Closing"); | |
455 | ++count; | |
456 | } | |
457 | } | |
458 | return count; | |
459 | } | |
460 | ||
461 | /** Creates a client which has just connected to us on the given fd. | |
462 | * The sockhost field is initialized with the ip# of the host. | |
463 | * The client is not added to the linked list of clients, it is | |
464 | * passed off to the auth handler for dns and ident queries. | |
465 | * @param listener Listening socket that received the connection. | |
466 | * @param fd File descriptor of new connection. | |
467 | */ | |
468 | void add_connection(struct Listener* listener, int fd) { | |
469 | struct irc_sockaddr addr; | |
470 | struct Client *new_client; | |
471 | time_t next_target = 0; | |
472 | ||
473 | const char* const throttle_message = | |
474 | "ERROR :Your host is trying to (re)connect too fast -- throttled\r\n"; | |
475 | /* 12345678901234567890123456789012345679012345678901234567890123456 */ | |
476 | const char* const register_message = | |
477 | "ERROR :Unable to complete your registration\r\n"; | |
478 | ||
479 | assert(0 != listener); | |
480 | ||
481 | /* | |
482 | * Removed preliminary access check. Full check is performed in m_server and | |
483 | * m_user instead. Also connection time out help to get rid of unwanted | |
484 | * connections. | |
485 | */ | |
486 | if (!os_get_peername(fd, &addr) || !os_set_nonblocking(fd)) { | |
487 | ++ServerStats->is_ref; | |
488 | close(fd); | |
489 | return; | |
490 | } | |
491 | /* | |
492 | * Disable IP (*not* TCP) options. In particular, this makes it impossible | |
493 | * to use source routing to connect to the server. If we didn't do this | |
494 | * (and if intermediate networks didn't drop source-routed packets), an | |
495 | * attacker could successfully IP spoof us...and even return the anti-spoof | |
496 | * ping, because the options would cause the packet to be routed back to | |
497 | * the spoofer's machine. When we disable the IP options, we delete the | |
498 | * source route, and the normal routing takes over. | |
499 | */ | |
500 | os_disable_options(fd); | |
501 | ||
502 | if (listener->server) | |
503 | { | |
504 | new_client = make_client(0, STAT_UNKNOWN_SERVER); | |
505 | } | |
506 | else | |
507 | { | |
508 | /* | |
509 | * Add this local client to the IPcheck registry. | |
510 | * | |
511 | * If they're throttled, murder them, but tell them why first. | |
512 | */ | |
513 | if (!IPcheck_local_connect(&addr.addr, &next_target)) | |
514 | { | |
515 | ++ServerStats->is_ref; | |
516 | write(fd, throttle_message, strlen(throttle_message)); | |
517 | close(fd); | |
518 | return; | |
519 | } | |
520 | new_client = make_client(0, STAT_UNKNOWN_USER); | |
521 | SetIPChecked(new_client); | |
522 | } | |
523 | ||
524 | /* | |
525 | * Copy ascii address to 'sockhost' just in case. Then we have something | |
526 | * valid to put into error messages... | |
527 | */ | |
528 | ircd_ntoa_r(cli_sock_ip(new_client), &addr.addr); | |
529 | strcpy(cli_sockhost(new_client), cli_sock_ip(new_client)); | |
530 | memcpy(&cli_ip(new_client), &addr.addr, sizeof(cli_ip(new_client))); | |
531 | ||
532 | if (next_target) | |
533 | cli_nexttarget(new_client) = next_target; | |
534 | ||
535 | cli_fd(new_client) = fd; | |
536 | if (!socket_add(&(cli_socket(new_client)), client_sock_callback, | |
537 | (void*) cli_connect(new_client), SS_CONNECTED, 0, fd)) { | |
538 | ++ServerStats->is_ref; | |
539 | write(fd, register_message, strlen(register_message)); | |
540 | close(fd); | |
541 | cli_fd(new_client) = -1; | |
542 | return; | |
543 | } | |
544 | cli_freeflag(new_client) |= FREEFLAG_SOCKET; | |
545 | cli_listener(new_client) = listener; | |
546 | ++listener->ref_count; | |
547 | ||
548 | Count_newunknown(UserStats); | |
549 | /* if we've made it this far we can put the client on the auth query pile */ | |
550 | start_auth(new_client); | |
551 | } | |
552 | ||
553 | /** Determines whether to tell the events engine we're interested in | |
554 | * writable events. | |
555 | * @param cptr Client for which to decide this. | |
556 | */ | |
557 | void update_write(struct Client* cptr) | |
558 | { | |
559 | /* If there are messages that need to be sent along, or if the client | |
560 | * is in the middle of a /list, then we need to tell the engine that | |
561 | * we're interested in writable events--otherwise, we need to drop | |
562 | * that interest. | |
563 | */ | |
564 | socket_events(&(cli_socket(cptr)), | |
565 | ((MsgQLength(&cli_sendQ(cptr)) || cli_listing(cptr)) ? | |
566 | SOCK_ACTION_ADD : SOCK_ACTION_DEL) | SOCK_EVENT_WRITABLE); | |
567 | } | |
568 | ||
569 | /** Read a 'packet' of data from a connection and process it. Read in | |
570 | * 8k chunks to give a better performance rating (for server | |
571 | * connections). Do some tricky stuff for client connections to make | |
572 | * sure they don't do any flooding >:-) -avalon | |
573 | * @param cptr Client from which to read data. | |
574 | * @param socket_ready If non-zero, more data can be read from the client's socket. | |
575 | * @return Positive number on success, zero on connection-fatal failure, negative | |
576 | * if user is killed. | |
577 | */ | |
578 | static int read_packet(struct Client *cptr, int socket_ready) | |
579 | { | |
580 | unsigned int dolen = 0; | |
581 | unsigned int length = 0; | |
582 | ||
583 | if (socket_ready && | |
584 | !(IsUser(cptr) && !IsOper(cptr) && | |
585 | DBufLength(&(cli_recvQ(cptr))) > feature_int(FEAT_CLIENT_FLOOD))) { | |
586 | switch (os_recv_nonb(cli_fd(cptr), readbuf, sizeof(readbuf), &length)) { | |
587 | case IO_SUCCESS: | |
588 | if (length) | |
589 | { | |
590 | if (!IsServer(cptr)) | |
591 | cli_lasttime(cptr) = CurrentTime; | |
592 | if (cli_lasttime(cptr) > cli_since(cptr)) | |
593 | cli_since(cptr) = cli_lasttime(cptr); | |
594 | ClrFlag(cptr, FLAG_PINGSENT); | |
595 | ClrFlag(cptr, FLAG_NONL); | |
596 | } | |
597 | break; | |
598 | case IO_BLOCKED: | |
599 | break; | |
600 | case IO_FAILURE: | |
601 | cli_error(cptr) = errno; | |
602 | /* SetFlag(cptr, FLAG_DEADSOCKET); */ | |
603 | return 0; | |
604 | } | |
605 | } | |
606 | ||
607 | /* | |
608 | * For server connections, we process as many as we can without | |
609 | * worrying about the time of day or anything :) | |
610 | */ | |
611 | if (length > 0 && IsServer(cptr)) | |
612 | return server_dopacket(cptr, readbuf, length); | |
613 | else if (length > 0 && (IsHandshake(cptr) || IsConnecting(cptr))) | |
614 | return connect_dopacket(cptr, readbuf, length); | |
615 | else | |
616 | { | |
617 | /* | |
618 | * Before we even think of parsing what we just read, stick | |
619 | * it on the end of the receive queue and do it when its | |
620 | * turn comes around. | |
621 | */ | |
622 | if (length > 0 && dbuf_put(&(cli_recvQ(cptr)), readbuf, length) == 0) | |
623 | return exit_client(cptr, cptr, &me, "dbuf_put fail"); | |
624 | ||
625 | if (IsUser(cptr)) { | |
626 | if (DBufLength(&(cli_recvQ(cptr))) > feature_int(FEAT_CLIENT_FLOOD) | |
627 | && !IsOper(cptr)) | |
628 | return exit_client(cptr, cptr, &me, "Excess Flood"); | |
629 | } | |
630 | ||
631 | while (DBufLength(&(cli_recvQ(cptr))) && !NoNewLine(cptr) && | |
632 | (IsTrusted(cptr) || cli_since(cptr) - CurrentTime < 10)) | |
633 | { | |
634 | dolen = dbuf_getmsg(&(cli_recvQ(cptr)), cli_buffer(cptr), BUFSIZE); | |
635 | /* | |
636 | * Devious looking...whats it do ? well..if a client | |
637 | * sends a *long* message without any CR or LF, then | |
638 | * dbuf_getmsg fails and we pull it out using this | |
639 | * loop which just gets the next 512 bytes and then | |
640 | * deletes the rest of the buffer contents. | |
641 | * -avalon | |
642 | */ | |
643 | if (dolen == 0) | |
644 | { | |
645 | if (DBufLength(&(cli_recvQ(cptr))) < 510) | |
646 | SetFlag(cptr, FLAG_NONL); | |
647 | else | |
648 | DBufClear(&(cli_recvQ(cptr))); | |
649 | } | |
650 | else if (client_dopacket(cptr, dolen) == CPTR_KILLED) | |
651 | return CPTR_KILLED; | |
652 | /* | |
653 | * If it has become registered as a Server | |
654 | * then skip the per-message parsing below. | |
655 | */ | |
656 | if (IsHandshake(cptr) || IsServer(cptr)) | |
657 | { | |
658 | while (-1) | |
659 | { | |
660 | dolen = dbuf_get(&(cli_recvQ(cptr)), readbuf, sizeof(readbuf)); | |
661 | if (dolen <= 0) | |
662 | return 1; | |
663 | else if (dolen == 0) | |
664 | { | |
665 | if (DBufLength(&(cli_recvQ(cptr))) < 510) | |
666 | SetFlag(cptr, FLAG_NONL); | |
667 | else | |
668 | DBufClear(&(cli_recvQ(cptr))); | |
669 | } | |
670 | else if ((IsServer(cptr) && | |
671 | server_dopacket(cptr, readbuf, dolen) == CPTR_KILLED) || | |
672 | (!IsServer(cptr) && | |
673 | connect_dopacket(cptr, readbuf, dolen) == CPTR_KILLED)) | |
674 | return CPTR_KILLED; | |
675 | } | |
676 | } | |
677 | } | |
678 | ||
679 | /* If there's still data to process, wait 2 seconds first */ | |
680 | if (DBufLength(&(cli_recvQ(cptr))) && !NoNewLine(cptr) && | |
681 | !t_onqueue(&(cli_proc(cptr)))) | |
682 | { | |
683 | Debug((DEBUG_LIST, "Adding client process timer for %C", cptr)); | |
684 | cli_freeflag(cptr) |= FREEFLAG_TIMER; | |
685 | timer_add(&(cli_proc(cptr)), client_timer_callback, cli_connect(cptr), | |
686 | TT_RELATIVE, 2); | |
687 | } | |
688 | } | |
689 | return 1; | |
690 | } | |
691 | ||
692 | /** Start a connection to another server. | |
693 | * @param aconf Connect block data for target server. | |
694 | * @param by Client who requested the connection (if any). | |
695 | * @return Non-zero on success; zero on failure. | |
696 | */ | |
697 | int connect_server(struct ConfItem* aconf, struct Client* by) | |
698 | { | |
699 | struct Client* cptr = 0; | |
700 | assert(0 != aconf); | |
701 | ||
702 | if (aconf->dns_pending) { | |
703 | sendto_opmask_butone(0, SNO_OLDSNO, "Server %s connect DNS pending", | |
704 | aconf->name); | |
705 | return 0; | |
706 | } | |
707 | Debug((DEBUG_NOTICE, "Connect to %s[@%s]", aconf->name, | |
708 | ircd_ntoa(&aconf->address.addr))); | |
709 | ||
710 | if ((cptr = FindClient(aconf->name))) { | |
711 | if (IsServer(cptr) || IsMe(cptr)) { | |
712 | sendto_opmask_butone(0, SNO_OLDSNO, "Server %s already present from %s", | |
713 | aconf->name, cli_name(cli_from(cptr))); | |
714 | if (by && IsUser(by) && !MyUser(by)) { | |
715 | sendcmdto_one(&me, CMD_NOTICE, by, "%C :Server %s already present " | |
716 | "from %s", by, aconf->name, cli_name(cli_from(cptr))); | |
717 | } | |
718 | return 0; | |
719 | } | |
720 | else if (IsHandshake(cptr) || IsConnecting(cptr)) { | |
721 | if (by && IsUser(by)) { | |
722 | sendcmdto_one(&me, CMD_NOTICE, by, "%C :Connection to %s already in " | |
723 | "progress", by, cli_name(cptr)); | |
724 | } | |
725 | return 0; | |
726 | } | |
727 | } | |
728 | /* | |
729 | * If we don't know the IP# for this host and it is a hostname and | |
730 | * not a ip# string, then try and find the appropriate host record. | |
731 | */ | |
732 | if (!irc_in_addr_valid(&aconf->address.addr) | |
733 | && !ircd_aton(&aconf->address.addr, aconf->host)) { | |
734 | char buf[HOSTLEN + 1]; | |
735 | ||
736 | host_from_uh(buf, aconf->host, HOSTLEN); | |
737 | gethost_byname(buf, connect_dns_callback, aconf); | |
738 | aconf->dns_pending = 1; | |
739 | return 0; | |
740 | } | |
741 | cptr = make_client(NULL, STAT_UNKNOWN_SERVER); | |
742 | ||
743 | /* | |
744 | * Copy these in so we have something for error detection. | |
745 | */ | |
746 | ircd_strncpy(cli_name(cptr), aconf->name, HOSTLEN); | |
747 | ircd_strncpy(cli_sockhost(cptr), aconf->host, HOSTLEN); | |
748 | ||
749 | /* | |
750 | * Attach config entries to client here rather than in | |
751 | * completed_connection. This to avoid null pointer references | |
752 | */ | |
753 | attach_confs_byhost(cptr, aconf->host, CONF_SERVER); | |
754 | ||
755 | if (!find_conf_byhost(cli_confs(cptr), aconf->host, CONF_SERVER)) { | |
756 | sendto_opmask_butone(0, SNO_OLDSNO, "Host %s is not enabled for " | |
757 | "connecting: no Connect block", aconf->name); | |
758 | if (by && IsUser(by) && !MyUser(by)) { | |
759 | sendcmdto_one(&me, CMD_NOTICE, by, "%C :Connect to host %s failed: no " | |
760 | "Connect block", by, aconf->name); | |
761 | } | |
762 | det_confs_butmask(cptr, 0); | |
763 | free_client(cptr); | |
764 | return 0; | |
765 | } | |
766 | /* | |
767 | * attempt to connect to the server in the conf line | |
768 | */ | |
769 | if (!connect_inet(aconf, cptr)) { | |
770 | if (by && IsUser(by) && !MyUser(by)) { | |
771 | sendcmdto_one(&me, CMD_NOTICE, by, "%C :Couldn't connect to %s", by, | |
772 | cli_name(cptr)); | |
773 | } | |
774 | det_confs_butmask(cptr, 0); | |
775 | free_client(cptr); | |
776 | return 0; | |
777 | } | |
778 | /* | |
779 | * NOTE: if we're here we have a valid C:Line and the client should | |
780 | * have started the connection and stored the remote address/port and | |
781 | * ip address name in itself | |
782 | * | |
783 | * The socket has been connected or connect is in progress. | |
784 | */ | |
785 | make_server(cptr); | |
786 | if (by && IsUser(by)) { | |
787 | ircd_snprintf(0, cli_serv(cptr)->by, sizeof(cli_serv(cptr)->by), "%s%s", | |
788 | NumNick(by)); | |
789 | assert(0 == cli_serv(cptr)->user); | |
790 | cli_serv(cptr)->user = cli_user(by); | |
791 | cli_user(by)->refcnt++; | |
792 | } | |
793 | else { | |
794 | *(cli_serv(cptr))->by = '\0'; | |
795 | /* strcpy(cptr->serv->by, "Auto"); */ | |
796 | } | |
797 | cli_serv(cptr)->up = &me; | |
798 | SetConnecting(cptr); | |
799 | ||
800 | if (cli_fd(cptr) > HighestFd) | |
801 | HighestFd = cli_fd(cptr); | |
802 | ||
803 | LocalClientArray[cli_fd(cptr)] = cptr; | |
804 | ||
805 | Count_newunknown(UserStats); | |
806 | /* Actually we lie, the connect hasn't succeeded yet, but we have a valid | |
807 | * cptr, so we register it now. | |
808 | * Maybe these two calls should be merged. | |
809 | */ | |
810 | add_client_to_list(cptr); | |
811 | hAddClient(cptr); | |
812 | /* nextping = CurrentTime; */ | |
813 | ||
814 | return (s_state(&cli_socket(cptr)) == SS_CONNECTED) ? | |
815 | completed_connection(cptr) : 1; | |
816 | } | |
817 | ||
818 | /** Find the real hostname for the host running the server (or one which | |
819 | * matches the server's name) and its primary IP#. Hostname is stored | |
820 | * in the client structure passed as a pointer. | |
821 | */ | |
822 | void init_server_identity(void) | |
823 | { | |
824 | const struct LocalConf* conf = conf_get_local(); | |
825 | assert(0 != conf); | |
826 | ||
827 | ircd_strncpy(cli_name(&me), conf->name, HOSTLEN); | |
828 | SetYXXServerName(&me, conf->numeric); | |
829 | } | |
830 | ||
831 | /** Process events on a client socket. | |
832 | * @param ev Socket event structure that has a struct Connection as | |
833 | * its associated data. | |
834 | */ | |
835 | static void client_sock_callback(struct Event* ev) | |
836 | { | |
837 | struct Client* cptr; | |
838 | struct Connection* con; | |
839 | char *fmt = "%s"; | |
840 | char *fallback = 0; | |
841 | ||
842 | assert(0 != ev_socket(ev)); | |
843 | assert(0 != s_data(ev_socket(ev))); | |
844 | ||
845 | con = (struct Connection*) s_data(ev_socket(ev)); | |
846 | ||
847 | assert(0 != con_client(con) || ev_type(ev) == ET_DESTROY); | |
848 | ||
849 | cptr = con_client(con); | |
850 | ||
851 | assert(0 == cptr || con == cli_connect(cptr)); | |
852 | ||
853 | switch (ev_type(ev)) { | |
854 | case ET_DESTROY: | |
855 | con_freeflag(con) &= ~FREEFLAG_SOCKET; | |
856 | ||
857 | if (!con_freeflag(con) && !cptr) | |
858 | free_connection(con); | |
859 | break; | |
860 | ||
861 | case ET_CONNECT: /* socket connection completed */ | |
862 | if (!completed_connection(cptr) || IsDead(cptr)) | |
863 | fallback = cli_info(cptr); | |
864 | break; | |
865 | ||
866 | case ET_ERROR: /* an error occurred */ | |
867 | fallback = cli_info(cptr); | |
868 | cli_error(cptr) = ev_data(ev); | |
869 | if (s_state(&(con_socket(con))) == SS_CONNECTING) { | |
870 | completed_connection(cptr); | |
871 | /* for some reason, the os_get_sockerr() in completed_connect() | |
872 | * can return 0 even when ev_data(ev) indicates a real error, so | |
873 | * re-assign the client error here. | |
874 | */ | |
875 | cli_error(cptr) = ev_data(ev); | |
876 | break; | |
877 | } | |
878 | /*FALLTHROUGH*/ | |
879 | case ET_EOF: /* end of file on socket */ | |
880 | Debug((DEBUG_ERROR, "READ ERROR: fd = %d %d", cli_fd(cptr), | |
881 | cli_error(cptr))); | |
882 | SetFlag(cptr, FLAG_DEADSOCKET); | |
883 | if ((IsServer(cptr) || IsHandshake(cptr)) && cli_error(cptr) == 0) { | |
884 | exit_client_msg(cptr, cptr, &me, "Server %s closed the connection (%s)", | |
885 | cli_name(cptr), cli_serv(cptr)->last_error_msg); | |
886 | return; | |
887 | } else { | |
888 | fmt = "Read error: %s"; | |
889 | fallback = "EOF from client"; | |
890 | } | |
891 | break; | |
892 | ||
893 | case ET_WRITE: /* socket is writable */ | |
894 | ClrFlag(cptr, FLAG_BLOCKED); | |
895 | if (cli_listing(cptr) && MsgQLength(&(cli_sendQ(cptr))) < 2048) | |
896 | list_next_channels(cptr); | |
897 | Debug((DEBUG_SEND, "Sending queued data to %C", cptr)); | |
898 | send_queued(cptr); | |
899 | break; | |
900 | ||
901 | case ET_READ: /* socket is readable */ | |
902 | if (!IsDead(cptr)) { | |
903 | Debug((DEBUG_DEBUG, "Reading data from %C", cptr)); | |
904 | if (read_packet(cptr, 1) == 0) /* error while reading packet */ | |
905 | fallback = "EOF from client"; | |
906 | } | |
907 | break; | |
908 | ||
909 | default: | |
910 | assert(0 && "Unrecognized socket event in client_sock_callback()"); | |
911 | break; | |
912 | } | |
913 | ||
914 | assert(0 == cptr || 0 == cli_connect(cptr) || con == cli_connect(cptr)); | |
915 | ||
916 | if (fallback) { | |
917 | const char* msg = (cli_error(cptr)) ? strerror(cli_error(cptr)) : fallback; | |
918 | if (!msg) | |
919 | msg = "Unknown error"; | |
920 | exit_client_msg(cptr, cptr, &me, fmt, msg); | |
921 | } | |
922 | } | |
923 | ||
924 | /** Process a timer on client socket. | |
925 | * @param ev Timer event that has a struct Connection as its | |
926 | * associated data. | |
927 | */ | |
928 | static void client_timer_callback(struct Event* ev) | |
929 | { | |
930 | struct Client* cptr; | |
931 | struct Connection* con; | |
932 | ||
933 | assert(0 != ev_timer(ev)); | |
934 | assert(0 != t_data(ev_timer(ev))); | |
935 | assert(ET_DESTROY == ev_type(ev) || ET_EXPIRE == ev_type(ev)); | |
936 | ||
937 | con = (struct Connection*) t_data(ev_timer(ev)); | |
938 | ||
939 | assert(0 != con_client(con) || ev_type(ev) == ET_DESTROY); | |
940 | ||
941 | cptr = con_client(con); | |
942 | ||
943 | assert(0 == cptr || con == cli_connect(cptr)); | |
944 | ||
945 | if (ev_type(ev)== ET_DESTROY) { | |
946 | con_freeflag(con) &= ~FREEFLAG_TIMER; /* timer has expired... */ | |
947 | ||
948 | if (!con_freeflag(con) && !cptr) | |
949 | free_connection(con); /* client is being destroyed */ | |
950 | } else { | |
951 | Debug((DEBUG_LIST, "Client process timer for %C expired; processing", | |
952 | cptr)); | |
953 | read_packet(cptr, 0); /* read_packet will re-add timer if needed */ | |
954 | } | |
955 | ||
956 | assert(0 == cptr || 0 == cli_connect(cptr) || con == cli_connect(cptr)); | |
957 | } |