├── .gitignore ├── Makefile ├── README.md └── tftpserv.c /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *# 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | gcc -o tftpserv tftpserv.c -ggdb 3 | 4 | clean: 5 | rm tftpserv 6 | 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | TFTP Server 2 | =========== 3 | 4 | Simple TFTP server implementation, supporting only the old part of the protocol, as documented in the first edition of TCP/IP Illustrated, Volume 1 (Stevenson). 5 | 6 | Implemented in pure C, no dependencies. 7 | 8 | Usage 9 | ----- 10 | 11 | The usage is really simple: 12 | ``` 13 | usage: 14 | ./tftpserv [base directory] [port] 15 | ``` 16 | 17 | The *base directory* argument specify the directory containing the files you want to distribute with the server. 18 | 19 | All files in this directory (and subdirectories) are available to be downloaded, if their permissions allow so. Attempts to access upper directories are blocked, and these are the only security features implemented. 20 | 21 | The *port* parameter is used to specify the UDP port the server should listen on. 22 | 23 | To upload and download files you also need a TFTP client. A good choice is atftp (http://www.freecode.com/projects/atftp/) or just the standard tftp client present in many unix-like systems. 24 | 25 | License 26 | ------- 27 | 28 | Copyright 2014 - Emanuele Acri 29 | 30 | Offer me a coffee license: use the code as you wish, but offer me a coffee :) 31 | 32 | -------------------------------------------------------------------------------- /tftpserv.c: -------------------------------------------------------------------------------- 1 | /* 2 | * TFTP Server 3 | * Copyright 2014 - Acri Emanuele 4 | * 5 | * This program is under a GPLv3+ license. 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #define RECV_TIMEOUT 5 22 | #define RECV_RETRIES 5 23 | 24 | /* tftp opcode mnemonic */ 25 | enum opcode { 26 | RRQ=1, 27 | WRQ, 28 | DATA, 29 | ACK, 30 | ERROR 31 | }; 32 | 33 | /* tftp transfer mode */ 34 | enum mode { 35 | NETASCII=1, 36 | OCTET 37 | }; 38 | 39 | /* tftp message structure */ 40 | typedef union { 41 | 42 | uint16_t opcode; 43 | 44 | struct { 45 | uint16_t opcode; /* RRQ or WRQ */ 46 | uint8_t filename_and_mode[514]; 47 | } request; 48 | 49 | struct { 50 | uint16_t opcode; /* DATA */ 51 | uint16_t block_number; 52 | uint8_t data[512]; 53 | } data; 54 | 55 | struct { 56 | uint16_t opcode; /* ACK */ 57 | uint16_t block_number; 58 | } ack; 59 | 60 | struct { 61 | uint16_t opcode; /* ERROR */ 62 | uint16_t error_code; 63 | uint8_t error_string[512]; 64 | } error; 65 | 66 | } tftp_message; 67 | 68 | /* base directory */ 69 | char *base_directory; 70 | 71 | void cld_handler(int sig) { 72 | int status; 73 | wait(&status); 74 | } 75 | 76 | ssize_t tftp_send_data(int s, uint16_t block_number, uint8_t *data, 77 | ssize_t dlen, struct sockaddr_in *sock, socklen_t slen) 78 | { 79 | tftp_message m; 80 | ssize_t c; 81 | 82 | m.opcode = htons(DATA); 83 | m.data.block_number = htons(block_number); 84 | memcpy(m.data.data, data, dlen); 85 | 86 | if ((c = sendto(s, &m, 4 + dlen, 0, 87 | (struct sockaddr *) sock, slen)) < 0) { 88 | perror("server: sendto()"); 89 | } 90 | 91 | return c; 92 | } 93 | 94 | ssize_t tftp_send_ack(int s, uint16_t block_number, 95 | struct sockaddr_in *sock, socklen_t slen) 96 | { 97 | tftp_message m; 98 | ssize_t c; 99 | 100 | m.opcode = htons(ACK); 101 | m.ack.block_number = htons(block_number); 102 | 103 | if ((c = sendto(s, &m, sizeof(m.ack), 0, 104 | (struct sockaddr *) sock, slen)) < 0) { 105 | perror("server: sendto()"); 106 | } 107 | 108 | return c; 109 | } 110 | 111 | ssize_t tftp_send_error(int s, int error_code, char *error_string, 112 | struct sockaddr_in *sock, socklen_t slen) 113 | { 114 | tftp_message m; 115 | ssize_t c; 116 | 117 | if(strlen(error_string) >= 512) { 118 | fprintf(stderr, "server: tftp_send_error(): error string too long\n"); 119 | return -1; 120 | } 121 | 122 | m.opcode = htons(ERROR); 123 | m.error.error_code = error_code; 124 | strcpy(m.error.error_string, error_string); 125 | 126 | if ((c = sendto(s, &m, 4 + strlen(error_string) + 1, 0, 127 | (struct sockaddr *) sock, slen)) < 0) { 128 | perror("server: sendto()"); 129 | } 130 | 131 | return c; 132 | } 133 | 134 | ssize_t tftp_recv_message(int s, tftp_message *m, struct sockaddr_in *sock, socklen_t *slen) 135 | { 136 | ssize_t c; 137 | 138 | if ((c = recvfrom(s, m, sizeof(*m), 0, (struct sockaddr *) sock, slen)) < 0 139 | && errno != EAGAIN) { 140 | perror("server: recvfrom()"); 141 | } 142 | 143 | return c; 144 | } 145 | 146 | void tftp_handle_request(tftp_message *m, ssize_t len, 147 | struct sockaddr_in *client_sock, socklen_t slen) 148 | { 149 | int s; 150 | struct protoent *pp; 151 | struct timeval tv; 152 | 153 | char *filename, *mode_s, *end; 154 | FILE *fd; 155 | 156 | int mode; 157 | uint16_t opcode; 158 | 159 | /* open new socket, on new port, to handle client request */ 160 | 161 | if ((pp = getprotobyname("udp")) == 0) { 162 | fprintf(stderr, "server: getprotobyname() error\n"); 163 | exit(1); 164 | } 165 | 166 | if ((s = socket(AF_INET, SOCK_DGRAM, pp->p_proto)) == -1) { 167 | perror("server: socket()"); 168 | exit(1); 169 | } 170 | 171 | tv.tv_sec = RECV_TIMEOUT; 172 | tv.tv_usec = 0; 173 | 174 | if(setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) { 175 | perror("server: setsockopt()"); 176 | exit(1); 177 | } 178 | 179 | /* parse client request */ 180 | 181 | filename = m->request.filename_and_mode; 182 | end = &filename[len - 2 - 1]; 183 | 184 | if (*end != '\0') { 185 | printf("%s.%u: invalid filename or mode\n", 186 | inet_ntoa(client_sock->sin_addr), ntohs(client_sock->sin_port)); 187 | tftp_send_error(s, 0, "invalid filename or mode", client_sock, slen); 188 | exit(1); 189 | } 190 | 191 | mode_s = strchr(filename, '\0') + 1; 192 | 193 | if (mode_s > end) { 194 | printf("%s.%u: transfer mode not specified\n", 195 | inet_ntoa(client_sock->sin_addr), ntohs(client_sock->sin_port)); 196 | tftp_send_error(s, 0, "transfer mode not specified", client_sock, slen); 197 | exit(1); 198 | } 199 | 200 | if(strncmp(filename, "../", 3) == 0 || strstr(filename, "/../") != NULL || 201 | (filename[0] == '/' && strncmp(filename, base_directory, strlen(base_directory)) != 0)) { 202 | printf("%s.%u: filename outside base directory\n", 203 | inet_ntoa(client_sock->sin_addr), ntohs(client_sock->sin_port)); 204 | tftp_send_error(s, 0, "filename outside base directory", client_sock, slen); 205 | exit(1); 206 | } 207 | 208 | opcode = ntohs(m->opcode); 209 | fd = fopen(filename, opcode == RRQ ? "r" : "w"); 210 | 211 | if (fd == NULL) { 212 | perror("server: fopen()"); 213 | tftp_send_error(s, errno, strerror(errno), client_sock, slen); 214 | exit(1); 215 | } 216 | 217 | mode = strcasecmp(mode_s, "netascii") ? NETASCII : 218 | strcasecmp(mode_s, "octet") ? OCTET : 219 | 0; 220 | 221 | if (mode == 0) { 222 | printf("%s.%u: invalid transfer mode\n", 223 | inet_ntoa(client_sock->sin_addr), ntohs(client_sock->sin_port)); 224 | tftp_send_error(s, 0, "invalid transfer mode", client_sock, slen); 225 | exit(1); 226 | } 227 | 228 | printf("%s.%u: request received: %s '%s' %s\n", 229 | inet_ntoa(client_sock->sin_addr), ntohs(client_sock->sin_port), 230 | ntohs(m->opcode) == RRQ ? "get" : "put", filename, mode_s); 231 | 232 | // TODO: add netascii handling 233 | 234 | if (opcode == RRQ) { 235 | tftp_message m; 236 | 237 | uint8_t data[512]; 238 | ssize_t dlen, c; 239 | 240 | uint16_t block_number = 0; 241 | 242 | int countdown; 243 | int to_close = 0; 244 | 245 | while (!to_close) { 246 | 247 | dlen = fread(data, 1, sizeof(data), fd); 248 | block_number++; 249 | 250 | if (dlen < 512) { // last data block to send 251 | to_close = 1; 252 | } 253 | 254 | for (countdown = RECV_RETRIES; countdown; countdown--) { 255 | 256 | c = tftp_send_data(s, block_number, data, dlen, client_sock, slen); 257 | 258 | if (c < 0) { 259 | printf("%s.%u: transfer killed\n", 260 | inet_ntoa(client_sock->sin_addr), ntohs(client_sock->sin_port)); 261 | exit(1); 262 | } 263 | 264 | c = tftp_recv_message(s, &m, client_sock, &slen); 265 | 266 | if (c >= 0 && c < 4) { 267 | printf("%s.%u: message with invalid size received\n", 268 | inet_ntoa(client_sock->sin_addr), ntohs(client_sock->sin_port)); 269 | tftp_send_error(s, 0, "invalid request size", client_sock, slen); 270 | exit(1); 271 | } 272 | 273 | if (c >= 4) { 274 | break; 275 | } 276 | 277 | if (errno != EAGAIN) { 278 | printf("%s.%u: transfer killed\n", 279 | inet_ntoa(client_sock->sin_addr), ntohs(client_sock->sin_port)); 280 | exit(1); 281 | } 282 | 283 | } 284 | 285 | if (!countdown) { 286 | printf("%s.%u: transfer timed out\n", 287 | inet_ntoa(client_sock->sin_addr), ntohs(client_sock->sin_port)); 288 | exit(1); 289 | } 290 | 291 | if (ntohs(m.opcode) == ERROR) { 292 | printf("%s.%u: error message received: %u %s\n", 293 | inet_ntoa(client_sock->sin_addr), ntohs(client_sock->sin_port), 294 | ntohs(m.error.error_code), m.error.error_string); 295 | exit(1); 296 | } 297 | 298 | if (ntohs(m.opcode) != ACK) { 299 | printf("%s.%u: invalid message during transfer received\n", 300 | inet_ntoa(client_sock->sin_addr), ntohs(client_sock->sin_port)); 301 | tftp_send_error(s, 0, "invalid message during transfer", client_sock, slen); 302 | exit(1); 303 | } 304 | 305 | if (ntohs(m.ack.block_number) != block_number) { // the ack number is too high 306 | printf("%s.%u: invalid ack number received\n", 307 | inet_ntoa(client_sock->sin_addr), ntohs(client_sock->sin_port)); 308 | tftp_send_error(s, 0, "invalid ack number", client_sock, slen); 309 | exit(1); 310 | } 311 | 312 | } 313 | 314 | } 315 | 316 | else if (opcode == WRQ) { 317 | 318 | tftp_message m; 319 | 320 | ssize_t c; 321 | 322 | uint16_t block_number = 0; 323 | 324 | int countdown; 325 | int to_close = 0; 326 | 327 | c = tftp_send_ack(s, block_number, client_sock, slen); 328 | 329 | if (c < 0) { 330 | printf("%s.%u: transfer killed\n", 331 | inet_ntoa(client_sock->sin_addr), ntohs(client_sock->sin_port)); 332 | exit(1); 333 | } 334 | 335 | while (!to_close) { 336 | 337 | for (countdown = RECV_RETRIES; countdown; countdown--) { 338 | 339 | c = tftp_recv_message(s, &m, client_sock, &slen); 340 | 341 | if (c >= 0 && c < 4) { 342 | printf("%s.%u: message with invalid size received\n", 343 | inet_ntoa(client_sock->sin_addr), ntohs(client_sock->sin_port)); 344 | tftp_send_error(s, 0, "invalid request size", client_sock, slen); 345 | exit(1); 346 | } 347 | 348 | if (c >= 4) { 349 | break; 350 | } 351 | 352 | if (errno != EAGAIN) { 353 | printf("%s.%u: transfer killed\n", 354 | inet_ntoa(client_sock->sin_addr), ntohs(client_sock->sin_port)); 355 | exit(1); 356 | } 357 | 358 | c = tftp_send_ack(s, block_number, client_sock, slen); 359 | 360 | if (c < 0) { 361 | printf("%s.%u: transfer killed\n", 362 | inet_ntoa(client_sock->sin_addr), ntohs(client_sock->sin_port)); 363 | exit(1); 364 | } 365 | 366 | } 367 | 368 | if (!countdown) { 369 | printf("%s.%u: transfer timed out\n", 370 | inet_ntoa(client_sock->sin_addr), ntohs(client_sock->sin_port)); 371 | exit(1); 372 | } 373 | 374 | block_number++; 375 | 376 | if (c < sizeof(m.data)) { 377 | to_close = 1; 378 | } 379 | 380 | if (ntohs(m.opcode) == ERROR) { 381 | printf("%s.%u: error message received: %u %s\n", 382 | inet_ntoa(client_sock->sin_addr), ntohs(client_sock->sin_port), 383 | ntohs(m.error.error_code), m.error.error_string); 384 | exit(1); 385 | } 386 | 387 | if (ntohs(m.opcode) != DATA) { 388 | printf("%s.%u: invalid message during transfer received\n", 389 | inet_ntoa(client_sock->sin_addr), ntohs(client_sock->sin_port)); 390 | tftp_send_error(s, 0, "invalid message during transfer", client_sock, slen); 391 | exit(1); 392 | } 393 | 394 | if (ntohs(m.ack.block_number) != block_number) { 395 | printf("%s.%u: invalid block number received\n", 396 | inet_ntoa(client_sock->sin_addr), ntohs(client_sock->sin_port)); 397 | tftp_send_error(s, 0, "invalid block number", client_sock, slen); 398 | exit(1); 399 | } 400 | 401 | c = fwrite(m.data.data, 1, c - 4, fd); 402 | 403 | if (c < 0) { 404 | perror("server: fwrite()"); 405 | exit(1); 406 | } 407 | 408 | c = tftp_send_ack(s, block_number, client_sock, slen); 409 | 410 | if (c < 0) { 411 | printf("%s.%u: transfer killed\n", 412 | inet_ntoa(client_sock->sin_addr), ntohs(client_sock->sin_port)); 413 | exit(1); 414 | } 415 | 416 | } 417 | 418 | } 419 | 420 | printf("%s.%u: transfer completed\n", 421 | inet_ntoa(client_sock->sin_addr), ntohs(client_sock->sin_port)); 422 | 423 | fclose(fd); 424 | close(s); 425 | 426 | exit(0); 427 | } 428 | 429 | int main(int argc, char *argv[]) 430 | { 431 | int s; 432 | uint16_t port = 0; 433 | struct protoent *pp; 434 | struct servent *ss; 435 | struct sockaddr_in server_sock; 436 | 437 | if (argc < 2) { 438 | printf("usage:\n\t%s [base directory] [port]\n", argv[0]); 439 | exit(1); 440 | } 441 | 442 | base_directory = argv[1]; 443 | 444 | if (chdir(base_directory) < 0) { 445 | perror("server: chdir()"); 446 | exit(1); 447 | } 448 | 449 | if (argc > 2) { 450 | if (sscanf(argv[2], "%hu", &port)) { 451 | port = htons(port); 452 | } else { 453 | fprintf(stderr, "error: invalid port number\n"); 454 | exit(1); 455 | } 456 | } else { 457 | if ((ss = getservbyname("tftp", "udp")) == 0) { 458 | fprintf(stderr, "server: getservbyname() error\n"); 459 | exit(1); 460 | } 461 | 462 | } 463 | 464 | if ((pp = getprotobyname("udp")) == 0) { 465 | fprintf(stderr, "server: getprotobyname() error\n"); 466 | exit(1); 467 | } 468 | 469 | if ((s = socket(AF_INET, SOCK_DGRAM, pp->p_proto)) == -1) { 470 | perror("server: socket() error"); 471 | exit(1); 472 | } 473 | 474 | server_sock.sin_family = AF_INET; 475 | server_sock.sin_addr.s_addr = htonl(INADDR_ANY); 476 | server_sock.sin_port = port ? port : ss->s_port; 477 | 478 | if (bind(s, (struct sockaddr *) &server_sock, sizeof(server_sock)) == -1) { 479 | perror("server: bind()"); 480 | close(s); 481 | exit(1); 482 | } 483 | 484 | signal(SIGCLD, (void *) cld_handler) ; 485 | 486 | printf("tftp server: listening on %d\n", ntohs(server_sock.sin_port)); 487 | 488 | while (1) { 489 | struct sockaddr_in client_sock; 490 | socklen_t slen = sizeof(client_sock); 491 | ssize_t len; 492 | 493 | tftp_message message; 494 | uint16_t opcode; 495 | 496 | if ((len = tftp_recv_message(s, &message, &client_sock, &slen)) < 0) { 497 | continue; 498 | } 499 | 500 | if (len < 4) { 501 | printf("%s.%u: request with invalid size received\n", 502 | inet_ntoa(client_sock.sin_addr), ntohs(client_sock.sin_port)); 503 | tftp_send_error(s, 0, "invalid request size", &client_sock, slen); 504 | continue; 505 | } 506 | 507 | opcode = ntohs(message.opcode); 508 | 509 | if (opcode == RRQ || opcode == WRQ) { 510 | 511 | /* spawn a child process to handle the request */ 512 | 513 | if (fork() == 0) { 514 | tftp_handle_request(&message, len, &client_sock, slen); 515 | exit(0); 516 | } 517 | 518 | } 519 | 520 | else { 521 | printf("%s.%u: invalid request received: opcode \n", 522 | inet_ntoa(client_sock.sin_addr), ntohs(client_sock.sin_port), 523 | opcode); 524 | tftp_send_error(s, 0, "invalid opcode", &client_sock, slen); 525 | } 526 | 527 | } 528 | 529 | close(s); 530 | 531 | return 0; 532 | } 533 | --------------------------------------------------------------------------------