mirror of
https://github.com/irssi/irssi.git
synced 2026-08-15 22:52:15 +02:00
a few changes to DCC so that it should be possible to send >4GB files. DCC protocol uses 32bit "n bytes transferred" notifications, so I had to bend the protocol a bit to allow 64bit files by truncating the value to lowest 32bits. I'm not sure how other clients handle those notifications, but irssi uses it only to figure out when the DCC SEND transfer is complete, so it's quite safe to assume that if we've managed to write() all the bytes and we receive the last 32bit of file size, it means the total file size instead of the total - (n+1)*4GB. git-svn-id: http://svn.irssi.org/repos/irssi/trunk@3018 dbcabf3a-b0e7-0310-adc4-f8d773084564
430 lines
11 KiB
C
430 lines
11 KiB
C
/*
|
|
dcc-send.c : irssi
|
|
|
|
Copyright (C) 1999-2001 Timo Sirainen
|
|
|
|
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "module.h"
|
|
#include "signals.h"
|
|
#include "commands.h"
|
|
#include "network.h"
|
|
#include "net-sendbuffer.h"
|
|
#include "misc.h"
|
|
#include "settings.h"
|
|
|
|
#include "irc-servers.h"
|
|
|
|
#include "dcc-send.h"
|
|
#include "dcc-chat.h"
|
|
#include "dcc-queue.h"
|
|
|
|
#include <glob.h>
|
|
|
|
#ifndef GLOB_TILDE
|
|
# define GLOB_TILDE 0 /* unsupported */
|
|
#endif
|
|
|
|
static int dcc_send_one_file(int queue, const char *target, const char *fname,
|
|
IRC_SERVER_REC *server, CHAT_DCC_REC *chat);
|
|
|
|
static void dcc_queue_send_next(int queue)
|
|
{
|
|
IRC_SERVER_REC *server;
|
|
DCC_QUEUE_REC *qrec;
|
|
int send_started = FALSE;
|
|
|
|
while ((qrec = dcc_queue_get_next(queue)) != NULL && !send_started) {
|
|
server = qrec->servertag == NULL ? NULL :
|
|
IRC_SERVER(server_find_tag(qrec->servertag));
|
|
|
|
if (server == NULL && qrec->chat == NULL) {
|
|
/* no way to send this request */
|
|
signal_emit("dcc error send no route", 2,
|
|
qrec->nick, qrec->file);
|
|
} else {
|
|
send_started = dcc_send_one_file(queue, qrec->nick,
|
|
qrec->file, server,
|
|
qrec->chat);
|
|
}
|
|
dcc_queue_remove_head(queue);
|
|
}
|
|
|
|
if (!send_started) {
|
|
/* no files in queue anymore, remove it */
|
|
dcc_queue_free(queue);
|
|
}
|
|
}
|
|
|
|
static void dcc_send_add(const char *servertag, CHAT_DCC_REC *chat,
|
|
const char *nick, char *fileargs, int add_mode)
|
|
{
|
|
struct stat st;
|
|
glob_t globbuf;
|
|
char *fname;
|
|
int i, ret, files, flags, queue, start_new_transfer;
|
|
|
|
globbuf.gl_offs = 0;
|
|
flags = GLOB_NOCHECK | GLOB_TILDE;
|
|
|
|
/* this loop parses all <file> parameters and adds them to glubbuf */
|
|
for (;;) {
|
|
fname = cmd_get_quoted_param(&fileargs);
|
|
if (*fname == '\0')
|
|
break;
|
|
|
|
if (glob(fname, flags, 0, &globbuf) < 0)
|
|
break;
|
|
|
|
/* this flag must not be set before first call to glob!
|
|
(man glob) */
|
|
flags |= GLOB_APPEND;
|
|
}
|
|
|
|
files = 0; queue = -1; start_new_transfer = 0;
|
|
|
|
/* add all globbed files to a proper queue */
|
|
for (i = 0; i < globbuf.gl_pathc; i++) {
|
|
const char *fname = globbuf.gl_pathv[i];
|
|
|
|
ret = stat(fname, &st);
|
|
if (ret == 0 && S_ISDIR(st.st_mode)) {
|
|
/* we don't want directories */
|
|
errno = EISDIR;
|
|
ret = -1;
|
|
}
|
|
|
|
if (ret < 0) {
|
|
signal_emit("dcc error file open", 3,
|
|
nick, fname, errno);
|
|
continue;
|
|
}
|
|
|
|
if (queue < 0) {
|
|
/* in append and prepend mode try to find an
|
|
old queue. if an old queue is not found
|
|
create a new queue. if not in append or
|
|
prepend mode, create a new queue */
|
|
if (add_mode != DCC_QUEUE_NORMAL)
|
|
queue = dcc_queue_old(nick, servertag);
|
|
start_new_transfer = 0;
|
|
if (queue < 0) {
|
|
queue = dcc_queue_new();
|
|
start_new_transfer = 1;
|
|
}
|
|
}
|
|
|
|
dcc_queue_add(queue, add_mode, nick,
|
|
fname, servertag, chat);
|
|
files++;
|
|
}
|
|
|
|
if (files > 0 && start_new_transfer)
|
|
dcc_queue_send_next(queue);
|
|
|
|
globfree(&globbuf);
|
|
}
|
|
|
|
/* DCC SEND [-append | -prepend | -flush | -rmtail | -rmhead]
|
|
<nick> <file> [<file> ...] */
|
|
static void cmd_dcc_send(const char *data, IRC_SERVER_REC *server,
|
|
WI_ITEM_REC *item)
|
|
{
|
|
const char *servertag;
|
|
char *nick, *fileargs;
|
|
void *free_arg;
|
|
CHAT_DCC_REC *chat;
|
|
GHashTable *optlist;
|
|
int queue, mode;
|
|
|
|
if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTIONS |
|
|
PARAM_FLAG_GETREST, "dcc send",
|
|
&optlist, &nick, &fileargs))
|
|
return;
|
|
|
|
chat = item_get_dcc(item);
|
|
if (chat != NULL &&
|
|
(chat->mirc_ctcp || g_strcasecmp(nick, chat->nick) != 0))
|
|
chat = NULL;
|
|
|
|
if (!IS_IRC_SERVER(server) || !server->connected)
|
|
servertag = NULL;
|
|
else
|
|
servertag = server->tag;
|
|
|
|
if (servertag == NULL && chat == NULL)
|
|
cmd_param_error(CMDERR_NOT_CONNECTED);
|
|
|
|
if (g_hash_table_lookup(optlist, "rmhead") != NULL) {
|
|
queue = dcc_queue_old(nick, servertag);
|
|
if (queue != -1)
|
|
dcc_queue_remove_head(queue);
|
|
} else if (g_hash_table_lookup(optlist, "rmtail") != NULL) {
|
|
queue = dcc_queue_old(nick, servertag);
|
|
if (queue != -1)
|
|
dcc_queue_remove_tail(queue);
|
|
} else if (g_hash_table_lookup(optlist, "flush") != NULL) {
|
|
queue = dcc_queue_old(nick, servertag);
|
|
if (queue != -1)
|
|
dcc_queue_free(queue);
|
|
} else {
|
|
if (g_hash_table_lookup(optlist, "append") != NULL)
|
|
mode = DCC_QUEUE_APPEND;
|
|
else if (g_hash_table_lookup(optlist, "prepend") != NULL)
|
|
mode = DCC_QUEUE_PREPEND;
|
|
else
|
|
mode = DCC_QUEUE_NORMAL;
|
|
|
|
if (*fileargs == '\0')
|
|
cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
|
|
|
|
dcc_send_add(servertag, chat, nick, fileargs, mode);
|
|
}
|
|
|
|
cmd_params_free(free_arg);
|
|
}
|
|
|
|
static SEND_DCC_REC *dcc_send_create(IRC_SERVER_REC *server,
|
|
CHAT_DCC_REC *chat,
|
|
const char *nick, const char *arg)
|
|
{
|
|
SEND_DCC_REC *dcc;
|
|
|
|
dcc = g_new0(SEND_DCC_REC, 1);
|
|
dcc->orig_type = module_get_uniq_id_str("DCC", "GET");
|
|
dcc->type = module_get_uniq_id_str("DCC", "SEND");
|
|
dcc->fhandle = -1;
|
|
dcc->queue = -1;
|
|
|
|
dcc_init_rec(DCC(dcc), server, chat, nick, arg);
|
|
return dcc;
|
|
}
|
|
|
|
static void sig_dcc_destroyed(SEND_DCC_REC *dcc)
|
|
{
|
|
if (!IS_DCC_SEND(dcc)) return;
|
|
|
|
if (dcc->fhandle != -1)
|
|
close(dcc->fhandle);
|
|
|
|
dcc_queue_send_next(dcc->queue);
|
|
}
|
|
|
|
/* input function: DCC SEND - we're ready to send more data */
|
|
static void dcc_send_data(SEND_DCC_REC *dcc)
|
|
{
|
|
char buffer[512];
|
|
int ret;
|
|
|
|
ret = read(dcc->fhandle, buffer, sizeof(buffer));
|
|
if (ret <= 0) {
|
|
/* no need to call this function anymore..
|
|
in fact it just eats all the cpu.. */
|
|
dcc->waitforend = TRUE;
|
|
g_source_remove(dcc->tagwrite);
|
|
dcc->tagwrite = -1;
|
|
return;
|
|
}
|
|
|
|
ret = net_transmit(dcc->handle, buffer, ret);
|
|
if (ret > 0) dcc->transfd += ret;
|
|
dcc->gotalldata = FALSE;
|
|
|
|
lseek(dcc->fhandle, dcc->transfd, SEEK_SET);
|
|
|
|
signal_emit("dcc transfer update", 1, dcc);
|
|
}
|
|
|
|
/* input function: DCC SEND - received some data */
|
|
static void dcc_send_read_size(SEND_DCC_REC *dcc)
|
|
{
|
|
guint32 bytes;
|
|
int ret;
|
|
|
|
ret = net_receive(dcc->handle, dcc->count_buf+dcc->count_pos,
|
|
4-dcc->count_pos);
|
|
if (ret == -1) {
|
|
dcc_close(DCC(dcc));
|
|
return;
|
|
}
|
|
|
|
dcc->count_pos += ret;
|
|
|
|
if (dcc->count_pos != 4)
|
|
return;
|
|
|
|
bytes = ntohl(*((guint32 *) dcc->count_buf));
|
|
dcc->count_pos = 0;
|
|
|
|
if (dcc->waitforend && bytes == (dcc->transfd & 0xffffffff)) {
|
|
/* file is sent */
|
|
dcc->gotalldata = TRUE;
|
|
dcc_close(DCC(dcc));
|
|
}
|
|
}
|
|
|
|
/* input function: DCC SEND - someone tried to connect to our socket */
|
|
static void dcc_send_connected(SEND_DCC_REC *dcc)
|
|
{
|
|
GIOChannel *handle;
|
|
IPADDR addr;
|
|
int port;
|
|
|
|
/* accept connection */
|
|
handle = net_accept(dcc->handle, &addr, &port);
|
|
if (handle == NULL)
|
|
return;
|
|
|
|
/* TODO: some kind of paranoia check would be nice. it would check
|
|
that the host of the nick who we sent the request matches the
|
|
address who connected us. */
|
|
|
|
net_disconnect(dcc->handle);
|
|
g_source_remove(dcc->tagconn);
|
|
dcc->tagconn = -1;
|
|
|
|
dcc->starttime = time(NULL);
|
|
dcc->handle = handle;
|
|
memcpy(&dcc->addr, &addr, sizeof(IPADDR));
|
|
net_ip2host(&dcc->addr, dcc->addrstr);
|
|
dcc->port = port;
|
|
|
|
dcc->tagread = g_input_add(handle, G_INPUT_READ,
|
|
(GInputFunction) dcc_send_read_size, dcc);
|
|
dcc->tagwrite = g_input_add(handle, G_INPUT_WRITE,
|
|
(GInputFunction) dcc_send_data, dcc);
|
|
|
|
signal_emit("dcc connected", 1, dcc);
|
|
}
|
|
|
|
static char *dcc_send_get_file(const char *fname)
|
|
{
|
|
char *str, *path;
|
|
|
|
str = convert_home(fname);
|
|
if (!g_path_is_absolute(str)) {
|
|
/* full path not given to file, use dcc_upload_path */
|
|
g_free(str);
|
|
|
|
path = convert_home(settings_get_str("dcc_upload_path"));
|
|
str = *path == '\0' ? g_strdup(fname) :
|
|
g_strconcat(path, G_DIR_SEPARATOR_S, fname, NULL);
|
|
g_free(path);
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
static int dcc_send_one_file(int queue, const char *target, const char *fname,
|
|
IRC_SERVER_REC *server, CHAT_DCC_REC *chat)
|
|
{
|
|
struct stat st;
|
|
char *str;
|
|
char host[MAX_IP_LEN];
|
|
int hfile, port;
|
|
SEND_DCC_REC *dcc;
|
|
IPADDR own_ip;
|
|
GIOChannel *handle;
|
|
|
|
if (dcc_find_request(DCC_SEND_TYPE, target, fname)) {
|
|
signal_emit("dcc error send exists", 2, target, fname);
|
|
return FALSE;
|
|
}
|
|
|
|
str = dcc_send_get_file(fname);
|
|
hfile = open(str, O_RDONLY);
|
|
g_free(str);
|
|
|
|
if (hfile == -1) {
|
|
signal_emit("dcc error file open", 3, target, fname,
|
|
GINT_TO_POINTER(errno));
|
|
return FALSE;
|
|
}
|
|
|
|
if (fstat(hfile, &st) < 0) {
|
|
g_warning("fstat() failed: %s", strerror(errno));
|
|
close(hfile);
|
|
return FALSE;
|
|
}
|
|
|
|
/* start listening */
|
|
handle = dcc_listen(chat != NULL ? chat->handle :
|
|
net_sendbuffer_handle(server->handle),
|
|
&own_ip, &port);
|
|
if (handle == NULL) {
|
|
close(hfile);
|
|
g_warning("dcc_listen() failed: %s", strerror(errno));
|
|
return FALSE;
|
|
}
|
|
|
|
fname = g_basename(fname);
|
|
|
|
/* Replace all the spaces with underscore so that lesser
|
|
intellgent clients can communicate.. */
|
|
if (!settings_get_bool("dcc_send_replace_space_with_underscore"))
|
|
str = NULL;
|
|
else {
|
|
str = g_strdup(fname);
|
|
g_strdelimit(str, " ", '_');
|
|
fname = str;
|
|
}
|
|
|
|
dcc = dcc_send_create(server, chat, target, fname);
|
|
g_free(str);
|
|
|
|
dcc->handle = handle;
|
|
dcc->port = port;
|
|
dcc->size = st.st_size;
|
|
dcc->fhandle = hfile;
|
|
dcc->queue = queue;
|
|
dcc->file_quoted = strchr(fname, ' ') != NULL;
|
|
dcc->tagconn = g_input_add(handle, G_INPUT_READ,
|
|
(GInputFunction) dcc_send_connected, dcc);
|
|
|
|
/* send DCC request */
|
|
signal_emit("dcc request send", 1, dcc);
|
|
|
|
dcc_ip2str(&own_ip, host);
|
|
str = g_strdup_printf(dcc->file_quoted ?
|
|
"DCC SEND \"%s\" %s %d %"PRIuUOFF_T :
|
|
"DCC SEND %s %s %d %"PRIuUOFF_T,
|
|
dcc->arg, host, port, dcc->size);
|
|
dcc_ctcp_message(server, target, chat, FALSE, str);
|
|
g_free(str);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void dcc_send_init(void)
|
|
{
|
|
dcc_register_type("SEND");
|
|
settings_add_str("dcc", "dcc_upload_path", "~");
|
|
settings_add_bool("dcc", "dcc_send_replace_space_with_underscore", FALSE);
|
|
signal_add("dcc destroyed", (SIGNAL_FUNC) sig_dcc_destroyed);
|
|
command_bind("dcc send", NULL, (SIGNAL_FUNC) cmd_dcc_send);
|
|
command_set_options("dcc send", "append flush prepend rmhead rmtail");
|
|
|
|
dcc_queue_init();
|
|
}
|
|
|
|
void dcc_send_deinit(void)
|
|
{
|
|
dcc_queue_deinit();
|
|
|
|
dcc_unregister_type("SEND");
|
|
signal_remove("dcc destroyed", (SIGNAL_FUNC) sig_dcc_destroyed);
|
|
command_unbind("dcc send", (SIGNAL_FUNC) cmd_dcc_send);
|
|
}
|