diff --git a/CHANGES b/CHANGES index b39b502..b29eaeb 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,11 @@ $Id$ +Changes in 0.32: + Better error messages; including the capability to send a + custom error message to the client when hitting an "a" rule in + a remapping table. + + Changes in 0.31: Put in a check to make sure xinetd (in particular) doesn't pass us an IPv6 socket. diff --git a/tftp/tftp.c b/tftp/tftp.c index b35473e..b6c1f5f 100644 --- a/tftp/tftp.c +++ b/tftp/tftp.c @@ -65,7 +65,7 @@ int timeout; sigjmp_buf toplevel; sigjmp_buf timeoutbuf; -static void nak(int); +static void nak(int, const char *); static int makerequest(int, const char *, struct tftphdr *, const char *); static void printstats(const char *, unsigned long); static void startclock(void); @@ -107,7 +107,7 @@ tftp_sendfile(int fd, const char *name, const char *mode) /* size = read(fd, dp->th_data, SEGSIZE); */ size = readit(file, &dp, convert); if (size < 0) { - nak(errno + 100); + nak(errno + 100, NULL); break; } dp->th_opcode = htons((u_short)DATA); @@ -274,7 +274,7 @@ send_ack: /* size = write(fd, dp->th_data, n - 4); */ size = writeit(file, &dp, n - 4, convert); if (size < 0) { - nak(errno + 100); + nak(errno + 100, NULL); break; } amount += size; @@ -331,29 +331,37 @@ struct errmsg { * offset by 100. */ static void -nak(int error) +nak(int error, const char *msg) { - struct errmsg *pe; - struct tftphdr *tp; - int length; - - tp = (struct tftphdr *)ackbuf; - tp->th_opcode = htons((u_short)ERROR); - tp->th_code = htons((u_short)error); - for (pe = errmsgs; pe->e_code >= 0; pe++) - if (pe->e_code == error) - break; - if (pe->e_code < 0) { - pe->e_msg = strerror(error - 100); - tp->th_code = EUNDEF; + struct errmsg *pe; + struct tftphdr *tp; + int length; + + tp = (struct tftphdr *)ackbuf; + tp->th_opcode = htons((u_short)ERROR); + tp->th_code = htons((u_short)error); + if ( !msg ) { + if ( error >= 100 ) { + msg = strerror(error - 100); + tp->th_code = EUNDEF; + } else { + for (pe = errmsgs; pe->e_code >= 0; pe++) { + if (pe->e_code == error) { + msg = pe->e_msg; + break; } - strcpy(tp->th_msg, pe->e_msg); - length = strlen(pe->e_msg) + 4; - if (trace) - tpacket("sent", tp, length); - if (sendto(f, ackbuf, length, 0, (struct sockaddr *)&peeraddr, - sizeof(peeraddr)) != length) - perror("nak"); + } + } + } + length = strlen(msg)+1; + memcpy(tp->th_msg, msg, length); + length += 4; /* Add space for header */ + + if (trace) + tpacket("sent", tp, length); + if (sendto(f, ackbuf, length, 0, (struct sockaddr *)&peeraddr, + sizeof(peeraddr)) != length) + perror("nak"); } static void diff --git a/tftpd/remap.c b/tftpd/remap.c index 86081f3..0faa7b8 100644 --- a/tftpd/remap.c +++ b/tftpd/remap.c @@ -1,7 +1,7 @@ /* $Id$ */ /* ----------------------------------------------------------------------- * * - * Copyright 2001 H. Peter Anvin - All Rights Reserved + * Copyright 2001-2002 H. Peter Anvin - All Rights Reserved * * This program is free software available under the same license * as the "OpenBSD" operating system, distributed at @@ -104,8 +104,11 @@ static int genmatchstring(char *string, const char *pattern, const char *input, return len; } -/* Extract a string terminated by non-escaped whitespace; ignore leading whitespace */ -/* Consider an unescaped # to be a comment marker, functionally \n */ +/* + * Extract a string terminated by non-escaped whitespace; ignoring + * leading whitespace. Consider an unescaped # to be a comment marker, + * functionally \n. + */ static int readescstring(char *buf, char **str) { char *p = *str; @@ -268,7 +271,8 @@ void freerules(struct rule *r) /* Execute a rule set on a string; returns a malloc'd new string. */ char *rewrite_string(const char *input, const struct rule *rules, - int is_put, match_pattern_callback macrosub) + int is_put, match_pattern_callback macrosub, + const char **errmsg) { char *current = tfstrdup(input); char *newstr; @@ -278,6 +282,9 @@ char *rewrite_string(const char *input, const struct rule *rules, int was_match = 0; int deadman = DEADMAN_MAX_STEPS; + /* Default error */ + *errmsg = "Remap table failure"; + if ( verbosity >= 3 ) { syslog(LOG_INFO, "remap: input: %s", current); } @@ -305,6 +312,17 @@ char *rewrite_string(const char *input, const struct rule *rules, syslog(LOG_INFO, "remap: rule %d: abort: %s", ruleptr->nrule, current); } + if ( ruleptr->pattern[0] ) { + /* Custom error message */ + len = genmatchstring(NULL, ruleptr->pattern, current, + pmatch, macrosub); + newstr = tfmalloc(len+1); + genmatchstring(newstr, ruleptr->pattern, current, + pmatch, macrosub); + *errmsg = newstr; + } else { + *errmsg = NULL; + } free(current); return(NULL); } diff --git a/tftpd/remap.h b/tftpd/remap.h index 123b7d7..5315074 100644 --- a/tftpd/remap.h +++ b/tftpd/remap.h @@ -37,7 +37,7 @@ void freerules(struct rule *); /* Execute a rule set on a string; returns a malloc'd new string. */ char *rewrite_string(const char *, const struct rule *, int, - match_pattern_callback); + match_pattern_callback, const char **); #endif /* WITH_REGEX */ #endif /* TFTPD_REMAP_H */ diff --git a/tftpd/tftpd.c b/tftpd/tftpd.c index 2865f35..7682af5 100644 --- a/tftpd/tftpd.c +++ b/tftpd/tftpd.c @@ -114,7 +114,7 @@ static struct rule *rewrite_rules = NULL; #endif int tftp(struct tftphdr *, int); -void nak(int); +static void nak(int, const char *); void timer(int); void justquit(int); void do_opt(char *, char *, char **); @@ -683,14 +683,14 @@ main(int argc, char **argv) exit(0); } -char *rewrite_access(char *, int); +char *rewrite_access(char *, int, const char **); int validate_access(char *, int, struct formats *); void tftp_sendfile(struct formats *, struct tftphdr *, int); void tftp_recvfile(struct formats *, struct tftphdr *, int); struct formats { const char *f_mode; - char *(*f_rewrite)(char *, int); + char *(*f_rewrite)(char *, int, const char **); int (*f_validate)(char *, int, struct formats *); void (*f_send)(struct formats *, struct tftphdr *, int); void (*f_recv)(struct formats *, struct tftphdr *, int); @@ -712,6 +712,7 @@ tftp(struct tftphdr *tp, int size) struct formats *pf = NULL; char *origfilename; char *filename, *mode = NULL; + const char *maperrmsg; char *val = NULL, *opt = NULL; char *ap = ackbuf + 2; @@ -727,7 +728,7 @@ tftp(struct tftphdr *tp, int size) } while (cp < buf + size && *cp); if ( *cp ) { - nak(EBADOP); /* Corrupt packet - no final NULL */ + nak(EBADOP, "Request not null-terminated"); exit(0); } @@ -742,11 +743,12 @@ tftp(struct tftphdr *tp, int size) break; } if (!pf->f_mode) { - nak(EBADOP); + nak(EBADOP, "Unknown mode"); exit(0); } - if ( !(filename = (*pf->f_rewrite)(origfilename, tp->th_opcode)) ) { - nak(EACCESS); /* File denied by mapping rule */ + if ( !(filename = + (*pf->f_rewrite)(origfilename, tp->th_opcode, &maperrmsg)) ) { + nak(EACCESS, maperrmsg); /* File denied by mapping rule */ exit(0); } if ( verbosity >= 1 ) { @@ -761,7 +763,7 @@ tftp(struct tftphdr *tp, int size) } ecode = (*pf->f_validate)(filename, tp->th_opcode, pf); if (ecode) { - nak(ecode); + nak(ecode, NULL); exit(0); } opt = ++cp; @@ -774,7 +776,7 @@ tftp(struct tftphdr *tp, int size) } if (!pf) { - nak(EBADOP); + nak(EBADOP, "Missing mode"); exit(0); } @@ -946,13 +948,13 @@ do_opt(char *opt, char *val, char **ap) if (po->o_fnc(val, &ret)) { if (*ap + strlen(opt) + strlen(ret) + 2 >= ackbuf + sizeof(ackbuf)) { - nak(ENOSPACE); /* EOPTNEG? */ + nak(EOPTNEG, "Insufficient space for options"); exit(0); } *ap = strrchr(strcpy(strrchr(strcpy(*ap, opt),'\0') + 1, ret),'\0') + 1; } else { - nak(EOPTNEG); + nak(EOPTNEG, "Unsupported option(s) requested"); exit(0); } break; @@ -999,11 +1001,11 @@ rewrite_macros(char macro, char *output) * Modify the filename, if applicable. If it returns NULL, deny the access. */ char * -rewrite_access(char *filename, int mode) +rewrite_access(char *filename, int mode, const char **msg) { if ( rewrite_rules ) { - char *newname = rewrite_string(filename, rewrite_rules, - mode != RRQ, rewrite_macros); + char *newname = rewrite_string(filename, rewrite_rules, mode != RRQ, + rewrite_macros, msg); filename = newname; } return filename; @@ -1011,9 +1013,10 @@ rewrite_access(char *filename, int mode) #else char * -rewrite_access(char *filename, int mode) +rewrite_access(char *filename, int mode, const char **msg) { (void)mode; /* Avoid warning */ + (void)msg; return filename; } #endif @@ -1172,7 +1175,7 @@ tftp_sendfile(struct formats *pf, struct tftphdr *oap, int oacklen) do { size = readit(file, &dp, pf->f_convert); if (size < 0) { - nak(errno + 100); + nak(errno + 100, NULL); goto abort; } dp->th_opcode = htons((u_short)DATA); @@ -1283,8 +1286,8 @@ tftp_recvfile(struct formats *pf, struct tftphdr *oap, int oacklen) /* 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); - else nak(ENOSPACE); + if (size < 0) nak(errno + 100, NULL); + else nak(ENOSPACE, NULL); goto abort; } } while (size == segsize); @@ -1308,21 +1311,19 @@ tftp_recvfile(struct formats *pf, struct tftphdr *oap, int oacklen) return; } -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" }, - { EOPTNEG, "Failure to negotiate RFC2347 options" }, - { -1, 0 } +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). @@ -1330,27 +1331,29 @@ struct errmsg { * standard TFTP codes, or a UNIX errno * offset by 100. */ -void -nak(int error) +static void +nak(int error, const char *msg) { struct tftphdr *tp; int length; - struct errmsg *pe; tp = (struct tftphdr *)buf; tp->th_opcode = htons((u_short)ERROR); tp->th_code = htons((u_short)error); - for (pe = errmsgs; pe->e_code >= 0; pe++) - if (pe->e_code == error) - break; - if (pe->e_code < 0) { - pe->e_msg = strerror(error - 100); - tp->th_code = EUNDEF; /* set 'undef' errorcode */ + if ( !msg ) { + if ( error >= 100 ) { + /* This is a Unix errno+100 */ + msg = strerror(error - 100); + tp->th_code = EUNDEF; + } else { + if ( (unsigned)error >= ERR_CNT ) + tp->th_code = error = EUNDEF; + msg = errmsgs[error]; + } } - strcpy(tp->th_msg, pe->e_msg); - length = strlen(pe->e_msg); - tp->th_msg[length] = '\0'; - length += 5; + 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",