/* IRCfs - IRC FileServ for *nix. * Copyright (C) 2002 Nick 'Zaf' Clifford * For licensing details, refer to the LICENSE file in the source * code directory. */ #include "runtime.h" #include "module.h" #include "dccfile.h" #include "user.h" #include "magic.h" #include "event.h" #include "list.h" #include "server.h" #include "socket.h" #include "file.h" #include "timer.h" #include "appconf/appconf.h" #include #include #define DCCFILE_BUFFER_SIZE 1024 #define MAGIC_DCCFILE 0xfd7ecd8a struct dccfile { MAGIC magic; POOL pool; struct server *server; struct user *user; char *nick; //Copy of users nickname. (they may leave IRC) char *buffer; size_t buffer_size; struct socket *socket; struct file *file; char *upload_filename; int acked; /* Number of bytes acked */ enum direction { dcc_dir_send, dcc_dir_recv } direction; size_t expected_size; struct timer *connect_timer, *data_timer; }; static int dccfile_server_goes(struct eventmsg *msg); static int dccfile_user_goes(struct eventmsg *msg); static int dccfile_raise_event(struct dccfile *df, enum dccfile_events e); static int dccfile_new_connect(struct socket *socket, enum socket_event_type e, void *d); static int dcc_send_more_file(struct dccfile *df); static void dccfile_failed(struct dccfile *df); static int dccfile_new_server(struct eventmsg *msg); static int dccfile_command_privmsg(struct server *s, const char *cmd, int argc, char *argv[],void *appdata); static int dccfile_parse_send(int argc, char *argv[], char *filename, size_t filename_size, struct in_addr *ip, unsigned short *port, unsigned long *size); static int dccfile_raise_pending(struct dccfile_pending_msg *pmsg); static struct dccfile *dccfile_start_recv(struct user *u, struct file *file, char *ip, unsigned short port, size_t size); static int dccfile_recv_connect(struct socket *socket, enum socket_event_type e, void *d); static int dccfile_socket_read(struct socket *socket, enum socket_event_type e, void *d); static int dccfile_socket_closed(struct socket *socket, enum socket_event_type e, void *d); static int dcc_recv_more_file(struct dccfile *df); static int dccfile_event_filter(struct eventmsg *msg, void *filter); static void dccfile_send_completed(struct dccfile *df); static void dccfile_recv_completed(struct dccfile *df); static int dccfile_send_timeout(struct timer *t, void *appdata); static int dccfile_recv_timeout(struct timer *t, void *appdata); static int dccfile_connect_timeout(struct timer *t, void *appdata); static int dccfile_each_server(struct server *s); MODULE_NAME("dccfile"); MODULE_INIT(dccfile_init); MODULE_DESTROY(dccfile_unload); MODULE_DEPENDS("user","select","socket","server","file","appconf"); MODULE_REGISTER(dccfile); /* Set to 1 to automatically close DCC Recv's when we get the expected * number of bytes, rather than wait for the other end to close. * Set to 0 if expecting files that are bigger than they appear, eg * sending a unix socket contents. */ int dccfile_opt_auto_close = 1; struct eventobj *dccfile_eventobj; DEBUG dccfile_debug; POOL dccfile_pool; static struct list *sessions; char *cfg_upload_dir="/tmp/"; struct eventobj *user_eventobj; struct conf_valid_range_data dcc_valid_port_range = { .lower_limit = 1, .upper_limit = 65535 }; int dccfile_init() { dccfile_debug = debug_register("dccfile"); dccfile_pool = pool_new(NULL); dccfile_eventobj = event_create_obj(NULL,NULL); event_set_filter_func(dccfile_eventobj, dccfile_event_filter); sessions = list_create(dccfile_pool); server_foreach(dccfile_each_server); event_add_listener(server_get_event_obj(), server_event_new, NULL, dccfile_new_server, NULL); return 0; } int dccfile_unload() { return -1; } int dccfile_each_server(struct server *s) { server_set_command_handler(s,"PRIVMSG",2, SERVER_TRIGGER_MYNICK, dccfile_command_privmsg,NULL); return 0; } int dccfile_new_server(struct eventmsg *msg) { struct server *s; s = (struct server *)msg->msg; return dccfile_each_server(s); } int dccfile_command_privmsg(struct server *s, const char *cmd, int argc, char *argv[],void *appdata) { char filename[256],ipstr[50]; struct in_addr ip; unsigned short port; unsigned long size; struct user *u; static struct dccfile_pending_msg msg; struct file *file; struct dir *upload_dir; struct file_handler *fh; struct dir_handler *dh; struct dccfile *df; const char *ccp; struct conf_value *cv; char *upload_type_str, *upload_dir_str; if (argc < 3) return 0; if (strncasecmp("\01DCC SEND ", argv[3],10) != 0) return 0; if (dccfile_parse_send(argc,argv,filename,sizeof(filename),&ip, &port,&size) == -1) { debug(dccfile_debug,"Parse error parsing DCC SEND message " "from %s (%s)", argv[0],argv[3]); return 0; } strncpy(ipstr,inet_ntoa(ip),sizeof(ipstr)-1); ipstr[sizeof(ipstr)-1] = 0; notice(dccfile_debug,"Got DCC Send request. From %s file %s " "IP: %s Port: %u Size: %lu", argv[0], filename, ipstr, port, size); u = user_find(s,argv[0]); if (!u) { u = user_create(s,argv[0]); } remove_path(filename); set_magic(&msg,MAGIC_DCCFILE_PENDING_MSG); msg.filename = filename; msg.ip = ipstr; msg.port = port; msg.size = size; msg.who = u; msg.reason = NULL; if (dccfile_raise_pending(&msg) == -1) { debug(dccfile_debug,"Got cancel from a pending event handler"); server_send(s,"NOTICE %s :Sorry, permission to send file " "has been denied, reason %s", argv[0], msg.reason ? msg.reason : "unknown"); return 0; } remove_path(filename); clean_filename(filename); /* TODO support resume */ cv = conf_get_value_str(dccfile_pool, "/upload/type"); ccp = conf_value_to_str_const(cv); if (ccp == NULL) { warn(dccfile_debug,"Can't accept uploads, don't know where " "to put them. Specify in the upload config " "setting"); server_send(s,"NOTICE %s :Sorry, permission to send file " "has been denied, reason: not configured", argv[0]); return 0; } upload_type_str = tmp_strdup(ccp); cv = conf_get_value_str(dccfile_pool, "/upload/path"); ccp = conf_value_to_str_const(cv); if (ccp == NULL) { warn(dccfile_debug,"Can't accept uploads, don't know where " "to put them. Specify in the upload config " "setting"); server_send(s,"NOTICE %s :Sorry, permission to send file " "has been denied, reason: not configured", argv[0]); return 0; } upload_dir_str = tmp_strdup(ccp); fh = find_file_handler(upload_type_str); if (!fh) { error(dccfile_debug,"Can't find upload file handler %s", upload_type_str); server_send(s,"NOTICE %s :Sorry, permission to send file " "has been denied, reason: not configured", argv[0]); return 0; } dh = find_dir_handler(upload_type_str); if (!dh) { error(dccfile_debug,"Can't find upload dir handler %s", upload_type_str); server_send(s,"NOTICE %s :Sorry, permission to send file " "has been denied, reason: not configured", argv[0]); return 0; } if (msg.upload_dir != NULL) { upload_dir = msg.upload_dir; } else { upload_dir = dh->open(NULL,upload_dir_str); if (upload_dir == NULL) { error(dccfile_debug,"Can't open upload dir %s:%s", upload_type_str,upload_dir_str); return 0; } } if (fh->exists(upload_dir,filename) != -1) { error(dccfile_debug,"Upload file exists %s:%s", upload_type_str,upload_dir_str); server_send(s,"NOTICE %s :Sorry, permission to send file " "has been denied, reason: file already exists", argv[0]); return 0; } file = fh->create(upload_dir, filename, file_flag_smash|file_flag_readwrite); if (file == NULL) { debug(dccfile_debug,"Can't open file"); return 0; } if (msg.upload_dir == NULL) dh->close(upload_dir); df = dccfile_start_recv(u, file,ipstr, port,size); if (df == NULL) { debug(dccfile_debug,"Can't create df"); return 0; } debug(dccfile_debug,"DCC Receive started from %s file %s " "IP: %s Port: %u Size: %lu", argv[0], filename, ipstr, port, size); return 0; } int dccfile_parse_send(int argc, char *argv[], char *filename, size_t filename_size, struct in_addr *ip, unsigned short *port, unsigned long *size) { char *next; const char *cp,*file; int cnt; unsigned long lip, lport; if (argc < 3) return -1; /* \x1DCC SEND \x1 */ if (strncasecmp("\01DCC SEND ", argv[3],10) != 0) return -1; cp = argv[3] + 9; cp = const_skip_whitespace(cp); file = cp; cp = const_skip_to_whitespace(cp); cnt = cp - file; if (!*cp) { return -1; } if (cnt > filename_size - 2) cnt = filename_size - 2; strncpy(filename,file,cnt); filename[cnt] = 0; cp = const_skip_whitespace(cp); next = NULL; lip = strtoul(cp,&next,10); if (!next || !isspace(*next)) { return -1; } cp = const_skip_to_whitespace(cp); cp = const_skip_whitespace(cp); next = NULL; lport = strtoul(cp,&next,10); if (!next || !isspace(*next) || lport > 65535 || !*cp) { return -1; } cp = const_skip_to_whitespace(cp); cp = const_skip_whitespace(cp); next = NULL; *size = strtoul(cp,&next,10); if (!next || *next != 0x01 || size == 0 || !*cp) { return -1; } *port = lport; (*ip).s_addr = ntohl(lip); return 0; } struct dccfile *dccfile_start_recv(struct user *u, struct file *file, char *ip, unsigned short port, size_t size) { struct dccfile *dc; POOL p; int timeout; p = pool_new(dccfile_pool); dc = XMALLOC(p,struct dccfile); set_magic(dc,MAGIC_DCCFILE); dc->pool = p; dc->server = user_get_server(u); dc->user = user_grab(u); dc->file = file; dc->direction = dcc_dir_recv; dc->expected_size = size; dc->nick = pstrdup(p,user_get_nick(u)); dc->socket = socket_create(socket_type_tcp); dc->buffer = (char *)palloc(p,sizeof(char) * DCCFILE_BUFFER_SIZE); dc->buffer_size = DCCFILE_BUFFER_SIZE; if (dc->socket == NULL) { user_release(dc->user); pool_destroy(p); return NULL; } event_add_listener(server_get_event_obj(), server_event_disconnected, dc->server, dccfile_server_goes, dc); event_add_listener(user_get_event_obj(), user_event_quit, dc->user, dccfile_user_goes, dc); list_add_end(sessions, dc); socket_set_remote_addr(dc->socket,ip); socket_set_remote_port(dc->socket,port); socket_bind(dc->socket); socket_set_callback(dc->socket,socket_event_connect, dccfile_recv_connect,dc); file->handler->set_app_data(file,dc); timeout = 120; conf_value_to_int(conf_get_value(dc->pool, "/dcc/send/accept_timeout"),&timeout); dc->connect_timer = timer_start(timeout,dccfile_connect_timeout, dc); socket_connect(dc->socket); return dc; } int dccfile_recv_connect(struct socket *socket, enum socket_event_type e, void *d) { struct dccfile *df; int timeout; df = (struct dccfile *) d; ASSERT(is_magic(df,MAGIC_DCCFILE)); ASSERT(df->socket == socket); socket_set_callback(df->socket,socket_event_read, dccfile_socket_read,df); socket_set_callback(df->socket,socket_event_close, dccfile_socket_closed,df); socket_set_buffer_size(df->socket,DCCFILE_BUFFER_SIZE); debug(dccfile_debug,"DCCFile(recv)(%s) user connected", df->nick); if (df->connect_timer) { timer_destroy(df->connect_timer); df->connect_timer = NULL; } dccfile_raise_event(df, dccfile_event_open); timeout = 240; conf_value_to_int(conf_get_value(df->pool, "/dcc/recv/data_timeout"),&timeout); df->data_timer = timer_start(timeout,dccfile_recv_timeout, df); return 0; } struct dccfile *dccfile_start_send(struct user *u, struct file *file) { static char tmpbuf[125]; struct dccfile *dc; unsigned long ip; POOL p; const char *filename; int timeout; p = pool_new(dccfile_pool); dc = XMALLOC(p,struct dccfile); set_magic(dc,MAGIC_DCCFILE); dc->pool = p; dc->server = user_get_server(u); dc->user = user_grab(u); dc->file = file; dc->direction = dcc_dir_send; dc->nick = pstrdup(p,user_get_nick(u)); dc->socket = socket_create(socket_type_tcp); dc->buffer = (char *)palloc(p,sizeof(char) * DCCFILE_BUFFER_SIZE); dc->buffer_size = DCCFILE_BUFFER_SIZE; if (dc->socket == NULL) { user_release(u); pool_destroy(p); return NULL; } event_add_listener(server_get_event_obj(), server_event_disconnected, dc->server, dccfile_server_goes, dc); event_add_listener(user_get_event_obj(), user_event_quit, dc->user, dccfile_user_goes, dc); list_add_end(sessions, dc); socket_bind(dc->socket); socket_set_callback(dc->socket,socket_event_connect, dccfile_new_connect,dc); socket_listen(dc->socket); socket_get_local_addr(dc->socket, tmpbuf, sizeof(tmpbuf)); debug(dccfile_debug,"Setup listening socket on %s:%d", tmpbuf,socket_get_local_port(dc->socket)); file->handler->set_app_data(file,dc); ip = server_get_my_addr_long(dc->server); if (ip == 0) { user_release(u); debug(dccfile_debug,"Can't do DCC SEND. Don't know my own " "IP address"); pool_destroy(p); return NULL; } ip = ntohl(ip); filename = file->handler->get_name(file); server_send(dc->server,"PRIVMSG %s :%cDCC SEND %s %lu %u %d%c", dc->nick,0x01,filename,ip, socket_get_local_port(dc->socket), file->handler->get_size(file),0x01); timeout = 120; conf_value_to_int(conf_get_value(dc->pool, "/dcc/send/request_timeout"),&timeout); dc->connect_timer = timer_start(timeout,dccfile_connect_timeout, dc); return dc; } void dccfile_destroy(struct dccfile *df) { ASSERT(is_magic(df,MAGIC_DCCFILE)); debug(dccfile_debug,"DCCFile(%s) destroying", df->nick); /* Disable callbacks */ socket_set_callback(df->socket,socket_event_close, NULL,NULL); socket_destroy(df->socket); dccfile_raise_event(df, dccfile_event_close); /* Remove any user event codes that refer to this dccfile */ event_del_listener_by_data(user_get_event_obj(), event_code_any, NULL, NULL, df); df->file->handler->close(df->file); if (df->user) user_release(df->user); if (list_find_data(sessions,df)) list_del_current(sessions); if (df->connect_timer) { timer_destroy(df->connect_timer); } if (df->data_timer) { timer_destroy(df->data_timer); } clear_magic(df); pool_destroy(df->pool); return; } struct eventobj *dccfile_get_event_obj() { return dccfile_eventobj; } int dccfile_user_goes(struct eventmsg *msg) { struct dccfile *df; struct user_event_msg *uem; df = msg->appdata; uem = msg->msg; ASSERT(is_magic(df,MAGIC_DCCFILE)); ASSERT(is_magic(uem, MAGIC_USER_EVENT_MSG)); ASSERT(df->user == uem->user); debug(dccfile_debug,"DCCFile(%s) user has left IRC", df->nick); /* Don't need to do anything, we have grabbed user */ return 0; } int dccfile_server_goes(struct eventmsg *msg) { struct dccfile *df; struct server *s; df = msg->appdata; s = msg->msg; ASSERT(is_magic(df,MAGIC_DCCFILE)); ASSERT(df->server == s); debug(dccfile_debug,"DCCFile(%s) server disconnected", df->nick); df->server = NULL; return 0; } int dccfile_socket_read(struct socket *socket, enum socket_event_type e, void *d) { struct dccfile *df; int r; unsigned int count; df = (struct dccfile *) d; ASSERT(is_magic(df,MAGIC_DCCFILE)); ASSERT(df->socket == socket); if (df->data_timer) { timer_reset(df->data_timer); } if (df->direction == dcc_dir_send) { r = socket_read(df->socket,(char *)&count,4); if (r == 0) return 0; if (r == -1) { dccfile_failed(df); return 0; } #if 0 while(1) { r = socket_read(df->socket,(char *)&count,4); if (r == 0) break; if (r == -1) { dccfile_failed(df); return 0; } } #endif df->acked = ntohl(count); /* TODO actually wait till they ack stuff */ debug(dccfile_debug,"Client acked %d bytes",df->acked); if (df->acked >= df->file->handler->get_size(df->file)) { /* TODO Close send */ dccfile_send_completed(df); return 0; } dcc_send_more_file(df); } else { dcc_recv_more_file(df); } return 0; } int dccfile_socket_write(struct socket *socket, enum socket_event_type e, void *d) { struct dccfile *df; df = (struct dccfile *) d; ASSERT(is_magic(df,MAGIC_DCCFILE)); ASSERT(df->socket == socket); if (df->direction == dcc_dir_send) { dcc_send_more_file(df); } else { socket_set_callback(df->socket,socket_event_read, dccfile_socket_read,df); } return 0; } int dccfile_file_read(struct file *f, enum file_callback_type type) { struct dccfile *df; df = (struct dccfile *) f->handler->get_app_data(f); ASSERT(is_magic(df,MAGIC_DCCFILE)); ASSERT(df->file == f); if (df->direction == dcc_dir_send) { dcc_send_more_file(df); } else { debug(dccfile_debug,"Huh? Got DCC File Read on DCCRECV"); } return 0; } int dccfile_file_write(struct file *f, enum file_callback_type type) { struct dccfile *df; df = (struct dccfile *) f->handler->get_app_data(f); ASSERT(is_magic(df,MAGIC_DCCFILE)); ASSERT(df->file == f); if (df->direction == dcc_dir_recv) { socket_set_callback(df->socket,socket_event_read, dccfile_socket_read,df); } else { debug(dccfile_debug,"Huh? Got DCC File Read on DCCSEND"); } return 0; } int dcc_recv_more_file(struct dccfile *df) { int r,w; unsigned long total; r = socket_read(df->socket,df->buffer,df->buffer_size); if (r == 0) { if (df->acked >= df->expected_size) { dccfile_recv_completed(df); } else { error(dccfile_debug,"File recv'd short, expected=%lu actual=%lu", df->expected_size, df->acked); dccfile_failed(df); } /* When EOF */ return 0; } else if (r == -1) { dccfile_failed(df); return 0; } w = df->file->handler->write(df->file,df->buffer,r); if (w == 0) { /* Need to pause, wait till we can write file */ /* Setup callback for read from file */ df->file->handler->set_callback(df->file, file_can_write, dccfile_file_write); /* Remove call back from read from socket */ socket_set_callback(df->socket, socket_event_read, NULL,NULL); return 0; } else if (w == -1 || w != r) { dccfile_failed(df); return 0; } df->acked += w; debug(dccfile_debug,"Recv'd another %lu bytes, total %lu", w,df->acked); total = htonl(df->acked); w = socket_write(df->socket,(void *)&total,4); if (w == -1) { dccfile_failed(df); } else if (w == 0) { /* Pause, can't write atm */ socket_set_callback(df->socket,socket_event_write, dccfile_socket_write,df); } if (dccfile_opt_auto_close && df->acked >= df->expected_size) { dccfile_recv_completed(df); } return 0; } int dcc_send_more_file(struct dccfile *df) { int r,w; r = df->file->handler->read(df->file,df->buffer,df->buffer_size); if (r == 0 && !df->file->handler->eof(df->file)) { /* Need to pause, wait till we can read file */ /* Setup callback for read from file */ df->file->handler->set_callback(df->file, file_can_read, dccfile_file_read); /* Remove call back from write to socket */ socket_set_callback(df->socket, socket_event_write, NULL,NULL); return 0; } else if (r == 0) { /* EOF. Wait till we get the rest of the * ack bytes */ /* Remove socket write callback */ socket_set_callback(df->socket, socket_event_write, NULL,NULL); return 0; } else if (r == -1) { dccfile_failed(df); return 0; } w = socket_write(df->socket,df->buffer,r); if (w == 0) { /* Need to pause reading, wait till can write */ df->file->handler->set_callback(df->file,file_can_read, NULL); socket_set_callback(df->socket, socket_event_write, dccfile_socket_write,df); return 0; } else if (w == -1 || w != r) { dccfile_failed(df); return 0; } return 0; } int dccfile_socket_closed(struct socket *socket, enum socket_event_type e, void *d) { struct dccfile *df; df = (struct dccfile *) d; ASSERT(is_magic(df,MAGIC_DCCFILE)); ASSERT(df->socket == socket); debug(dccfile_debug,"DCCFile socket closed"); //dccfile_destroy(df); return 0; } int dccfile_new_connect(struct socket *socket, enum socket_event_type e, void *d) { struct dccfile *df; struct socket *newsock; int timeout; df = (struct dccfile *) d; ASSERT(is_magic(df,MAGIC_DCCFILE)); ASSERT(df->socket == socket); newsock = socket_accept(socket); if (newsock == NULL) { debug(dccfile_debug,"socket_accept() failed!!!"); return 0; } socket_destroy(df->socket); df->socket = newsock; socket_set_callback(df->socket,socket_event_read, dccfile_socket_read,df); socket_set_callback(df->socket,socket_event_close, dccfile_socket_closed,df); socket_set_buffer_size(df->socket,DCCFILE_BUFFER_SIZE); debug(dccfile_debug,"DCCFile(%s) user connected", df->nick); if (df->connect_timer) { timer_destroy(df->connect_timer); df->connect_timer = NULL; } dccfile_raise_event(df, dccfile_event_open); timeout = 240; conf_value_to_int(conf_get_value(df->pool, "/dcc/send/data_timeout"),&timeout); df->data_timer = timer_start(timeout,dccfile_send_timeout, df); if (df->direction == dcc_dir_send) { /* Start send */ dccfile_socket_write(df->socket,socket_event_write,df); } return 0; } static int dccfile_event_filter(struct eventmsg *msg, void *filter) { struct dccfile *df; struct dccfile_event_msg *dem; struct dccfile_pending_msg *pem; df = (struct dccfile *) filter; if (df == NULL) return 0; dem = msg->msg; if (!is_magic(dem,MAGIC_DCCFILE_EVENT_MSG)) { dem = NULL; pem = msg->msg; ASSERT(is_magic(pem,MAGIC_DCCFILE_PENDING_MSG)); } if (dem) { if (dem->dccfile == df) return 0; return -1; } return 0; } int dccfile_raise_event(struct dccfile *df, enum dccfile_events e) { struct dccfile_event_msg msg; set_magic(&msg,MAGIC_DCCFILE_EVENT_MSG); msg.who = df->user; msg.file = df->file; msg.dccfile = df; return event_raise(dccfile_eventobj,e,&msg); } int dccfile_raise_pending(struct dccfile_pending_msg *pmsg) { return event_raise(dccfile_eventobj,dccfile_event_pending,pmsg); } void dccfile_recv_completed(struct dccfile *df) { debug(dccfile_debug,"DCCFile recv completed"); dccfile_raise_event(df, dccfile_event_finished); dccfile_destroy(df); } void dccfile_send_completed(struct dccfile *df) { debug(dccfile_debug,"DCCFile send completed"); dccfile_raise_event(df, dccfile_event_finished); dccfile_destroy(df); } void dccfile_failed(struct dccfile *df) { error(dccfile_debug,"DCCFile failed!"); dccfile_raise_event(df, dccfile_event_aborted); dccfile_destroy(df); } int dccfile_connect_timeout(struct timer *t, void *appdata) { struct dccfile *df; df = appdata; ASSERT(is_magic(df, MAGIC_DCCFILE)); warn(dccfile_debug,"DCC %s connect timeout!", df->direction == dcc_dir_send ? "Send" : "Recv"); dccfile_destroy(df); return 0; } int dccfile_send_timeout(struct timer *t, void *appdata) { struct dccfile *df; df = appdata; ASSERT(is_magic(df, MAGIC_DCCFILE)); warn(dccfile_debug,"DCC Send timeout!"); dccfile_destroy(df); return 0; } int dccfile_recv_timeout(struct timer *t, void *appdata) { struct dccfile *df; df = appdata; ASSERT(is_magic(df, MAGIC_DCCFILE)); warn(dccfile_debug,"DCC Recv timeout!"); dccfile_destroy(df); return 0; }