summary refs log blame commit diff stats
path: root/src/fe-gtk/banlist.c
blob: 27aee073e1ccffc6fb2c47a1b37fc947c1c86131 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15














                                                                       
                                                                            




                   


                  



                   

                   
                              


                               
                               



                    








                                                                    
                           
                          






                                 
                              
                             






                                    
                              
                             






                                    
                             
                            







                                   


                                
                    








                               
                                                            







                                                                          












                                           
 
                                          
                                   
                       

                              
                         

                
                       





                               
                                 

                     
               
 


                               

 
























































                                                                                      
 

                                                

                            
 

                             
 








                                                                                     
         


                                                     
                                                                                                   
                                                                                         















                                                                        
         












                                                                                                      
         




























                                                                                                       
 





                                                                 
 
























                                                                                             




                                                          


                    
                  



                                         
































































                                                                                                                         
                                                            
 











                                                                                              





                                             
                                       
 
                                   
                       




                                 



                                    
                                                                                 
                                                                                         
                                                  


                                             


                                              
                 






                                                                                                        

                                                           

            





                                                               
                                                     






                                                                                       
                                  


          
                                                                     
 
                                   



                              

                                   
 



                                                            
 
                                                          

                         

                                                           



                                                                     
                                                                     
                                                                                                      
 
                                                                           
                                                                        


                                                                            
                                                           
                                      
                                      




                                                        

                                                                                                   
 
                                 

                                   
                       




                       
                                                   
 
                       
 

                                                          
 
                                                            





                                                                           
                                  


           
                                                                  
 
                                  





                                                 
                                                                          
                                                    
                                           



           
                                                   




                                                                                                            

                                                                                                                           
                                                        
                                                                                             





















                                                                                                  
                                                  
 
                                   

















                                                                              
 


                                                            
                                           



                                                                           















                                                         
                                                                                                   















                                                                         
                                          



                                                                                                                   
                                                           

































                                                                                           

                                                              

                                       







                               
                  
                                                         




                                 
                                  

                                                                            
                                                                                                               
                                                   

                                             
                                                                                                                        
 
                                                                       



                                                                                                              
                                                                                                            


                                                                           
                                                      
                                                                   

                                                                             
 



                                                                           


                                                                           

                                                                             


                                                                           


                                                                             

                                                                    
                                                                                                   






                                                                     
                                                     
 






                                          




                                      


                                       

                       
                                       
         
                                                                                                              


                       
                                
         










                                                                                       


                       








                                                                        

                                                                  





                                                                                                     

                                 

                                                           
                                                  












                                                                                                                            



                                                                                
                                                         

                               
                                                                                          
                                     
                                                                                       
                                   
                                                                                        

                                    
                                                                                                             
 
                                  
 
                                           
 
/* X-Chat
 * Copyright (C) 1998 Peter Zelezny.
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>

#ifndef WIN32
#include <unistd.h>
#endif

#include "fe-gtk.h"

#include "../common/hexchat.h"
#include "../common/fe.h"
#include "../common/modes.h"
#include "../common/outbound.h"
#include "../common/hexchatc.h"
#include "gtkutil.h"
#include "maingui.h"
#include "banlist.h"

/*
 * These supports_* routines set capable, readable, writable bits */
static void supports_bans (banlist_info *, int);
static void supports_exempt (banlist_info *, int);
static void supports_invite (banlist_info *, int);
static void supports_quiet (banlist_info *, int);

static mode_info modes[MODE_CT] = {
	{
		N_("Bans"),
		N_("Ban"),
		'b',
		RPL_BANLIST,
		RPL_ENDOFBANLIST,
		1<<MODE_BAN,
		supports_bans
	}
	,{
		N_("Exempts"),
		N_("Exempt"),
		'e',
		RPL_EXCEPTLIST,
		RPL_ENDOFEXCEPTLIST,
		1<<MODE_EXEMPT,
		supports_exempt
	}
	,{
		N_("Invites"),
		N_("Invite"),
		'I',
		RPL_INVITELIST,
		RPL_ENDOFINVITELIST,
		1<<MODE_INVITE,
		supports_invite
	}
	,{
		N_("Quiets"),
		N_("Quiet"),
		'q',
		RPL_QUIETLIST,
		RPL_ENDOFQUIETLIST,
		1<<MODE_QUIET,
		supports_quiet
	}
};

/* model for the banlist tree */
enum
{
	TYPE_COLUMN,
	MASK_COLUMN,
	FROM_COLUMN,
	DATE_COLUMN,
	N_COLUMNS
};

static GtkTreeView *
get_view (struct session *sess)
{
	return GTK_TREE_VIEW (sess->res->banlist->treeview);
}

static GtkListStore *
get_store (struct session *sess)
{
	return GTK_LIST_STORE (gtk_tree_view_get_model (get_view (sess)));
}

static void
supports_bans (banlist_info *banl, int i)
{
	int bit = 1<<i;

	banl->capable |= bit;
	banl->readable |= bit;
	banl->writeable |= bit;
	return;
}

static void
supports_exempt (banlist_info *banl, int i)
{
	server *serv = banl->sess->server;
	char *cm = serv->chanmodes;
	int bit = 1<<i;

	if (serv->have_except)
		goto yes;

	if (!cm)
		return;

	while (*cm)
	{
		if (*cm == ',')
			break;
		if (*cm == 'e')
			goto yes;
		cm++;
	}
	return;

yes:
	banl->capable |= bit;
	banl->writeable |= bit;
}

static void
supports_invite (banlist_info *banl, int i)
{
	server *serv = banl->sess->server;
	char *cm = serv->chanmodes;
	int bit = 1<<i;

	if (serv->have_invite)
		goto yes;

	if (!cm)
		return;

	while (*cm)
	{
		if (*cm == ',')
			break;
		if (*cm == 'I')
			goto yes;
		cm++;
	}
	return;

yes:
	banl->capable |= bit;
	banl->writeable |= bit;
}

static void
supports_quiet (banlist_info *banl, int i)
{
	server *serv = banl->sess->server;
	char *cm = serv->chanmodes;
	int bit = 1<<i;

	if (!cm)
		return;

	while (*cm)
	{
		if (*cm == ',')
			break;
		if (*cm == modes[i].letter)
			goto yes;
		cm++;
	}
	return;

yes:
	banl->capable |= bit;
	banl->readable |= bit;
	banl->writeable |= bit;
}

/* fe_add_ban_list() and fe_ban_list_end() return TRUE if consumed, FALSE otherwise */
gboolean
fe_add_ban_list (struct session *sess, char *mask, char *who, char *when, int rplcode)
{
	banlist_info *banl = sess->res->banlist;
	int i;
	GtkListStore *store;
	GtkTreeIter iter;

	if (!banl)
		return FALSE;

	for (i = 0; i < MODE_CT; i++)
		if (modes[i].code == rplcode)
			break;
	if (i == MODE_CT)
	{
		/* printf ("Unexpected value in fe_add_ban_list:  %d\n", rplcode); */
		return FALSE;
	}
	if (banl->pending & 1<<i)
	{
		store = get_store (sess);
		gtk_list_store_append (store, &iter);

		gtk_list_store_set (store, &iter, TYPE_COLUMN, _(modes[i].type), MASK_COLUMN, mask,
						FROM_COLUMN, who, DATE_COLUMN, when, -1);

		banl->line_ct++;
		return TRUE;
	}
	else return FALSE;
}

/* Sensitize checkboxes and buttons as appropriate for the moment  */
static void
banlist_sensitize (banlist_info *banl)
{
	int checkable, i;

	/* CHECKBOXES -- */
	checkable = banl->sess->me->op? banl->writeable: banl->readable;
	for (i = 0; i < MODE_CT; i++)
	{
		if (banl->checkboxes[i] == NULL)
			continue;
		if ((checkable & 1<<i) == 0)
		/* Checkbox is not checkable.  Grey it and uncheck it. */
		{
			gtk_widget_set_sensitive (banl->checkboxes[i], FALSE);
			gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (banl->checkboxes[i]), FALSE);
		}
		else
		/* Checkbox is checkable.  Be sure it's sensitive. */
		{
			gtk_widget_set_sensitive (banl->checkboxes[i], TRUE);
		}
	}

	/* BUTTONS --- */
	if (banl->sess->me->op == 0 || banl->line_ct == 0)
	{
		/* If user is not op or list is empty, buttons should be all greyed */
		gtk_widget_set_sensitive (banl->but_clear, FALSE);
		gtk_widget_set_sensitive (banl->but_crop, FALSE);
		gtk_widget_set_sensitive (banl->but_remove, FALSE);
	}
	else
	{
		/* If no lines are selected, only the CLEAR button should be sensitive */
		if (banl->select_ct == 0)
		{
			gtk_widget_set_sensitive (banl->but_clear, TRUE);
			gtk_widget_set_sensitive (banl->but_crop, FALSE);
			gtk_widget_set_sensitive (banl->but_remove, FALSE);
		}
		/* If any lines are selected, only the REMOVE and CROP buttons should be sensitive */
		else
		{
			gtk_widget_set_sensitive (banl->but_clear, FALSE);
			gtk_widget_set_sensitive (banl->but_crop, TRUE);
			gtk_widget_set_sensitive (banl->but_remove, TRUE);
		}
	}

	/* Set "Refresh" sensitvity */
	gtk_widget_set_sensitive (banl->but_refresh, banl->pending? FALSE: banl->checked? TRUE: FALSE);
}
/* fe_ban_list_end() returns TRUE if consumed, FALSE otherwise */
gboolean
fe_ban_list_end (struct session *sess, int rplcode)
{
	banlist_info *banl = sess->res->banlist;
	int i;

	if (!banl)
		return FALSE;

	for (i = 0; i < MODE_CT; i++)
		if (modes[i].endcode == rplcode)
			break;
	if (i == MODE_CT)
	{
		/* printf ("Unexpected rplcode value in fe_ban_list_end:  %d\n", rplcode); */
		return FALSE;
	}
	if (banl->pending & modes[i].bit)
	{
		banl->pending &= ~modes[i].bit;
		if (!banl->pending)
		{
			gtk_widget_set_sensitive (banl->but_refresh, TRUE);
			banlist_sensitize (banl);
		}
		return TRUE;
	}
	else return FALSE;
}

static void
banlist_copyentry (GtkWidget *menuitem, GtkTreeView *view)
{
	GtkTreeModel *model;
	GtkTreeSelection *sel;
	GtkTreeIter iter;
	GValue mask;
	GValue from;
	GValue date;
	char *str;

	memset (&mask, 0, sizeof (mask));
	memset (&from, 0, sizeof (from));
	memset (&date, 0, sizeof (date));
	
	/* get selection (which should have been set on click)
	 * and temporarily switch to single mode to get selected iter */
	sel = gtk_tree_view_get_selection (view);
	gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE);
	if (gtk_tree_selection_get_selected (sel, &model, &iter))
	{
		gtk_tree_model_get_value (model, &iter, MASK_COLUMN, &mask);
		gtk_tree_model_get_value (model, &iter, FROM_COLUMN, &from);
		gtk_tree_model_get_value (model, &iter, DATE_COLUMN, &date);

		/* poor way to get which is selected but it works */
		if (strcmp (_("Copy mask"), gtk_menu_item_get_label (GTK_MENU_ITEM(menuitem))) == 0)
			str = g_value_dup_string (&mask);
		else
			str = g_strdup_printf (_("%s on %s by %s"), g_value_get_string (&mask),
								g_value_get_string (&date), g_value_get_string (&from));

		if (str[0] != 0)
			gtkutil_copy_to_clipboard (menuitem, NULL, str);
			
		g_value_unset (&mask);
		g_value_unset (&from);
		g_value_unset (&date);
		g_free (str);
	}
	gtk_tree_selection_set_mode (sel, GTK_SELECTION_MULTIPLE);
}

static gboolean
banlist_button_pressed (GtkWidget *wid, GdkEventButton *event, gpointer userdata)
{
	GtkTreePath *path;
	GtkWidget *menu, *maskitem, *allitem;

	/* Check for right click */
	if (event->type == GDK_BUTTON_PRESS && event->button == 3)
	{
		if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (wid), event->x, event->y,
												&path, NULL, NULL, NULL))
		{
			/* Must set the row active for use in callback */
			gtk_tree_view_set_cursor (GTK_TREE_VIEW(wid), path, NULL, FALSE);
			gtk_tree_path_free (path);
			
			menu = gtk_menu_new ();
			maskitem = gtk_menu_item_new_with_label (_("Copy mask"));
			allitem = gtk_menu_item_new_with_label (_("Copy entry"));
			g_signal_connect (maskitem, "activate", G_CALLBACK(banlist_copyentry), wid);
			g_signal_connect (allitem, "activate", G_CALLBACK(banlist_copyentry), wid);
			gtk_menu_shell_append (GTK_MENU_SHELL(menu), maskitem);
			gtk_menu_shell_append (GTK_MENU_SHELL(menu), allitem);
			gtk_widget_show_all (menu);
			
			gtk_menu_popup (GTK_MENU(menu), NULL, NULL, NULL, NULL, 
							event->button, gtk_get_current_event_time ());
		}
		
		return TRUE;
	}
	
	return FALSE;
}

static void
banlist_select_changed (GtkWidget *item, banlist_info *banl)
{
	GList *list;

	if (banl->line_ct == 0)
		banl->select_ct = 0;
	else
	{
		list = gtk_tree_selection_get_selected_rows (GTK_TREE_SELECTION (item), NULL);
		banl->select_ct = list? 1: 0;
		g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
		g_list_free (list);
	}
	banlist_sensitize (banl);
}

/**
 *  * Performs the actual refresh operations.
 *  */
static void
banlist_do_refresh (banlist_info *banl)
{
	session *sess = banl->sess;
	char tbuf[256];
	int i;
	char *tbufp;

	banlist_sensitize (banl);

	if (sess->server->connected)
	{
		GtkListStore *store;

		g_snprintf (tbuf, sizeof tbuf, DISPLAY_NAME": Ban List (%s, %s)",
						sess->channel, sess->server->servername);
		mg_set_title (banl->window, tbuf);

		store = get_store (sess);
		gtk_list_store_clear (store);
		banl->line_ct = 0;
		banl->pending = banl->checked;
		if (banl->pending)
		{
			tbufp = tbuf + g_snprintf (tbuf, sizeof tbuf, "quote mode %s +", sess->channel);
			for (i = 0; i < MODE_CT; i++)
				if (banl->pending & 1<<i)
				{
					*tbufp++ = modes[i].letter;
				}
			*tbufp = 0;
			handle_command (sess, tbuf, FALSE);
		}
	}
	else
	{
		fe_message (_("Not connected."), FE_MSG_ERROR);
	}
}

static void
banlist_refresh (GtkWidget * wid, banlist_info *banl)
{
	/* JG NOTE: Didn't see actual use of wid here, so just forwarding
	   *          * this to chanlist_do_refresh because I use it without any widget
	   *          * param in chanlist_build_gui_list when the user presses enter
	   *          * or apply for the first time if the list has not yet been
	   *          * received.
	   *          */
	banlist_do_refresh (banl);
}

static int
banlist_unban_inner (gpointer none, banlist_info *banl, int mode_num)
{
	session *sess = banl->sess;
	GtkTreeModel *model;
	GtkTreeSelection *sel;
	GtkTreeIter iter;
	char tbuf[2048];
	char **masks, *mask, *type;
	int num_sel, i;


	/* grab the list of selected items */
	model = GTK_TREE_MODEL (get_store (sess));
	sel = gtk_tree_view_get_selection (get_view (sess));

	if (!gtk_tree_model_get_iter_first (model, &iter))
		return 0;

	masks = g_malloc (sizeof (char *) * banl->line_ct);
	num_sel = 0;
	do
	{
		if (gtk_tree_selection_iter_is_selected (sel, &iter))
		{
			/* Get the mask part of this selected line */
			gtk_tree_model_get (model, &iter, TYPE_COLUMN, &type, MASK_COLUMN, &mask, -1);

			/* If it's the wrong type of mask, just continue */
			if (strcmp (_(modes[mode_num].type), type) != 0)
				continue;

			/* Otherwise add it to our array of mask pointers */
			masks[num_sel++] = g_strdup (mask);
			g_free (mask);
			g_free (type);
		}
	}
	while (gtk_tree_model_iter_next (model, &iter));

	/* and send to server */
	if (num_sel)
		send_channel_modes (sess, tbuf, masks, 0, num_sel, '-', modes[mode_num].letter, 0);

	/* now free everything */
	for (i=0; i < num_sel; i++)
		g_free (masks[i]);
	g_free (masks);

	return num_sel;
}

static void
banlist_unban (GtkWidget * wid, banlist_info *banl)
{
	int i, num = 0;

	for (i = 0; i < MODE_CT; i++)
		num += banlist_unban_inner (wid, banl, i);

	/* This really should not occur with the redesign */
	if (num < 1)
	{
		fe_message (_("You must select some bans."), FE_MSG_ERROR);
		return;
	}

	banlist_do_refresh (banl);
}

static void
banlist_clear_cb (GtkDialog *dialog, gint response, gpointer data)
{
	banlist_info *banl = data;
	GtkTreeSelection *sel;

	gtk_widget_destroy (GTK_WIDGET (dialog));

	if (response == GTK_RESPONSE_OK)
	{
		sel = gtk_tree_view_get_selection (get_view (banl->sess));
		gtk_tree_selection_select_all (sel);
		banlist_unban (NULL, banl);
	}
}

static void
banlist_clear (GtkWidget * wid, banlist_info *banl)
{
	GtkWidget *dialog;

	dialog = gtk_message_dialog_new (NULL, 0,
								GTK_MESSAGE_QUESTION, GTK_BUTTONS_OK_CANCEL,
					_("Are you sure you want to remove all listed items in %s?"), banl->sess->channel);

	g_signal_connect (G_OBJECT (dialog), "response",
							G_CALLBACK (banlist_clear_cb), banl);
	gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
	gtk_widget_show (dialog);
}

static void
banlist_add_selected_cb (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
{
	GSList **lp = data;
	GSList *list = NULL;
	GtkTreeIter *copy;

	if (!lp) return;
	list = *lp;
	copy = g_malloc (sizeof (GtkTreeIter));
	g_return_if_fail (copy != NULL);
	*copy = *iter;

	list = g_slist_append (list, copy);
	*(GSList **)data = list;
}

static void
banlist_crop (GtkWidget * wid, banlist_info *banl)
{
	session *sess = banl->sess;
	GtkTreeSelection *select;
	GSList *list = NULL, *node;
	int num_sel;

	/* remember which bans are selected */
	select = gtk_tree_view_get_selection (get_view (sess));
	/* gtk_tree_selected_get_selected_rows() isn't present in gtk 2.0.x */
	gtk_tree_selection_selected_foreach (select, banlist_add_selected_cb,
	                                     &list);

	num_sel = g_slist_length (list);
	/* select all, then unselect those that we remembered */
	if (num_sel)
	{
		gtk_tree_selection_select_all (select);

		for (node = list; node; node = node->next)
			gtk_tree_selection_unselect_iter (select, node->data);

		g_slist_foreach (list, (GFunc)g_free, NULL);
		g_slist_free (list);

		banlist_unban (NULL, banl);
	} else
		fe_message (_("You must select some bans."), FE_MSG_ERROR);
}

static void
banlist_toggle (GtkWidget *item, gpointer data)
{
	banlist_info *banl = data;
	int i, bit = 0;

	for (i = 0; i < MODE_CT; i++)
		if (banl->checkboxes[i] == item)
		{
			bit = 1<<i;
			break;
		}

	if (bit)		/* Should be gassert() */
	{
		banl->checked &= ~bit;
		banl->checked |= (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (item)))? bit: 0;
		banlist_do_refresh (banl);
	}
}

/* NOTICE:  The official strptime() is not available on all platforms so
 * I've implemented a special version here.  The official version is
 * vastly more general than this:  it uses locales for weekday and month
 * names and its second arg is a format character-string.  This special
 * version depends on the format returned by ctime(3) whose manpage
 * says it returns:
 *     "a null-terminated string of the form "Wed Jun 30 21:49:08 1993\n"
 *
 * If the real strpftime() comes available, use this format string:
 *		#define DATE_FORMAT "%a %b %d %T %Y"
 */
static void
banlist_strptime (char *ti, struct tm *tm)
{
	/* Expect something like "Sat Mar 16 21:24:27 2013" */
	static char *mon[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
								  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL };
	int M = -1, d = -1, h = -1, m = -1, s = -1, y = -1;

	if (*ti == 0)
	{
		memset (tm, 0, sizeof *tm);
		return;
	}
	/* No need to supply tm->tm_wday; mktime() doesn't read it */
	ti += 4;
	while ((mon[++M]))
		if (strncmp (ti, mon[M], 3) == 0)
			break;
	ti += 4;

	d = strtol (ti, &ti, 10);
	h = strtol (++ti, &ti, 10);
	m = strtol (++ti, &ti, 10);
	s = strtol (++ti, &ti, 10);
	y = strtol (++ti, NULL, 10) - 1900;

	tm->tm_sec = s;
	tm->tm_min = m;
	tm->tm_hour = h;
	tm->tm_mday = d;
	tm->tm_mon = M;
	tm->tm_year = y;
}

gint
banlist_date_sort (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user_data)
{
	struct tm tm1, tm2;
	time_t t1, t2;
	char *time1, *time2;

	gtk_tree_model_get(model, a, DATE_COLUMN, &time1, -1);
	gtk_tree_model_get(model, b, DATE_COLUMN, &time2, -1);
	banlist_strptime (time1, &tm1);
	banlist_strptime (time2, &tm2);
	t1 = mktime (&tm1);
	t2 = mktime (&tm2);

	if (t1 < t2) return 1;
	if (t1 == t2) return 0;
	return -1;
}

static GtkWidget *
banlist_treeview_new (GtkWidget *box, banlist_info *banl)
{
	GtkListStore *store;
	GtkWidget *view;
	GtkTreeSelection *select;
	GtkTreeViewColumn *col;
	GtkTreeSortable *sortable;

	store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING,
										 G_TYPE_STRING, G_TYPE_STRING);
	g_return_val_if_fail (store != NULL, NULL);

	sortable = GTK_TREE_SORTABLE (store);
	gtk_tree_sortable_set_sort_func (sortable, DATE_COLUMN, banlist_date_sort, GINT_TO_POINTER (DATE_COLUMN), NULL);

	view = gtkutil_treeview_new (box, GTK_TREE_MODEL (store), NULL,
										  TYPE_COLUMN, _("Type"),
										  MASK_COLUMN, _("Mask"),
										  FROM_COLUMN, _("From"),
										  DATE_COLUMN, _("Date"), -1);
	g_signal_connect (G_OBJECT (view), "button-press-event", G_CALLBACK (banlist_button_pressed), NULL);

	col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), MASK_COLUMN);
	gtk_tree_view_column_set_alignment (col, 0.5);
	gtk_tree_view_column_set_min_width (col, 100);
	gtk_tree_view_column_set_sort_column_id (col, MASK_COLUMN);
	gtk_tree_view_column_set_sizing (col, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
	gtk_tree_view_column_set_resizable (col, TRUE);

	col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), TYPE_COLUMN);
	gtk_tree_view_column_set_alignment (col, 0.5);
	gtk_tree_view_column_set_sort_column_id (col, TYPE_COLUMN);

	col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), FROM_COLUMN);
	gtk_tree_view_column_set_alignment (col, 0.5);
	gtk_tree_view_column_set_sort_column_id (col, FROM_COLUMN);
	gtk_tree_view_column_set_sizing (col, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
	gtk_tree_view_column_set_resizable (col, TRUE);

	col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), DATE_COLUMN);
	gtk_tree_view_column_set_alignment (col, 0.5);
	gtk_tree_view_column_set_sort_column_id (col, DATE_COLUMN);
	gtk_tree_view_column_set_sizing (col, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
	gtk_tree_view_column_set_resizable (col, TRUE);

	select = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
	g_signal_connect (G_OBJECT (select), "changed", G_CALLBACK (banlist_select_changed), banl);
	gtk_tree_selection_set_mode (select, GTK_SELECTION_MULTIPLE);

	gtk_widget_show (view);
	return view;
}

static void
banlist_closegui (GtkWidget *wid, banlist_info *banl)
{
	session *sess = banl->sess;

	if (sess->res->banlist == banl)
	{
		g_free (banl);
		sess->res->banlist = NULL;
	}
}

void
banlist_opengui (struct session *sess)
{
	banlist_info *banl;
	int i;
	GtkWidget *table, *vbox, *bbox;
	char tbuf[256];

	if (sess->type != SESS_CHANNEL)
	{
		fe_message (_("You can only open the Ban List window while in a channel tab."), FE_MSG_ERROR);
		return;
	}

	if (!sess->res->banlist)
	{
		sess->res->banlist = g_malloc0 (sizeof (banlist_info));
		if (!sess->res->banlist)
		{
			fe_message (_("Banlist initialization failed."), FE_MSG_ERROR);
			return;
		}
	}
	banl = sess->res->banlist;
	if (banl->window)
	{
		mg_bring_tofront (banl->window);
		return;
	}

	/* New banlist for this session -- Initialize it */
	banl->sess = sess;
	/* For each mode set its bit in capable/readable/writeable */
	for (i = 0; i < MODE_CT; i++)
		modes[i].tester (banl, i);
	/* Force on the checkmark in the "Bans" box */
	banl->checked = 1<<MODE_BAN;

	g_snprintf (tbuf, sizeof tbuf, _(DISPLAY_NAME": Ban List (%s)"),
					sess->server->servername);

	banl->window = mg_create_generic_tab ("BanList", tbuf, FALSE,
					TRUE, banlist_closegui, banl, 550, 200, &vbox, sess->server);
	gtkutil_destroy_on_esc (banl->window);

	gtk_container_set_border_width (GTK_CONTAINER (banl->window), 3);
	gtk_box_set_spacing (GTK_BOX (vbox), 3);

	/* create banlist view */
	banl->treeview = banlist_treeview_new (vbox, banl);

	table = gtk_table_new (1, MODE_CT, FALSE);
	gtk_table_set_col_spacings (GTK_TABLE (table), 16);
	gtk_box_pack_start (GTK_BOX (vbox), table, 0, 0, 0);

	for (i = 0; i < MODE_CT; i++)
	{
		if (!(banl->capable & 1<<i))
			continue;
		banl->checkboxes[i] = gtk_check_button_new_with_label (_(modes[i].name));
		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (banl->checkboxes[i]), (banl->checked & 1<<i? TRUE: FALSE));
		g_signal_connect (G_OBJECT (banl->checkboxes[i]), "toggled",
								G_CALLBACK (banlist_toggle), banl);
		gtk_table_attach (GTK_TABLE (table), banl->checkboxes[i], i+1, i+2, 0, 1, GTK_FILL, GTK_FILL, 0, 0);
	}

	bbox = gtk_hbutton_box_new ();
	gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_SPREAD);
	gtk_container_set_border_width (GTK_CONTAINER (bbox), 5);
	gtk_box_pack_end (GTK_BOX (vbox), bbox, 0, 0, 0);
	gtk_widget_show (bbox);

	banl->but_remove = gtkutil_button (bbox, GTK_STOCK_REMOVE, 0, banlist_unban, banl,
	                _("Remove"));
	banl->but_crop = gtkutil_button (bbox, GTK_STOCK_REMOVE, 0, banlist_crop, banl,
	                _("Crop"));
	banl->but_clear = gtkutil_button (bbox, GTK_STOCK_CLEAR, 0, banlist_clear, banl,
	                _("Clear"));

	banl->but_refresh = gtkutil_button (bbox, GTK_STOCK_REFRESH, 0, banlist_refresh, banl, _("Refresh"));

	banlist_do_refresh (banl);

	gtk_widget_show_all (banl->window);
}
(proxy->phase == 3) { while (1) { /* read until blank line */ if (proxy_read_line (dcc)) { if (proxy->bufferused < 1 || (proxy->bufferused == 2 && proxy->buffer[0] == '\r')) { break; } if (proxy->bufferused > 1) PrintText (dcc->serv->front_session, proxy->buffer); proxy->bufferused = 0; } else return TRUE; } fe_input_remove (dcc->iotag); dcc->iotag = 0; dcc_connect_finished (source, 0, dcc); } return TRUE; } static gboolean dcc_proxy_connect (GIOChannel *source, GIOCondition condition, struct DCC *dcc) { fe_input_remove (dcc->iotag); dcc->iotag = 0; if (!dcc_did_connect (source, condition, dcc)) return TRUE; dcc->proxy = malloc (sizeof (struct proxy_state)); if (!dcc->proxy) { dcc->dccstat = STAT_FAILED; fe_dcc_update (dcc); return TRUE; } memset (dcc->proxy, 0, sizeof (struct proxy_state)); switch (prefs.proxy_type) { case 1: return dcc_wingate_proxy_traverse (source, condition, dcc); case 2: return dcc_socks_proxy_traverse (source, condition, dcc); case 3: return dcc_socks5_proxy_traverse (source, condition, dcc); case 4: return dcc_http_proxy_traverse (source, condition, dcc); } return TRUE; } static int dcc_listen_init (struct DCC *, struct session *); static void dcc_connect (struct DCC *dcc) { int ret; char tbuf[400]; if (dcc->dccstat == STAT_CONNECTING) return; dcc->dccstat = STAT_CONNECTING; if (dcc->pasvid && dcc->port == 0) { /* accepted a passive dcc send */ ret = dcc_listen_init (dcc, dcc->serv->front_session); if (!ret) { dcc_close (dcc, STAT_FAILED, FALSE); return; } /* possible problems with filenames containing spaces? */ if (dcc->type == TYPE_RECV) snprintf (tbuf, sizeof (tbuf), strchr (dcc->file, ' ') ? "DCC SEND \"%s\" %u %d %"DCC_SFMT" %d" : "DCC SEND %s %u %d %"DCC_SFMT" %d", dcc->file, dcc->addr, dcc->port, dcc->size, dcc->pasvid); else snprintf (tbuf, sizeof (tbuf), "DCC CHAT chat %u %d %d", dcc->addr, dcc->port, dcc->pasvid); dcc->serv->p_ctcp (dcc->serv, dcc->nick, tbuf); } else { dcc->sok = dcc_connect_sok (dcc); if (dcc->sok == -1) { dcc->dccstat = STAT_FAILED; fe_dcc_update (dcc); return; } if (DCC_USE_PROXY ()) dcc->iotag = fe_input_add (dcc->sok, FIA_WRITE|FIA_EX, dcc_proxy_connect, dcc); else dcc->iotag = fe_input_add (dcc->sok, FIA_WRITE|FIA_EX, dcc_connect_finished, dcc); } fe_dcc_update (dcc); } static gboolean dcc_send_data (GIOChannel *source, GIOCondition condition, struct DCC *dcc) { char *buf; int len, sent, sok = dcc->sok; if (prefs.dcc_blocksize < 1) /* this is too little! */ prefs.dcc_blocksize = 1024; if (prefs.dcc_blocksize > 102400) /* this is too much! */ prefs.dcc_blocksize = 102400; if (dcc->throttled) { fe_input_remove (dcc->wiotag); dcc->wiotag = 0; return FALSE; } if (!dcc->fastsend) { if (dcc->ack < dcc->pos) return TRUE; } else if (!dcc->wiotag) dcc->wiotag = fe_input_add (sok, FIA_WRITE, dcc_send_data, dcc); buf = malloc (prefs.dcc_blocksize); if (!buf) return TRUE; lseek (dcc->fp, dcc->pos, SEEK_SET); len = read (dcc->fp, buf, prefs.dcc_blocksize); if (len < 1) goto abortit; sent = send (sok, buf, len, 0); if (sent < 0 && !(would_block ())) { abortit: free (buf); EMIT_SIGNAL (XP_TE_DCCSENDFAIL, dcc->serv->front_session, file_part (dcc->file), dcc->nick, errorstring (sock_error ()), NULL, 0); dcc_close (dcc, STAT_FAILED, FALSE); return TRUE; } if (sent > 0) { dcc->pos += sent; dcc->lasttime = time (0); } /* have we sent it all yet? */ if (dcc->pos >= dcc->size) { /* it's all sent now, so remove the WRITE/SEND handler */ if (dcc->wiotag) { fe_input_remove (dcc->wiotag); dcc->wiotag = 0; } } free (buf); return TRUE; } static gboolean dcc_handle_new_ack (struct DCC *dcc) { guint32 ack; char buf[16]; gboolean done = FALSE; memcpy (&ack, dcc->ack_buf, 4); dcc->ack = ntohl (ack); /* this could mess up when xfering >32bit files */ if (dcc->size <= 0xffffffff) { /* fix for BitchX */ if (dcc->ack < dcc->resumable) dcc->ackoffset = TRUE; if (dcc->ackoffset) dcc->ack += dcc->resumable; } /* DCC complete check */ if (dcc->pos >= dcc->size && dcc->ack >= (dcc->size & 0xffffffff)) { dcc->ack = dcc->size; /* force 100% ack for >4 GB */ dcc_close (dcc, STAT_DONE, FALSE); dcc_calc_average_cps (dcc); /* this must be done _after_ dcc_close, or dcc_remove_from_sum will see the wrong value in dcc->cps */ sprintf (buf, "%d", dcc->cps); EMIT_SIGNAL (XP_TE_DCCSENDCOMP, dcc->serv->front_session, file_part (dcc->file), dcc->nick, buf, NULL, 0); done = TRUE; } else if ((!dcc->fastsend) && (dcc->ack >= (dcc->pos & 0xffffffff))) { dcc_send_data (NULL, 0, (gpointer)dcc); } #ifdef USE_DCC64 /* take the top 32 of "bytes send" and bottom 32 of "ack" */ dcc->ack = (dcc->pos & G_GINT64_CONSTANT (0xffffffff00000000)) | (dcc->ack & 0xffffffff); /* dcc->ack is only used for CPS and PERCENTAGE calcs from now on... */ #endif return done; } static gboolean dcc_read_ack (GIOChannel *source, GIOCondition condition, struct DCC *dcc) { int len; while (1) { /* try to fill up 4 bytes */ len = recv (dcc->sok, dcc->ack_buf, 4 - dcc->ack_pos, 0); if (len < 1) { if (len < 0) { if (would_block ()) /* ok - keep waiting */ return TRUE; } EMIT_SIGNAL (XP_TE_DCCSENDFAIL, dcc->serv->front_session, file_part (dcc->file), dcc->nick, errorstring ((len < 0) ? sock_error () : 0), NULL, 0); dcc_close (dcc, STAT_FAILED, FALSE); return TRUE; } dcc->ack_pos += len; if (dcc->ack_pos >= 4) { dcc->ack_pos = 0; if (dcc_handle_new_ack (dcc)) return TRUE; } /* loop again until would_block() returns true */ } } static gboolean dcc_accept (GIOChannel *source, GIOCondition condition, struct DCC *dcc) { char host[128]; struct sockaddr_in CAddr; int sok; socklen_t len; len = sizeof (CAddr); sok = accept (dcc->sok, (struct sockaddr *) &CAddr, &len); fe_input_remove (dcc->iotag); dcc->iotag = 0; closesocket (dcc->sok); if (sok < 0) { dcc->sok = -1; dcc_close (dcc, STAT_FAILED, FALSE); return TRUE; } set_nonblocking (sok); dcc->sok = sok; dcc->addr = ntohl (CAddr.sin_addr.s_addr); if (dcc->pasvid) return dcc_connect_finished (NULL, 0, dcc); dcc->dccstat = STAT_ACTIVE; dcc->lasttime = dcc->starttime = time (0); dcc->fastsend = prefs.fastdccsend; snprintf (host, sizeof (host), "%s:%d", net_ip (dcc->addr), dcc->port); switch (dcc->type) { case TYPE_SEND: if (dcc->fastsend) dcc->wiotag = fe_input_add (sok, FIA_WRITE, dcc_send_data, dcc); dcc->iotag = fe_input_add (sok, FIA_READ|FIA_EX, dcc_read_ack, dcc); dcc_send_data (NULL, 0, (gpointer)dcc); EMIT_SIGNAL (XP_TE_DCCCONSEND, dcc->serv->front_session, dcc->nick, host, dcc->file, NULL, 0); break; case TYPE_CHATSEND: dcc_open_query (dcc->serv, dcc->nick); dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, dcc_read_chat, dcc); dcc->dccchat = malloc (sizeof (struct dcc_chat)); dcc->dccchat->pos = 0; EMIT_SIGNAL (XP_TE_DCCCONCHAT, dcc->serv->front_session, dcc->nick, host, NULL, NULL, 0); break; } fe_dcc_update (dcc); return TRUE; } guint32 dcc_get_my_address (void) /* the address we'll tell the other person */ { struct hostent *dns_query; guint32 addr = 0; if (prefs.ip_from_server && prefs.dcc_ip) addr = prefs.dcc_ip; else if (prefs.dcc_ip_str[0]) { dns_query = gethostbyname ((const char *) prefs.dcc_ip_str); if (dns_query != NULL && dns_query->h_length == 4 && dns_query->h_addr_list[0] != NULL) { /*we're offered at least one IPv4 address: we take the first*/ addr = *((guint32*) dns_query->h_addr_list[0]); } } return addr; } static int dcc_listen_init (struct DCC *dcc, session *sess) { guint32 my_addr; struct sockaddr_in SAddr; int i, bindretval = -1; socklen_t len; dcc->sok = socket (AF_INET, SOCK_STREAM, 0); if (dcc->sok == -1) return FALSE; memset (&SAddr, 0, sizeof (struct sockaddr_in)); len = sizeof (SAddr); getsockname (dcc->serv->sok, (struct sockaddr *) &SAddr, &len); SAddr.sin_family = AF_INET; /*if local_ip is specified use that*/ if (prefs.local_ip != 0xffffffff) { my_addr = prefs.local_ip; SAddr.sin_addr.s_addr = prefs.local_ip; } /*otherwise use the default*/ else my_addr = SAddr.sin_addr.s_addr; /*if we have a valid portrange try to use that*/ if (prefs.first_dcc_send_port > 0) { SAddr.sin_port = 0; i = 0; while ((prefs.last_dcc_send_port > ntohs(SAddr.sin_port)) && (bindretval == -1)) { SAddr.sin_port = htons (prefs.first_dcc_send_port + i); i++; /*printf("Trying to bind against port: %d\n",ntohs(SAddr.sin_port));*/ bindretval = bind (dcc->sok, (struct sockaddr *) &SAddr, sizeof (SAddr)); } /* with a small port range, reUseAddr is needed */ len = 1; setsockopt (dcc->sok, SOL_SOCKET, SO_REUSEADDR, (char *) &len, sizeof (len)); } else { /* try random port */ SAddr.sin_port = 0; bindretval = bind (dcc->sok, (struct sockaddr *) &SAddr, sizeof (SAddr)); } if (bindretval == -1) { /* failed to bind */ PrintText (sess, "Failed to bind to any address or port.\n"); return FALSE; } len = sizeof (SAddr); getsockname (dcc->sok, (struct sockaddr *) &SAddr, &len); dcc->port = ntohs (SAddr.sin_port); /*if we have a dcc_ip, we use that, so the remote client can connect*/ /*else we try to take an address from dcc_ip_str*/ /*if something goes wrong we tell the client to connect to our LAN ip*/ dcc->addr = dcc_get_my_address (); /*if nothing else worked we use the address we bound to*/ if (dcc->addr == 0) dcc->addr = my_addr; dcc->addr = ntohl (dcc->addr); set_nonblocking (dcc->sok); listen (dcc->sok, 1); set_blocking (dcc->sok); dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, dcc_accept, dcc); return TRUE; } static struct session *dccsess; static char *dccto; /* lame!! */ static int dccmaxcps; static int recursive = FALSE; static void dcc_send_wild (char *file) { dcc_send (dccsess, dccto, file, dccmaxcps, 0); } void dcc_send (struct session *sess, char *to, char *file, int maxcps, int passive) { char outbuf[512]; struct stat st; struct DCC *dcc; char *file_fs; /* this is utf8 */ file = expand_homedir (file); if (!recursive && (strchr (file, '*') || strchr (file, '?'))) { char path[256]; char wild[256]; char *path_fs; /* local filesystem encoding */ safe_strcpy (wild, file_part (file), sizeof (wild)); path_part (file, path, sizeof (path)); if (path[0] != '/' || path[1] != '\0') path[strlen (path) - 1] = 0; /* remove trailing slash */ dccsess = sess; dccto = to; dccmaxcps = maxcps; free (file); /* for_files() will use opendir, so we need local FS encoding */ path_fs = xchat_filename_from_utf8 (path, -1, 0, 0, 0); if (path_fs) { recursive = TRUE; for_files (path_fs, wild, dcc_send_wild); recursive = FALSE; g_free (path_fs); } return; } dcc = new_dcc (); if (!dcc) return; dcc->file = file; dcc->maxcps = maxcps; /* get the local filesystem encoding */ file_fs = xchat_filename_from_utf8 (file, -1, 0, 0, 0); if (stat (file_fs, &st) != -1) { #ifndef USE_DCC64 if (sizeof (st.st_size) > 4 && st.st_size > 4294967295U) { PrintText (sess, "Cannot send files larger than 4 GB.\n"); goto xit; } #endif if (!(*file_part (file_fs)) || S_ISDIR (st.st_mode) || st.st_size < 1) { PrintText (sess, "Cannot send directories or empty files.\n"); goto xit; } dcc->starttime = dcc->offertime = time (0); dcc->serv = sess->server; dcc->dccstat = STAT_QUEUED; dcc->size = st.st_size; dcc->type = TYPE_SEND; dcc->fp = open (file_fs, OFLAGS | O_RDONLY); if (dcc->fp != -1) { g_free (file_fs); if (passive || dcc_listen_init (dcc, sess)) { char havespaces = 0; file = dcc->file; while (*file) { if (*file == ' ') { if (prefs.dcc_send_fillspaces) *file = '_'; else havespaces = 1; } file++; } dcc->nick = strdup (to); if (prefs.autoopendccsendwindow) { if (fe_dcc_open_send_win (TRUE)) /* already open? add */ fe_dcc_add (dcc); } else fe_dcc_add (dcc); if (passive) { dcc->pasvid = new_id(); snprintf (outbuf, sizeof (outbuf), (havespaces) ? "DCC SEND \"%s\" 199 0 %"DCC_SFMT" %d" : "DCC SEND %s 199 0 %"DCC_SFMT" %d", file_part (dcc->file), dcc->size, dcc->pasvid); } else { snprintf (outbuf, sizeof (outbuf), (havespaces) ? "DCC SEND \"%s\" %u %d %"DCC_SFMT : "DCC SEND %s %u %d %"DCC_SFMT, file_part (dcc->file), dcc->addr, dcc->port, dcc->size); } sess->server->p_ctcp (sess->server, to, outbuf); EMIT_SIGNAL (XP_TE_DCCOFFER, sess, file_part (dcc->file), to, dcc->file, NULL, 0); } else { dcc_close (dcc, 0, TRUE); } return; } } PrintTextf (sess, _("Cannot access %s\n"), dcc->file); PrintTextf (sess, "%s %d: %s\n", _("Error"), errno, errorstring (errno)); xit: g_free (file_fs); dcc_close (dcc, 0, TRUE); } static struct DCC * find_dcc_from_id (int id, int type) { struct DCC *dcc; GSList *list = dcc_list; while (list) { dcc = (struct DCC *) list->data; if (dcc->pasvid == id && dcc->dccstat == STAT_QUEUED && dcc->type == type) return dcc; list = list->next; } return 0; } static struct DCC * find_dcc_from_port (int port, int type) { struct DCC *dcc; GSList *list = dcc_list; while (list) { dcc = (struct DCC *) list->data; if (dcc->port == port && dcc->dccstat == STAT_QUEUED && dcc->type == type) return dcc; list = list->next; } return 0; } struct DCC * find_dcc (char *nick, char *file, int type) { GSList *list = dcc_list; struct DCC *dcc; while (list) { dcc = (struct DCC *) list->data; if (nick == NULL || !rfc_casecmp (nick, dcc->nick)) { if (type == -1 || dcc->type == type) { if (!file[0]) return dcc; if (!strcasecmp (file, file_part (dcc->file))) return dcc; if (!strcasecmp (file, dcc->file)) return dcc; } } list = list->next; } return 0; } /* called when we receive a NICK change from server */ void dcc_change_nick (struct server *serv, char *oldnick, char *newnick) { struct DCC *dcc; GSList *list = dcc_list; while (list) { dcc = (struct DCC *) list->data; if (dcc->serv == serv) { if (!serv->p_cmp (dcc->nick, oldnick)) { if (dcc->nick) free (dcc->nick); dcc->nick = strdup (newnick); } } list = list->next; } } /* is the destination file the same? new_dcc is not opened yet */ static int is_same_file (struct DCC *dcc, struct DCC *new_dcc) { struct stat st_a, st_b; /* if it's the same filename, must be same */ if (strcmp (dcc->destfile, new_dcc->destfile) == 0) return TRUE; /* now handle case-insensitive Filesystems: HFS+, FAT */ #ifdef WIN32 /* warning no win32 implementation - behaviour may be unreliable */ #else /* this fstat() shouldn't really fail */ if ((dcc->fp == -1 ? stat (dcc->destfile_fs, &st_a) : fstat (dcc->fp, &st_a)) == -1) return FALSE; if (stat (new_dcc->destfile_fs, &st_b) == -1) return FALSE; /* same inode, same device, same file! */ if (st_a.st_ino == st_b.st_ino && st_a.st_dev == st_b.st_dev) return TRUE; #endif return FALSE; } static int is_resumable (struct DCC *dcc) { dcc->resumable = 0; /* Check the file size */ if (access (dcc->destfile_fs, W_OK) == 0) { struct stat st; if (stat (dcc->destfile_fs, &st) != -1) { if (st.st_size < dcc->size) { dcc->resumable = st.st_size; dcc->pos = st.st_size; } else dcc->resume_error = 2; } else { dcc->resume_errno = errno; dcc->resume_error = 1; } } else { dcc->resume_errno = errno; dcc->resume_error = 1; } /* Now verify that this DCC is not already in progress from someone else */ if (dcc->resumable) { GSList *list = dcc_list; struct DCC *d; while (list) { d = list->data; if (d->type == TYPE_RECV && d->dccstat != STAT_ABORTED && d->dccstat != STAT_DONE && d->dccstat != STAT_FAILED) { if (d != dcc && is_same_file (d, dcc)) { dcc->resume_error = 3; /* dccgui.c uses it */ dcc->resumable = 0; dcc->pos = 0; break; } } list = list->next; } } return dcc->resumable; } void dcc_get (struct DCC *dcc) { switch (dcc->dccstat) { case STAT_QUEUED: if (dcc->type != TYPE_CHATSEND) { if (dcc->type == TYPE_RECV && prefs.autoresume && dcc->resumable) { dcc_resume (dcc); } else { dcc->resumable = 0; dcc->pos = 0; dcc_connect (dcc); } } break; case STAT_DONE: case STAT_FAILED: case STAT_ABORTED: dcc_close (dcc, 0, TRUE); break; } } /* for "Save As..." dialog */ void dcc_get_with_destfile (struct DCC *dcc, char *file) { g_free (dcc->destfile); dcc->destfile = g_strdup (file); /* utf-8 */ /* get the local filesystem encoding */ g_free (dcc->destfile_fs); dcc->destfile_fs = xchat_filename_from_utf8 (dcc->destfile, -1, 0, 0, 0); /* since destfile changed, must check resumability again */ is_resumable (dcc); dcc_get (dcc); } void dcc_get_nick (struct session *sess, char *nick) { struct DCC *dcc; GSList *list = dcc_list; while (list) { dcc = (struct DCC *) list->data; if (!sess->server->p_cmp (nick, dcc->nick)) { if (dcc->dccstat == STAT_QUEUED && dcc->type == TYPE_RECV) { dcc->resumable = 0; dcc->pos = 0; dcc->ack = 0; dcc_connect (dcc); return; } } list = list->next; } if (sess) EMIT_SIGNAL (XP_TE_DCCIVAL, sess, NULL, NULL, NULL, NULL, 0); } static struct DCC * new_dcc (void) { struct DCC *dcc = malloc (sizeof (struct DCC)); if (!dcc) return 0; memset (dcc, 0, sizeof (struct DCC)); dcc->sok = -1; dcc->fp = -1; dcc_list = g_slist_prepend (dcc_list, dcc); return (dcc); } void dcc_chat (struct session *sess, char *nick, int passive) { char outbuf[512]; struct DCC *dcc; dcc = find_dcc (nick, "", TYPE_CHATSEND); if (dcc) { switch (dcc->dccstat) { case STAT_ACTIVE: case STAT_QUEUED: case STAT_CONNECTING: EMIT_SIGNAL (XP_TE_DCCCHATREOFFER, sess, nick, NULL, NULL, NULL, 0); return; case STAT_ABORTED: case STAT_FAILED: dcc_close (dcc, 0, TRUE); } } dcc = find_dcc (nick, "", TYPE_CHATRECV); if (dcc) { switch (dcc->dccstat) { case STAT_QUEUED: dcc_connect (dcc); break; case STAT_FAILED: case STAT_ABORTED: dcc_close (dcc, 0, TRUE); } return; } /* offer DCC CHAT */ dcc = new_dcc (); if (!dcc) return; dcc->starttime = dcc->offertime = time (0); dcc->serv = sess->server; dcc->dccstat = STAT_QUEUED; dcc->type = TYPE_CHATSEND; dcc->nick = strdup (nick); if (passive || dcc_listen_init (dcc, sess)) { if (prefs.autoopendccchatwindow) { if (fe_dcc_open_chat_win (TRUE)) /* already open? add only */ fe_dcc_add (dcc); } else fe_dcc_add (dcc); if (passive) { dcc->pasvid = new_id (); snprintf (outbuf, sizeof (outbuf), "DCC CHAT chat 199 %d %d", dcc->port, dcc->pasvid); } else { snprintf (outbuf, sizeof (outbuf), "DCC CHAT chat %u %d", dcc->addr, dcc->port); } dcc->serv->p_ctcp (dcc->serv, nick, outbuf); EMIT_SIGNAL (XP_TE_DCCCHATOFFERING, sess, nick, NULL, NULL, NULL, 0); } else { dcc_close (dcc, 0, TRUE); } } static void dcc_malformed (struct session *sess, char *nick, char *data) { EMIT_SIGNAL (XP_TE_MALFORMED, sess, nick, data, NULL, NULL, 0); } int dcc_resume (struct DCC *dcc) { char tbuf[500]; if (dcc->dccstat == STAT_QUEUED && dcc->resumable) { dcc->resume_sent = 1; /* filename contains spaces? Quote them! */ snprintf (tbuf, sizeof (tbuf) - 10, strchr (dcc->file, ' ') ? "DCC RESUME \"%s\" %d %"DCC_SFMT : "DCC RESUME %s %d %"DCC_SFMT, dcc->file, dcc->port, dcc->resumable); if (dcc->pasvid) sprintf (tbuf + strlen (tbuf), " %d", dcc->pasvid); dcc->serv->p_ctcp (dcc->serv, dcc->nick, tbuf); return 1; } return 0; } static void dcc_confirm_send (void *ud) { struct DCC *dcc = (struct DCC *) ud; dcc_get (dcc); } static void dcc_deny_send (void *ud) { struct DCC *dcc = (struct DCC *) ud; dcc_abort (dcc->serv->front_session, dcc); } static void dcc_confirm_chat (void *ud) { struct DCC *dcc = (struct DCC *) ud; dcc_connect (dcc); } static void dcc_deny_chat (void *ud) { struct DCC *dcc = (struct DCC *) ud; dcc_abort (dcc->serv->front_session, dcc); } static struct DCC * dcc_add_chat (session *sess, char *nick, int port, guint32 addr, int pasvid) { struct DCC *dcc; dcc = new_dcc (); if (dcc) { dcc->serv = sess->server; dcc->type = TYPE_CHATRECV; dcc->dccstat = STAT_QUEUED; dcc->addr = addr; dcc->port = port; dcc->pasvid = pasvid; dcc->nick = strdup (nick); dcc->starttime = time (0); EMIT_SIGNAL (XP_TE_DCCCHATOFFER, sess->server->front_session, nick, NULL, NULL, NULL, 0); if (prefs.autoopendccchatwindow) { if (fe_dcc_open_chat_win (TRUE)) /* already open? add only */ fe_dcc_add (dcc); } else fe_dcc_add (dcc); if (prefs.autodccchat == 1) dcc_connect (dcc); else if (prefs.autodccchat == 2) { char buff[128]; snprintf (buff, sizeof (buff), "%s is offering DCC Chat. Do you want to accept?", nick); fe_confirm (buff, dcc_confirm_chat, dcc_deny_chat, dcc); } } return dcc; } static struct DCC * dcc_add_file (session *sess, char *file, DCC_SIZE size, int port, char *nick, guint32 addr, int pasvid) { struct DCC *dcc; char tbuf[512]; dcc = new_dcc (); if (dcc) { dcc->file = strdup (file); dcc->destfile = g_malloc (strlen (prefs.dccdir) + strlen (nick) + strlen (file) + 4); strcpy (dcc->destfile, prefs.dccdir); if (prefs.dccdir[strlen (prefs.dccdir) - 1] != '/') strcat (dcc->destfile, "/"); if (prefs.dccwithnick) { #ifdef WIN32 char *t = strlen (dcc->destfile) + dcc->destfile; strcpy (t, nick); while (*t) { if (*t == '\\' || *t == '|') *t = '_'; t++; } #else strcat (dcc->destfile, nick); #endif strcat (dcc->destfile, "."); } strcat (dcc->destfile, file); /* get the local filesystem encoding */ dcc->destfile_fs = xchat_filename_from_utf8 (dcc->destfile, -1, 0, 0, 0); dcc->resumable = 0; dcc->pos = 0; dcc->serv = sess->server; dcc->type = TYPE_RECV; dcc->dccstat = STAT_QUEUED; dcc->addr = addr; dcc->port = port; dcc->pasvid = pasvid; dcc->size = size; dcc->nick = strdup (nick); dcc->maxcps = prefs.dcc_max_get_cps; is_resumable (dcc); /* autodccsend is really autodccrecv.. right? */ if (prefs.autodccsend == 1) { dcc_get (dcc); } else if (prefs.autodccsend == 2) { snprintf (tbuf, sizeof (tbuf), _("%s is offering \"%s\". Do you want to accept?"), nick, file); fe_confirm (tbuf, dcc_confirm_send, dcc_deny_send, dcc); } if (prefs.autoopendccrecvwindow) { if (fe_dcc_open_recv_win (TRUE)) /* was already open? just add*/ fe_dcc_add (dcc); } else fe_dcc_add (dcc); } sprintf (tbuf, "%"DCC_SFMT, size); snprintf (tbuf + 24, 300, "%s:%d", net_ip (addr), port); EMIT_SIGNAL (XP_TE_DCCSENDOFFER, sess->server->front_session, nick, file, tbuf, tbuf + 24, 0); return dcc; } void handle_dcc (struct session *sess, char *nick, char *word[], char *word_eol[]) { char tbuf[512]; struct DCC *dcc; char *type = word[5]; int port, pasvid = 0; guint32 addr; DCC_SIZE size; int psend = 0; if (!strcasecmp (type, "CHAT")) { port = atoi (word[8]); addr = strtoul (word[7], NULL, 10); if (port == 0) pasvid = atoi (word[9]); else if (word[9][0] != 0) { pasvid = atoi (word[9]); psend = 1; } if (!addr /*|| (port < 1024 && port != 0)*/ || port > 0xffff || (port == 0 && pasvid == 0)) { dcc_malformed (sess, nick, word_eol[4] + 2); return; } if (psend) { dcc = find_dcc_from_id (pasvid, TYPE_CHATSEND); if (dcc) { dcc->addr = addr; dcc->port = port; dcc_connect (dcc); } else { dcc_malformed (sess, nick, word_eol[4] + 2); } return; } dcc = find_dcc (nick, "", TYPE_CHATSEND); if (dcc) dcc_close (dcc, 0, TRUE); dcc = find_dcc (nick, "", TYPE_CHATRECV); if (dcc) dcc_close (dcc, 0, TRUE); dcc_add_chat (sess, nick, port, addr, pasvid); return; } if (!strcasecmp (type, "Resume")) { port = atoi (word[7]); if (port == 0) { /* PASSIVE */ pasvid = atoi(word[9]); dcc = find_dcc_from_id(pasvid, TYPE_SEND); } else { dcc = find_dcc_from_port (port, TYPE_SEND); } if (!dcc) dcc = find_dcc (nick, word[6], TYPE_SEND); if (dcc) { size = BIG_STR_TO_INT (word[8]); dcc->resumable = size; if (dcc->resumable < dcc->size) { dcc->pos = dcc->resumable; dcc->ack = dcc->resumable; lseek (dcc->fp, dcc->pos, SEEK_SET); /* Checking if dcc is passive and if filename contains spaces */ if (dcc->pasvid) snprintf (tbuf, sizeof (tbuf), strchr (file_part (dcc->file), ' ') ? "DCC ACCEPT \"%s\" %d %"DCC_SFMT" %d" : "DCC ACCEPT %s %d %"DCC_SFMT" %d", file_part (dcc->file), port, dcc->resumable, dcc->pasvid); else snprintf (tbuf, sizeof (tbuf), strchr (file_part (dcc->file), ' ') ? "DCC ACCEPT \"%s\" %d %"DCC_SFMT : "DCC ACCEPT %s %d %"DCC_SFMT, file_part (dcc->file), port, dcc->resumable); dcc->serv->p_ctcp (dcc->serv, dcc->nick, tbuf); } sprintf (tbuf, "%"DCC_SFMT, dcc->pos); EMIT_SIGNAL (XP_TE_DCCRESUMEREQUEST, sess, nick, file_part (dcc->file), tbuf, NULL, 0); } return; } if (!strcasecmp (type, "Accept")) { port = atoi (word[7]); dcc = find_dcc_from_port (port, TYPE_RECV); if (dcc && dcc->dccstat == STAT_QUEUED) { dcc_connect (dcc); } return; } if (!strcasecmp (type, "SEND")) { char *file = file_part (word[6]); port = atoi (word[8]); addr = strtoul (word[7], NULL, 10); size = BIG_STR_TO_INT (word[9]); if (port == 0) /* Passive dcc requested */ pasvid = atoi (word[10]); else if (word[10][0] != 0) { /* Requesting passive dcc. * Destination user of an active dcc is giving his * TRUE address/port/pasvid data. * This information will be used later to * establish the connection to the user. * We can recognize this type of dcc using word[10] * because this field is always null (no pasvid) * in normal dcc sends. */ pasvid = atoi (word[10]); psend = 1; } if (!addr || !size /*|| (port < 1024 && port != 0)*/ || port > 0xffff || (port == 0 && pasvid == 0)) { dcc_malformed (sess, nick, word_eol[4] + 2); return; } if (psend) { /* Third Step of Passive send. * Connecting to the destination and finally * sending file. */ dcc = find_dcc_from_id (pasvid, TYPE_SEND); if (dcc) { dcc->addr = addr; dcc->port = port; dcc_connect (dcc); } else { dcc_malformed (sess, nick, word_eol[4] + 2); } return; } dcc_add_file (sess, file, size, port, nick, addr, pasvid); } else { EMIT_SIGNAL (XP_TE_DCCGENERICOFFER, sess->server->front_session, word_eol[4] + 2, nick, NULL, NULL, 0); } } void dcc_show_list (struct session *sess) { int i = 0; struct DCC *dcc; GSList *list = dcc_list; EMIT_SIGNAL (XP_TE_DCCHEAD, sess, NULL, NULL, NULL, NULL, 0); while (list) { dcc = (struct DCC *) list->data; i++; PrintTextf (sess, " %s %-10.10s %-7.7s %-7"DCC_SFMT" %-7"DCC_SFMT" %s\n", dcctypes[dcc->type], dcc->nick, _(dccstat[dcc->dccstat].name), dcc->size, dcc->pos, file_part (dcc->file)); list = list->next; } if (!i) PrintText (sess, _("No active DCCs\n")); }