ViewVC Help
View File | Revision Log | Show Annotations | View Changeset | Root Listing
root/svn/ircd-hybrid/branches/8.2.x/src/user.c
Revision: 10038
Committed: Tue May 24 12:44:41 2022 UTC (22 months, 3 weeks ago) by michael
Content type: text/x-csrc
File size: 21139 byte(s)
Log Message:
- Dynamically build buffers containing channel modes for RPL_ISUPPORT and RPL_MYINFO

File Contents

# Content
1 /*
2 * ircd-hybrid: an advanced, lightweight Internet Relay Chat Daemon (ircd)
3 *
4 * Copyright (c) 1997-2022 ircd-hybrid development team
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 2 of the License, or
9 * (at your option) 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
19 * USA
20 */
21
22 /*! \file user.c
23 * \brief User related functions.
24 * \version $Id$
25 */
26
27 #include "stdinc.h"
28 #include "list.h"
29 #include "user.h"
30 #include "channel.h"
31 #include "channel_mode.h"
32 #include "client.h"
33 #include "hash.h"
34 #include "id.h"
35 #include "irc_string.h"
36 #include "ircd.h"
37 #include "listener.h"
38 #include "motd.h"
39 #include "numeric.h"
40 #include "conf.h"
41 #include "conf_gecos.h"
42 #include "log.h"
43 #include "server.h"
44 #include "send.h"
45 #include "memory.h"
46 #include "packet.h"
47 #include "rng_mt.h"
48 #include "misc.h"
49 #include "parse.h"
50 #include "monitor.h"
51 #include "isupport.h"
52 #include "tls.h"
53 #include "patchlevel.h"
54
55 static char umode_buffer[UMODE_MAX_STR];
56
57 const struct user_modes *umode_map[256];
58 const struct user_modes umode_tab[] =
59 {
60 { 'B', UMODE_BOT },
61 { 'D', UMODE_DEAF },
62 { 'F', UMODE_FARCONNECT },
63 { 'G', UMODE_SOFTCALLERID },
64 { 'H', UMODE_HIDDEN },
65 { 'X', UMODE_EXPIRATION },
66 { 'R', UMODE_REGONLY },
67 { 'S', UMODE_SECURE },
68 { 'W', UMODE_WEBIRC },
69 { 'a', UMODE_ADMIN },
70 { 'c', UMODE_CCONN },
71 { 'd', UMODE_DEBUG },
72 { 'e', UMODE_EXTERNAL },
73 { 'f', UMODE_FLOOD },
74 { 'g', UMODE_CALLERID },
75 { 'i', UMODE_INVISIBLE },
76 { 'j', UMODE_REJ },
77 { 'k', UMODE_SKILL },
78 { 'l', UMODE_LOCOPS },
79 { 'n', UMODE_NCHANGE },
80 { 'o', UMODE_OPER },
81 { 'p', UMODE_HIDECHANS },
82 { 'q', UMODE_HIDEIDLE },
83 { 'r', UMODE_REGISTERED },
84 { 's', UMODE_SERVNOTICE },
85 { 'w', UMODE_WALLOP },
86 { 'y', UMODE_SPY },
87 { '\0', 0 }
88 };
89
90 void
91 user_modes_init(void)
92 {
93 char *umode_buffer_ptr = umode_buffer;
94
95 for (const struct user_modes *tab = umode_tab; tab->c; ++tab)
96 {
97 umode_map[tab->c] = tab;
98 *umode_buffer_ptr++ = tab->c;
99 }
100
101 *umode_buffer_ptr = '\0';
102 }
103
104 /* show_lusers()
105 *
106 * inputs - pointer to client
107 * output - NONE
108 * side effects - display to client user counts etc.
109 */
110 void
111 show_lusers(struct Client *client)
112 {
113 if (ConfigServerHide.hide_servers == 0 || HasUMode(client, UMODE_OPER))
114 sendto_one_numeric(client, &me, RPL_LUSERCLIENT, (dlink_list_length(&global_client_list) - Count.invisi),
115 Count.invisi, dlink_list_length(&global_server_list));
116 else
117 sendto_one_numeric(client, &me, RPL_LUSERCLIENT, (dlink_list_length(&global_client_list) - Count.invisi),
118 Count.invisi, 1);
119
120 if (Count.oper)
121 sendto_one_numeric(client, &me, RPL_LUSEROP, Count.oper);
122
123 if (dlink_list_length(&unknown_list))
124 sendto_one_numeric(client, &me, RPL_LUSERUNKNOWN, dlink_list_length(&unknown_list));
125
126 if (dlink_list_length(channel_get_list()))
127 sendto_one_numeric(client, &me, RPL_LUSERCHANNELS, dlink_list_length(channel_get_list()));
128
129 if (ConfigServerHide.hide_servers == 0 || HasUMode(client, UMODE_OPER))
130 {
131 sendto_one_numeric(client, &me, RPL_LUSERME, dlink_list_length(&local_client_list), dlink_list_length(&local_server_list));
132 sendto_one_numeric(client, &me, RPL_LOCALUSERS, dlink_list_length(&local_client_list), Count.max_loc);
133 sendto_one_numeric(client, &me, RPL_GLOBALUSERS, dlink_list_length(&global_client_list), Count.max_tot);
134 sendto_one_numeric(client, &me, RPL_STATSCONN, Count.max_loc_con, Count.max_loc, Count.totalrestartcount);
135 }
136 else
137 {
138 sendto_one_numeric(client, &me, RPL_LUSERME, dlink_list_length(&global_client_list), 0);
139 sendto_one_numeric(client, &me, RPL_LOCALUSERS, dlink_list_length(&global_client_list), Count.max_tot);
140 sendto_one_numeric(client, &me, RPL_GLOBALUSERS, dlink_list_length(&global_client_list), Count.max_tot);
141 }
142 }
143
144 /* report_and_set_user_flags()
145 *
146 * inputs - pointer to client
147 * - pointer to conf for this user
148 * output - NONE
149 * side effects - Report to user any special flags
150 * they are getting, and set them.
151 */
152 static void
153 report_and_set_user_flags(struct Client *client, const struct MaskItem *conf)
154 {
155 /* If this user is being spoofed, tell them so */
156 if (IsConfDoSpoofIp(conf))
157 sendto_one_notice(client, &me, ":*** Spoofing your IP");
158
159 /* If this user is in the exception class, set it "E lined" */
160 if (IsConfExemptKline(conf))
161 {
162 AddFlag(client, FLAGS_EXEMPTKLINE);
163 sendto_one_notice(client, &me, ":*** You are exempt from K/D lines");
164 }
165
166 if (IsConfExemptXline(conf))
167 {
168 AddFlag(client, FLAGS_EXEMPTXLINE);
169 sendto_one_notice(client, &me, ":*** You are exempt from X lines");
170 }
171
172 if (IsConfExemptResv(conf))
173 {
174 AddFlag(client, FLAGS_EXEMPTRESV);
175 sendto_one_notice(client, &me, ":*** You are exempt from resvs");
176 }
177
178 /* If this user is exempt from user limits set it "F lined" */
179 if (IsConfExemptLimits(conf))
180 {
181 AddFlag(client, FLAGS_NOLIMIT);
182 sendto_one_notice(client, &me, ":*** You are exempt from user limits");
183 }
184
185 if (IsConfCanFlood(conf))
186 {
187 AddFlag(client, FLAGS_CANFLOOD);
188 sendto_one_notice(client, &me, ":*** You are exempt from flood protection");
189 }
190 }
191
192 /* introduce_client()
193 *
194 * inputs - client
195 * output - NONE
196 * side effects - This common function introduces a client to the rest
197 * of the net, either from a local client connect or
198 * from a remote connect.
199 */
200 static void
201 introduce_client(struct Client *client)
202 {
203 char buf[UMODE_MAX_STR] = "";
204
205 send_umode(client, MyConnect(client), 0, buf);
206 monitor_signon(client);
207
208 if (buf[0] == '\0')
209 {
210 buf[0] = '+';
211 buf[1] = '\0';
212 }
213
214 sendto_server(client, 0, 0, ":%s UID %s %u %ju %s %s %s %s %s %s %s :%s",
215 client->servptr->id, client->name, client->hopcount + 1,
216 client->tsinfo, buf, client->username, client->host,
217 client->realhost, client->sockhost, client->id,
218 client->account, client->info);
219
220 if (!EmptyString(client->tls_certfp))
221 sendto_server(client, 0, 0, ":%s CERTFP %s", client->id, client->tls_certfp);
222 }
223
224 /* user_welcome()
225 *
226 * inputs - client pointer to client to welcome
227 * output - NONE
228 * side effects -
229 */
230 static void
231 user_welcome(struct Client *client)
232 {
233 static const char built_date[] = __DATE__ " at " __TIME__;
234
235 if (HasFlag(client, FLAGS_TLS))
236 {
237 AddUMode(client, UMODE_SECURE);
238
239 client->tls_cipher = xstrdup(tls_get_cipher(&client->connection->fd->tls));
240 sendto_one_notice(client, &me, ":*** Connected securely via %s",
241 client->tls_cipher);
242 }
243
244 sendto_one_numeric(client, &me, RPL_WELCOME, ConfigServerInfo.network_name,
245 client->name, client->username, client->realhost);
246 sendto_one_numeric(client, &me, RPL_YOURHOST,
247 listener_get_name(client->connection->listener), PATCHLEVEL);
248 sendto_one_numeric(client, &me, RPL_CREATED, built_date);
249 sendto_one_numeric(client, &me, RPL_MYINFO, me.name, PATCHLEVEL, umode_buffer,
250 cmode_rpl04[0], cmode_rpl04[1]);
251
252 isupport_show(client);
253 show_lusers(client);
254 motd_signon(client);
255 }
256
257 /*! \brief This function is called when both NICK and USER messages
258 * have been accepted for the client, in whatever order. Only
259 * after this, is the UID message propagated.
260 * \param client Pointer to given client to introduce
261 */
262 void
263 register_local_user(struct Client *client)
264 {
265 const struct MaskItem *conf = NULL;
266
267 assert(client == client->from);
268 assert(client->connection->registration == 0);
269 assert(MyConnect(client));
270 assert(IsUnknown(client));
271
272 if (ConfigGeneral.ping_cookie)
273 {
274 if (!HasFlag(client, FLAGS_PINGSENT) && client->connection->random_ping == 0)
275 {
276 do
277 client->connection->random_ping = genrand_int32();
278 while (client->connection->random_ping == 0);
279
280 sendto_one(client, "PING :%u", client->connection->random_ping);
281 AddFlag(client, FLAGS_PINGSENT);
282 return;
283 }
284
285 if (!HasFlag(client, FLAGS_PING_COOKIE))
286 return;
287 }
288
289 if (conf_check_client(client) == false)
290 return;
291
292 conf = client->connection->confs.head->data;
293
294 if (!HasFlag(client, FLAGS_GOTID))
295 {
296 if (IsNeedIdentd(conf))
297 {
298 ++ServerStats.is_ref;
299 sendto_one_notice(client, &me, ":*** Notice -- You need to install "
300 "identd to use this server");
301 exit_client(client, "Install identd");
302 return;
303 }
304 }
305
306 if (valid_username(client->username, true) == false)
307 {
308 char buf[sizeof("Invalid username []") + sizeof(client->username)];
309
310 sendto_realops_flags(UMODE_REJ, L_ALL, SEND_NOTICE,
311 "Invalid username: %s (%s@%s)",
312 client->name, client->username, client->host);
313 ++ServerStats.is_ref;
314 snprintf(buf, sizeof(buf), "Invalid username [%s]", client->username);
315 exit_client(client, buf);
316 return;
317 }
318
319 /* Password check */
320 if (!EmptyString(conf->passwd))
321 {
322 if (match_conf_password(client->connection->password, conf) == false)
323 {
324 ++ServerStats.is_ref;
325
326 sendto_one_numeric(client, &me, ERR_PASSWDMISMATCH);
327 exit_client(client, "Bad Password");
328 return;
329 }
330 }
331
332 xfree(client->connection->password);
333 client->connection->password = NULL;
334
335 /*
336 * Report if user has &^>= etc. and set flags as needed in client
337 */
338 report_and_set_user_flags(client, conf);
339
340 /*
341 * Limit clients -
342 * We want to be able to have servers and F-line clients
343 * connect, so save room for "buffer" connections.
344 * Smaller servers may want to decrease this, and it should
345 * probably be just a percentage of the MAXCLIENTS...
346 * -Taner
347 */
348 if ((dlink_list_length(&local_client_list) >= GlobalSetOptions.maxclients + MAX_BUFFER) ||
349 (dlink_list_length(&local_client_list) >= GlobalSetOptions.maxclients && !HasFlag(client, FLAGS_NOLIMIT)))
350 {
351 sendto_realops_flags(UMODE_REJ, L_ALL, SEND_NOTICE,
352 "Too many clients, rejecting %s[%s].",
353 client->name, client->host);
354 ++ServerStats.is_ref;
355 exit_client(client, "Sorry, server is full - try later");
356 return;
357 }
358
359 if (!HasFlag(client, FLAGS_EXEMPTXLINE))
360 {
361 const struct GecosItem *gecos = gecos_find(client->info, match);
362 if (gecos)
363 {
364 sendto_realops_flags(UMODE_REJ, L_ALL, SEND_NOTICE,
365 "X-line Rejecting [%s] [%s], user %s [%s]",
366 client->info, gecos->reason,
367 client_get_name(client, HIDE_IP),
368 client->sockhost);
369 ++ServerStats.is_ref;
370 exit_client(client, "Bad user info");
371 return;
372 }
373 }
374
375 const char *id;
376 while (hash_find_id((id = uid_get())))
377 ;
378
379 strlcpy(client->id, id, sizeof(client->id));
380 hash_add_id(client);
381
382 sendto_realops_flags(UMODE_CCONN, L_ALL, SEND_NOTICE,
383 "Client connecting: %s (%s@%s) [%s] {%s} [%s] <%s>",
384 client->name, client->username, client->realhost,
385 client->sockhost,
386 get_client_class(&client->connection->confs),
387 client->info, client->id);
388
389 if (ConfigGeneral.invisible_on_connect)
390 {
391 AddUMode(client, UMODE_INVISIBLE);
392 ++Count.invisi;
393 }
394
395 SetClient(client);
396
397 client->servptr = &me;
398 client->connection->last_privmsg = event_base->time.sec_monotonic;
399
400 dlinkAdd(client, &client->lnode, &client->servptr->serv->client_list);
401 dlinkAdd(client, &client->node, &global_client_list);
402
403 assert(dlinkFind(&unknown_list, client));
404
405 dlink_move_node(&client->connection->lclient_node,
406 &unknown_list, &local_client_list);
407
408 if (dlink_list_length(&local_client_list) > Count.max_loc)
409 {
410 Count.max_loc = dlink_list_length(&local_client_list);
411
412 if (!(Count.max_loc % 10))
413 sendto_realops_flags(UMODE_SERVNOTICE, L_ALL, SEND_NOTICE,
414 "New maximum local client connections: %u",
415 Count.max_loc);
416 }
417
418 if ((dlink_list_length(&local_client_list) +
419 dlink_list_length(&local_server_list)) > Count.max_loc_con)
420 Count.max_loc_con = dlink_list_length(&local_client_list) +
421 dlink_list_length(&local_server_list);
422
423 if (dlink_list_length(&global_client_list) > Count.max_tot)
424 Count.max_tot = dlink_list_length(&global_client_list);
425 ++Count.totalrestartcount;
426
427 user_welcome(client);
428
429 introduce_client(client);
430 }
431
432 /* register_remote_user()
433 *
434 * inputs - client remote or directly connected client
435 * - username to register as
436 * - host name to register as
437 * - server name
438 * output - NONE
439 * side effects - This function is called when a remote client
440 * is introduced by a server.
441 */
442 void
443 register_remote_user(struct Client *client)
444 {
445 assert(client->servptr->from == client->from);
446
447 /*
448 * If the nick has been introduced by a services server,
449 * make it a service as well.
450 */
451 if (HasFlag(client->servptr, FLAGS_SERVICE))
452 AddFlag(client, FLAGS_SERVICE);
453
454 SetClient(client);
455 dlinkAdd(client, &client->lnode, &client->servptr->serv->client_list);
456 dlinkAdd(client, &client->node, &global_client_list);
457
458 if (dlink_list_length(&global_client_list) > Count.max_tot)
459 Count.max_tot = dlink_list_length(&global_client_list);
460
461 if (HasFlag(client->servptr, FLAGS_EOB))
462 sendto_realops_flags(UMODE_FARCONNECT, L_ALL, SEND_NOTICE,
463 "Client connecting at %s: %s (%s@%s) [%s] [%s] <%s>",
464 client->servptr->name,
465 client->name, client->username, client->realhost,
466 client->sockhost, client->info, client->id);
467
468 introduce_client(client);
469 }
470
471 /* valid_hostname()
472 *
473 * Inputs - pointer to hostname
474 * Output - 1 if valid, 0 if not
475 * Side effects - check hostname for validity
476 *
477 * NOTE: this doesn't allow a hostname to begin with a dot and
478 * will not allow more dots than chars.
479 */
480 bool
481 valid_hostname(const char *hostname)
482 {
483 const char *p = hostname;
484
485 assert(p);
486
487 if (EmptyString(p) || *p == '.' || *p == ':')
488 return false;
489
490 for (; *p; ++p)
491 if (!IsHostChar(*p))
492 return false;
493
494 return p - hostname <= HOSTLEN;
495 }
496
497 /* valid_username()
498 *
499 * Inputs - pointer to user
500 * Output - 1 if valid, 0 if not
501 * Side effects - check username for validity
502 *
503 * Absolutely always reject any '*' '!' '?' '@' in an user name
504 * reject any odd control characters names.
505 * Allow '.' in username to allow for "first.last"
506 * style of username
507 */
508 bool
509 valid_username(const char *username, bool local)
510 {
511 const char *p = username;
512
513 assert(p);
514
515 if (*p == '~')
516 ++p;
517
518 /*
519 * Reject usernames that don't start with an alphanum
520 * i.e. reject jokers who have '-@somehost' or '.@somehost'
521 * or "-hi-@somehost", "h-----@somehost" would still be accepted.
522 */
523 if (!IsAlNum(*p))
524 return false;
525
526 if (local)
527 {
528 unsigned int special = 0;
529
530 while (*p)
531 {
532 if (*p == '-' || *p == '_' || *p == '.')
533 {
534 if (ConfigGeneral.specials_in_ident < ++special)
535 return false;
536 }
537 else if (!IsUser2Char(*p))
538 return false;
539
540 ++p;
541 }
542 }
543 else
544 {
545 while (*++p)
546 if (!IsUserChar(*p))
547 return false;
548 }
549
550 return p - username <= USERLEN;
551 }
552
553 /* clean_nick_name()
554 *
555 * input - nickname
556 * - whether it's a local nick (1) or remote (0)
557 * output - none
558 * side effects - walks through the nickname, returning 0 if erroneous
559 */
560 bool
561 valid_nickname(const char *nickname, bool local)
562 {
563 const char *p = nickname;
564
565 assert(p);
566
567 /*
568 * Nicks can't start with a digit or - or be 0 length.
569 */
570 if (EmptyString(p) || *p == '-' || (IsDigit(*p) && local))
571 return false;
572
573 for (; *p; ++p)
574 if (!IsNickChar(*p))
575 return false;
576
577 return p - nickname <= NICKLEN;
578 }
579
580 /*! \brief Builds a mode change string to buffer pointed by \a buf
581 * \param client Pointer to client
582 * \param dispatch Whether to send a MODE message to client
583 * \param old Old user mode to compare against when building new mode buffer
584 * \param buf Pointer to buffer to build string in
585 */
586 void
587 send_umode(struct Client *client, bool dispatch, unsigned int old, char *buf)
588 {
589 char *m = buf;
590 int what = 0;
591
592 /*
593 * Build a string in umode_buf to represent the change in the user's
594 * mode between the new (client->umodes) and 'old'.
595 */
596 for (const struct user_modes *tab = umode_tab; tab->c; ++tab)
597 {
598 if ((tab->flag & old) && !HasUMode(client, tab->flag))
599 {
600 if (what == MODE_DEL)
601 *m++ = tab->c;
602 else
603 {
604 what = MODE_DEL;
605 *m++ = '-';
606 *m++ = tab->c;
607 }
608 }
609 else if (!(tab->flag & old) && HasUMode(client, tab->flag))
610 {
611 if (what == MODE_ADD)
612 *m++ = tab->c;
613 else
614 {
615 what = MODE_ADD;
616 *m++ = '+';
617 *m++ = tab->c;
618 }
619 }
620 }
621
622 *m = '\0';
623
624 if (dispatch == true && *buf)
625 sendto_one(client, ":%s!%s@%s MODE %s :%s",
626 client->name, client->username,
627 client->host, client->name, buf);
628 }
629
630 /* send_umode_out()
631 *
632 * inputs -
633 * output - NONE
634 * side effects - Only send ubuf out to servers that know about this client
635 */
636 void
637 send_umode_out(struct Client *client, unsigned int old)
638 {
639 char buf[UMODE_MAX_STR] = "";
640
641 send_umode(client, MyConnect(client), old, buf);
642
643 if (buf[0])
644 sendto_server(client, 0, 0, ":%s MODE %s :%s",
645 client->id, client->id, buf);
646 }
647
648 void
649 user_set_hostmask(struct Client *client, const char *hostname)
650 {
651 dlink_node *node;
652
653 if (strcmp(client->host, hostname) == 0)
654 return;
655
656 if (ConfigGeneral.cycle_on_host_change)
657 sendto_common_channels_local(client, false, 0, CAP_CHGHOST, ":%s!%s@%s QUIT :Changing hostname",
658 client->name, client->username, client->host);
659
660 sendto_common_channels_local(client, true, CAP_CHGHOST, 0, ":%s!%s@%s CHGHOST %s %s",
661 client->name, client->username,
662 client->host, client->username, hostname);
663
664 strlcpy(client->host, hostname, sizeof(client->host));
665
666 if (MyConnect(client))
667 {
668 sendto_one_numeric(client, &me, RPL_VISIBLEHOST, client->host);
669 clear_ban_cache_list(&client->channel);
670 }
671
672 if (ConfigGeneral.cycle_on_host_change == 0)
673 return;
674
675 DLINK_FOREACH(node, client->channel.head)
676 {
677 char modebuf[CMEMBER_STATUS_FLAGS_LEN + 1];
678 char nickbuf[CMEMBER_STATUS_FLAGS_LEN * NICKLEN + CMEMBER_STATUS_FLAGS_LEN] = "";
679 char *p = modebuf;
680 int len = 0;
681 const struct ChannelMember *member = node->data;
682
683 if (member_has_flags(member, CHFL_CHANOP) == true)
684 {
685 *p++ = 'o';
686 len += snprintf(nickbuf + len, sizeof(nickbuf) - len, len ? " %s" : "%s", client->name);
687 }
688
689 if (member_has_flags(member, CHFL_HALFOP) == true)
690 {
691 *p++ = 'h';
692 len += snprintf(nickbuf + len, sizeof(nickbuf) - len, len ? " %s" : "%s", client->name);
693 }
694
695 if (member_has_flags(member, CHFL_VOICE) == true)
696 {
697 *p++ = 'v';
698 len += snprintf(nickbuf + len, sizeof(nickbuf) - len, len ? " %s" : "%s", client->name);
699 }
700
701 *p = '\0';
702
703 sendto_channel_local(client, member->channel, 0, CAP_EXTENDED_JOIN, CAP_CHGHOST, ":%s!%s@%s JOIN %s %s :%s",
704 client->name, client->username,
705 client->host, member->channel->name,
706 client->account, client->info);
707 sendto_channel_local(client, member->channel, 0, 0, CAP_EXTENDED_JOIN | CAP_CHGHOST, ":%s!%s@%s JOIN :%s",
708 client->name, client->username,
709 client->host, member->channel->name);
710
711 if (nickbuf[0])
712 sendto_channel_local(client, member->channel, 0, 0, CAP_CHGHOST, ":%s MODE %s +%s %s",
713 client->servptr->name, member->channel->name,
714 modebuf, nickbuf);
715 }
716
717 if (client->away[0])
718 sendto_common_channels_local(client, false, CAP_AWAY_NOTIFY, CAP_CHGHOST,
719 ":%s!%s@%s AWAY :%s",
720 client->name, client->username,
721 client->host, client->away);
722 }

Properties

Name Value
svn:eol-style native
svn:keywords Id