Exemple #1
0
static void
ms_pong(struct Client *client_p, struct Client *source_p, int parc, char *parv[])
{
	struct Client *target_p;
	const char *origin, *destination;

	if(parc < 2 || *parv[1] == '\0')
	{
		sendto_one(source_p, form_str(ERR_NOORIGIN), me.name, parv[0]);
		return;
	}

	origin = parv[1];
	destination = parv[2];

	/* Now attempt to route the PONG, comstud pointed out routable PING
	 * is used for SPING.  routable PING should also probably be left in
	 *        -Dianora
	 * That being the case, we will route, but only for registered clients (a
	 * case can be made to allow them only from servers). -Shadowfax
	 */
	if(!EmptyString(destination) && !match(destination, me.name) && irccmp(destination, me.id))
	{
		if((target_p = find_client(destination)) || (target_p = find_server(destination)))
			sendto_one(target_p, ":%s PONG %s %s", parv[0], origin, destination);
		else
		{
			sendto_one(source_p, form_str(ERR_NOSUCHSERVER),
				   me.name, parv[0], destination);
			return;
		}
	}
	else if(!HasSentEob(source_p))
		server_eob(source_p);
}
Exemple #2
0
/*
** ms_eob
**      parv[0] = sender prefix
**      parv[1] = opt. comma separated list of SIDs for which this EOB is
**                also valid
*/
static int
ms_eob(struct Client *client_p, struct Client *source_p, int parc, const char *parv[])
{
	char *copy, *state, *id;
	struct Client *target_p;
	int act = 0;

	if(!HasSentEob(source_p))
	{
		if(MyConnect(source_p))
		{
			sendto_realops_flags(UMODE_ALL, L_ALL,
					     "End of burst from %s (%d seconds)",
					     source_p->name,
					     (signed int)(rb_current_time() -
							  source_p->localClient->firsttime));
			sendto_one(source_p, ":%s EOBACK", me.id);
		}
		act = 1;
		SetEob(source_p);
		eob_count++;
	}
	if(parc > 1 && *parv[1] != '\0')
	{
		copy = LOCAL_COPY(parv[1]);
		for(id = rb_strtok_r(copy, ",", &state); id != NULL;
				id = rb_strtok_r(NULL, ",", &state))
		{
			target_p = find_id(id);
			if(target_p != NULL && IsServer(target_p) &&
					target_p->from == client_p &&
					!HasSentEob(target_p))
			{
				SetEob(target_p);
				eob_count++;
				act = 1;
			}
		}
	}
	if(!act)
		return 0;
	sendto_server(client_p, NULL, CAP_IRCNET, NOCAPS, ":%s EOB%s%s",
			source_p->id,
			parc > 1 ? " :" : "", parc > 1 ? parv[1] : "");

	return 0;
}
Exemple #3
0
static void
h_gcn_new_remote_user(struct Client *source_p)
{

	if (!HasSentEob(source_p->servptr))
		return;
	sendto_realops_snomask_from(snomask_modes['F'], L_ALL, source_p->servptr,
			"Client connecting: %s (%s@%s) [%s] {%s} [%s]",
			source_p->name, source_p->username, source_p->orighost,
			show_ip(NULL, source_p) ? source_p->sockhost : "255.255.255.255",
			"?", source_p->info);
}
Exemple #4
0
/* m_tb()
 *
 * parv[1] - channel
 * parv[2] - topic ts
 * parv[3] - optional topicwho/topic
 * parv[4] - topic
 */
static int
ms_tb(struct Client *client_p, struct Client *source_p, int parc, const char *parv[])
{
    struct Channel *chptr;
    const char *newtopic;
    const char *newtopicwho;
    time_t newtopicts;
    struct Client *fakesource_p;

    chptr = find_channel(parv[1]);

    if(chptr == NULL)
        return 0;

    newtopicts = atol(parv[2]);

    /* Hide connecting server on netburst -- jilles */
    if (ConfigServerHide.flatten_links && !HasSentEob(source_p))
        fakesource_p = &me;
    else
        fakesource_p = source_p;

    if(parc == 5) {
        newtopic = parv[4];
        newtopicwho = parv[3];
    } else {
        newtopic = parv[3];
        newtopicwho = fakesource_p->name;
    }

    if (EmptyString(newtopic))
        return 0;

    if(chptr->topic == NULL || chptr->topic_time > newtopicts) {
        /* its possible the topicts is a few seconds out on some
         * servers, due to lag when propagating it, so if theyre the
         * same topic just drop the message --fl
         */
        if(chptr->topic != NULL && strcmp(chptr->topic, newtopic) == 0)
            return 0;

        set_channel_topic(chptr, newtopic, newtopicwho, newtopicts);
        sendto_channel_local(ALL_MEMBERS, chptr, ":%s TOPIC %s :%s",
                             fakesource_p->name, chptr->chname, newtopic);
        sendto_server(client_p, chptr, CAP_TB|CAP_TS6, NOCAPS,
                      ":%s TB %s %ld %s%s:%s",
                      use_id(source_p), chptr->chname, (long) chptr->topic_time,
                      ConfigChannel.burst_topicwho ? chptr->topic_info : "",
                      ConfigChannel.burst_topicwho ? " " : "", chptr->topic);
    }

    return 0;
}
static void
h_sgo_umode_changed(void *vdata)
{
	hook_data_umode_changed *data = (hook_data_umode_changed *)vdata;
	struct Client *source_p = data->client;
	if (MyConnect(source_p) || !HasSentEob(source_p->servptr))
		return;
	if (!(data->oldumodes & UMODE_OPER) && IsOper(source_p))
		sendto_realops_snomask_from(SNO_GENERAL, L_ALL, source_p->servptr,
		                            "%s (%s@%s) is now an operator",
		                            source_p->name, source_p->username, source_p->host);
}
static int
ms_pong(struct Client *client_p, struct Client *source_p, int parc, const char *parv[])
{
    struct Client *target_p;
    const char *destination;

    destination = parv[2];
    source_p->flags &= ~FLAGS_PINGSENT;

    /* Now attempt to route the PONG, comstud pointed out routable PING
     * is used for SPING.  routable PING should also probably be left in
     *        -Dianora
     * That being the case, we will route, but only for registered clients (a
     * case can be made to allow them only from servers). -Shadowfax
     */
    if(!EmptyString(destination) && !match(destination, me.name) &&
            irccmp(destination, me.id))
    {
        if((target_p = find_client(destination)) ||
                (target_p = find_server(NULL, destination)))
            sendto_one(target_p, ":%s PONG %s %s",
                       get_id(source_p, target_p), parv[1],
                       get_id(target_p, target_p));
        else
        {
            if(!IsDigit(*destination))
                sendto_one_numeric(source_p, ERR_NOSUCHSERVER,
                                   form_str(ERR_NOSUCHSERVER), destination);
            return 0;
        }
    }

    /* destination is us, emulate EOB */
    if(IsServer(source_p) && !HasSentEob(source_p))
    {
        if(MyConnect(source_p))
            sendto_realops_snomask(SNO_GENERAL, L_ALL,
                                   "End of burst (emulated) from %s (%d seconds)",
                                   source_p->name,
                                   (signed int) (CurrentTime - source_p->localClient->firsttime));
        SetEob(source_p);
        eob_count++;
        call_hook(h_server_eob, source_p);
    }

    return 0;
}
Exemple #7
0
static void
h_gcn_client_exit(hook_data_client_exit *hdata)
{
	struct Client *source_p;

	source_p = hdata->target;

	if (MyConnect(source_p) || !IsClient(source_p))
		return;
	if (!HasSentEob(source_p->servptr))
		return;
	sendto_realops_snomask_from(snomask_modes['F'], L_ALL, source_p->servptr,
			     "Client exiting: %s (%s@%s) [%s] [%s]",
			     source_p->name,
			     source_p->username, source_p->host, hdata->comment,
                             show_ip(NULL, source_p) ? source_p->sockhost : "255.255.255.255");
}
Exemple #8
0
/* ms_ban()
 *
 * parv[1] - type
 * parv[2] - username mask or *
 * parv[3] - hostname mask
 * parv[4] - creation TS
 * parv[5] - duration (relative to creation)
 * parv[6] - lifetime (relative to creation)
 * parv[7] - oper or *
 * parv[8] - reason (possibly with |operreason)
 */
static int
ms_ban(struct Client *client_p, struct Client *source_p, int parc, const char *parv[])
{
	rb_dlink_node *ptr;
	struct ConfItem *aconf;
	unsigned int ntype;
	const char *oper, *stype;
	time_t now, created, hold, lifetime;
	char *p;
	int act;
	int valid;

	now = rb_current_time();
	if (strlen(parv[1]) != 1)
	{
		sendto_realops_snomask(SNO_GENERAL, L_NETWIDE,
				"Unknown BAN type %s from %s",
				parv[1], source_p->name);
		return 0;
	}
	switch (parv[1][0])
	{
		case 'K':
			ntype = CONF_KILL;
			stype = "K-Line";
			break;
		case 'X':
			ntype = CONF_XLINE;
			stype = "X-Line";
			break;
		case 'R':
			ntype = IsChannelName(parv[3]) ? CONF_RESV_CHANNEL :
				CONF_RESV_NICK;
			stype = "RESV";
			break;
		default:
			sendto_realops_snomask(SNO_GENERAL, L_NETWIDE,
					"Unknown BAN type %s from %s",
					parv[1], source_p->name);
			return 0;
	}
	created = atol(parv[4]);
	hold = created + atoi(parv[5]);
	lifetime = created + atoi(parv[6]);
	if (!strcmp(parv[7], "*"))
		oper = IsServer(source_p) ? source_p->name : get_oper_name(source_p);
	else
		oper = parv[7];
	ptr = find_prop_ban(ntype, parv[2], parv[3]);
	if (ptr != NULL)
	{
		/* We already know about this ban mask. */
		aconf = ptr->data;
		if (aconf->created > created ||
				(aconf->created == created &&
				 aconf->lifetime >= lifetime))
		{
			if (IsPerson(source_p))
				sendto_one_notice(source_p,
						":Your %s [%s%s%s] has been superseded",
						stype,
						aconf->user ? aconf->user : "",
						aconf->user ? "@" : "",
						aconf->host);
			return 0;
		}
		/* act indicates if something happened (from the oper's
		 * point of view). This is the case if the ban was
		 * previously active (not deleted) or if the new ban
		 * is not a removal and not already expired.
		 */
		act = !(aconf->status & CONF_ILLEGAL) || (hold != created &&
				hold > now);
		if (lifetime > aconf->lifetime)
			aconf->lifetime = lifetime;
		/* already expired, hmm */
		if (aconf->lifetime <= now)
			return 0;
		/* Deactivate, it will be reactivated later if appropriate. */
		deactivate_conf(aconf, ptr, now);
		rb_free(aconf->user);
		aconf->user = NULL;
		rb_free(aconf->host);
		aconf->host = NULL;
		operhash_delete(aconf->info.oper);
		aconf->info.oper = NULL;
		rb_free(aconf->passwd);
		aconf->passwd = NULL;
		rb_free(aconf->spasswd);
		aconf->spasswd = NULL;
	}
	else
	{
		/* New ban mask. */
		aconf = make_conf();
		aconf->status = CONF_ILLEGAL | ntype;
		aconf->lifetime = lifetime;
		rb_dlinkAddAlloc(aconf, &prop_bans);
		act = hold != created && hold > now;
	}
	aconf->flags &= ~CONF_FLAGS_MYOPER;
	aconf->flags |= CONF_FLAGS_TEMPORARY;
	aconf->user = ntype == CONF_KILL ? rb_strdup(parv[2]) : NULL;
	aconf->host = rb_strdup(parv[3]);
	aconf->info.oper = operhash_add(oper);
	aconf->created = created;
	aconf->hold = hold;
	if (ntype != CONF_KILL || (p = strchr(parv[parc - 1], '|')) == NULL)
		aconf->passwd = rb_strdup(parv[parc - 1]);
	else
	{
		aconf->passwd = rb_strndup(parv[parc - 1], p - parv[parc - 1] + 1);
		aconf->spasswd = rb_strdup(p + 1);
	}
	/* The ban is fully filled in and in the prop_bans list
	 * but still deactivated. Now determine if it should be activated
	 * and send the server notices.
	 */
	/* We only reject *@* and the like here.
	 * Otherwise malformed bans are fairly harmless and can be removed.
	 */
	switch (ntype)
	{
		case CONF_KILL:
			valid = valid_wild_card(aconf->user, aconf->host);
			break;
		case CONF_RESV_CHANNEL:
			valid = 1;
			break;
		default:
			valid = valid_wild_card_simple(aconf->host);
			break;
	}
	if (act && hold != created && !valid)
	{
		sendto_realops_snomask(SNO_GENERAL, L_ALL,
				       "Ignoring global %d min. %s from %s%s%s for [%s%s%s]: too few non-wildcard characters",
				       (int)((hold - now) / 60),
				       stype,
				       IsServer(source_p) ? source_p->name : get_oper_name(source_p),
				       strcmp(parv[7], "*") ? " on behalf of " : "",
				       strcmp(parv[7], "*") ? parv[7] : "",
				       aconf->user ? aconf->user : "",
				       aconf->user ? "@" : "",
				       aconf->host);
		if(IsPerson(source_p))
			sendto_one_notice(source_p,
					":Your %s [%s%s%s] has too few non-wildcard characters",
					stype,
					aconf->user ? aconf->user : "",
					aconf->user ? "@" : "",
					aconf->host);
		/* Propagate it, but do not apply it locally. */
	}
	else if (act && hold != created)
	{
		/* Keep the notices in sync with modules/m_kline.c etc. */
		sendto_realops_snomask(SNO_GENERAL, L_ALL,
				       "%s added global %d min. %s%s%s for [%s%s%s] [%s]",
				       IsServer(source_p) ? source_p->name : get_oper_name(source_p),
				       (int)((hold - now) / 60),
				       stype,
				       strcmp(parv[7], "*") ? " from " : "",
				       strcmp(parv[7], "*") ? parv[7] : "",
				       aconf->user ? aconf->user : "",
				       aconf->user ? "@" : "",
				       aconf->host,
				       parv[parc - 1]);
		ilog(L_KLINE, "%s %s %d %s%s%s %s", parv[1],
				IsServer(source_p) ? source_p->name : get_oper_name(source_p),
				(int)((hold - now) / 60),
				aconf->user ? aconf->user : "",
				aconf->user ? " " : "",
				aconf->host,
				parv[parc - 1]);
		aconf->status &= ~CONF_ILLEGAL;
	}
	else if (act)
	{
		sendto_realops_snomask(SNO_GENERAL, L_ALL,
				"%s has removed the global %s for: [%s%s%s]%s%s",
				IsServer(source_p) ? source_p->name : get_oper_name(source_p),
				stype,
				aconf->user ? aconf->user : "",
				aconf->user ? "@" : "",
				aconf->host,
				strcmp(parv[7], "*") ? " on behalf of " : "",
				strcmp(parv[7], "*") ? parv[7] : "");
		ilog(L_KLINE, "U%s %s %s%s %s", parv[1],
				IsServer(source_p) ? source_p->name : get_oper_name(source_p),
				aconf->user ? aconf->user : "",
				aconf->user ? " " : "",
				aconf->host);
	}
	/* If CONF_ILLEGAL is still set at this point, remove entries from the
	 * reject cache (for klines and xlines).
	 * If CONF_ILLEGAL is not set, add the ban to the type-specific data
	 * structure and take action on matched clients/channels.
	 */
	switch (ntype)
	{
		case CONF_KILL:
			if (aconf->status & CONF_ILLEGAL)
				remove_reject_mask(aconf->user, aconf->host);
			else
			{
				add_conf_by_address(aconf->host, CONF_KILL, aconf->user, NULL, aconf);
				if(ConfigFileEntry.kline_delay ||
						(IsServer(source_p) &&
						 !HasSentEob(source_p)))
				{
					if(kline_queued == 0)
					{
						rb_event_addonce("check_klines", check_klines_event, NULL,
							ConfigFileEntry.kline_delay ?
								ConfigFileEntry.kline_delay : 1);
						kline_queued = 1;
					}
				}
				else
					check_klines();
			}
			break;
		case CONF_XLINE:
			if (aconf->status & CONF_ILLEGAL)
				remove_reject_mask(aconf->host, NULL);
			else
			{
				rb_dlinkAddAlloc(aconf, &xline_conf_list);
				check_xlines();
			}
			break;
		case CONF_RESV_CHANNEL:
			if (!(aconf->status & CONF_ILLEGAL))
			{
				add_to_resv_hash(aconf->host, aconf);
				resv_chan_forcepart(aconf->host, aconf->passwd, hold - now);
			}
			break;
		case CONF_RESV_NICK:
			if (!(aconf->status & CONF_ILLEGAL))
				rb_dlinkAddAlloc(aconf, &resv_conf_list);
			break;
	}
	sendto_server(client_p, NULL, CAP_BAN|CAP_TS6, NOCAPS,
			":%s BAN %s %s %s %s %s %s %s :%s",
			source_p->id,
			parv[1],
			parv[2],
			parv[3],
			parv[4],
			parv[5],
			parv[6],
			parv[7],
			parv[parc - 1]);
	return 0;
}
Exemple #9
0
static int
ms_bmask(struct Client *client_p, struct Client *source_p, int parc, const char *parv[])
{
	static char modebuf[BUFSIZE];
	static char parabuf[BUFSIZE];
	struct Channel *chptr;
	rb_dlink_list *banlist;
	const char *s;
	char *t;
	char *mbuf;
	char *pbuf;
	long mode_type;
	int mlen;
	int plen = 0;
	int tlen;
	int arglen;
	int modecount = 0;
	int needcap = NOCAPS;
	int mems;
	struct Client *fakesource_p;

	if(!IsChanPrefix(parv[2][0]) || !check_channel_name(parv[2]))
		return 0;

	if((chptr = find_channel(parv[2])) == NULL)
		return 0;

	/* TS is higher, drop it. */
	if(atol(parv[1]) > chptr->channelts)
		return 0;

	switch (parv[3][0])
	{
	case 'b':
		banlist = &chptr->banlist;
		mode_type = CHFL_BAN;
		mems = ALL_MEMBERS;
		break;

	case 'e':
		banlist = &chptr->exceptlist;
		mode_type = CHFL_EXCEPTION;
		needcap = CAP_EX;
		mems = ONLY_HALFOPSANDUP;
		break;

	case 'I':
		banlist = &chptr->invexlist;
		mode_type = CHFL_INVEX;
		needcap = CAP_IE;
		mems = ONLY_HALFOPSANDUP;
		break;

	case 'Z':
		banlist = &chptr->quietlist;
		mode_type = CHFL_QUIET;
		mems = ALL_MEMBERS;
		break;
	/* XXX should use encap? */
	case 'y':
		banlist = &chptr->censorlist;
		mode_type = CHFL_CENSOR;
		mems = ALL_MEMBERS;
		break;
		/* maybe we should just blindly propagate this? */
	default:
		return 0;
	}

	parabuf[0] = '\0';
	s = LOCAL_COPY(parv[4]);

	/* Hide connecting server on netburst -- jilles */
	if (ConfigServerHide.flatten_links && !HasSentEob(source_p))
		fakesource_p = &me;
	else
		fakesource_p = source_p;
	mlen = rb_sprintf(modebuf, ":%s MODE %s +", fakesource_p->name, chptr->chname);
	mbuf = modebuf + mlen;
	pbuf = parabuf;

	while(*s == ' ')
		s++;

	/* next char isnt a space, point t to the next one */
	if((t = strchr(s, ' ')) != NULL)
	{
		*t++ = '\0';

		/* double spaces will break the parser */
		while(*t == ' ')
			t++;
	}

	/* couldve skipped spaces and got nothing.. */
	while(!EmptyString(s))
	{
		/* ban with a leading ':' -- this will break the protocol */
		if(*s == ':')
			goto nextban;

		tlen = strlen(s);

		/* I dont even want to begin parsing this.. */
		if(tlen > MODEBUFLEN)
			break;

		if(add_id(fakesource_p, chptr, s, banlist, mode_type))
		{
			/* this new one wont fit.. */
			if(mlen + MAXMODEPARAMS + plen + tlen > BUFSIZE - 5 ||
			   modecount >= MAXMODEPARAMS)
			{
				*mbuf = '\0';
				*(pbuf - 1) = '\0';
				sendto_channel_local(mems, chptr, "%s %s", modebuf, parabuf);
				sendto_server(client_p, chptr, needcap, CAP_TS6,
					      "%s %s", modebuf, parabuf);

				mbuf = modebuf + mlen;
				pbuf = parabuf;
				plen = modecount = 0;
			}

			*mbuf++ = parv[3][0];
			arglen = rb_sprintf(pbuf, "%s ", s);
			pbuf += arglen;
			plen += arglen;
			modecount++;
		}

	      nextban:
		s = t;

		if(s != NULL)
		{
			if((t = strchr(s, ' ')) != NULL)
			{
				*t++ = '\0';

				while(*t == ' ')
					t++;
			}
		}
	}

	if(modecount)
	{
		*mbuf = '\0';
		*(pbuf - 1) = '\0';
		sendto_channel_local(mems, chptr, "%s %s", modebuf, parabuf);
		sendto_server(client_p, chptr, needcap, CAP_TS6, "%s %s", modebuf, parabuf);
	}

	sendto_server(client_p, chptr, CAP_TS6 | needcap, NOCAPS, ":%s BMASK %ld %s %s :%s",
		      source_p->id, (long) chptr->channelts, chptr->chname, parv[3], parv[4]);
	return 0;
}
Exemple #10
0
/* ms_etb()
 *
 * parv[1] - channel ts
 * parv[2] - channel
 * parv[3] - topic ts
 * parv[4] - topicwho
 * parv[5] - topic
 */
static int
ms_etb(struct Client *client_p, struct Client *source_p, int parc, const char *parv[])
{
    struct Channel *chptr;
    const char *newtopic;
    const char *newtopicwho;
    time_t channelts, newtopicts;
    struct Client *fakesource_p, *source_server_p;
    int textchange, can_use_tb, member;

    channelts = atol(parv[1]);
    chptr = find_channel(parv[2]);

    if(chptr == NULL)
        return 0;

    newtopicts = atol(parv[3]);

    /* Hide connecting server on netburst -- jilles */
    if (IsServer(source_p) && ConfigServerHide.flatten_links &&
        !HasSentEob(source_p))
        fakesource_p = &me;
    else
        fakesource_p = source_p;

    newtopicwho = parv[4];
    newtopic = parv[parc - 1];

    if(chptr->topic == NULL || chptr->channelts > channelts ||
       (chptr->channelts == channelts && chptr->topic_time < newtopicts)) {
        textchange = chptr->topic == NULL || strcmp(chptr->topic, newtopic);
        can_use_tb = textchange && !EmptyString(newtopic) &&
                     (chptr->topic == NULL || chptr->topic_time > newtopicts);

        set_channel_topic(chptr, newtopic, newtopicwho, newtopicts);
        newtopic = chptr->topic ? chptr->topic : "";
        if (chptr->topic_info)
            newtopicwho = chptr->topic_info;

        /* Do not send a textually identical topic to clients,
         * but do propagate the new topicts/topicwho to servers.
         */
        if(textchange) {
            if (IsPerson(fakesource_p))
                sendto_channel_local(ALL_MEMBERS, chptr,
                                     ":%s!%s@%s TOPIC %s :%s",
                                     fakesource_p->name,
                                     fakesource_p->username,
                                     fakesource_p->host,
                                     chptr->chname,
                                     newtopic);
            else
                sendto_channel_local(ALL_MEMBERS, chptr,
                                     ":%s TOPIC %s :%s",
                                     fakesource_p->name,
                                     chptr->chname, newtopic);
        }
        /* Propagate channelts as given, because an older channelts
         * forces any change.
         */
        sendto_server(client_p, chptr, CAP_EOPMOD|CAP_TS6, NOCAPS,
                      ":%s ETB %ld %s %ld %s :%s",
                      use_id(source_p), (long)channelts, chptr->chname,
                      (long)newtopicts, newtopicwho, newtopic);
        source_server_p = IsServer(source_p) ? source_p : source_p->servptr;
        if (can_use_tb)
            sendto_server(client_p, chptr, CAP_TB|CAP_TS6, CAP_EOPMOD,
                          ":%s TB %s %ld %s :%s",
                          use_id(source_server_p),
                          chptr->chname, (long)newtopicts,
                          newtopicwho, newtopic);
        else if (IsPerson(source_p) && textchange) {
            member = IsMember(source_p, chptr);
            if (!member)
                sendto_server(client_p, chptr, CAP_TS6, CAP_EOPMOD,
                              ":%s SJOIN %ld %s + :@%s",
                              use_id(source_server_p),
                              (long)chptr->channelts,
                              chptr->chname, use_id(source_p));
            if (EmptyString(newtopic) ||
                newtopicts >= rb_current_time() - 60)
                sendto_server(client_p, chptr, CAP_TS6, CAP_EOPMOD,
                              ":%s TOPIC %s :%s",
                              use_id(source_p),
                              chptr->chname, newtopic);
            else {
                sendto_server(client_p, chptr, CAP_TS6, CAP_EOPMOD,
                              ":%s TOPIC %s :%s",
                              use_id(source_p),
                              chptr->chname, "");
                sendto_server(client_p, chptr, CAP_TB|CAP_TS6, CAP_EOPMOD,
                              ":%s TB %s %ld %s :%s",
                              use_id(source_server_p),
                              chptr->chname, (long)newtopicts,
                              newtopicwho, newtopic);
            }
            if (!member)
                sendto_server(client_p, chptr, CAP_TS6, CAP_EOPMOD,
                              ":%s PART %s :Topic set for %s",
                              use_id(source_p),
                              chptr->chname, newtopicwho);
        } else if (textchange) {
            /* Should not send :server ETB if not all servers
             * support EOPMOD.
             */
            sendto_server(client_p, chptr, CAP_TS6, CAP_EOPMOD,
                          ":%s NOTICE %s :*** Notice -- Dropping topic change for %s",
                          me.id, chptr->chname, chptr->chname);
        }
    }

    return 0;
}