Commit | Line | Data |
---|---|---|
8c5eec2f DF |
1 | /* |
2 | * Copyright (c) 1983 Regents of the University of California. | |
3 | * All rights reserved. The Berkeley software License Agreement | |
4 | * specifies the terms and conditions for redistribution. | |
5 | */ | |
6 | ||
7 | #ifndef lint | |
8 | char copyright[] = | |
9 | "@(#) Copyright (c) 1983 Regents of the University of California.\n\ | |
10 | All rights reserved.\n"; | |
11 | #endif not lint | |
12 | ||
ce4fd43b | 13 | #ifndef lint |
8c5eec2f DF |
14 | static char sccsid[] = "@(#)tftpd.c 5.1 (Berkeley) %G%"; |
15 | #endif not lint | |
f8675e8e SL |
16 | |
17 | /* | |
18 | * Trivial file transfer protocol server. | |
19 | */ | |
20 | #include <sys/types.h> | |
f8675e8e | 21 | #include <sys/socket.h> |
f8675e8e | 22 | #include <sys/ioctl.h> |
ce4fd43b SL |
23 | #include <sys/wait.h> |
24 | #include <sys/stat.h> | |
de3b21e8 SL |
25 | |
26 | #include <netinet/in.h> | |
27 | ||
533343f1 SL |
28 | #include <arpa/tftp.h> |
29 | ||
de3b21e8 | 30 | #include <signal.h> |
f8675e8e | 31 | #include <stdio.h> |
f8675e8e SL |
32 | #include <errno.h> |
33 | #include <ctype.h> | |
4d5e33bd | 34 | #include <netdb.h> |
103499bc | 35 | #include <setjmp.h> |
3f99c0f7 | 36 | #include <syslog.h> |
103499bc | 37 | |
103499bc | 38 | #define TIMEOUT 5 |
de3b21e8 | 39 | |
f8675e8e | 40 | extern int errno; |
4d5e33bd | 41 | struct sockaddr_in sin = { AF_INET }; |
bb933cc2 | 42 | int peer; |
103499bc SL |
43 | int rexmtval = TIMEOUT; |
44 | int maxtimeout = 5*TIMEOUT; | |
f8675e8e | 45 | char buf[BUFSIZ]; |
bb933cc2 MK |
46 | struct sockaddr_in from; |
47 | int fromlen; | |
f8675e8e | 48 | |
bb933cc2 | 49 | main() |
f8675e8e | 50 | { |
f8675e8e SL |
51 | register struct tftphdr *tp; |
52 | register int n; | |
53 | ||
bb933cc2 MK |
54 | alarm(10); |
55 | fromlen = sizeof (from); | |
56 | n = recvfrom(0, buf, sizeof (buf), 0, | |
57 | (caddr_t)&from, &fromlen); | |
58 | if (n < 0) { | |
59 | perror("tftpd: recvfrom"); | |
4d5e33bd SL |
60 | exit(1); |
61 | } | |
bb933cc2 MK |
62 | from.sin_family = AF_INET; |
63 | alarm(0); | |
bb933cc2 MK |
64 | close(0); |
65 | close(1); | |
66 | peer = socket(AF_INET, SOCK_DGRAM, 0); | |
67 | if (peer < 0) { | |
3f99c0f7 RC |
68 | openlog("tftpd", LOG_PID, 0); |
69 | syslog(LOG_ERR, "socket: %m"); | |
bb933cc2 MK |
70 | exit(1); |
71 | } | |
72 | if (bind(peer, (caddr_t)&sin, sizeof (sin)) < 0) { | |
3f99c0f7 RC |
73 | openlog("tftpd", LOG_PID, 0); |
74 | syslog(LOG_ERR, "bind: %m"); | |
bb933cc2 | 75 | exit(1); |
f8675e8e | 76 | } |
bb933cc2 | 77 | if (connect(peer, (caddr_t)&from, sizeof(from)) < 0) { |
3f99c0f7 RC |
78 | openlog("tftpd", LOG_PID, 0); |
79 | syslog(LOG_ERR, "connect: %m"); | |
bb933cc2 | 80 | exit(1); |
f8675e8e | 81 | } |
bb933cc2 MK |
82 | tp = (struct tftphdr *)buf; |
83 | tp->th_opcode = ntohs(tp->th_opcode); | |
84 | if (tp->th_opcode == RRQ || tp->th_opcode == WRQ) | |
85 | tftp(tp, n); | |
86 | exit(1); | |
103499bc SL |
87 | } |
88 | ||
f8675e8e SL |
89 | int validate_access(); |
90 | int sendfile(), recvfile(); | |
91 | ||
92 | struct formats { | |
93 | char *f_mode; | |
94 | int (*f_validate)(); | |
95 | int (*f_send)(); | |
96 | int (*f_recv)(); | |
97 | } formats[] = { | |
98 | { "netascii", validate_access, sendfile, recvfile }, | |
99 | { "octet", validate_access, sendfile, recvfile }, | |
100 | #ifdef notdef | |
101 | { "mail", validate_user, sendmail, recvmail }, | |
102 | #endif | |
103 | { 0 } | |
104 | }; | |
105 | ||
f8675e8e SL |
106 | /* |
107 | * Handle initial connection protocol. | |
108 | */ | |
bb933cc2 | 109 | tftp(tp, size) |
f8675e8e SL |
110 | struct tftphdr *tp; |
111 | int size; | |
112 | { | |
113 | register char *cp; | |
114 | int first = 1, ecode; | |
115 | register struct formats *pf; | |
116 | char *filename, *mode; | |
117 | ||
f8675e8e SL |
118 | filename = cp = tp->th_stuff; |
119 | again: | |
120 | while (cp < buf + size) { | |
121 | if (*cp == '\0') | |
122 | break; | |
123 | cp++; | |
124 | } | |
125 | if (*cp != '\0') { | |
126 | nak(EBADOP); | |
127 | exit(1); | |
128 | } | |
129 | if (first) { | |
130 | mode = ++cp; | |
131 | first = 0; | |
132 | goto again; | |
133 | } | |
134 | for (cp = mode; *cp; cp++) | |
135 | if (isupper(*cp)) | |
136 | *cp = tolower(*cp); | |
137 | for (pf = formats; pf->f_mode; pf++) | |
138 | if (strcmp(pf->f_mode, mode) == 0) | |
139 | break; | |
140 | if (pf->f_mode == 0) { | |
141 | nak(EBADOP); | |
142 | exit(1); | |
143 | } | |
bb933cc2 | 144 | ecode = (*pf->f_validate)(filename, tp->th_opcode); |
f8675e8e SL |
145 | if (ecode) { |
146 | nak(ecode); | |
147 | exit(1); | |
148 | } | |
149 | if (tp->th_opcode == WRQ) | |
150 | (*pf->f_recv)(pf); | |
151 | else | |
152 | (*pf->f_send)(pf); | |
153 | exit(0); | |
154 | } | |
155 | ||
bb933cc2 MK |
156 | int fd; |
157 | ||
f8675e8e SL |
158 | /* |
159 | * Validate file access. Since we | |
160 | * have no uid or gid, for now require | |
161 | * file to exist and be publicly | |
162 | * readable/writable. | |
163 | * Note also, full path name must be | |
164 | * given as we have no login directory. | |
165 | */ | |
bb933cc2 | 166 | validate_access(file, mode) |
f8675e8e | 167 | char *file; |
f8675e8e SL |
168 | int mode; |
169 | { | |
170 | struct stat stbuf; | |
171 | ||
172 | if (*file != '/') | |
173 | return (EACCESS); | |
174 | if (stat(file, &stbuf) < 0) | |
175 | return (errno == ENOENT ? ENOTFOUND : EACCESS); | |
176 | if (mode == RRQ) { | |
177 | if ((stbuf.st_mode&(S_IREAD >> 6)) == 0) | |
178 | return (EACCESS); | |
179 | } else { | |
180 | if ((stbuf.st_mode&(S_IWRITE >> 6)) == 0) | |
181 | return (EACCESS); | |
182 | } | |
183 | fd = open(file, mode == RRQ ? 0 : 1); | |
184 | if (fd < 0) | |
185 | return (errno + 100); | |
186 | return (0); | |
187 | } | |
188 | ||
103499bc SL |
189 | int timeout; |
190 | jmp_buf timeoutbuf; | |
f8675e8e SL |
191 | |
192 | timer() | |
193 | { | |
103499bc SL |
194 | |
195 | timeout += rexmtval; | |
196 | if (timeout >= maxtimeout) | |
f8675e8e | 197 | exit(1); |
103499bc | 198 | longjmp(timeoutbuf, 1); |
f8675e8e SL |
199 | } |
200 | ||
201 | /* | |
202 | * Send the requested file. | |
203 | */ | |
204 | sendfile(pf) | |
205 | struct format *pf; | |
206 | { | |
207 | register struct tftphdr *tp; | |
208 | register int block = 1, size, n; | |
209 | ||
103499bc | 210 | signal(SIGALRM, timer); |
f8675e8e SL |
211 | tp = (struct tftphdr *)buf; |
212 | do { | |
213 | size = read(fd, tp->th_data, SEGSIZE); | |
214 | if (size < 0) { | |
215 | nak(errno + 100); | |
bb933cc2 | 216 | return; |
f8675e8e SL |
217 | } |
218 | tp->th_opcode = htons((u_short)DATA); | |
219 | tp->th_block = htons((u_short)block); | |
220 | timeout = 0; | |
103499bc | 221 | (void) setjmp(timeoutbuf); |
bb933cc2 MK |
222 | if (send(peer, buf, size + 4, 0) != size + 4) { |
223 | perror("tftpd: send"); | |
224 | return; | |
f8675e8e | 225 | } |
103499bc SL |
226 | do { |
227 | alarm(rexmtval); | |
bb933cc2 | 228 | n = recv(peer, buf, sizeof (buf), 0); |
f8675e8e | 229 | alarm(0); |
103499bc | 230 | if (n < 0) { |
bb933cc2 MK |
231 | perror("tftpd: recv"); |
232 | return; | |
103499bc SL |
233 | } |
234 | tp->th_opcode = ntohs((u_short)tp->th_opcode); | |
235 | tp->th_block = ntohs((u_short)tp->th_block); | |
236 | if (tp->th_opcode == ERROR) | |
bb933cc2 | 237 | return; |
103499bc | 238 | } while (tp->th_opcode != ACK || tp->th_block != block); |
f8675e8e SL |
239 | block++; |
240 | } while (size == SEGSIZE); | |
f8675e8e SL |
241 | } |
242 | ||
243 | /* | |
244 | * Receive a file. | |
245 | */ | |
246 | recvfile(pf) | |
247 | struct format *pf; | |
248 | { | |
249 | register struct tftphdr *tp; | |
250 | register int block = 0, n, size; | |
251 | ||
103499bc | 252 | signal(SIGALRM, timer); |
f8675e8e SL |
253 | tp = (struct tftphdr *)buf; |
254 | do { | |
255 | timeout = 0; | |
f8675e8e SL |
256 | tp->th_opcode = htons((u_short)ACK); |
257 | tp->th_block = htons((u_short)block); | |
258 | block++; | |
103499bc | 259 | (void) setjmp(timeoutbuf); |
bb933cc2 MK |
260 | if (send(peer, buf, 4, 0) != 4) { |
261 | perror("tftpd: send"); | |
103499bc | 262 | goto abort; |
f8675e8e | 263 | } |
103499bc SL |
264 | do { |
265 | alarm(rexmtval); | |
bb933cc2 | 266 | n = recv(peer, buf, sizeof (buf), 0); |
f8675e8e | 267 | alarm(0); |
103499bc | 268 | if (n < 0) { |
bb933cc2 | 269 | perror("tftpd: recv"); |
103499bc SL |
270 | goto abort; |
271 | } | |
272 | tp->th_opcode = ntohs((u_short)tp->th_opcode); | |
273 | tp->th_block = ntohs((u_short)tp->th_block); | |
274 | if (tp->th_opcode == ERROR) | |
275 | goto abort; | |
276 | } while (tp->th_opcode != DATA || block != tp->th_block); | |
f8675e8e SL |
277 | size = write(fd, tp->th_data, n - 4); |
278 | if (size < 0) { | |
279 | nak(errno + 100); | |
103499bc | 280 | goto abort; |
f8675e8e SL |
281 | } |
282 | } while (size == SEGSIZE); | |
103499bc | 283 | abort: |
f8675e8e SL |
284 | tp->th_opcode = htons((u_short)ACK); |
285 | tp->th_block = htons((u_short)(block)); | |
bb933cc2 | 286 | (void) send(peer, buf, 4, 0); |
f8675e8e SL |
287 | } |
288 | ||
289 | struct errmsg { | |
290 | int e_code; | |
291 | char *e_msg; | |
292 | } errmsgs[] = { | |
293 | { EUNDEF, "Undefined error code" }, | |
294 | { ENOTFOUND, "File not found" }, | |
295 | { EACCESS, "Access violation" }, | |
296 | { ENOSPACE, "Disk full or allocation exceeded" }, | |
297 | { EBADOP, "Illegal TFTP operation" }, | |
298 | { EBADID, "Unknown transfer ID" }, | |
299 | { EEXISTS, "File already exists" }, | |
300 | { ENOUSER, "No such user" }, | |
301 | { -1, 0 } | |
302 | }; | |
303 | ||
304 | /* | |
305 | * Send a nak packet (error message). | |
306 | * Error code passed in is one of the | |
307 | * standard TFTP codes, or a UNIX errno | |
308 | * offset by 100. | |
309 | */ | |
310 | nak(error) | |
311 | int error; | |
312 | { | |
313 | register struct tftphdr *tp; | |
314 | int length; | |
315 | register struct errmsg *pe; | |
316 | extern char *sys_errlist[]; | |
317 | ||
318 | tp = (struct tftphdr *)buf; | |
319 | tp->th_opcode = htons((u_short)ERROR); | |
320 | tp->th_code = htons((u_short)error); | |
321 | for (pe = errmsgs; pe->e_code >= 0; pe++) | |
322 | if (pe->e_code == error) | |
323 | break; | |
324 | if (pe->e_code < 0) | |
325 | pe->e_msg = sys_errlist[error - 100]; | |
326 | strcpy(tp->th_msg, pe->e_msg); | |
327 | length = strlen(pe->e_msg); | |
328 | tp->th_msg[length] = '\0'; | |
329 | length += 5; | |
bb933cc2 | 330 | if (send(peer, buf, length, 0) != length) |
f8675e8e | 331 | perror("nak"); |
bb933cc2 | 332 | exit(1); |
f8675e8e | 333 | } |