/* Copyright (C) 2000-1 drscholl@users.sourceforge.net
   This is free software distributed under the terms of the
   GNU Public License.  See the file COPYING for details.

   $Id: change.c,v 1.65 2001/02/15 08:39:45 drscholl Exp $ */

#include <stdlib.h>
#include <string.h>
#include "opennap.h"
#include "debug.h"

/* user request to change the data port they are listening on.
   703 [ :<user> ] <port> */
HANDLER (change_data_port)
{
    int     port;
    USER   *user;

    (void) tag;
    (void) len;
    ASSERT (validate_connection (con));
    if (pop_user (con, &pkt, &user) != 0)
	return;
    ASSERT (validate_user (user));
    port = atoi (pkt);

    /* the official server doesn't seem to check the value sent, so this
       error is unique to this implementation */
    if (port >= 0 && port <= 65535)
    {
	user->port = port;
	pass_message_args (con, tag, ":%s %hu", user->nick, user->port);
    }
    else if (ISUSER (con))
	send_cmd (con, MSG_SERVER_NOSUCH, "invalid data port");
}

/* 700 [ :<user> ] <speed> */
/* client is changing link speed */
HANDLER (change_speed)
{
    USER   *user;
    int     spd;

    (void) tag;
    (void) len;
    ASSERT (validate_connection (con));
    if (pop_user (con, &pkt, &user) != 0)
	return;
    spd = atoi (pkt);
    if (spd >= 0 && spd <= 10)
    {
	user->speed = spd;
	pass_message_args (con, tag, ":%s %d", user->nick, spd);
    }
    else if (ISUSER (con))
	send_cmd (con, MSG_SERVER_NOSUCH, "invalid speed");
}

/* 701 [ :<user> ] <password>
   change user password */
HANDLER (change_pass)
{
    USER   *user;
    USERDB *db;

    (void) tag;
    (void) len;
    if (pop_user (con, &pkt, &user) != 0)
	return;
    if (!pkt || !*pkt)
    {
	log ("change_pass(): missing new password");
	unparsable (con);
	return;
    }
    /* pass this along even if it is not locally registered.  the user db
     * is distributed so a record for it may reside on another server */
    pass_message_args (con, tag, ":%s %s", user->nick, pkt);
    db = hash_lookup (User_Db, user->nick);
    if (!db)
    {
	log ("change_pass(): %s is not registered", user->nick);
	return;
    }
    FREE (db->password);
    db->password = generate_pass (pkt);
    if (ISUSER (con))
	send_cmd (con, MSG_SERVER_NOSUCH, "password changed");
}

/* 702 [ :<user> ] <email>
   change email address */
HANDLER (change_email)
{
#if EMAIL
    USER   *user;
    USERDB *db;

    (void) tag;
    (void) len;
    if (pop_user (con, &pkt, &user) != 0)
	return;
    if (!pkt || !*pkt)
    {
	log ("change_email(): missing new email address");
	unparsable (con);
	return;
    }
    pass_message_args (con, tag, ":%s %s", user->nick, pkt);
    db = hash_lookup (User_Db, user->nick);
    if (!db)
    {
	log ("change_email(): could not find user %s in the database",
	     user->nick);
	return;
    }
    FREE (db->email);
    db->email = STRDUP (pkt);
#else
    (void) tag;
    (void) len;
    (void) pkt;
    (void) con;
#endif
}

/* 613 [ :<sender> ] <user> <port> [ <reason> ]
   admin request to change a user's data port */
HANDLER (alter_port)
{
    USER   *sender, *user;
    char   *nick, *port;
    int     p;

    (void) tag;
    (void) len;
    ASSERT (validate_connection (con));
    if (pop_user (con, &pkt, &sender) != 0)
	return;
    /* check for privilege */
    if (sender->level < LEVEL_MODERATOR)
    {
	log ("alter_port(): %s has no privilege to change ports",
	     sender->nick);
	permission_denied (con);
	return;
    }

    nick = next_arg (&pkt);
    port = next_arg (&pkt);
    if (!nick || !port)
    {
	unparsable (con);
	return;
    }
    user = hash_lookup (Users, nick);
    if (!user)
    {
	nosuchuser (con);
	return;
    }
    p = atoi (port);
    if (p < 0 || p > 65535)
    {
	if (ISUSER (con))
	    send_cmd (con, MSG_SERVER_NOSUCH, "%d is an invalid port", p);
	return;
    }

    if (pkt)
	truncate_reason (pkt);

    if (user->port != p)
    {
	/* only log when the port value is actually changed, not resets */
	notify_mods (CHANGELOG_MODE, "%s changed %s's data port to %d: %s",
		     sender->nick, user->nick, p, NONULL (pkt));
	user->port = p;
    }

    /* if local user, send them the message */
    if (user->local)
	send_cmd (user->con, MSG_CLIENT_ALTER_PORT, "%d", p);

    pass_message_args (con, tag, ":%s %s %d", sender->nick, user->nick, p);

    log ("alter_port: %s set %s's data port to %d", sender->nick,
	 user->nick, p);
}

/* 753 [ :<sender> ] <nick> <pass> ["reason"]
   admin command to change a user's password */
HANDLER (alter_pass)
{
    USER   *sender;
    int     ac = -1;
    char   *av[3];
    char   *sender_name;
    USERDB *db;
    USER	*target;

    ASSERT (validate_connection);
    (void) tag;
    (void) len;
    if (pop_user_server (con, tag, &pkt, &sender_name, &sender))
	return;
    if (sender->level < LEVEL_ADMIN)
    {
	permission_denied (con);
	return;
    }
    if (pkt)
	ac = split_line (av, FIELDS (av), pkt);

    if (ac < 2)
    {
	log ("alter_pass(): wrong number of arguments");
	print_args (ac, av);
	unparsable (con);
	return;
    }
    if (invalid_nick (av[0]))
    {
	if (ISUSER (con))
	    send_cmd (con, MSG_SERVER_NOSUCH,
		      "alter password failed: invalid nickname");
	return;
    }
    target = hash_lookup (Users, av[0]);
    if (target)
    {
	if (target->level >= sender->level)
	{
	    send_cmd (con, MSG_SERVER_NOSUCH,
		    "alter password failed: permission denied");
	    return;
	}
    }

    if (ac > 2)
	truncate_reason (av[2]);
    /* send this now since the account might not be locally registered */
    pass_message_args (con, tag, ":%s %s %s \"%s\"", sender->nick, av[0],
		       av[1], (ac > 2) ? av[2] : "");
    db = hash_lookup (User_Db, av[0]);
    if (db)
    {
	char   *newpass;

	if (db->level >= sender->level)
	{
	    send_cmd (con, MSG_SERVER_NOSUCH,
		    "alter password failed: permission denied");
	    return;
	}
	newpass = generate_pass (av[1]);
	if (!newpass)
	{
	    OUTOFMEMORY ("alter_pass");
	    return;
	}
	FREE (db->password);
	db->password = newpass;
    }
    notify_mods (CHANGELOG_MODE, "%s changed %s's password: %s",
		 sender->nick, av[0], (ac > 2) ? av[2] : "");
}

/* 625 [ :<sender> ] <nick> <speed>
   admin command to change another user's reported line speed */
HANDLER (alter_speed)
{
    USER   *sender, *user;
    int     ac, speed;
    char   *av[2];

    ASSERT (validate_connection (con));
    (void) len;
    if (pop_user (con, &pkt, &sender))
	return;
    ac = split_line (av, sizeof (av) / sizeof (char *), pkt);

    if (ac < 2)
    {
	unparsable (con);
	return;
    }
    if (sender->level < LEVEL_MODERATOR)
    {
	permission_denied (con);
	return;
    }
    speed = atoi (av[1]);
    if (speed < 0 || speed > 10)
    {
	if (ISUSER (con))
	    send_cmd (con, MSG_SERVER_NOSUCH, "Invalid speed");
	return;
    }
    user = hash_lookup (Users, av[0]);
    if (!user)
    {
	nosuchuser (con);
	return;
    }
    ASSERT (validate_user (user));
    if (user->speed == speed)
    {
	if (ISUSER (con))
	    send_cmd (con, MSG_SERVER_NOSUCH, "%s's speed is already %d",
		      user->nick, speed);
	return;
    }
    user->speed = speed;
    pass_message_args (con, tag, ":%s %s %d", sender->nick, user->nick,
		       speed);
    notify_mods (CHANGELOG_MODE, "%s changed %s's speed to %d.", sender->nick,
		 user->nick, speed);
}

/* 611 [ :<sender> ] <user> [ <reason> ]
   nuke a user's account */
HANDLER (nuke)
{
    USER   *sender, *user;
    USERDB *db;
    char   *nick, *sender_name;
    int     level = -1;

    ASSERT (validate_connection (con));
    (void) len;
    if (pop_user_server (con, tag, &pkt, &sender_name, &sender))
	return;
    nick = next_arg (&pkt);
    if (!nick)
    {
	if (ISUSER (con))
	    send_cmd (con, MSG_SERVER_NOSUCH,
		      "nuke failed: missing nickname");
	else
	    log ("nuke: missing nick (from server %s)", con->host);
	return;
    }

    if (sender && sender->level < LEVEL_MODERATOR)
    {
	send_user (sender, MSG_SERVER_NOSUCH,
		"[%s] nuke failed: permission denied", Server_Name);
	return;
    }

    db = hash_lookup (User_Db, nick);
    user = hash_lookup (Users, nick);

    if (sender && sender->level < LEVEL_ELITE && (db || user))
    {
	level = user ? user->level : db->level;

	if (sender->level <= level &&
	    strcasecmp (sender->nick, db ? db->nick : user->nick) != 0)
	{
	    send_user (sender, MSG_SERVER_NOSUCH,
		       "[%s] nuke failed: permission denied", Server_Name);
	    return;
	}
    }

    if (db)
	hash_remove (User_Db, db->nick);

    if (pkt)
	truncate_reason (pkt);

    /* if the user is currently logged in, set them to a sane state (one
     * which would not require a db entry.
     */
    if (user)
    {
	user->level = LEVEL_USER;
	if (user->cloaked)
	{
	    if (ISUSER (user->con))
	    {
		send_cmd (user->con, MSG_SERVER_NOSUCH,
			  "You are no longer cloaked.");
	    }
	    user->cloaked = 0;
	}
	user->muzzled = 0;
	if (ISUSER (user->con))
	{
	    send_cmd (user->con, MSG_SERVER_NOSUCH,
		      "%s nuked your account: %s",
		      sender && sender->cloaked ? "Operator" : sender_name,
		      NONULL (pkt));
	}
    }

    pass_message_args (con, tag, ":%s %s %s", sender_name, nick,
		       NONULL (pkt));

    notify_mods (CHANGELOG_MODE, "%s nuked %s's account: %s",
		 sender_name, nick, NONULL (pkt));
}

/* 652 [ :<sender> ] [0 | 1]
 * toggle the invisible state of the current user.  when a server is the
 * sender of the message, the 1 signifies that the cloak status should
 * absolutely be turned on rather than toggled (used for synch)
 */
HANDLER (cloak)
{
    USER   *sender;
    int     bit = -1;
    char   *sender_name;
    char   *bitptr;

    (void) len;
    ASSERT (validate_connection (con));
    if (pop_user_server (con, tag, &pkt, &sender_name, &sender))
	return;

    bitptr = next_arg (&pkt);

    if (bitptr)
    {
	bit = atoi (bitptr);
	if (bit > 1 || bit < 0)
	{
	    log ("cloak: invalid cloak state %s", bitptr);
	    if (ISUSER (con))
		send_cmd (con, MSG_SERVER_NOSUCH,
			  "cloak failed: invalid cloak state %s", bitptr);
	    return;
	}
    }

    if (bit == -1)
	bit = !sender->cloaked;	/* toggle */

    /* always allow the decloak to go through in order to help fix desyncs */
    if (bit == 1)
    {
	if (sender->level < LEVEL_MODERATOR)
	{
	    send_user (sender, MSG_SERVER_NOSUCH,
		       "[%s] cloak failed: permission denied", Server_Name);
	    if (ISSERVER (con))
	    {
		log ("cloak: %s can't cloak, %s desycned", sender->nick,
		     con->host);
		/*force a decloak */
		send_cmd (con, MSG_CLIENT_CLOAK, ":%s 0", sender->nick);
	    }
	    return;
	}
    }

    if ((bit == 1 && sender->cloaked) || (bit == 0 && !sender->cloaked))
	return;			/*no change */

    sender->cloaked = bit;

    /* always send the absolute state when passing server messages */
    pass_message_args (con, tag, ":%s %d", sender->nick, bit);

    notify_mods (CLOAKLOG_MODE, "%s has %scloaked", sender->nick,
		 sender->cloaked ? "" : "de");

    if (ISUSER (con))
	send_cmd (con, MSG_SERVER_NOSUCH, "You are %s cloaked.",
		  sender->cloaked ? "now" : "no longer");
}
