#pragma module TFTP "TFTP-1-A" #define __MODULE__ "TFTP" /* **++ ** FACILITY: TFTP Client - Command Line Utility ** ** MODULE DESCRIPTION: ** ** This standalone program implements a TFTP Client functionality. ** ** AUTHORS: Ruslan R. Laishev ** ** ** CREATION DATE: 14-MAY-2004 ** ** ** MODIFICATION HISTORY: ** ** 2004-09-08 SMS. Fixed the "va_end()" argument in "tftp__log()", ** eliminating the "%CC-I-LVALUECAST" compiler ** diagnostic. ** Changed to use the "fromfile" name as a default ** "tofile" name, making the "tofile" argument ** optional. ** Changed to use the "tofile" name (not the ** "fromfile" name) as the remote file name for a PUT ** operation. ** Changed the GETSTART, GETSTOP, PUTSTART, and ** PUTSTOP messages. ** Changed "/LOG" to "/VERBOSE". ** Included "/DEBUG" in the usage instructions. ** Removed TAB characters from the usage instructions. ** Reduced source width to 80 columns. ** **-- */ /* ** ** INCLUDE FILES ** */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tftpdef.h" #include "tftp_msg.h" /* ** ** MACRO DEFINITIONS ** */ #define INIT_DDESC(dsc) {(dsc).dsc$b_dtype = DSC$K_DTYPE_T;\ (dsc).dsc$b_class = DSC$K_CLASS_D;\ (dsc).dsc$w_length = 0;\ (dsc).dsc$a_pointer = 0;} #define INIT_SDESC(dsc, len, ptr) {(dsc).dsc$b_dtype = DSC$K_DTYPE_T;\ (dsc).dsc$b_class = DSC$K_CLASS_S;\ (dsc).dsc$w_length = (short) (len);\ (dsc).dsc$a_pointer = (char*)(ptr);} #define DEBUG if(debug_flag) \ printf("%-24.24s:%-08.0u",__MODULE__,__LINE__);\ if(debug_flag)printf #define _PUT_MSG_(s) {int msgvec[] = {1,s};sys$putmsg(msgvec);} #ifdef __VAX #define LONGWORD_SZ 4 #else #define LONGWORD_SZ 8 #endif /* ** ** CLI$ Declaration stuff ** */ extern void *TFTP_CLD; $DESCRIPTOR(usage, "TFTP 2.1 Client - Command Line Utility\n\r \ Usage:\n\r \ $ TFTP :==$:TFTP.EXE\n\r \ \n\r \ $ TFTP PUT host local_file [ remote_file ]\n\r \ $ TFTP GET host remote_file [ local_file ]\n\r \ /MODE = [ ASCII | BINARY | NETASCII | OCTET ]\n\r \ (ASCII = NETASCII, BINARY = OCTET.)\n\r \ /VERBOSE Enables progress messages.\n\r \ /DEBUG Enables debug messages.\n\r \ Example:\n\r \ $ TFTP PUT TFTPd.ZZ.NET zz.txt \"/zz/zz.txt\"\n\r \ $ TFTP GET TFTPd.ZZ.NET zz.txt zz.lis /VERBOSE /MODE = NETASCII\n\r \ Note:\n\r \ Supported file record formats:\n\r \ Stream_LF, Stream_CR, Stream, Fixed Length"); $DESCRIPTOR(p_host, "HOST"); $DESCRIPTOR(p_cmd, "CMD"); $DESCRIPTOR(p_fromfile, "FROMFILE"); $DESCRIPTOR(p_tofile, "TOFILE"); $DESCRIPTOR(q_mode, "MODE"); $DESCRIPTOR(amode_dsc, "ASCII/NETASCII"); $DESCRIPTOR(bmode_dsc, "OCTET/BINARY"); $DESCRIPTOR(q_fdl, "FDL"); $DESCRIPTOR(q_log, "VERBOSE"); $DESCRIPTOR(q_debug, "DEBUG"); $DESCRIPTOR(dsc_ucxdev, "UCX$DEVICE"); $DESCRIPTOR(dsc_tcpipdev,"TCPIP$DEVICE"); int gl_status = SS$_NORMAL; int log_flag = 0,debug_flag = 0,iotmo[2]; int iotmo [2] = { -100000000, -1 }; /* 10 seconds */ /* **++ ** FUNCTIONAL DESCRIPTION: ** ** Put message to standard output device (SYS$OUTPUT). ** ** FORMAL PARAMETERS: ** ** msgid: ** VMS condition code ** variable agriments list ** ** RETURN VALUE: ** ** VMS condition code ** ** **-- */ int tftp__log ( int msgid, ... ) { long status,retvalue = msgid; va_list args; char buf[1024] = {"!%D "},msg_buf[1024],outadr[4]; struct dsc$descriptor opr_dsc,buf_dsc,fao_dsc; int argc,argl[32],idx,flag=15,lvl; /* ** Get a message text with given msgid */ INIT_SDESC(fao_dsc,sizeof(buf)-4,&buf[4]); if ( !(1 & (status = sys$getmsg( msgid, &fao_dsc.dsc$w_length, &fao_dsc, flag, &outadr))) ) lib$signal(status); /* ** Reorganize parameters list for $FAOL */ va_start(args,msgid); argl[0] = 0; for (idx = 1,va_count(argc);idx < argc;idx++,args += LONGWORD_SZ) argl[idx] = *((long *)args); va_end( args); /* ** Format a message, put it to SYS$OUTPUT */ fao_dsc.dsc$a_pointer -=4; fao_dsc.dsc$w_length +=4; INIT_SDESC(buf_dsc, sizeof(msg_buf),msg_buf); if ( !(1 & (status = sys$faol( &fao_dsc, &buf_dsc.dsc$w_length, &buf_dsc, argl))) ) lib$signal(status); lib$put_output(&buf_dsc); return retvalue; } /* **++ ** FUNCTIONAL DESCRIPTION: ** ** Canceling all queued and processed request due of expiration timer. ** ** FORMAL PARAMETERS: ** ** reqidt: pointer to a network channel ** ** RETURN VALUE: ** ** None ** ** SIDE EFFECTS: ** ** Cancel _all_ request for the given I/O channel. **-- */ void timer_ast ( int *reqidt ) { sys$cantim(reqidt,0); sys$cancel(*reqidt); } int net_write ( unsigned short chan, void *buf, unsigned short buflen, struct sockaddr_in *remhost ) { int status,remhostlen; iosb netiosb; ile3 rsck_adrs = {sizeof(struct sockaddr_in), 0, remhost, (unsigned short*) &remhostlen}; /* ** */ status = sys$qiow (0,chan,IO$_WRITEVBLK,&netiosb,0,0, buf,buflen,&rsck_adrs,0,0,0); return (1 & status)?netiosb.iosb$w_status:status; } int net_read ( unsigned short chan, void *buf, unsigned short bufsz, unsigned short *retlen, struct sockaddr_in *remhost ) { int status,remhostlen,reqidt = 0; iosb netiosb; ile3 rsck_adrs = {sizeof(struct sockaddr_in), 0, remhost, (unsigned short*) &remhostlen}; *retlen = 0; /* ** Setup a Timer */ reqidt = chan; if ( !(1 & (status = sys$setimr (0,&iotmo, timer_ast,&reqidt,0))) ) return status; /* ** Read data block */ if ( !(1 & (status = sys$qiow( 0, chan, IO$_READVBLK, &netiosb, 0, 0, buf, bufsz, &rsck_adrs, 0, 0, 0))) && ((status == SS$_CANCEL) || (netiosb.iosb$w_status == SS$_CANCEL)) ) *retlen = 0; else *retlen = netiosb.iosb$w_bcnt; /* ** Cancel a timer request */ status = sys$cantim(&reqidt,0); return (1 & status)?netiosb.iosb$w_status:status; } /* **++ ** FUNCTIONAL DESCRIPTION: ** ** Perform TFTP GET from remote node. ** ** FORMAL PARAMETERS: ** ** chan: An network I/O channel number ** ipaddr: A remote node IP address ** filename: A filename on remote node ** remhost: Socket Address IN structure ** rab: A RAB of the has been opened localy file ** tmode: A mode of transfering ** bytes: Total sent bytes ** ** RETURN VALUE: ** ** VMS condition code ** **-- */ int tftp_do_get ( unsigned short chan, unsigned ipaddr, struct dsc$descriptor *filename, struct sockaddr_in *remhost, struct RAB *rab, unsigned tmode, unsigned *bytes ) { unsigned status; char buf [ 1024]; struct tftp_pdu *pdu = (struct tftp_pdu *)&buf; struct dsc$descriptor buf_dsc; $DESCRIPTOR(fao_dsc,"!AS\x00!AZ\x00"); unsigned short lbn,retlen = 0; /* ** Send Read ReQuest (RRQ) */ pdu->tftp_pdu$w_opcode = htons(TFTP_OPC$K_RRQ); INIT_SDESC(buf_dsc,sizeof(pdu->tftp_pdu$b_args),&pdu->tftp_pdu$b_args); if ( !(1 & (status = sys$fao(&fao_dsc,&retlen,&buf_dsc,filename, (tmode==TFTP__$K_NETASCII)?"netascii":"octet"))) ) lib$signal(status); if ( !(1 & (status = net_write (chan,buf,2+retlen,remhost))) ) return status; /* ** Start main loop processing input stream of blocks */ rab->rab$l_rbf = (char *)pdu->tftp_pdu$b_data; for (lbn = 1;;lbn++) { if ( !(1 & (status = net_read( chan, &buf, sizeof(buf), &retlen, remhost))) ) return status; status = ntohs(pdu->tftp_pdu$w_opcode); DEBUG("Got %u bytes, opcode = %u\n",retlen,status); retlen -= 4; switch ( status ) { case TFTP_OPC$K_ERROR: status = ntohs(pdu->tftp_pdu$w_code); tftp__log( TFTP_ERROR, status, pdu->tftp_pdu$b_msg); { int errt [] = {2, SS$_NOSUCHFILE, SS$_NOPRIV, SS$_DEVICEFULL, SS$_PROTOCOL, SS$_NOSUCHTID, SS$_DUPFILENAME, SS$_NOSUCHUSER}; if ( status < 8 ) return errt[status]; return SS$_ABORT; } case TFTP_OPC$K_DATA: break; default: return SS$_ABORT; } if ( lbn != ntohs(pdu->tftp_pdu$w_lbn) ) { tftp__log(TFTP_ERRLBN,lbn,ntohs(pdu->tftp_pdu$w_lbn)); return SS$_DATALOST; } /* ** Write a gotten datablock to a file */ if ( retlen ) { rab->rab$w_rsz = retlen; if ( !(1 & (status = sys$write(rab))) ) return status; } /* ** Send ACKnoledgment of received data block */ pdu->tftp_pdu$w_opcode = htons(TFTP_OPC$K_ACK); if ( !(1 & (status = net_write (chan,pdu,4,remhost))) ) return status; /* ** If a data block is not 512 bytes, then it's the last ** block of the file. */ *bytes += retlen; if ( retlen < sizeof(pdu->tftp_pdu$b_data) ) break; } return status; } /* **++ ** FUNCTIONAL DESCRIPTION: ** ** Perform TFTP GET from remote node. ** ** FORMAL PARAMETERS: ** ** chan: An network I/O channel number ** ipaddr: A remote node IP address ** filename: A filename on remote node ** remhost: Socket Address IN structure ** rab: A RAB of the has been opened localy file ** tmode: A mode of transfering ** bytes: Total sent bytes ** ebk: A total number of blocks in local file ** ffb: A first free byte ** ** RETURN VALUE: ** ** VMS condition code ** **-- */ int tftp_do_put ( unsigned short chan, unsigned ipaddr, struct dsc$descriptor *filename, struct sockaddr_in *remhost, struct RAB *rab, unsigned tmode, unsigned *bytes, unsigned ebk, unsigned short ffb ) { unsigned status; char buf [ 1024]; struct tftp_pdu *pdu = (struct tftp_pdu *)&buf; struct dsc$descriptor buf_dsc; $DESCRIPTOR(fao_dsc,"!AS\x00!AZ\x00"); unsigned short lbn,retlen = 0,acklbn = 0; DEBUG("ebk = %u, ffb = %u\n",ebk,ffb); /* ** Send Write ReQuest (WRQ) */ pdu->tftp_pdu$w_opcode = htons(TFTP_OPC$K_WRQ); INIT_SDESC(buf_dsc,sizeof(pdu->tftp_pdu$b_args),&pdu->tftp_pdu$b_args); if ( !(1 & (status = sys$fao(&fao_dsc,&retlen,&buf_dsc,filename, (tmode==TFTP__$K_NETASCII)?"netascii":"octet"))) ) lib$signal(status); if ( !(1 & (status = net_write (chan,buf,2+retlen,remhost))) ) return status; /* ** Start main loop processing input stream of blocks */ rab->rab$l_ubf = (char *)pdu->tftp_pdu$b_data; rab->rab$w_usz = sizeof(pdu->tftp_pdu$b_data); for (lbn = 1;;lbn++) { if ( !(1 & (status = net_read( chan, &buf, sizeof(buf), &retlen, remhost))) ) return status; status = ntohs(pdu->tftp_pdu$w_opcode); DEBUG("Got %u bytes, opcode = %u, lbn = %u\n", retlen, status, ntohs(pdu->tftp_pdu$w_lbn)); retlen -= 4; switch ( status ) { case TFTP_OPC$K_ERROR: status = ntohs(pdu->tftp_pdu$w_code); tftp__log( TFTP_ERROR, status, pdu->tftp_pdu$b_msg); { int errt [] = {2, SS$_NOSUCHFILE, SS$_NOPRIV, SS$_DEVICEFULL, SS$_PROTOCOL, SS$_NOSUCHTID, SS$_DUPFILENAME, SS$_NOSUCHUSER}; if ( status < 8 ) return errt[status]; return SS$_ABORT; } case TFTP_OPC$K_ACK: break; default: return SS$_ABORT; } /* ** Check a gottent LBN */ acklbn = ntohs(pdu->tftp_pdu$w_lbn); if ( acklbn ) { if ( lbn < acklbn ) { tftp__log(TFTP_ERRLBN,lbn,acklbn); return SS$_DATALOST; } else if ( acklbn < (lbn-1) ) continue; } /* ** Read a data block from the file */ if ( !(1 & (status = sys$read(rab))) && (status != RMS$_EOF) ) return status; else if ( status == RMS$_EOF ) rab->rab$w_usz = 0; /* ** Send DATAblock */ pdu->tftp_pdu$w_opcode = htons(TFTP_OPC$K_DATA); pdu->tftp_pdu$w_lbn = htons(lbn); retlen = (lbn == ebk)?ffb:rab->rab$w_usz; if ( !(1 & (status = net_write (chan,pdu,4 + retlen,remhost))) ) return status; DEBUG("Sent %u bytes, lbn = %u\n",retlen,lbn); *bytes += retlen; if ( lbn == ebk ) break; } return status; } /* **++ ** FUNCTIONAL DESCRIPTION: ** ** Parse a command line, extract a parameter set and dispatch to ** operation-specific routine. ** ** FORMAL PARAMETERS: ** ** None. ** ** RETURN VALUE: ** ** VMS condition code ** **-- */ int tftp_cmd_run (void) { unsigned status,tmode = TFTP__$K_OCTET,bytes = 0,ebk = 0,ffb = 0; int ipaddr; short chan = 0, port = TFTP__$K_PORT; char buf[256]; struct dsc$descriptor host,cmd,mode,fromfile,tofile; struct FAB fab; struct RAB rab; struct XABFHC fhc; struct { short proto; char type; char domain; } sck_parm = {INET$C_UDP,INET_PROTYP$C_DGRAM,INET$C_AF_INET}; iosb netiosb; struct sockaddr_in sock_loc_host = {INET$C_AF_INET,0,INET$C_INADDR_ANY,0}, sock_rem_host = {INET$C_AF_INET,0,INET$C_INADDR_ANY,0}; ile3 loc_host = {sizeof(struct sockaddr_in),0,&sock_loc_host}; struct hostent *hp; /* ** A TFTP command: GET/PUT */ if ( CLI$_PRESENT != cli$present (&p_cmd) ) return SS$_INSFARG; INIT_DDESC(cmd); if ( !(1 & (status = cli$get_value(&p_cmd,&cmd,NULL))) ) return status; /* ** HOST(s) */ if ( CLI$_PRESENT != cli$present (&p_host) ) return SS$_INSFARG; INIT_DDESC(host); if ( !(1 & (status = cli$get_value(&p_host,&host,&host.dsc$w_length))) ) return status; /* ** From File & To File */ if ( CLI$_PRESENT != cli$present (&p_fromfile) ) return SS$_INSFARG; INIT_DDESC(fromfile); if ( !(1 & (status = cli$get_value(&p_fromfile,&fromfile,NULL))) ) return status; if ( *fromfile.dsc$a_pointer == '"' ) { fromfile.dsc$a_pointer++; fromfile.dsc$w_length -= 2; } INIT_DDESC(tofile); if ( CLI$_PRESENT != cli$present (&p_tofile) ) { /* return SS$_INSFARG; */ tofile.dsc$w_length = fromfile.dsc$w_length; tofile.dsc$a_pointer = fromfile.dsc$a_pointer; } else { if ( !(1 & (status = cli$get_value(&p_tofile,&tofile,NULL))) ) return status; if ( *tofile.dsc$a_pointer == '"' ) { tofile.dsc$a_pointer++; tofile.dsc$w_length -= 2; } } /* ** MODE */ if ( CLI$_PRESENT == cli$present (&q_mode) ) { INIT_DDESC(mode); if ( !(1 & (status = cli$get_value(&q_mode,&mode,NULL))) ) return status; if ( (*mode.dsc$a_pointer == 'N') || (*mode.dsc$a_pointer == 'A') ) tmode = TFTP__$K_NETASCII; else tmode = TFTP__$K_OCTET; } /* ** /LOG & /DEBUG */ debug_flag = CLI$_PRESENT == cli$present (&q_debug); log_flag = CLI$_PRESENT == cli$present (&q_log); /* ** Initialize a network stuff ** Resolve a host name/ip to IP address */ strncpy(buf,host.dsc$a_pointer,host.dsc$w_length); buf[host.dsc$w_length] = '\0'; if ( -1 != (ipaddr = inet_addr(buf)) ) sock_rem_host.sin_addr.s_addr = ipaddr; else if ( hp = gethostbyname(buf) ) sock_rem_host.sin_addr.s_addr = ipaddr = *(int *)hp->h_addr; else return SS$_NOSUCHNODE; sock_rem_host.sin_port = htons(TFTP__$K_PORT); /* ** Initialize network stuff */ if ( !(1 & (status = sys$assign( &dsc_ucxdev,&chan,0,0))) ) { if ( status != SS$_NOSUCHDEV ) return status; if ( !(1 & (status = sys$assign( &dsc_tcpipdev,&chan,0,0))) ) return status; } /* ** Create a UDP device */ status = sys$qiow( 0, chan, IO$_SETMODE, &netiosb, 0, 0, &sck_parm, 0, &loc_host, 0, 0, 0); if ( !(status & 1) || !(netiosb.iosb$w_status & 1) ) { sys$dassgn (chan); return (1 & status)?netiosb.iosb$w_status:status; } /* ** Open/create file for using RMS Block I/O */ fab = cc$rms_fab; fhc = cc$rms_xabfhc; fab.fab$l_xab = &fhc; fab.fab$b_rfm = FAB$C_FIX; fab.fab$w_mrs = fhc.xab$w_lrl = 512; fab.fab$b_shr = FAB$M_NIL; fab.fab$v_dfw = fab.fab$v_sqo = 1; rab = cc$rms_rab; rab.rab$l_fab = &fab; rab.rab$v_bio = rab.rab$v_rah = rab.rab$v_wbh = 1; if ( *cmd.dsc$a_pointer == 'G' ) { fab.fab$b_fac = FAB$M_PUT | FAB$M_BIO; fab.fab$l_fna = tofile.dsc$a_pointer; fab.fab$b_fns = tofile.dsc$w_length; if ( tmode == TFTP__$K_NETASCII ) fab.fab$b_rfm = FAB$C_STM; if ( !(1 & (status = sys$create (&fab))) ) return status; } else { fab.fab$b_fac = FAB$M_GET | FAB$M_BIO; fab.fab$l_fna = fromfile.dsc$a_pointer; fab.fab$b_fns = fromfile.dsc$w_length; if ( !(1 & (status = sys$open (&fab))) ) return status; } if ( !(1 & (status = sys$connect(&rab))) ) return status; /* ** Now we ready to dispatch processing of entered command ** to a specifc routine. */ if ( *cmd.dsc$a_pointer == 'G' ) { if ( log_flag ) { tftp__log( TFTP_GETSTART, &fromfile, &host, &tofile, ((tmode == TFTP__$K_NETASCII) ? &amode_dsc : &bmode_dsc), &iotmo); } status = tftp_do_get( chan, ipaddr, &fromfile, &sock_rem_host, &rab, tmode, &bytes); if ( (1 & status) && log_flag ) tftp__log( TFTP_GETSTOP, &fromfile, bytes, &tofile, &host); else if ( !(1 & status) ) tftp__log(TFTP_GETERR,status); } else { if ( log_flag ) tftp__log( TFTP_PUTSTART, &fromfile, &tofile, &host, ((tmode == TFTP__$K_NETASCII) ? &amode_dsc : &bmode_dsc), &iotmo); status = tftp_do_put( chan, ipaddr, &tofile, &sock_rem_host, &rab, tmode, &bytes, fhc.xab$l_ebk, fhc.xab$w_ffb); if ( (1 & status) && log_flag ) tftp__log( TFTP_PUTSTOP, &fromfile, bytes, &tofile, &host); else if ( !(1 & status) ) tftp__log(TFTP_PUTERR,status); } /* ** Cleanup & return status; */ sys$close(&fab); sys$dassgn (chan); return status; } /* **++ ** FUNCTIONAL DESCRIPTION: ** ** A main procedure performs reading input, parsing and dispatch a ** command processing. ** ** FORMAL PARAMETERS: ** ** None ** ** RETURN VALUE: ** ** VMS condition code ** **-- */ int main (int argc,char **argv) { int status,flag = 0; char buf[256] = {"RUN "}; struct dsc$descriptor cmd_dsc; /* ** Check the presence of the comand line agruments */ INIT_SDESC(cmd_dsc,sizeof(buf)-4,buf+4); if ( !(1 & (status = lib$get_foreign( &cmd_dsc, 0, &cmd_dsc.dsc$w_length, &flag))) ) return status; /* ** No arguments - display usage info and exit */ if ( !cmd_dsc.dsc$w_length ) return lib$put_output(&usage); cmd_dsc.dsc$w_length += 4; cmd_dsc.dsc$a_pointer -= 4; if ( cmd_dsc.dsc$w_length ) if ( CLI$_NORMAL == (status = cli$dcl_parse( &cmd_dsc, &TFTP_CLD, 0, 0, 0)) ) status = cli$dispatch(); return status; }