📄 tftpd.c
字号:
if ( to < 10000UL || to > 255000000UL || *vend ) return 0; rexmtval = timeout = to; maxtimeout = rexmtval*TIMEOUT_LIMIT; sprintf(*ret = b_ret, "%lu", to); return(1);}/* * Parse RFC2347 style options */voiddo_opt(char *opt, char *val, char **ap){ struct options *po; char *ret; /* Global option-parsing variables initialization */ blksize_set = 0; if ( !*opt ) return; for (po = options; po->o_opt; po++) if (!strcasecmp(po->o_opt, opt)) { if (po->o_fnc(val, &ret)) { if (*ap + strlen(opt) + strlen(ret) + 2 >= ackbuf + sizeof(ackbuf)) { nak(EOPTNEG, "Insufficient space for options"); exit(0); } *ap = strrchr(strcpy(strrchr(strcpy(*ap, opt),'\0') + 1, ret),'\0') + 1; } else { nak(EOPTNEG, "Unsupported option(s) requested"); exit(0); } break; } return;}#ifdef WITH_REGEX/* * This is called by the remap engine when it encounters macros such * as \i. It should write the output in "output" if non-NULL, and * return the length of the output (generated or not). * * Return -1 on failure. */intrewrite_macros(char macro, char *output);intrewrite_macros(char macro, char *output){ char *p; switch (macro) { case 'i': p = inet_ntoa(from.sin_addr); if ( output ) strcpy(output, p); return strlen(p); case 'x': if ( output ) sprintf(output, "%08X", ntohl(from.sin_addr.s_addr)); return 8; default: return -1; }}/* * Modify the filename, if applicable. If it returns NULL, deny the access. */char *rewrite_access(char *filename, int mode, const char **msg){ if ( rewrite_rules ) { char *newname = rewrite_string(filename, rewrite_rules, mode != RRQ, rewrite_macros, msg); filename = newname; } return filename;}#elsechar *rewrite_access(char *filename, int mode, const char **msg){ (void)mode; /* Avoid warning */ (void)msg; return filename;}#endifFILE *file;/* * Validate file access. Since we * have no uid or gid, for now require * file to exist and be publicly * readable/writable, unless -p specified. * If we were invoked with arguments * from inetd then the file must also be * in one of the given directory prefixes. * Note also, full path name must be * given as we have no login directory. */intvalidate_access(char *filename, int mode, struct formats *pf, const char **errmsg){ struct stat stbuf; int i, len; int fd, wmode, rmode; char *cp; const char **dirp; char stdio_mode[3]; tsize_ok = 0; *errmsg = NULL; if (!secure) { if (*filename != '/') { *errmsg = "Only absolute filenames allowed"; return (EACCESS); } /* * prevent tricksters from getting around the directory * restrictions */ len = strlen(filename); for ( i = 1 ; i < len-3 ; i++ ) { cp = filename + i; if ( *cp == '.' && memcmp(cp-1, "/../", 4) == 0 ) { *errmsg = "Reverse path not allowed"; return(EACCESS); } } for (dirp = dirs; *dirp; dirp++) if (strncmp(filename, *dirp, strlen(*dirp)) == 0) break; if (*dirp==0 && dirp!=dirs) { *errmsg = "Forbidden directory"; return (EACCESS); } } /* * We use different a different permissions scheme if `cancreate' is * set. */ wmode = O_WRONLY | (cancreate ? O_CREAT : 0) | (unixperms ? O_TRUNC : 0) | (pf->f_convert ? O_TEXT : O_BINARY); rmode = O_RDONLY | (pf->f_convert ? O_TEXT : O_BINARY); fd = open(filename, mode == RRQ ? rmode : wmode, 0666); if (fd < 0) { switch (errno) { case ENOENT: case ENOTDIR: return ENOTFOUND; case ENOSPC: return ENOSPACE; case EEXIST: return EEXISTS; default: return errno+100; } } if ( fstat(fd, &stbuf) < 0 ) exit(EX_OSERR); /* This shouldn't happen */ if (mode == RRQ) { if ( !unixperms && (stbuf.st_mode & (S_IREAD >> 6)) == 0 ) { *errmsg = "File must have global read permissions"; return (EACCESS); } tsize = stbuf.st_size; /* We don't know the tsize if conversion is needed */ tsize_ok = !pf->f_convert; } else { if ( !unixperms ) { if ( (stbuf.st_mode & (S_IWRITE >> 6)) == 0 ) { *errmsg = "File must have global write permissions"; return (EACCESS); } /* We didn't get to truncate the file at open() time */#ifdef HAVE_FTRUNCATE if ( ftruncate(fd, (off_t)0) ) { *errmsg = "Cannot reset file size"; return(EACCESS); }#endif } tsize = 0; tsize_ok = 1; } stdio_mode[0] = (mode == RRQ) ? 'r':'w'; stdio_mode[1] = (pf->f_convert) ? 't':'b'; stdio_mode[2] = '\0'; file = fdopen(fd, stdio_mode); if (file == NULL) exit(EX_OSERR); /* Internal error */ return (0);}/* * Send the requested file. */voidtftp_sendfile(struct formats *pf, struct tftphdr *oap, int oacklen){ struct tftphdr *dp; struct tftphdr *ap; /* ack packet */ static u_short block = 1; /* Static to avoid longjmp funnies */ u_short ap_opcode, ap_block; unsigned long r_timeout; int size, n; if (oap) { timeout = rexmtval; (void)sigsetjmp(timeoutbuf,1); oack: r_timeout = timeout; if (send(peer, oap, oacklen, 0) != oacklen) { syslog(LOG_WARNING, "tftpd: oack: %m\n"); goto abort; } for ( ; ; ) { n = recv_time(peer, ackbuf, sizeof(ackbuf), 0, &r_timeout); if (n < 0) { syslog(LOG_WARNING, "tftpd: read: %m\n"); goto abort; } ap = (struct tftphdr *)ackbuf; ap_opcode = ntohs((u_short)ap->th_opcode); ap_block = ntohs((u_short)ap->th_block); if (ap_opcode == ERROR) { syslog(LOG_WARNING, "tftp: client does not accept options\n"); goto abort; } if (ap_opcode == ACK) { if (ap_block == 0) break; /* Resynchronize with the other side */ (void)synchnet(peer); goto oack; } } } dp = r_init(); do { size = readit(file, &dp, pf->f_convert); if (size < 0) { nak(errno + 100, NULL); goto abort; } dp->th_opcode = htons((u_short)DATA); dp->th_block = htons((u_short)block); timeout = rexmtval; (void) sigsetjmp(timeoutbuf,1); r_timeout = timeout; if (send(peer, dp, size + 4, 0) != size + 4) { syslog(LOG_WARNING, "tftpd: write: %m"); goto abort; } read_ahead(file, pf->f_convert); for ( ; ; ) { n = recv_time(peer, ackbuf, sizeof (ackbuf), 0, &r_timeout); if (n < 0) { syslog(LOG_WARNING, "tftpd: read(ack): %m"); goto abort; } ap = (struct tftphdr *)ackbuf; ap_opcode = ntohs((u_short)ap->th_opcode); ap_block = ntohs((u_short)ap->th_block); if (ap_opcode == ERROR) goto abort; if (ap_opcode == ACK) { if (ap_block == block) { break; } /* Re-synchronize with the other side */ (void) synchnet(peer); /* * RFC1129/RFC1350: We MUST NOT re-send the DATA * packet in response to an invalid ACK. Doing so * would cause the Sorcerer's Apprentice bug. */ } } block++; } while (size == segsize); abort: (void) fclose(file);}/* Bail out signal handler */voidjustquit(int sig){ (void)sig; /* Suppress unused warning */ exit(0);}/* * Receive a file. */voidtftp_recvfile(struct formats *pf, struct tftphdr *oap, int oacklen){ struct tftphdr *dp; int n, size; /* These are "static" to avoid longjmp funnies */ static struct tftphdr *ap; /* ack buffer */ static u_short block = 0; static int acksize; u_short dp_opcode, dp_block; unsigned long r_timeout; dp = w_init(); do { timeout = rexmtval; if (!block && oap) { ap = (struct tftphdr *)ackbuf; acksize = oacklen; } else { ap = (struct tftphdr *)ackbuf; ap->th_opcode = htons((u_short)ACK); ap->th_block = htons((u_short)block); acksize = 4; } block++; (void) sigsetjmp(timeoutbuf,1); send_ack: r_timeout = timeout; if (send(peer, ackbuf, acksize, 0) != acksize) { syslog(LOG_WARNING, "tftpd: write(ack): %m"); goto abort; } write_behind(file, pf->f_convert); for ( ; ; ) { n = recv_time(peer, dp, PKTSIZE, 0, &r_timeout); if (n < 0) { /* really? */ syslog(LOG_WARNING, "tftpd: read: %m"); goto abort; } dp_opcode = ntohs((u_short)dp->th_opcode); dp_block = ntohs((u_short)dp->th_block); if (dp_opcode == ERROR) goto abort; if (dp_opcode == DATA) { if (dp_block == block) { break; /* normal */ } /* Re-synchronize with the other side */ (void) synchnet(peer); if (dp_block == (block-1)) goto send_ack; /* rexmit */ } } /* size = write(file, dp->th_data, n - 4); */ size = writeit(file, &dp, n - 4, pf->f_convert); if (size != (n-4)) { /* ahem */ if (size < 0) nak(errno + 100, NULL); else nak(ENOSPACE, NULL); goto abort; } } while (size == segsize); write_behind(file, pf->f_convert); (void) fclose(file); /* close data file */ ap->th_opcode = htons((u_short)ACK); /* send the "final" ack */ ap->th_block = htons((u_short)(block)); (void) send(peer, ackbuf, 4, 0); timeout_quit = 1; /* just quit on timeout */ n = recv_time(peer, buf, sizeof (buf), 0, &timeout); /* normally times out and quits */ timeout_quit = 0; if (n >= 4 && /* if read some data */ dp_opcode == DATA && /* and got a data block */ block == dp_block) { /* then my last ack was lost */ (void) send(peer, ackbuf, 4, 0); /* resend final ack */ } abort: return;}static const char * const errmsgs[] ={ "Undefined error code", /* 0 - EUNDEF */ "File not found", /* 1 - ENOTFOUND */ "Access denied", /* 2 - EACCESS */ "Disk full or allocation exceeded", /* 3 - ENOSPACE */ "Illegal TFTP operation", /* 4 - EBADOP */ "Unknown transfer ID", /* 5 - EBADID */ "File already exists", /* 6 - EEXISTS */ "No such user", /* 7 - ENOUSER */ "Failure to negotiate RFC2347 options" /* 8 - EOPTNEG */};#define ERR_CNT (sizeof(errmsgs)/sizeof(const char *))/* * Send a nak packet (error message). * Error code passed in is one of the * standard TFTP codes, or a UNIX errno * offset by 100. */static voidnak(int error, const char *msg){ struct tftphdr *tp; int length; tp = (struct tftphdr *)buf; tp->th_opcode = htons((u_short)ERROR); if ( error >= 100 ) { /* This is a Unix errno+100 */ if ( !msg ) msg = strerror(error - 100); error = EUNDEF; } else { if ( (unsigned)error >= ERR_CNT ) error = EUNDEF; if ( !msg ) msg = errmsgs[error]; } tp->th_code = htons((u_short)error); length = strlen(msg)+1; memcpy(tp->th_msg, msg, length); length += 4; /* Add space for header */ if ( verbosity >= 2 ) { syslog(LOG_INFO, "sending NAK (%d, %s) to %s", error, tp->th_msg, inet_ntoa(from.sin_addr)); } if (send(peer, buf, length, 0) != length) syslog(LOG_WARNING, "nak: %m");}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -