/* ARISA - Common DCC Functionality * Copyright (C) 2003, 2004 Carl Ritson * * 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 "arisa.h" #include "dcc.h" #include "user.h" #include #include #include #include /** dcc_safe_fn * Returns 1 if the filename contains no spaces, 0 if it does. * It also modifies the file name passed in place to replacing harmful * characters with '_'. */ int dcc_safe_fn(char *fn) { int i,has_space; for(i = 0, has_space = 0; fn[i] != '\0'; ++i) { if(isspace(fn[i])) has_space = 1; else { switch (fn[i]) { case '"': case '/': case '\\': fn[i] = '_'; break; default: break; } } } return has_space; } /** dcc_send * Handles incomming DCC SEND commands from IRC, attempts to locate * a RECV interface and upload directory to use. Rejects sends from * unauthorised hosts. * * If a send is accepted then the ip addr and port from the DCC SEND * message are embedded in the label of the pack_t structure in the * new created send type structure. So pack->label is a pointer to * sizeof(struct sockaddr_in)+1, the first byte is '\0' so it acts * as a valid string. The original file name is placed in pack->file. */ send_t *dcc_send(network_t *net, const char *host, const char *hostmask, const char *nick, const char *cmd) { interface_t *interface = NULL, **interfaces = NULL; dcc_aux_t *aux; access_t *access; send_t *send; char *error = NULL,**opt,buffer[512],netname[64]; off_t size; int i,no_opt,no_found,no_interfaces; if(net == NULL || nick == NULL || host == NULL || cmd == NULL) return NULL; LOCK(net); xstrncpy(netname,net->name,sizeof(netname)); UNLOCK(net); xstrncpy(buffer,cmd,sizeof(buffer)); no_opt = 6; opt = split_opt(buffer,buffer,&no_opt); if(opt == NULL) return NULL; if(no_opt != 6) { xfree(opt); return NULL; } size = (off_t) strtoull(opt[5],NULL,10); if(size <= 0) { xfree(opt); return NULL; } LOCK(global); interfaces = PTRARR_DUP(global->interfaces, global->no_interfaces,&no_interfaces); for(i = 0, no_found = 0; i < no_interfaces; ++i) { LOCK(interfaces[i]); if(interfaces[i]->deleted != 0 || interfaces[i]->type != INTERFACE_RECV) { UNLOCK(interfaces[i]); interfaces[i] = NULL; } else { no_found++; UNLOCK(interfaces[i]); } } UNLOCK(global); if(no_found == 0) error = "No suitable RECV interface found"; /* Final all those with access */ for(i = 0; i < no_interfaces && no_found > 0; ++i) { if(interfaces[i] == NULL) continue; LOCK(interfaces[i]); if(interfaces[i]->deleted == 0) { access = interfaces[i]->recv.access; } else access = NULL; UNLOCK(interfaces[i]); if(access != NULL) { if(!access_check(access,net,host,hostmask,nick)) { interfaces[i] = NULL; } } else interfaces[i] = NULL; if(interfaces[i] == NULL) no_found--; } if(no_found == 0) error = "Your host is not permitted to upload"; for(i = 0; i < no_interfaces && no_found > 0; ++i) { if(interfaces[i] == NULL) continue; error = NULL; LOCK(interfaces[i]); if(interfaces[i]->recv.no_uploads >= interfaces[i]->recv.max_no_uploads) { error = "No upload slots available"; } else if(interfaces[i]->recv.max_size != 0 && size > interfaces[i]->recv.max_size) { error = "File exceeds maximum allowed size"; } else { interface = interfaces[i]; } if(error != NULL) { UNLOCK(interfaces[i]); //interfaces[i] = NULL; no_found--; } else { /* interface is still locked */ break; } } if(interfaces != NULL) xfree(interfaces); if(interface == NULL) { xfree(opt); snprintf(buffer,sizeof(buffer), "Upload denied. Error: %s. Host: %s. File size: %lld.", error,host,size); irc_send_notice(net,nick,buffer); LOGP(L_XDC,"DCC SEND from [%s:%s] (%s), Rejected: %s.", netname,nick,host,error); return NULL; } send = alloc_send(); send->state = STATE_QUEUED; send->started = xtime(); send->pack = alloc_pack(); send->network = net; send->nick = xstrdup(nick); send->host = xstrdup(host); send->pack->file = xstrdup(opt[2]); send->pack->size = size; send->aux = xalloc(sizeof(dcc_aux_t)); aux = (dcc_aux_t *)send->aux; aux->reverse = 0; // FIXME aux->addr.s_addr = htonl(strtoul(opt[3],NULL,10)); aux->port = (unsigned short)strtoul(opt[4],NULL,10); xfree(opt); PTRARR_ADD(&interface->recv.uploads,&interface->recv.no_uploads,send); LOGP(L_XDC, "DCC SEND from [%s:%s] (%s) Accepted on Interface \"%s\".", netname,nick,host,interface->name); UNLOCK(interface); return send; } /** dcc_resume * Handles DCC RESUME requests from IRC networks, it attempts to find * a matching send for the request. If a matching send is found then * it is marked STATE_RESUME and the pos field set to the position. * The interface thread will then send a DCC ACCEPT when it has validated * the resume. * * Note: Interfaces initialise the bytes_sent field to the listening * port number to allow matching against it. This is a documented * field reuse. */ void dcc_resume(network_t *net, const char *host, const char *nick, const char *cmd) { dcc_aux_t *aux; char buffer[512],file[512]; char **opt; pool_t *p; off_t pos; unsigned short port; int i,j,k,found,no_opt; //D("%s,%s,%d",host,nick,cmd); xstrncpy(buffer,cmd,sizeof(buffer)); no_opt = 5; opt = split_opt(buffer,buffer,&no_opt); if(opt == NULL) return; if(no_opt != 5) { //D("opt err: %d",no_opt); xfree(opt); return; } pos = strtoull(opt[4],NULL,10); if(pos < 0) { //D("pos err"); xfree(opt); return; } port = (unsigned short)strtoul(opt[3],NULL,10); //D("search"); LOCK(global); for(k = 0, found = 0; k < global->no_interfaces && !found; ++k) { LOCK(global->interfaces[k]); if(global->interfaces[k]->type != INTERFACE_SEND || global->interfaces[k]->deleted != 0) { UNLOCK(global->interfaces[k]); continue; } for(i = 0; i < global->interfaces[k]->send.no_pools && !found; ++i) { p = global->interfaces[k]->send.pools[i]; LOCK(p); if(p->deleted == 0) { for(j = 0; j < p->no_csends && !found; ++j) { LOCK(p->csends[j]); aux = (dcc_aux_t *)p->csends[j]->aux; if(p->csends[j]->state == STATE_LISTENING && aux != NULL) { if(p->csends[j]->network == net && irc_strcasecmp(p->csends[j]->nick,nick) == 0 && aux->port == port) { RLOCK(p->csends[j]->pack); pathtofn(p->csends[j]->pack->file,file,sizeof(file)); dcc_safe_fn(file); /* don't bother checking the file because mIRC doesn't use it */ if(pos < p->csends[j]->pack->size) { p->csends[j]->state = STATE_RESUME; p->csends[j]->pos = pos; found = 1; } RUNLOCK(p->csends[j]->pack); } } UNLOCK(p->csends[j]); } } UNLOCK(p); } UNLOCK(global->interfaces[k]); } UNLOCK(global); if(found) { char msg[512]; snprintf(msg,sizeof(msg), strchr(file,' ') != NULL ? "DCC ACCEPT \"%s\" %u %lld" : "DCC ACCEPT %s %u %lld", file,port,pos); irc_send_ctcp_msg(net,nick,msg); } LOCK(net); LOGP(L_XDC,"%sDCC RESUME from [%s:%s] for [\"%s\",%lld]", found == 0 ? "Unknown " : "", net->name,nick, found == 0 ? opt[2] : file,pos); UNLOCK(net); xfree(opt); } /** dcc_accept * Handles DCC ACCEPT messages from IRC, it locates the approriate upload * in a RECV interface and marks it as STATE_RESUMED. */ void dcc_accept(network_t *net, const char *host, const char *nick, const char *cmd) { dcc_aux_t *aux; long port; char buffer[512]; char **opt; send_t *u; off_t pos; int i,j,found,no_opt; xstrncpy(buffer,cmd,sizeof(buffer)); no_opt = 5; opt = split_opt(buffer,buffer,&no_opt); if(opt == NULL) return; if(no_opt != 5) { xfree(opt); return; } port = strtol(opt[3],NULL,10); pos = strtoull(opt[4],NULL,10); LOCK(global); for(i = 0, found = 0; i < global->no_interfaces && !found; ++i) { LOCK(global->interfaces[i]); if(global->interfaces[i]->type != INTERFACE_RECV || global->interfaces[i]->deleted != 0) { UNLOCK(global->interfaces[i]); continue; } for(j = 0; j < global->interfaces[i]->recv.no_uploads && !found; ++j) { u = global->interfaces[i]->recv.uploads[j]; LOCK(u); aux = (dcc_aux_t *)u->aux; if(u->state == STATE_RESUME && aux != NULL) { if(net == u->network && pos == u->pos && aux->port == port && irc_strcasecmp(u->nick,nick) == 0) { RLOCK(u->pack); if(strcmp(u->pack->file,opt[2]) == 0) { u->state = STATE_RESUMED; found = 1; } RUNLOCK(u->pack); } } UNLOCK(u); } UNLOCK(global->interfaces[i]); } UNLOCK(global); xfree(opt); LOCK(net); LOGP(L_XDC,"%sDCC ACCEPT from [%s:%s]", (found == 0 ? "" : "Unknown "),net->name,nick); UNLOCK(net); } void dcc_recv(network_t *net, const char *host, const char *nick, const char *cmd) { dcc_aux_t *aux; char buffer[512]; } /** dcc_cmd * Multiplexs DCC commands to the dcc_send, dcc_resume and dcc_accept * functions. */ void dcc_cmd(network_t *net, const char *host, const char *hostmask, const char *nick, const char *cmd) { //D("%s,%s,%s",host,nick,cmd); if(strncasecmp(cmd,"DCC SEND",8) == 0) dcc_send(net,host,hostmask,nick,cmd); else if(strncasecmp(cmd,"DCC RESUME",10) == 0) dcc_resume(net,host,nick,cmd); else if(strncasecmp(cmd,"DCC ACCEPT",10) == 0) dcc_accept(net,host,nick,cmd); else if(strncasecmp(cmd,"DCC RECV",8) == 0) dcc_recv(net,host,nick,cmd); } void dcc_send_msg(struct in_addr *adv, send_t *send, unsigned short port) { char buffer[512], file[512], nick[64], ip[32] = "$IIP"; dcc_aux_t *aux = xalloc(sizeof(dcc_aux_t)); network_t *net; if(adv != NULL) snprintf(ip,sizeof(ip),"%lu", (unsigned long) ntohl(adv->s_addr)); aux->reverse = 0; aux->port = port; LOCK(send); send->state = STATE_LISTENING; send->aux = aux; RLOCK(send->pack); net = send->network; xstrncpy(nick,send->nick,sizeof(nick)); pathtofn(send->pack->file,file,sizeof(file)); snprintf(buffer,sizeof(buffer), dcc_safe_fn(file) != 0 ? "DCC SEND \"%s\" %s %u %lld" : "DCC SEND %s %s %u %lld", file,ip,port,send->pack->size); RUNLOCK(send->pack); UNLOCK(send); LOCK(net); if(adv != NULL) inet_ntop(AF_INET,adv,ip,sizeof(ip)); LOGTP(L_INF,"Port (%s:%u) offered to [%s:%s] for send of {\"%s\"}.", adv != NULL ? ip : "AUTO",port, net->name,nick,file); UNLOCK(net); D("%s",buffer); irc_send_ctcp_msg(net,nick,buffer); } void dcc_rsend_accept_msg(struct in_addr *adv, send_t *send, unsigned short port) { char buffer[512], file[512], nick[64], ip[32] = "$IIP"; network_t *net; if(adv != NULL) snprintf(ip,sizeof(ip),"%lu", (unsigned long) ntohl(adv->s_addr)); LOCK(send); send->state = STATE_LISTENING; RLOCK(send->pack); net = send->network; xstrncpy(nick,send->nick,sizeof(nick)); xstrncpy(file,send->pack->file,sizeof(file)); snprintf(buffer,sizeof(buffer), strchr(send->pack->file,' ') != NULL ? "DCC RECV \"%s\" %s %u %lld" : "DCC RECV %s %s %u %lld", send->pack->file,ip,port,send->pos); RUNLOCK(send->pack); UNLOCK(send); LOCK(net); if(adv != NULL) inet_ntop(AF_INET,adv,ip,sizeof(ip)); LOGTP(L_INF,"Port (%s:%u) offered to [%s:%s] for received of {\"%s\"}.", adv != NULL ? ip : "AUTO",port, net->name,nick,file); irc_send_ctcp_msg(net,nick,buffer); } void dcc_chat_msg(struct in_addr *adv, chat_t *chat, unsigned short port) { char buffer[512], nick[64], ip[32] = "$IIP"; network_t *net; if(adv != NULL) snprintf(ip,sizeof(ip),"%lu", (unsigned long) ntohl(adv->s_addr)); LOCK(chat); chat->state = STATE_LISTENING; net = chat->network; xstrncpy(nick,chat->nick,sizeof(nick)); snprintf(buffer,sizeof(buffer),"DCC CHAT CHAT %s %u",ip,port); UNLOCK(chat); LOCK(net); if(adv != NULL) inet_ntop(AF_INET,adv,ip,sizeof(ip)); LOGTP(L_INF,"Port (%s:%u) offered to [%s:%s] for chat.", adv != NULL ? ip : "AUTO", port,net->name,nick); UNLOCK(net); irc_send_ctcp_msg(net,nick,buffer); } void dcc_rsend_msg(send_t *send) { char buffer[512], file[512], nick[64]; network_t *net; LOCK(send); RLOCK(send->pack); net = send->network; xstrncpy(nick,send->nick,sizeof(nick)); pathtofn(send->pack->file,file,sizeof(file)); snprintf(buffer,sizeof(buffer), dcc_safe_fn(file) != 0 ? "DCC RSEND \"%s\" %lld" : "DCC RSEND %s %lld", file,send->pack->size); RUNLOCK(send->pack); UNLOCK(send); LOCK(net); LOGTP(L_INF,"Port requested from [%s:%s] for send of {\"%s\"}.", net->name,nick,file); UNLOCK(net); irc_send_ctcp_msg(net,nick,buffer); } void dcc_rchat_msg(chat_t *chat) { char buffer[512], nick[64]; network_t *net; LOCK(chat); net = chat->network; xstrncpy(nick,chat->nick,sizeof(nick)); snprintf(buffer,sizeof(buffer),"DCC RCHAT CHAT"); UNLOCK(chat); LOCK(net); LOGTP(L_INF,"Port requested from [%s:%s] for chat.", net->name,nick); UNLOCK(net); irc_send_ctcp_msg(net,nick,buffer); } void dcc_recv_msg(send_t *send) { char buffer[512], file[512], nick[64]; network_t *net; dcc_aux_t *aux; off_t pos; LOCK(send); if(send->pos == 0) { UNLOCK(send); return; } send->state = STATE_RESUME; RLOCK(send->pack); aux = (dcc_aux_t *)send->aux; net = send->network; xstrncpy(nick,send->nick,sizeof(nick)); xstrncpy(file,send->pack->file,sizeof(file)); pos = send->pos; snprintf(buffer,sizeof(buffer), strchr(send->pack->file,' ') != NULL ? "DCC RESUME \"%s\" %u %lld" : "DCC RESUME %s %u %lld", send->pack->file,aux->port,send->pos); RUNLOCK(send->pack); UNLOCK(send); LOCK(net); LOGTP(L_INF,"Resume requested from [%s:%s] for {\"%s\",%lld}.", net->name,nick,file,pos); UNLOCK(net); irc_send_ctcp_msg(net,nick,buffer); }