nuts4_141/
nuts4_141/n4/boards/0002/
nuts4_141/n4/boards/0003/
nuts4_141/n4/boards/0004/
nuts4_141/n4/boards/0006/
nuts4_141/n4/boards/0FFF/
nuts4_141/n4/etc/
nuts4_141/n4/pubgroups/0004/
nuts4_141/n4/pubgroups/0006/
nuts4_141/n4/users/0FFF/
nuts4_141/n4b/boards/0001/
nuts4_141/n4b/boards/0002/
nuts4_141/n4b/boards/0FFF/
nuts4_141/n4b/etc/
nuts4_141/n4b/pubgroups/0004/
nuts4_141/n4b/pubgroups/0005/
nuts4_141/n4b/src/
nuts4_141/n4b/users/0FFF/
/***************************************************************************
 FILE: cl_group.cc
 LVU : 1.4.1

 DESC:
 Object for a group including code to join/leave users, set to private/public
 and so on.

 Copyright (C) Neil Robertson 2003-2005

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

 ***************************************************************************/

#include "globals.h"


/*** Given id load config file. This is for public and user groups ***/
cl_group::cl_group(uint16_t gid, cl_local_user *own)
{
FILE *fp;
cl_group *grp;
struct stat fs;
int linenum,ban_level,i,err;
uint16_t uid;
cl_splitline sl(1);
char path[MAXPATHLEN];
char line[ARR_SIZE];
char *config_option[]={
	"name",
	"private",
	"fixed",
	"ban"
	};
enum {
	OPT_NAME,
	OPT_PRIVATE,
	OPT_FIXED,
	OPT_BAN,

	OPT_END
	};

name = NULL;

/* Its inefficient having to call init first but if theres an error on
   group loading everything has to be set up ok for the destructor to
   work properly */
init(gid,own ? GROUP_TYPE_USER : GROUP_TYPE_PUBLIC,own);

if ((type == GROUP_TYPE_USER && id < MIN_LOCAL_USER_ID) ||
    (type == GROUP_TYPE_PUBLIC && id >= MIN_LOCAL_USER_ID)) {
	error = ERR_INVALID_ID;  return;
	}

// See if group with this id already exists
if ((grp = get_group(gid)) && grp != this) {
	error = ERR_DUPLICATE_ID;  return;
	}

// User group config
if (own) {
	sprintf(path,"%s/%04X/%s",USER_DIR,id,USER_GROUP_CONFIG_FILE);
	sprintf(glogfile,"%s/%04X/%s",USER_DIR,id,USER_GROUP_LOG_FILE);
	}
else {
	// Public group config
	sprintf(path,"%s/%04X/",PUB_GROUP_DIR,gid);
	if (stat(path,&fs) == -1 || (fs.st_mode & S_IFMT) != S_IFDIR) {
		error = ERR_NO_DIR;  return;
		}
	strcat(path,PUB_GROUP_CONFIG_FILE);

	sprintf(glogfile,"%s/%04X/%s",PUB_GROUP_DIR,gid,PUB_GROUP_LOG_FILE);
	}

// Open config file
if (!(fp=fopen(path,"r"))) {
	error = ERR_CANT_OPEN_FILE;  return;
	}

err = 0;
linenum = 1;
fgets(line,ARR_SIZE-1,fp);

while(!feof(fp) && !(err = ferror(fp))) {
	if (sl.parse(line) != OK) goto ERROR;
	if (sl.wcnt) {
		for(i=0;i < OPT_END;++i)
			if (!strcmp(sl.word[0],config_option[i])) break;

		switch(i) {
			case OPT_NAME:
			if (sl.wcnt > 2 || !sl.word[1][0]) goto ERROR;
			set_name(sl.word[1]);
			break;


			case OPT_PRIVATE:
			if (!strcasecmp(sl.word[1],"YES")) 
				SETFLAG(GROUP_FLAG_PRIVATE);
			else
			if (strcasecmp(sl.word[1],"NO")) goto ERROR;
			break;
		

			case OPT_FIXED:
			if (!strcasecmp(sl.word[1],"YES")) 
				SETFLAG(GROUP_FLAG_FIXED);
			else
			if (strcasecmp(sl.word[1],"NO")) goto ERROR;
			break;


			case OPT_BAN:
			if (sl.wcnt < 3 ||
			    (ban_level = get_level(sl.word[1])) == -1 ||
			    !(uid = idstr_to_id(sl.word[2])))
				goto ERROR;

			switch(sl.wcnt) {
				case 3:
				ban(ban_level,uid,0,0);
				break;
			
				case 5:
				ban(ban_level,uid,
				    (uint32_t)inet_addr(sl.word[3]),
				    htons((uint16_t)atoi(sl.word[4])));
				break;
	
				default: goto ERROR;
				}
			break;


			default:
			if (own) own->warnprintf("Unknown option '%s' on group config file line %d.\n",sl.word[0],linenum);
			else {
				if (booting)
					printf("   WARNING: Unknown option '%s' on config file line %d.\n",sl.word[0],linenum);
				else
					log(1,"WARNING: Unknown option '%s' for group %04X on config file line %d.\n",sl.word[0],id,linenum);
				}
			}
		}

	fgets(line,ARR_SIZE-1,fp);
	++linenum;
	continue;

	ERROR:
	if (own) own->warnprintf("Invalid configuration for option '%s' on group config file line %d.\n",config_option[i],linenum);
	else {
		if (booting) 
			printf("   WARNING: Invalid configuration for option '%s' on config file line %d.\n",config_option[i],linenum);
		else
			log(1,"WARNING: Invalid configuration for option '%s' for group %04X on config file line %d.\n",config_option[i],id,linenum);
		}
	fgets(line,ARR_SIZE-1,fp);
	++linenum;
	}
fclose(fp);

grouplog(false,"~BG******************** GROUP LOADED/CREATED *******************\n");

if (err) log(1,"ERROR: Read failure while reading config file: %s\n",strerror(err));

if (!name) error = ERR_NAME_NOT_SET;
}




/*** Constructor for hard coded and user groups if user group config file
     doesn't exist for some reason and for public groups created by the
     "group create" command. ***/
cl_group::cl_group(uint16_t gid, char *nme, int typ, cl_local_user *own)
{
cl_group *grp;
char path[MAXPATHLEN];

name = NULL;
glogfile[0] = 0;
set_name(nme);
init(gid,typ,own);

// Errors checked after init since init sets up stuff that destructor
// will undo so it must be run regardless...
if ((grp = get_group(gid)) && grp != this) {
	error = ERR_DUPLICATE_ID;  return;
	}
switch(type) {
	case GROUP_TYPE_SYSTEM:
	if (id >= MIN_LOCAL_USER_ID) error = ERR_INVALID_ID; 
	return;

	case GROUP_TYPE_PUBLIC:
	if (id >= MIN_LOCAL_USER_ID) {
		error = ERR_INVALID_ID;  return;
		}

	// Create directory. Don't check result since any errors will become 
	// apparent on save.
	sprintf(path,"%s/%04X",PUB_GROUP_DIR,gid);
	mkdir(path,0700);
	error = save();
	sprintf(glogfile,"%s/%04X/%s",PUB_GROUP_DIR,gid,PUB_GROUP_LOG_FILE);
	break;

	case GROUP_TYPE_USER:
	if (id < MIN_LOCAL_USER_ID) error = ERR_INVALID_ID;
	}
grouplog(false,"~BG*********************** GROUP CREATED ***********************\n");
}




/*** Initialise everything except the name ***/
void cl_group::init(uint16_t gid, int typ, cl_local_user *own)
{
int i;

id = gid;
type = typ;
error = OK;
ucnt = 0;
flags = 0;
owner = own;
revpos = 0;
first_ban = NULL;
last_ban = NULL;

board  = ((id != GONE_REMOTE_GROUP_ID) ? new cl_board(this) : NULL);

revbuff = new revline[num_review_lines];
for(i=0;i < num_review_lines;++i) {
	revbuff[i].line = NULL;
	revbuff[i].alloc = 0;
	}

switch(type) {
	case GROUP_TYPE_SYSTEM: system_group_count++;  break;
	case GROUP_TYPE_PUBLIC: public_group_count++;  break;
	case GROUP_TYPE_USER  : user_group_count++;
	}

add_list_item(first_group,last_group,this);
}




/*** Destructor ***/
cl_group::~cl_group()
{
cl_user *u;
st_user_ban *gb,*gbn;
int i;

// Return all users to their own group and if they were using the board
// reader terminate it etc
FOR_ALL_USERS(u) {
	/* If user is being prompted about deleting this group then reset.
	   User who is actually deleting group will have had this set to NULL
	   already */
	if (u->del_group == this) u->reset_to_cmd_stage();

	// If user is in group then bung him back to his home group unless it
	// is his home group in which case hes logging off so dont bother
	if (u->group == this && this != u->home_group) {
		u->uprintf("\n~FYYour current group has been deleted. Returning to your home group...\n");
		u->home_group->join(u);
		u->prompt();
		}

	// Delete any invites for this group
	for(i=0;i < MAX_INVITES;++i) {
		if (u->invite[i].grp == this) {
			u->infoprintf("Your invite into group %04X (%s~RS) has been revoked because the group has been deleted.\n",id,name);
			u->invite[i].grp = NULL;
			break;
			}
		}

	// If user is paging info on this group then reset pointer
	if (u->com_page_ptr == (void *)this) u->com_page_ptr = NULL;

	// If user was monitoring this group then reset
	if (u->mon_group == this) {
		u->infoprintf("You can no longer monitor group ~FT%04X~RS (%s~RS)\n",id,name);

		u->mon_group = NULL;
		}
	if (u->prev_group == this) u->prev_group = NULL;
	}

FREE(name);

// Free review lines & delete board 
for(i=0;i < num_review_lines;++i) FREE(revbuff[i].line);
delete revbuff;
if (board) delete board;

// Delete bans
for(gb = first_ban;gb;gb = gbn) {
	gbn = gb->next;
	delete gb;
	}

// Write messge to log
grouplog(false,"~BY******************* GROUP UNLOADED/DELETED ******************\n");

switch(type) {
	case GROUP_TYPE_SYSTEM: system_group_count--;  break;
	case GROUP_TYPE_PUBLIC: public_group_count--;  break;
	case GROUP_TYPE_USER  : user_group_count--;
	}

remove_list_item(first_group,last_group,this);
}



//////////////////// METHODS ///////////////////////

/*** Set the name ***/
int cl_group::set_name(char *nme)
{
char *tmp;

if (!nme) nme = "";
if (!(tmp = strdup(nme))) return ERR_MALLOC;
if ((int)strlen(tmp) > max_group_name_len) tmp[max_group_name_len] = '\0';
FREE(name);
name = tmp;

return OK;
}




/*** Set group to private ***/
int cl_group::set_private()
{
cl_user *u;

if (type == GROUP_TYPE_SYSTEM) return ERR_SYSTEM_GROUP;
if (FLAGISSET(GROUP_FLAG_PRIVATE)) return ERR_GROUP_ACCESS_SAME;
if (FLAGISSET(GROUP_FLAG_FIXED)) return ERR_GROUP_FIXED;
SETFLAG(GROUP_FLAG_PRIVATE);

// If anyone monitoring this group then switch it off. Can't be arsed to
// check if they would still have permission or not, let em just reset it.
FOR_ALL_USERS(u) {
	if (u->mon_group == this) {
		u->infoprintf("Group ~FT%04X~RS (%s~RS) has been set to private so you are no longer monitoring the group.\n",id,name);
		u->mon_group = NULL;
		}
	}

// Only user groups can be loaded as private if they're not fixed
return (type != GROUP_TYPE_SYSTEM) ? save() : OK;
}




/*** Set group to public ***/
int cl_group::set_public()
{
cl_user *u;
int i;

if (type == GROUP_TYPE_SYSTEM) return ERR_SYSTEM_GROUP;
if (!FLAGISSET(GROUP_FLAG_PRIVATE)) return ERR_GROUP_ACCESS_SAME;
if (FLAGISSET(GROUP_FLAG_FIXED)) return ERR_GROUP_FIXED;
UNSETFLAG(GROUP_FLAG_PRIVATE);

// Revoke any invites
FOR_ALL_USERS(u) {
	if (u->level != USER_LEVEL_LOGIN) {
		for(i=0;i < MAX_INVITES;++i) {
			if (u->invite[i].grp == this) {
				u->infoprintf("Your invite to group %04X (%s~RS) has been revoked because the group has returned to public access.\n",id,name);
				u->invite[i].grp = NULL;
				break;
				}
			}
		}
	}
return (type != GROUP_TYPE_SYSTEM) ? save() : OK;
}




/*** Set group to fixed ***/
int cl_group::set_fixed()
{
if (type == GROUP_TYPE_SYSTEM) return ERR_SYSTEM_GROUP;
if (FLAGISSET(GROUP_FLAG_FIXED)) return ERR_GROUP_ACCESS_SAME;
SETFLAG(GROUP_FLAG_FIXED);
return (type != GROUP_TYPE_SYSTEM) ? save() : OK;
}




/*** Set group to unfixed ***/
int cl_group::set_unfixed()
{
if (type == GROUP_TYPE_SYSTEM) return ERR_SYSTEM_GROUP;
if (!FLAGISSET(GROUP_FLAG_FIXED)) return ERR_GROUP_ACCESS_SAME;
UNSETFLAG(GROUP_FLAG_FIXED);
return (type != GROUP_TYPE_SYSTEM) ? save() : OK;
}




/*** Save the groups config ***/
int cl_group::save()
{
char path[MAXPATHLEN];
char path2[MAXPATHLEN];
FILE *fp;
st_user_ban *gb;

switch(type) {
	case GROUP_TYPE_SYSTEM: return OK;

	case GROUP_TYPE_USER: 
	sprintf(path,"%s/%04X/groupconfig.tmp",USER_DIR,id);
	break;

	case GROUP_TYPE_PUBLIC:
	sprintf(path,"%s/%04X/config.tmp",PUB_GROUP_DIR,id);
	}


if (!(fp = fopen(path,"w"))) return ERR_CANT_OPEN_FILE;

fprintf(fp,"name = \"%s\"\n",name);
fprintf(fp,"private = %s\n",noyes[FLAGISSET(GROUP_FLAG_PRIVATE)]);
fprintf(fp,"fixed = %s\n",noyes[FLAGISSET(GROUP_FLAG_FIXED)]);

FOR_ALL_GROUP_BANS(gb) {
	if (gb->utype == USER_TYPE_LOCAL) 
		fprintf(fp,"ban = %s, %04X\n",user_level[gb->level],gb->uid);
	else fprintf(fp,"ban = %s, %04X, %s, %u\n",
		user_level[gb->level],
		gb->uid,
		inet_ntoa(gb->home_addr.sin_addr),
		ntohs(gb->home_addr.sin_port));
	}
fclose(fp);

if (type == GROUP_TYPE_USER)
	sprintf(path2,"%s/%04X/%s",USER_DIR,id,USER_GROUP_CONFIG_FILE);
else 
	sprintf(path2,"%s/%04X/%s",PUB_GROUP_DIR,id,PUB_GROUP_CONFIG_FILE);

if (rename(path,path2)) {
	unlink(path);  return ERR_CANT_RENAME_FILE;
	}
return OK;
}




/** Save the description to a file ***/
int cl_group::save_desc(char *desc)
{
char path[MAXPATHLEN];
FILE *fp;
int ret;

if (type == GROUP_TYPE_USER) 
	sprintf(path,"%s/%04X/%s",USER_DIR,id,USER_GROUP_DESC_FILE);
else 
	sprintf(path,"%s/%04X/%s",PUB_GROUP_DIR,id,PUB_GROUP_DESC_FILE);

if (!(fp = fopen(path,"w"))) return ERR_CANT_OPEN_FILE;	
ret=fputs(desc,fp);
fclose(fp);
return (ret == EOF ? ERR_WRITE : OK);
}




/*** User joins the group ***/
void cl_group::join(cl_user *u, cl_server *server, uint16_t remgid)
{
cl_server *svr;

// Leaves old group. Will be null if new remote user
if (u->group &&
    !O_FLAGISSET(u,USER_FLAG_LEFT) &&
    (u->group != gone_remote_group ||
    (u->group == gone_remote_group && u->server_to != server))) 
	u->group->leave(u);

u->prev_group = u->group;
u->group=this;
u->server_to = server;

if (this == gone_remote_group) 
	// Joining remote group
	u->uprintf("You join group %04X@%s\n",remgid,server->name);
else {
	// Joining this local group
	if (!O_FLAGISSET(u,USER_FLAG_INVISIBLE))
		geprintf(
			MSG_MISC,
			u,
			NULL,"~BTJOINED:~RS ~FT%04X~RS, %s %s\n",u->id,u->name,u->desc);

	// Don't look if we're a new login joining start group because thats
	// done by do_login_messages()
	if (!O_FLAGISSET(u,USER_FLAG_NEW_LOGIN)) u->look(u->group,0);
	}
ucnt++;

// Send out group change notification
FOR_ALL_SERVERS(svr) 
	if (svr->stage == SERVER_STAGE_CONNECTED) svr->send_group_change(u);
}




/*** User leaves a group. This just resets some stuff. ***/
void cl_group::leave(cl_user *u)
{
if (this == gone_remote_group) {
	u->server_to->send_leave(u->id);
	u->server_to = NULL;
	u->group = NULL;
	}
else 
if (!O_FLAGISSET(u,USER_FLAG_INVISIBLE))
	geprintf(MSG_MISC,u,NULL,"~BMLEFT:~RS ~FT%04X~RS, %s %s\n",
		u->id,u->name,u->desc);

// If doing anything on the board then quit as user could have been forced
// to leave group.
switch(u->stage) {
	case USER_STAGE_BOARD:
	case USER_STAGE_BOARD_DEL:
	case USER_STAGE_BOARD_SUBJECT:
	case USER_STAGE_BOARD_READ_FROM:
	u->uprintf("~NP\n\n~FYForcing exit of board reader.\n\n");
	FREE(u->msg_subject);
	u->page_pos = 0;
	u->stage = USER_STAGE_CMD_LINE;
	if (u->editor) delete u->editor;
	u->flags = u->prev_flags;
	u->prompt();
	}

// If no one left and group is private, return it to public.
if (!--ucnt && type == GROUP_TYPE_PUBLIC) set_public();
}




/*** Send text to all users in the group ***/
void cl_group::gprintf(int mtype, char *fmtstr,...)
{
char str[ARR_SIZE],str2[ARR_SIZE],*s;
va_list args;
cl_user *u;
int ret;

va_start(args,fmtstr);
vsnprintf(str,ARR_SIZE,fmtstr,args);
va_end(args);

FOR_ALL_USERS(u) { 
	if (u->group == this || u->mon_group == this) {
		if (u->mon_group == this) {
			sprintf(str2,"~OLMONITOR %04X: ~RS%s",id,str);
			s = str2;
			}
		else s = str;

		switch(mtype) {
			case MSG_SPEECH:
			if (!O_FLAGISSET(u,USER_FLAG_NO_SPEECH)) u->uprintf(s);
			break;

			case MSG_SHOUT:
			// This will never be used here but include
			// for the sake of completeness.
			if (!O_FLAGISSET(u,USER_FLAG_NO_SHOUTS)) u->uprintf(s);
			break;

			case MSG_INFO:
			u->infoprintf(s);
			break;

			case MSG_MISC:
			if (!O_FLAGISSET(u,USER_FLAG_NO_MISC)) u->uprintf(s);
			break;

			case MSG_SYSTEM:
			u->sysprintf(s);
			break;

			case MSG_BCAST:
			u->uprintf(s);
			}
		}
	}

grouplog(false,str);

if ((ret=add_review_line(revbuff,&revpos,str)) != OK) 
	log(1,"ERROR: cl_group::gprintf() -> add_revline_line(): %s",
		err_string[ret]);
}




/*** Send text to all users in group except those specified ***/
void cl_group::geprintf(int mtype, cl_user *u1, cl_user *u2, char *fmtstr, ...)
{
char str[ARR_SIZE],str2[ARR_SIZE],*s;
va_list args;
cl_user *u;
int ret;

va_start(args,fmtstr);
vsnprintf(str,ARR_SIZE,fmtstr,args);
va_end(args);

FOR_ALL_USERS(u) {
	if (u != u1 &&
	    u != u2 && u->group == this) {
		if (u->mon_group == this) {
			sprintf(str2,"~OLMONITOR %04X: ~RS%s",id,str);
			s = str2;
			}
		else s = str;

		switch(mtype) {
			case MSG_SPEECH:
			if (!O_FLAGISSET(u,USER_FLAG_NO_SPEECH)) u->uprintf(s);
			break;

			case MSG_SHOUT:
			// This will never be used here but include
			// for the sake of completeness.
			if (!O_FLAGISSET(u,USER_FLAG_NO_SHOUTS)) u->uprintf(s);
			break;

			case MSG_INFO:
			u->infoprintf(s);
			break;

			case MSG_MISC:
			if (!O_FLAGISSET(u,USER_FLAG_NO_MISC)) u->uprintf(s);
			break;

			case MSG_SYSTEM:
			u->sysprintf(s);
			break;

			case MSG_BCAST:
			u->uprintf(s);
			}
		}
	}

grouplog(false,str);

if ((ret=add_review_line(revbuff,&revpos,str)) != OK) 
	log(1,"ERROR: cl_group::gprintf() -> add_revline_line(): %s",
		err_string[ret]);
}




/*** User speaks ***/
int cl_group::speak(int comnum, cl_user *u, char *txt)
{
// Cheat a bit. Don't return an error here but print a message.
if (O_FLAGISSET(u,USER_FLAG_MUZZLED)) {
	if (u->muzzle_end_time) 
		u->uprintf("You cannot %s because you are muzzled for another %s.\n",
			command[comnum],
			time_period(u->muzzle_end_time - server_time));
	else 
		u->uprintf("You cannot %s because you are muzzled indefinately.\n",command[comnum]);
	return OK;
	}

if (this == gone_remote_group) return ERR_RESTRICTED_GROUP;
if (O_FLAGISSET(u,USER_FLAG_NO_SPEECH)) return ERR_NOSPEECH;

switch(comnum) {
	case COM_SAY:
	gprintf(MSG_SPEECH,"~FT%04X,%s~FG:~RS %s\n",u->id,u->name,txt);
	break;

	case COM_EMOTE:
	gprintf(MSG_SPEECH,"~FT%04X~FG:~RS %s %s\n",u->id,u->name,txt);
	break;

	case COM_THINK:
	gprintf(MSG_SPEECH,"~FT%04X~FG:~RS %s thinks . o O ( %s )\n",
		u->id,u->name,txt);
	}
return OK;
}




/*** Returns whether user can modify anything about the group ***/
int cl_group::user_can_modify(cl_user *u)
{
return (type != GROUP_TYPE_SYSTEM && 
        (this == u->home_group || u->level >= group_modify_level));
}




/*** Returns whether user can join the group. Return invite number in inv
     parameter if only let it because of this ***/
int cl_group::user_can_join(cl_user *u, int *inv)
{
int i;

*inv = -1;

if (this == u->home_group) return 1;

if (this == gone_remote_group || this == prison_group || user_is_banned(u))
	return 0;

if (!FLAGISSET(GROUP_FLAG_PRIVATE) || u->level >= group_gatecrash_level) 
	return 1;

// Group is private. See if user has an invite
for(i=0;i < MAX_INVITES;++i) {
	if (u->invite[i].grp == this) {
		*inv = i;  return 1;
		}
	}
return 0;
}




/*** Evict someone from the group ***/
void cl_group::evict(cl_user *evictor, cl_user *evictee)
{
evictor->uprintf("You evict user ~FT%04X~RS (%s).\n",evictee->id,evictee->name);

geprintf(
	MSG_INFO,evictor,evictee,
	"User ~FT%04X~RS (%s) is evicted from this group by ~FT%04X~RS (%s)\n",
	evictee->id,evictee->name,evictor->id,evictor->name);

evictee->uprintf(
	"\n~OL~FRYou have been evicted from this group by user ~FT%04X~RS (%s)!\n\n",
	evictor->id,evictor->name);

evictee->home_group->join(evictee);
evictee->prompt();
}




/*** Ban user using id or ip info. Port must be in network order. ***/
void cl_group::ban(int ban_level, uint16_t uid, uint32_t addr, uint16_t port)
{
st_user_ban *gb;

gb = new st_user_ban;
gb->level = ban_level;
gb->uid = uid; // For remote users this will be orig_id
bzero(&gb->home_addr,sizeof(sockaddr_in));

if (addr) { 
	gb->utype = USER_TYPE_REMOTE;
	gb->user = get_remote_user(uid,addr,port);
	gb->home_addr.sin_addr.s_addr = addr;
	gb->home_addr.sin_port = port;
	}
else {
	gb->utype = USER_TYPE_LOCAL;
	gb->user = get_user(uid,0);
	}

add_list_item(first_ban,last_ban,gb);
}




/*** Ban a user. This saves the info. ***/
int cl_group::ban(int ban_level, cl_user *u)
{
st_user_ban *gb;
int i;

if (type == GROUP_TYPE_SYSTEM) return ERR_SYSTEM_GROUP;
if (u->mon_group == this) u->mon_group = NULL;

// Remove invite to group if it exists and add to banned list
for(i=0;i < MAX_INVITES;++i) {
	if (u->invite[i].grp == this) {
		u->invite[i].grp = NULL;  break;
		}
	}

gb = new st_user_ban;
gb->level = ban_level;
gb->utype = u->type;
gb->user = u;
if (u->type == USER_TYPE_REMOTE) {
	gb->uid = ((cl_remote_user *)u)->orig_id;
	gb->home_addr = u->ip_addr;
	}
else gb->uid = u->id;

add_list_item(first_ban,last_ban,gb);
return save();
}




/*** Return whether user is banned. For logged on users. ***/
int cl_group::user_is_banned(cl_user *u)
{
st_user_ban *gb;

if (this == u->home_group || u->level >= group_gatecrash_level) return 0;

// Can't just check against the user pointer in the structure since the
// user may have left/logged off then returned.
if (u->type == USER_TYPE_LOCAL) {
	FOR_ALL_GROUP_BANS(gb) 
		if (gb->utype == USER_TYPE_LOCAL &&
		    u->id == gb->uid) return 1;
	return 0;
	}

FOR_ALL_GROUP_BANS(gb) {
	// Check original id matches as they may have come over multiple hops
	if (gb->utype == USER_TYPE_REMOTE &&
	    gb->uid == ((cl_remote_user *)u)->orig_id &&
	    gb->home_addr.sin_addr.s_addr == u->ip_addr.sin_addr.s_addr &&
	    gb->home_addr.sin_port == u->ip_addr.sin_port) return 1;
	}
return 0;
}




/*** Return whether user is banned. For remote users trying to connect ***/
int cl_group::user_is_banned(pkt_user_info *pkt)
{
st_user_ban *gb;

FOR_ALL_GROUP_BANS(gb) {
	if (gb->utype == USER_TYPE_REMOTE &&
	    gb->uid == pkt->orig_uid &&
	    gb->home_addr.sin_addr.s_addr == pkt->home_addr.ip4 &&
	    gb->home_addr.sin_port == pkt->home_port) return 1;
	}
return 0;
}




/*** Log some text. We can't error in this since we'd get an error in the
     main log every time someone spoke etc which would be a pain. The 
     file pointer is re-opened each time so that A) we use up less descriptors
     and B) so that the current file can be removed or renamed without 
     affecting subsequent writes. ***/
void cl_group::grouplog(bool force, char *str)
{
FILE *fp;
char logstr[ARR_SIZE];
char tstr[20];

if ((force || SYS_FLAGISSET(SYS_FLAG_LOG_GROUPS)) &&
    (fp = fopen(glogfile,"a"))) {
	strftime(tstr,sizeof(tstr),"%d/%m %H:%M:%S: ",&server_time_tms);
	snprintf(logstr,ARR_SIZE-20,"%s%s",tstr,str);
	fputs(logstr,fp);
	fclose(fp);
	}
}