/* * bsk_tftpd.cpp * * Created on: 25/ago/2016 * Author: chessaa */ #include "bsk_tftpd.h" #include #include #include /* * Trivial File Transfer Protocol (IEN-133) */ #define TFTP_SEGSIZE 512 /* data segment size */ //#define TFTP_MAXSEGSIZE 16384 /* data segment size */ #define TFTP_MAXSEGSIZE 1428 /*16384 */ /* data segment size */ #define TFTP_MINSEGSIZE 8 /* data segment size */ /* * Packet types. */ #define TFTP_RRQ 01 /* read request */ #define TFTP_WRQ 02 /* write request */ #define TFTP_DATA 03 /* data packet */ #define TFTP_ACK 04 /* acknowledgement */ #define TFTP_ERROR 05 /* error code */ #define TFTP_OACK 06 /* option acknowledgement */ typedef struct { unsigned char th_opcode[2]; /* packet type */ union { unsigned char tu_block[2]; /* block # */ unsigned char tu_code[2]; /* error code */ unsigned char tu_stuff[1]; /* request packet stuff */ } th_u; } tftphdr_t; typedef struct { tftphdr_t header; char th_data[2]; /* data or error string */ } tftppkt_t; static void tftp_htons(unsigned char* const p, unsigned short v) { p[0]=(unsigned char)(v>>8); p[1]=(unsigned char)(v); } static unsigned short tftp_ntos(const unsigned char* n) { return n[1]|(n[0]<<8); } #define th_block th_u.tu_block #define th_code th_u.tu_code #define th_stuff th_u.tu_stuff #define th_msg th_data #define TFTP_DATA_HEADERSIZE ( offsetof (struct tftphdr, th_data ) ) #define TFTP_ACK_HEADERSIZE ( offsetof (struct tftphdr, th_block ) \ + sizeof ( ((struct tftphdr *) (0))->th_block) ) /* * Error codes. */ #define EUNDEF 0 /* not defined */ #define ENOTFOUND 1 /* file not found */ #define EACCESS 2 /* access violation */ #define ENOSPACE 3 /* disk full or allocation exceeded */ #define EBADOP 4 /* illegal TFTP operation */ #define EBADID 5 /* unknown transfer ID */ #define EEXISTS 6 /* file already exists */ #define ENOUSER 7 /* no such user */ #define EBADOPTION 8 /* bad option */ #define ECANCELLED 99 /* cancelled by administrator */ /* * options */ #define TFTP_OPT_TSIZE "tsize" #define TFTP_OPT_TIMEOUT "timeout" #define TFTP_OPT_BLKSIZE "blksize" #define TFTP_OPT_MCAST "multicast" #define TFTP_OPT_PORT "udpport" #define IS_OPT(s,opt) (lstrcmpi (s, opt)==0) #define PKTSIZE TFTP_SEGSIZE+4 #define MAXPKTSIZE TFTP_MAXSEGSIZE+4 static int fake_log_delegate(bsk_tftpd_context_id_t, const char* msg, ...) { (void)msg; return 0; } static int fake_send_delegate(bsk_tftpd_context_id_t, unsigned int, unsigned int, const void* const data, unsigned int size) { (void)data; (void)size; return 0; } static bsk_tftpd_receive_status_t fake_receive_delegate(bsk_tftpd_context_id_t, bsk_tftpd_receive_delegate_operation_t op, const void* const data, unsigned int block, unsigned int offset, unsigned int size_bytes) { static bsk_tftpd_receive_status_t reply={bsk_tftpd_ok, 0}; (void)op; (void)data; (void)block; (void)offset; (void)size_bytes; return reply; } typedef enum { t_unconnected=0, t_connected, t_pending} tftp_state_t; struct bsk_tftpd_cx_t { bsk_tftpd_context_info_t info; bsk_tftpd_log_delegate_t log_delegate; bsk_tftpd_send_delegate_t send_delegate; bsk_tftpd_receive_delegate_t receive_delegate; tftp_state_t state; unsigned int client_port; unsigned int client_address; unsigned int block_size; const char* busy_msg; char filename[1024]; char optmp[1024]; char optval[1024]; unsigned long tsize; unsigned int ack_buff[1024]; tftppkt_t* ackmsg; unsigned int ack_len; int force_blocksize; }; #define MAX_CX 8 static struct bsk_tftpd_cx_t cxdb[MAX_CX]; static void validate_delegates(bsk_tftpd_context_id_t id) { bsk_tftpd_cx_t& cx=cxdb[id]; //static struct bsk_tftpd_cx_t cx={fake_log_delegate, fake_send_delegate, fake_receive_delegate}; if (!cx.receive_delegate) cx.receive_delegate=fake_receive_delegate; if (!cx.log_delegate) cx.log_delegate=fake_log_delegate; if (!cx.send_delegate) cx.send_delegate=fake_send_delegate; } const bsk_tftpd_context_info_t* bsk_tftpd_context_info(bsk_tftpd_context_id_t id) { return &cxdb[id].info; } bsk_tftpd_context_id_t bsk_tftpd_context_initialize(bsk_tftpd_context_id_t id, const char* name, unsigned int port, bsk_tftp_cookie_t cookie) { static int allocated_cx; if (id<0) { id=allocated_cx; ++allocated_cx; } cxdb[id].info.name=name; cxdb[id].info.port=port; cxdb[id].info.cookie=cookie; return id; } void bsk_tftpd_set_receive_delegate(bsk_tftpd_context_id_t id, bsk_tftpd_receive_delegate_t receive_delegate) { cxdb[id].receive_delegate=receive_delegate ? receive_delegate : fake_receive_delegate; } void bsk_tftpd_set_send_delegate(bsk_tftpd_context_id_t id, bsk_tftpd_send_delegate_t send_delegate) { cxdb[id].send_delegate=send_delegate ? send_delegate : fake_send_delegate; } void bsk_tftpd_set_log_delegate(bsk_tftpd_context_id_t id, bsk_tftpd_log_delegate_t log_delegate) { cxdb[id].log_delegate=log_delegate ? log_delegate : fake_log_delegate; } #define LOG(msg_, ...) cx.log_delegate(id, msg_, ##__VA_ARGS__) //#define LOG_DBG(msg_) /*cx.log_delegate msg_*/ ((void)0) //#define LOG_DBG(msg_) cx.log_delegate msg_ #define LOG_DBG(msg_, ...) cx.log_delegate(id, msg_, ##__VA_ARGS__) static void nakmsg(bsk_tftpd_context_id_t id, unsigned short code, const char* msg, unsigned int adr=0, unsigned int port=0) { const struct errmsg { int e_code; const char *e_msg; } errmsgs[] = { { EUNDEF, "Undefined error code" }, { ENOTFOUND, "File not found" }, { EACCESS, "Access violation" }, { ENOSPACE, "Disk full or allocation exceeded" }, { EBADOP, "Illegal TFTP operation" }, { EBADID, "Unknown transfer ID" }, { EEXISTS, "File already exists" }, { ENOUSER, "No such user" }, { ECANCELLED, "Cancelled by administrator" }, { -1, 0 } }; if (msg==0) { int i; int n=0; for(i=0; /*i<*/errmsgs[i].e_code!=-1; ++i) if (errmsgs[i].e_code==code) { n=i; break; } msg=errmsgs[n].e_msg; } bsk_tftpd_cx_t& cx=cxdb[id]; tftp_htons(cx.ackmsg->header.th_opcode, TFTP_ERROR); tftp_htons(cx.ackmsg->header.th_u.tu_code, code); strcpy((char*)cx.ackmsg->th_data, msg); if (adr==0) adr=cx.client_address; if (port==0) port=cx.client_port; cx.send_delegate(id, adr, port, cx.ackmsg, sizeof(cx.ackmsg->header)+2+strlen(msg)); LOG("%d: %u %s (%x:%d)", id, tftp_ntos(cx.ackmsg->header.th_u.tu_code), cx.ackmsg->th_data, adr, port); } static void nak(bsk_tftpd_context_id_t id, unsigned short code) { nakmsg(id, code, 0); } static int extract_str(char* dst, unsigned int dsize, char** src, unsigned int* ssize, const char* errmsg) { bsk_tftpd_context_id_t id=0; bsk_tftpd_cx_t& cx=cxdb[id]; char* const p=*src; unsigned int s=*ssize; unsigned int i; for(i=0; (i=(dsize-1)) { LOG("TFTP: %s too long", errmsg); return -1; } if (p[i]>='A' && p[i]<='Z') dst[i]=p[i]; //disable case conversion!!!! +('a'-'A'); else dst[i]=p[i]; dst[i+1]=0; } if (i==0) return -1; s=s-i; if (s>1 && p[i]==0) { --s; ++i; } *ssize=s; *src=&p[i]; return 0; } static void tftp_itoa(int v, char* const dst) { int i; char str[16]; for(i=0; v; v/=10, ++i) { int tmp=v/10; tmp=v-tmp*10; str[i]='0'+tmp; } str[i]=0; --i; if (i<0) i=0; int j; for(j=0; i>=0; --i, ++j) { dst[j]=str[i]; } dst[j]=0; } static int tftp_strcmp(const char* const s1, const char* const s2) { int i; for(i=0; s1[i] && s2[i]; ++i) { char c1=s1[i]; if (c1>='A' && c1<='Z') c1=c1+('a'-'A'); char c2=s2[i]; if (c2>='A' && c2<='Z') c2=c2+('a'-'A'); if (c1!=c2) return -1; } return 0; } static unsigned int tftp_atoi(const char* s) { unsigned int value=0; for(; *s; ++s) { value=value*10; value=value+*s-'0'; } return value; } static int doConnect(bsk_tftpd_context_id_t id, unsigned int c_adr, unsigned int c_port, const tftppkt_t* const pkt, unsigned int size) { char* p=(char*)pkt->header.th_u.tu_stuff; unsigned int s=size-sizeof(tftphdr_t); int opterr=0; int ack_counter=0; int has_bklsize=0; bsk_tftpd_receive_status_t sts; bsk_tftpd_cx_t& cx=cxdb[id]; cx.block_size=512; cx.tsize=0; memset(cx.ack_buff, 0, sizeof cx.ack_buff); cx.ackmsg=(tftppkt_t*)cx.ack_buff; tftp_htons(cx.ackmsg->header.th_opcode, TFTP_OACK); if (extract_str(cx.filename, sizeof cx.filename, &p, &s, "filename")) { LOG("TFTP: invalid filename"); nak(id, EBADOP); return 1; } /*TODO: add grant request*/ LOG("TFTP: filename <%s>\n", cx.filename); if (extract_str(cx.optmp, sizeof cx.optmp, &p, &s, "mode")) { LOG("TFTP: unsupported mode"); nak(id, EBADOP); return 1; } if (tftp_strcmp(cx.optmp, "octet") && tftp_strcmp(cx.optmp, "binary")) { LOG(("TFTP: unsupported mode\n")); nak(id, EBADOP); return 1; } while(extract_str(cx.optmp, sizeof cx.optmp, &p, &s, "opt")==0) { if (extract_str(cx.optval, sizeof cx.optval, &p, &s, "optval")) { LOG("TFTP: opt=%s, value=ERROR", cx.optmp); nak(id, EBADOP); return 1; } if (tftp_strcmp(cx.optmp, TFTP_OPT_BLKSIZE)==0) { unsigned int v=tftp_atoi(cx.optval); LOG_DBG("TFTP: opt=%s, value=%s (%u)", cx.optmp, cx.optval, v); if (vTFTP_MAXSEGSIZE) v=TFTP_MAXSEGSIZE; strcpy((char*)&cx.ackmsg->header.th_u.tu_stuff[ack_counter], TFTP_OPT_BLKSIZE); ack_counter+=sizeof(TFTP_OPT_BLKSIZE); char* p=(char*)&cx.ackmsg->header.th_u.tu_stuff[ack_counter]; tftp_itoa(v, p); ack_counter+=strlen((char*)&cx.ackmsg->header.th_u.tu_stuff[ack_counter]); has_bklsize=1; cx.block_size=v; LOG("TFTP: block size=%d (%s)", cx.block_size, p); } else if (tftp_strcmp(cx.optmp, TFTP_OPT_TSIZE)==0) { cx.tsize=tftp_atoi(cx.optval); LOG("TFTP: opt=%s, value=%s", cx.optmp, cx.optval); } else { LOG("TFTP: opt=%s, value=%s - unsupported option", cx.optmp, cx.optval); ++opterr; } } if (opterr) { nak(id, EBADOP); return 1; } sts=cx.receive_delegate(id, bsk_tftpd_open_file, cx.filename, 0, 0, cx.tsize); if (sts.sts) { nakmsg(id, sts.sts, sts.ack_msg); //EBADOP); return 1; } if (!has_bklsize && cx.force_blocksize) { strcpy((char*)&cx.ackmsg->header.th_u.tu_stuff[ack_counter], TFTP_OPT_BLKSIZE); ack_counter+=sizeof(TFTP_OPT_BLKSIZE); tftp_itoa(TFTP_MAXSEGSIZE, (char*)&cx.ackmsg->header.th_u.tu_stuff[ack_counter]); ack_counter+=strlen((char*)&cx.ackmsg->header.th_u.tu_stuff[ack_counter]); } if (ack_counter==0) { tftp_htons(cx.ackmsg->header.th_opcode, TFTP_ACK); cx.ackmsg->header.th_u.tu_block[0]=0; cx.ackmsg->header.th_u.tu_block[1]=0; ack_counter=1; } cx.send_delegate(id, c_adr, c_port, cx.ackmsg, (ack_counter+sizeof(tftphdr_t))-1); return 0; } static int doProcessData(bsk_tftpd_context_id_t id, const tftppkt_t* const pkt, unsigned int size) { bsk_tftpd_receive_status_t sts; unsigned short block=tftp_ntos(pkt->header.th_u.tu_block); unsigned int s=size-sizeof(pkt->header); bsk_tftpd_cx_t& cx=cxdb[id]; LOG_DBG("TFTP: receive b%u, %ubytes\n", block, s); int end=s0) offset=(block-1)*cx.block_size; sts=cx.receive_delegate(id, end ? bsk_tftpd_end : bsk_tftpd_data, pkt->th_data, block, offset, s); if (sts.sts && sts.stsheader.th_opcode, TFTP_ACK); cx.ackmsg->header.th_u.tu_block[0]=pkt->header.th_u.tu_block[0]; cx.ackmsg->header.th_u.tu_block[1]=pkt->header.th_u.tu_block[1]; if (sts.sts!=bsk_tftpd_dont_ack) { cx.send_delegate(id, cx.client_address, cx.client_port, cx.ackmsg, sizeof(cx.ackmsg->header)); } if (end) return sts.sts!=bsk_tftpd_dont_ack ? 1 : 2; return 0; } int bsk_tftpd_process_packet(bsk_tftpd_context_id_t id, unsigned int client_address, unsigned int client_port, const void* packet, unsigned int size_bytes) { //static int initialized; unsigned short opcode; tftphdr_t* hdr=(tftphdr_t*)packet; tftppkt_t* pkt=(tftppkt_t*)packet; bsk_tftpd_cx_t& cx=cxdb[id]; //if (!initialized) { cx.ackmsg=(tftppkt_t*)cx.ack_buff; memset(cx.ack_buff, 0, sizeof cx.ack_buff); //initialized=1; } LOG_DBG("TFTP: process packet: %u %x %u", size_bytes, client_address, client_port); if (size_bytesth_opcode); if (opcode==TFTP_ERROR) { unsigned short code=tftp_ntos(pkt->header.th_u.tu_code); char* p=(char*)pkt->th_data; unsigned int s=size_bytes-sizeof(pkt->header); if (!extract_str(cx.optmp, sizeof cx.optmp, &p, &s, 0)) LOG("TFTP: error from peer: %u %s\n", code, cx.optmp); else LOG("TFTP: error from peer: %u (unknown)\n", code); if (cx.state==t_connected) { cx.receive_delegate(id, bsk_tftpd_error, 0, 0, 0, 0); cx.state=t_unconnected; } return -1; } if (opcode==TFTP_ACK) { LOG(("TFTP: unexpected ACK from peer\n")); return -1; } if (opcode==TFTP_RRQ) { char* p=(char*)pkt->header.th_u.tu_stuff; #if 0 unsigned int s=size_bytes-sizeof(tftphdr_t); if (extract_str(cx.filename, sizeof cx.filename, &p, &s, "filename")) { LOG("TFTP: invalid filename"); nak(id, EBADOP); return 0; } #endif bsk_tftpd_receive_status_t sts=cx.receive_delegate(id, bsk_tftpd_open_read_not_supported, p, 0, 0, 0); nakmsg(id, sts.sts, sts.ack_msg, client_address, client_port); return -100; } if (cx.busy_msg) { nakmsg(id, EACCESS, cx.busy_msg, client_address, client_port); return -10; } switch(cx.state) { case t_unconnected: cx.client_address=client_address; cx.client_port=client_port; if (opcode!=TFTP_WRQ) { LOG("TFTP: Unexpected request %d from peer", opcode); nak(id, EBADOP); return -2; } if (!doConnect(id, client_address, client_port, pkt, size_bytes)) { LOG("TFTP: connected: %x %u", client_address, client_port); cx.state=t_connected; } break; case t_connected: case t_pending: if (opcode==TFTP_WRQ) { if (cx.state==t_pending) { nakmsg(id, EACCESS, "pending operation"); return 2; } } if ((cx.client_address!=client_address) || (cx.client_port!=client_port)) { LOG("TFTP: ERROR - already connected!"); nak(id, EBADID); return -5; } if (opcode==TFTP_DATA) { if (cx.state==t_pending) { return 2; } int res=doProcessData(id, pkt, size_bytes); if (res<0) { LOG("TFTP: ERROR - disconnect!\n"); cx.state=t_unconnected; return -3; } if (res>0) { if (res==2) { cx.state=t_pending; LOG("TFTP: completed - pending ack\n"); } else { LOG("TFTP: completed (%d blocks)", 0); cx.state=t_unconnected; } return res; } } else { LOG("TFTP: disconnect - invalid opcode: %u\n", opcode); nak(id, EBADOP); cx.state=t_unconnected; return -4; } break; } return 0; } int bsk_tftpd_send_ack(bsk_tftpd_context_id_t id, bsk_tftpd_status_t /*code*/, const char* /*msg*/) { bsk_tftpd_cx_t& cx=cxdb[id]; LOG("ACK!"); tftp_htons(cx.ackmsg->header.th_opcode, TFTP_ACK); cx.ackmsg->header.th_u.tu_block[0]=0; cx.ackmsg->header.th_u.tu_block[1]=0; cx.send_delegate(id, cx.client_address, cx.client_port, cx.ackmsg, sizeof(cx.ackmsg->header)); cx.state=t_unconnected; return 0; } int bsk_tftpd_set_busy(bsk_tftpd_context_id_t id, const char* msg) //msg=0 means NO BUSY { bsk_tftpd_cx_t& cx=cxdb[id]; cx.busy_msg=msg; return 0; } //Client I/F