+/**
+ * Acknowledge user JOIN request and send "channel info" numerics.
+ *
+ * @param Client Client used to prefix the genrated commands
+ * @param target Forward commands/numerics to this user
+ * @param chan Channel structure
+ * @param channame Channel name
+ */
+static bool
+join_send_topic(CLIENT *Client, CLIENT *target, CHANNEL *chan,
+ const char *channame)
+{
+ const char *topic;
+
+ if (Client_Type(Client) != CLIENT_USER)
+ return true;
+ /* acknowledge join */
+ if (!IRC_WriteStrClientPrefix(Client, target, "JOIN :%s", channame))
+ return false;
+
+ /* Send topic to client, if any */
+ topic = Channel_Topic(chan);
+ assert(topic != NULL);
+ if (*topic) {
+ if (!IRC_WriteStrClient(Client, RPL_TOPIC_MSG,
+ Client_ID(Client), channame, topic))
+ return false;
+#ifndef STRICT_RFC
+ if (!IRC_WriteStrClient(Client, RPL_TOPICSETBY_MSG,
+ Client_ID(Client), channame,
+ Channel_TopicWho(chan),
+ Channel_TopicTime(chan)))
+ return false;
+#endif
+ }
+ /* send list of channel members to client */
+ if (!IRC_Send_NAMES(Client, chan))
+ return false;
+ return IRC_WriteStrClient(Client, RPL_ENDOFNAMES_MSG, Client_ID(Client),
+ Channel_Name(chan));
+} /* join_send_topic */
+
+/**
+ * Handler for the IRC "JOIN" command.
+ *
+ * @param Client The client from which this command has been received.
+ * @param Req Request structure with prefix and all parameters.
+ * @return CONNECTED or DISCONNECTED.
+ */
+GLOBAL bool
+IRC_JOIN( CLIENT *Client, REQUEST *Req )
+{
+ char *channame, *key = NULL, *flags, *lastkey = NULL, *lastchan = NULL;
+ CLIENT *target;
+ CHANNEL *chan;
+
+ assert (Client != NULL);
+ assert (Req != NULL);
+
+ _IRC_GET_SENDER_OR_RETURN_(target, Req, Client)
+
+ /* Is argument "0"? */
+ if (Req->argc == 1 && !strncmp("0", Req->argv[0], 2))
+ return part_from_all_channels(Client, target);
+
+ /* Are channel keys given? */
+ if (Req->argc > 1)
+ key = strtok_r(Req->argv[1], ",", &lastkey);
+
+ channame = Req->argv[0];
+ channame = strtok_r(channame, ",", &lastchan);
+
+ /* Make sure that "channame" is not the empty string ("JOIN :") */
+ if (!channame)
+ return IRC_WriteErrClient(Client, ERR_NEEDMOREPARAMS_MSG,
+ Client_ID(Client), Req->command);
+
+ while (channame) {
+ flags = NULL;
+
+ /* Did the server include channel-user-modes? */
+ if (Client_Type(Client) == CLIENT_SERVER) {
+ flags = strchr(channame, 0x7);
+ if (flags) {
+ *flags = '\0';
+ flags++;
+ }
+ }
+
+ chan = Channel_Search(channame);
+
+ /* Local client? */
+ if (Client_Type(Client) == CLIENT_USER) {
+ if (chan) {
+ /* Already existing channel: already member? */
+ if (Channel_IsMemberOf(chan, Client))
+ goto join_next;
+ } else {
+ /* Channel must be created */
+ if (!strchr(Conf_AllowedChannelTypes, channame[0])) {
+ /* ... but channel type is not allowed! */
+ IRC_WriteErrClient(Client,
+ ERR_NOSUCHCHANNEL_MSG,
+ Client_ID(Client), channame);
+ goto join_next;
+ }
+ }
+
+ /* Test if the user has reached the channel limit */
+ if ((Conf_MaxJoins > 0) &&
+ (Channel_CountForUser(Client) >= Conf_MaxJoins)) {
+ if (!IRC_WriteErrClient(Client,
+ ERR_TOOMANYCHANNELS_MSG,
+ Client_ID(Client), channame))
+ return DISCONNECTED;
+ goto join_next;
+ }
+
+ if (chan) {
+ /* Already existing channel: check if the
+ * client is allowed to join */
+ if (!join_allowed(Client, chan, channame, key))
+ goto join_next;
+ } else {
+ /* New channel: first user will become channel
+ * operator unless this is a modeless channel */
+ if (*channame != '+')
+ flags = "o";
+ }
+
+ /* Local client: update idle time */
+ Conn_UpdateIdle(Client_Conn(Client));
+ } else {
+ /* Remote server: we don't need to know whether the
+ * client is invited or not, but we have to make sure
+ * that the "one shot" entries (generated by INVITE
+ * commands) in this list become deleted when a user
+ * joins a channel this way. */
+ if (chan)
+ (void)Lists_Check(Channel_GetListInvites(chan),
+ target);