# /* conn.c */ /* EMACS_MODES: c !fill */ /* This file contains the routines which perform the network transfers * for the tftp protocol. */ #include #include #include #include "tftp.h" #include "conn.h" struct conn *cn_rq (dir, fhost, file, mode, c_mode) /* Open up the connection, make a request packet, and send the * packet out on it. Allocate space for the connection control * block and fill it in. Allocate another packet for data and, * on writes, another to hold received packets. Don't wait * for connection ack; it will be waited for in cn_rcv or cn_wrt. * Return pointer to the connection control block, or NULL on error. * * Arguments: */ int dir; /* connection direction */ in_name fhost; /* foreign host */ char *file; /* foreign file name */ int mode; /* transfer mode */ char *c_mode; /* transfer mode string */ { caddr_t ppkt; /* current packet */ struct ip *pip; /* internet packet header */ register struct udp *pup; /* udp packet header */ register struct tftp *ptftp; /* tftp packet header */ register char *pdata; /* packet data */ int len; /* packet length */ register struct conn *cn; /* connection block */ if ((cn = (struct conn *)calloc (1, sizeof (struct conn))) == NULL) { cn_log ("Unable to alloc conn. block\n", 0, 0); return (NULL); } cn->type = USER; /* user mode transfer */ cn->synced = FALSE; /* conn. unsynchronized yet */ cn->file = strsave (file); /* alloc space to save filename */ cn->dir = dir; cn->mode = mode; cn->c_mode = strsave (c_mode); /* and mode string */ cn->fhost = fhost; cn->locsock = udp_sock(); cn->forsock = TFTPSOCK; cn->timeout = ICPTIMEOUT; if ((cn->netfd = udp_open (0L, 0, cn->locsock)) < 0) { cn_log ("Unable to open connection\n", 0, 0); cfree (cn); return (NULL); } if ((cn->last_sent = udp_alloc (INETLEN, 0)) == NULL) { cn_log ("Couldn't alloc packet\n", 0, 0); cn_close (cn); return (NULL); } pip = in_head(cn->last_sent); /* get all the headers */ pup = udp_head(pip); ptftp = (struct tftp *)udp_data(pup); pip->ip_id = 0; /* first transmission */ pdata = &ptftp->fp_data.f_rfc; /* build rfc */ ptftp->fp_opcode = dir; strcpy (pdata, file); len = strlen (pdata) + 1; pdata += len; strcpy (pdata, c_mode); len += strlen (pdata) + 1; len += sizeof (ptftp->fp_opcode); #ifndef BIGINDIAN cn_swab (ptftp, OUTPKT); #endif cn_send (cn, cn->last_sent, len); /* send it out */ cn->block_num = (dir == WRITE ? 0 : 1); /* write bno is smaller */ cn->retrans = 0; if ((cn->cur_pkt = udp_alloc (INETLEN, 0)) == NULL) { cn_log ("Couldn't alloc packet\n", 0, 0); cn_close (cn); return (FALSE); } if (dir == READ) cn->last_rcv = cn->cur_pkt; /* hack optimization */ else if ((cn->last_rcv = udp_alloc (INETLEN, 0)) == NULL) { cn_log ("Couldn't alloc packet\n", 0, 0); cn_close (cn); return (FALSE); } return (cn); } #define SRVR_TIME 60 /* sleep time per read */ struct conn *cn_lstn () /* Listen for a connection request on the TFTP ICP socket. When a * valid request is received, fork a child process. The child * forks again to perform the actual transfer, and the parent, * after waiting for the child to exit, goes back to listening. * The grandchile closes the ICP net connection, allocates a local port, and * opens a new net connection on that port. It then fills in a * connection block (allocated in the parent) and returns it to * the caller to do the transfer. Note that it does not send the * response (ack or data) to the initial connection request; this * must be done by the caller. In the parent this routine never * returns. */ { register struct conn *cn; /* connection control block */ static int intsoff = 0; /* to turn net interrupts off */ static int intson = 1; /* to turn net interrupts on */ register int pid; /* process id */ int len; /* packet length */ extern char cmd_intrpt; /* command interrupt flag */ if ((cn = (struct conn *)calloc (1, sizeof (struct conn))) == NULL) { cn_log ("Unable to alloc conn block\n", 0, 0); return (NULL); } /* Fill in conn block as much as possible now */ cn->type = SERVER; /* server mode transfer */ cn->synced = FALSE; cn->timeout = TIMEOUT; cn->locsock = TFTPSOCK; /* Now open the server connection, if possible */ if ((cn->netfd = udp_open (0L, 0, TFTPSOCK)) < 0) { cn_log ("Server port in use!\n", 0, 0); cfree (cn); return (NULL); } if ((cn->last_rcv = udp_alloc (INETLEN, 0)) == NULL || (cn->last_sent = udp_alloc (INETLEN, 0)) == NULL || (cn->cur_pkt = udp_alloc (INETLEN, 0)) == NULL) { cn_log ("Couldn't alloc packet\n", 0, 0); cn_close (cn); return (NULL); } /* Now the big loop. Parent waits for ICP request, forks, and waits */ for (;;) { /* parent server loops forever */ while ((len = udp_bread (cn->netfd, cn->last_rcv, INETLEN, SRVR_TIME)) == 0) { /* loop 'til packet arrives */ if (tst_and_clr (&cmd_intrpt)) /* command waiting? */ do_cmd (cn); } /* Got a packet. Fork; child forks again and exits. Parent waits for child */ ioctl (cn->netfd, NIOCSETL, &intsoff); /* turn off net intr */ if ((pid = fork ()) < 0) { /* fork error; punt */ cn_log ("Unable to fork server\n", 0, 0); continue; /* back to loop */ } else if (pid > 0) { /* parent; turn net intrs on... */ ioctl (cn->netfd, NIOCSETL, &intson); while (wait (0) != pid) ; /* wait for child to exit */ continue; /* back to loop */ } /* Child; fork again and exit. This makes grandchild an orphan */ if ((pid = fork ()) < 0) { /* fork error - punt */ cn_log ("Unable to fork server\n", 0, 0); exit (1); } else if (pid > 0) { /* child; exit back to parent */ exit (0); } /* Grandchild; finally do the transfer */ udp_close (cn->netfd); cn->locsock = udp_sock (); /* get a unique socket */ if ((cn->netfd = udp_open (0L, 0, cn->locsock)) < 0) { cn_log ("Unable to open connection\n", 0, 0); return (NULL); /* punt... */ } if (!cn_parse (cn, cn->last_rcv)) /* good request? */ return (NULL); /* no, report lossage */ else return (cn); /* yes, give conn. block to user */ } } struct tftp *cn_rcv (cn) /* Receive a tftp packet into the packet buffer pointed to by cn->cur_pkt. * The packet to be received must be a packet of block number cn->block_num. * Returns a pointer to the tftp part of received packet. Also performs * ack sending and retransmission. * * Arguments: */ register struct conn *cn; /* connection block */ { register struct tftp *ptftp; /* tftp header */ if ((ptftp = cn_wait (cn, DATA)) == NULL) return (NULL); cn->cur_pkt = cn->last_rcvd; /* hack optimization */ cn->cur_len = cn->rcv_len; cn_ack (cn); return (ptftp); } cn_wrt (cn, len) /* Write the data packet contained in cn->cur_pkt, with data length len, * to the net. Wait first for an ack for the previous packet to arrive, * retransmitting it as needed. Then fill in the net headers, etc. and * send the packet out. Return TRUE if the packet is sent successfully, * or FALSE if a timeout or error occurs. * * Arguments: */ register struct conn *cn; /* connection */ int len; /* data length of packet */ { register struct tftp *ptftp; /* tftp header */ register struct ip *pip; /* ip hdr */ caddr_t temp; /* temp for swap */ pip = in_head (cn->cur_pkt); ptftp = (struct tftp *)(udp_data (udp_head (pip))); pip->ip_id = 0; ptftp->fp_opcode = DATA; ptftp->fp_data.f_data.f_blkno = cn->block_num + 1; #ifndef BIGINDIAN cn_swab (ptftp, OUTPKT); #endif len += 4; if (cn->block_num != 0 || cn->type != SERVER) { if (cn_wait (cn, DACK) == NULL) return (FALSE); } cn->block_num++; /* next expected block number */ cn->retrans = 0; temp = cn->last_sent; /* next write packet buffer */ cn_send (cn, cn->cur_pkt, len); /* sets up last_sent... */ cn->cur_pkt = temp; /* for next mkwrt */ return (TRUE); } struct tftp *cn_wait (cn, opcode) /* Wait for a valid tftp packet of the specified type to arrive on the * specified tftp connection, retransmitting the previous packet as needed up * to the timeout period. When a packet comes in, check it out. * Return a pointer to the received packet or NULL if error or timeout. * * Arguments: */ register struct conn *cn; /* connection */ short opcode; /* expected pkt type (DATA or DACK) */ { long now; /* current time */ long tmo; /* timeout time */ struct ip *pip; /* internet header */ struct udp *pup; /* udp header */ register struct tftp *ptftp; /* tftp header */ int len; /* packet length */ for (;;) { time (&now); tmo = cn->nxt_retrans - now; if ((len = udp_bread (cn->netfd, cn->last_rcv, INETLEN, (int)tmo)) == 0) { if (!cn_retrans (cn, TMO)) /* timeout */ break; continue; } /* Got a packet; check it out */ pip = in_head(cn->last_rcv); pup = udp_head(pip); ptftp = (struct tftp *)udp_data(pup); if (cn->intrace) in_logpkt (cn->last_rcv, pup->ud_len, INPKT); #ifndef BIGINDIAN cn_swab (ptftp, INPKT); #endif /* First, check the packet length for validity */ cn->rcv_len = pup->ud_len - sizeof(struct udp); if (len < pup->ud_len || cn->rcv_len < 2) { cn_log ("Received bad packet length %d\n", TEUNDEF, cn->rcv_len); cn_err (cn, pip->ip_src, pup->ud_srcp, TEUNDEF, "bad tftp packet length"); return (NULL); } /* Next check the foreign address */ if (pip->ip_src != cn->fhost) { cn_inform ("Received packet from bad host %X\n", pip->ip_src); cn_err (cn, pip->ip_src, pup->ud_srcp, TETID, "Sorry, wasn't talking to you!"); continue; } /* Next, the foreign socket. If still unsynchronized, use his socket */ if (!(cn->synced) && ((ptftp->fp_opcode == opcode && ptftp->fp_data.f_data.f_blkno == cn->block_num) || (ptftp->fp_opcode == ERROR))) { cn->synced = TRUE; cn->forsock = pup->ud_srcp; cn->timeout = TIMEOUT; /* normal data timeout */ } else if (pup->ud_srcp != cn->forsock) { /* bad port */ cn_inform ("Received packet on bad foreign port %o\n", pup->ud_srcp); cn_err (cn, pip->ip_src, pup->ud_srcp, TETID, "unexpected socket number"); continue; } /* Now check out the tftp opcode */ if (ptftp->fp_opcode == opcode) { if (ptftp->fp_data.f_data.f_blkno == cn->block_num) { return (ptftp); } else if (ptftp->fp_data.f_data.f_blkno == (cn->block_num - 1) && opcode == DATA) { if (!cn_retrans (cn, DUP)) /* timeout */ break; } else if (ptftp->fp_data.f_data.f_blkno > cn->block_num) { cn_log ("Received packet with unexpected block no. %d\n", TETFTP, ptftp->fp_data.f_data.f_blkno); cn_err (cn, cn->fhost, cn->forsock, TETFTP, "block num > expected"); cn_close (cn); return (NULL); } else /* old duplicate; ignore */ continue; } else if (ptftp->fp_opcode == ERROR) { cn_log ("Error packet received: %s\n", ptftp->fp_data.f_error.f_errcode, ptftp->fp_data.f_error.f_errmsg); cn_close (cn); return (NULL); } else { /* unexpected TFTP opcode */ cn_log ("Bad opcode %d received\n", TETFTP, ptftp->fp_opcode); cn_err (cn, cn->fhost, cn->forsock, TETFTP, "bad opcode received"); cn_close (cn); return (NULL); } } cn_log ("Long timeout occurred\n", TEUNDEF, 0); cn_err (cn, cn->fhost, cn->forsock, TEUNDEF, "timeout on receive"); cn_close (cn); return (NULL); } cn_ack(cn) /* Generate and send an ack packet for the specified connection. Also * update the block number. Use the packet stored in cn->last_sent to build * the ack in. */ register struct conn *cn; /* the connection being acked */ { struct ip *pip; struct udp *pup; register struct tftp *ptftp; register int len; pip = in_head(cn->last_sent); pup = udp_head(pip); ptftp = (struct tftp *)udp_data(pup); len = 4; pip->ip_id = 0; ptftp->fp_opcode = DACK; ptftp->fp_data.f_data.f_blkno = cn->block_num; #ifndef BIGINDIAN cn_swab (ptftp, OUTPKT); #endif cn_send(cn, cn->last_sent, len); cn->retrans = 0; cn->block_num++; } cn_err (cn, fhost, fsock, ecode, emsg) /* Make an error packet to send to the specified foreign host and socket * with the specified error code and error message. This routine is * used to send error messages in response to packets received from * unexpected foreign hosts or tid's as well as those received for the * current connection. It allocates a packet specially * for the error message because such error messages will not be * retransmitted. Send it out on the connection. * * Arguments: */ register struct conn *cn; /* connection */ in_name fhost; /* foreign host */ unshort fsock; /* foreign socket */ int ecode; /* error code */ char *emsg; /* char. string error message */ { caddr_t ppkt; /* packet */ struct ip *pip; /* ip header */ struct udp *pup; /* udp header */ register struct tftp *ptftp; /* tftp packet */ register int len; if ((ppkt = udp_alloc (DATALEN, 0)) == NULL) /* punt */ return; pip = in_head(ppkt); pup = udp_head(pip); ptftp = (struct tftp *)udp_data(pup); len = 4; ptftp->fp_opcode = ERROR; ptftp->fp_data.f_error.f_errcode = ecode; strcpy(ptftp->fp_data.f_error.f_errmsg, emsg); len += (strlen(emsg) + 1); #ifndef BIGINDIAN cn_swab (ptftp, OUTPKT); #endif pip->ip_dest = fhost; /* fill ip and upd hdrs. */ pip->ip_id = 0; /* kernel fills id */ pup->ud_srcp = cn->locsock; pup->ud_dstp = fsock; if (udp_write (cn->netfd, ppkt, len) <= 0) cn_inform ("Net write error, errno = %d\n", errno); else if (cn->outtrace) in_logpkt (ppkt, len + sizeof (struct udp), OUTPKT); udp_free (ppkt); } cn_send (cn, ppkt, len) /* Send the specified packet, with the specified tftp length (length - * udp and ip headers) out on the current connection. Fill in the * needed parts of the udp and ip headers, byte-swap the tftp packet, * etc; then write it out. Then set up for retransmit. * * Arguments: */ register struct conn *cn; /* connection */ caddr_t ppkt; /* pointer to packet to send */ int len; /* tftp length of packet */ { register struct ip *pip; /* internet packet hdr */ register struct udp *pup; /* udp packet header */ long now; /* current time */ pip = in_head(ppkt); pup = udp_head(pip); pip->ip_dest = cn->fhost; /* fill ip and upd hdrs. */ pup->ud_srcp = cn->locsock; pup->ud_dstp = cn->forsock; if (udp_write (cn->netfd, ppkt, len) <= 0) cn_inform ("Net write error, errno = %d\n", errno); else if (cn->outtrace) in_logpkt (ppkt, len + sizeof (struct udp), OUTPKT); cn->last_sent = ppkt; cn->last_len = len; time (&now); cn->nxt_retrans = now + cn->timeout; } struct tftp *cn_mkwrt (cn) /* Return a pointer to the next tftp packet suitable for filling for * writes on the connection. * * Arguments: */ register struct conn *cn; { return ((struct tftp *)(udp_data (udp_head (in_head (cn->cur_pkt))))); } cn_rcvf (cn) /* Finish off a receive connection. Just close the connection, * return. * * Arguments: */ struct conn *cn; /* connection */ { cn_close (cn); } cn_wrtf (cn) /* Finish off a write connection. Wait for the last ack, then * close the connection and return. * * Arguments: */ struct conn *cn; /* connection */ { register struct tftp *ptftp; /* received packet */ if ((ptftp = cn_wait (cn, DACK)) == NULL) return (FALSE);; cn_close (cn); return (TRUE); } cn_retrans (cn, dup) /* Retransmit the last-sent packet, up to MAX_RETRANS times. Exponentially * back off the timeout time up to a maximum of MAX_TIMEOUT. This algorithm * may be replaced by a better one in which the timeout time is set from * the maximum round-trip time to date. * The second argument indicates whether the retransmission is due to the * arrival of a duplicate packet or a timeout. If a duplicate, don't include * this retransmission in the maximum retransmission count. */ register struct conn *cn; /* connection */ int dup; /* retransmit due to duplicate? */ { if ((dup != DUP) && (++cn->retrans >= MAX_RETRANS)) return (FALSE); cn->timeout <<= 1; if (cn->timeout > MAX_TIMEOUT) cn->timeout = MAX_TIMEOUT; cn_send (cn, cn->last_sent, cn->last_len); return (TRUE); } cn_close (cn) /* Close the specified connection. Close the net connection, deallocate * all the packets and the connection control block, etc. * * Arguments: */ register struct conn *cn; /* connection */ { udp_close (cn->netfd); if (cn->cur_pkt != NULL) udp_free (cn->cur_pkt); if (cn->last_sent != NULL) udp_free (cn->last_sent); if (cn->last_rcv != NULL && cn->last_rcv != cn->cur_pkt) udp_free (cn->last_rcv); if (cn->file != NULL) cfree (cn->file); if (cn->c_mode != NULL) cfree (cn->c_mode); cfree (cn); } cn_parse (cn, pkt) /* Parse the request packet pkt, to determine the request type, local * file name, and transfer mode, plus udp and ip information. If the * request packet is invalid respond with an appropriate error. Set * up the connection block according to the request packet. Return * TRUE if the request is valid, and FALSE otherwise. * * Arguments: */ register struct conn *cn; /* connection block */ caddr_t pkt; /* received packet */ { struct ip *pip; /* internet header */ register struct udp *pup; /* udp header */ register struct tftp *ptftp; /* tftp header */ char *pdata; /* tftp data pointer */ pip = in_head (pkt); /* get all the headers */ pup = udp_head (pip); ptftp = udp_data (pup); if (cn->intrace) in_logpkt (pkt, pup->ud_len, INPKT); #ifndef BIGINDIAN cn_swab (ptftp, INPKT); #endif if (ptftp->fp_opcode != RRQ && ptftp->fp_opcode != WRQ) { cn_log ("Bad ICP opcode %d received\n", TETFTP, ptftp->fp_opcode); cn_err (cn, pip->ip_src, pup->ud_srcp, TETFTP, "bad opcode received"); cn_close (cn); return (FALSE); } cn->fhost = pip->ip_src; /* set up conn block */ cn->forsock = pup->ud_srcp; cn->dir = ptftp->fp_opcode; cn->synced = TRUE; cn->block_num = 0; /* write blkno will be incremented */ pdata = &ptftp->fp_data.f_rfc; /* now parse up req. pkt */ cn->file = strsave (pdata); /* save off filename */ pdata += strlen (pdata) + 1; cn->c_mode = strsave (pdata); /* and mode */ if (lwccmp (cn->c_mode, "netascii") == 0) cn->mode = NETASCII; else if (lwccmp (cn->c_mode, "image") == 0) cn->mode = IMAGE; else if (lwccmp (cn->c_mode, "octet") == 0) /* gotta support both */ cn->mode = IMAGE; else if (lwccmp (cn->c_mode, "mail") == 0) cn->mode = MAIL; else { cn_log("Bad transfer mode %s specified\n", TETFTP, cn->c_mode); cn_err (cn, pip->ip_src, pup->ud_srcp, TETFTP, "bad transfer mode specified"); cn_close (cn); return (FALSE); } return (TRUE); } char *strsave (str) /* Save the string pointed to by str in a safe (allocated) place. * * Arguments: */ register char *str; { register char *save; save = calloc (1, strlen (str) + 1); strcpy (save, str); return (save); } #define tolower(c) ((c) >= 'A' && (c) <= 'Z' ? (c) + 'a' - 'A' : (c)) lwccmp (s1, s2) /* Compare two strings ignoring case considerations. * Returns: * <0 if s1 < s2 * =0 if s1 = s2 * >0 if s1 > s2 * * Arguments: */ register char *s1; /* first string */ register char *s2; /* second string */ { while (*s1 == *s2 || (tolower (*s1) == tolower (*s2))) { if (*s1++ == '\0') return (0); else s2++; } return (tolower (*s1) - tolower (*s2)); } #ifndef BIGINDIAN cn_swab (ptftp, pktdir) /* Swap the bytes in integer fields of a tftp packet. The only such * fields are the opcode, the block number in DATA and DACK packets, * and the error code field in ERROR packets. * * Arguments: */ register struct tftp *ptftp; /* ptr. to tftp packet */ register int pktdir; /* packet direction for byteswap */ { register int opcode; opcode = ptftp->fp_opcode; ptftp->fp_opcode = swab (ptftp->fp_opcode); if (pktdir == INPKT) opcode = ptftp->fp_opcode; switch (opcode) { case DATA: case DACK: ptftp->fp_data.f_data.f_blkno = swab (ptftp->fp_data.f_data.f_blkno); break; case ERROR: ptftp->fp_data.f_error.f_errcode = swab (ptftp->fp_data.f_error.f_errcode); break; default: break; } } #endif