📄 tftpd.c
字号:
/* hey emacs! -*- Mode: C; c-file-style: "k&r"; indent-tabs-mode: nil -*- */
/*
* tftpd.c
* main server file
*
* $Id: tftpd.c,v 1.25 2001/07/08 17:45:46 jp Exp $
*
* Copyright (c) 2000 Jean-Pierre Lefebvre <HELIX@STEP.POLYMTL.CA>
* and Remi Lefebvre <REMI@DEBIAN.ORG>
*
* atftp is free software; you can redistribute them and/or modify them
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
*/
#include <CONFIG.H>
#include <STDIO.H>
#include <STDLIB.H>
#include <SYS time.h>
#include <SYS types.h>
#include <SYS socket.h>
#include <ARPA inet.h>
#include <NETDB.H>
#include <GETOPT.H>
#include <UNISTD.H>
#include <SIGNAL.H>
#include <PTHREAD.H>
#include <STRING.H>
#include <PWD.H>
#include <GRP.H>
#include "tftpd.h"
#include "tftp_io.h"
#include "tftp_def.h"
#include "logger.h"
#include "options.h"
#include "stats.h"
/*
* Global variables set by main when starting. Read-only for threads
*/
int tftpd_max_thread = 100; /* number of concurent thread allowed */
int tftpd_timeout = 300; /* number of second of inactivity
before exiting */
char directory[MAXLEN] = "/tftpboot/";
int retry_timeout = S_TIMEOUT;
int tftpd_daemon = 0; /* By default we are started by inetd */
short tftpd_port = 0;
int tftpd_cancel = 0; /* When true, thread must exit. */
/*
* "logging level" is the maximum error level that will get logged.
* This can be increased with the -v switch.
*/
int logging_level = LOG_NOTICE;
char *log_file = NULL;
/*
* We need a lock on stdin from the time we notice fresh data coming from
* stdin to the time the freshly created server thread as read it.
*/
pthread_mutex_t stdin_mutex = PTHREAD_MUTEX_INITIALIZER;
/*
* Function defined in this file
*/
void *tftpd_receive_request(void *);
void signal_handler(int signal);
int tftpd_cmd_line_options(int argc, char **argv);
void tftpd_log_options(void);
void tftpd_usage(void);
/*
* Main thread. Do required initialisation and then go through a loop
* listening for client requests. When a request arrives, we allocate
* memory for a thread data structure and start a thread to serve the
* new client. If theres no activity for more than 'tftpd_timeout'
* seconds, we exit and tftpd must be respawned by inetd.
*/
int main(int argc, char **argv)
{
fd_set rfds; /* for select */
struct timeval tv; /* for select */
int run = 1; /* while (run) loop */
struct thread_data *new; /* for allocation of new thread_data */
int sockfd; /* used in daemon mode */
struct sockaddr_in sa; /* used in daemon mode */
struct servent *serv;
struct passwd *user;
struct group *group;
/*
* Parse command line options. We parse before verifying
* if we are running on a tty or not to make it possible to
* verify the command line arguments
*/
if (tftpd_cmd_line_options(argc, argv) == ERR)
exit(1);
/*
* Can't be started from the prompt without explicitely specifying
* the --daemon option.
*/
if (isatty(0) && !(tftpd_daemon))
{
tftpd_usage();
exit(1);
}
/*
* In tftpd is run in daemon mode ...
*/
if (tftpd_daemon)
{
/* daemonize here */
if (daemon(0, 0) == -1)
exit(2);
/* find the port */
if (tftpd_port == 0)
{
if ((serv = getservbyname("tftp", "udp")) == NULL)
{
logger(LOG_ERR, "atftpd: udp/tftp, unknown service");
exit(1);
}
tftpd_port = ntohs(serv->s_port);
}
/* initialise sockaddr_in structure */
memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_addr.s_addr = htonl(INADDR_ANY);
sa.sin_port = htons(tftpd_port);
/* open the socket */
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == 0)
{
logger(LOG_ERR, "atftpd: can't open socket");
exit(1);
}
/* bind the socket to the tftp port */
if (bind(sockfd, (struct sockaddr*)&sa, sizeof(sa)) < 0)
{
logger(LOG_ERR, "atftpd: can't bind port");
exit(1);
}
/* dup sockfd on 0,1,2 */
dup2(sockfd, 0);
dup2(sockfd, 1);
dup2(sockfd, 2);
close(sockfd);
/* release priviliedge */
user = getpwnam("nobody");
group = getgrnam("nogroup");
setgid(group->gr_gid);
setuid(user->pw_uid);
}
/* Register signal handler. */
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
signal(SIGKILL, signal_handler);
/* Using syslog facilties through a wrapper. This call divert logs
* to a file as specified or do nothing
*/
open_logger("tftpd", log_file, logging_level);
logger(LOG_NOTICE, "Trivial FTP server started (%s)", VERSION);
/* print summary of options */
tftpd_log_options();
/* start collecting stats */
stats_start();
/* Wait for read or write request and exit if timeout. */
while (run)
{
/*
* inetd dups the socket file descriptor to 0, 1 and 2 so we can
* use any of those as the socket fd. We use 0. stdout and stderr
* may not be used to print messages.
*/
FD_ZERO(&rfds);
FD_SET(0, &rfds);
tv.tv_sec = tftpd_timeout;
tv.tv_usec = 0;
/* We need to lock stdin, and release it when the thread
is done reading the request. */
pthread_mutex_lock(&stdin_mutex);
/* A timeout of 0 is interpreted as infinity */
if (!tftpd_cancel)
{
if ((tftpd_timeout == 0) || (tftpd_daemon))
select(FD_SETSIZE, &rfds, NULL, NULL, NULL);
else
select(FD_SETSIZE, &rfds, NULL, NULL, &tv);
}
if (FD_ISSET(0, &rfds) && (!tftpd_cancel))
{
/* Allocate memory for thread_data structure. */
if ((new = malloc(sizeof(struct thread_data))) == NULL)
{
logger(LOG_ERR, "%s: %d: Memory allocation failed",
__FILE__, __LINE__);
exit(1);
}
/*
* Initialisation of thread_data structure.
*/
pthread_mutex_init(&new->opt_mutex, NULL);
pthread_mutex_init(&new->client_mutex, NULL);
new->sockfd = 0;
/* Allocate data buffer for tftp transfer. */
if ((new->data_buffer = malloc((size_t)SEGSIZE + 4)) == NULL)
{
logger(LOG_ERR, "%s: %d: Memory allocation failed",
__FILE__, __LINE__);
exit(1);
}
new->data_buffer_size = SEGSIZE + 4;
/* Allocate ack buffer for tftp acknowledge. */
if ((new->ack_buffer = malloc((size_t)SEGSIZE + 4)) == NULL)
{
logger(LOG_ERR, "%s: %d: Memory allocation failed",
__FILE__, __LINE__);
exit(1);
}
new->ack_buffer_size = SEGSIZE + 4;
/* Allocate memory for tftp option structure. */
if ((new->tftp_options =
malloc(sizeof(tftp_default_options))) == NULL)
{
logger(LOG_ERR, "%s: %d: Memory allocation failed",
__FILE__, __LINE__);
exit(1);
}
/* Copy default options. */
memcpy(new->tftp_options, tftp_default_options,
sizeof(tftp_default_options));
/* default timeout */
new->timeout = retry_timeout;
/* Allocate memory for a client structure. */
if ((new->client_info =
calloc(1, sizeof(struct client_info))) == NULL)
{
logger(LOG_ERR, "%s: %d: Memory allocation failed",
__FILE__, __LINE__);
exit(1);
}
new->client_info->master_client = 1;
new->client_info->next = NULL;
/* Start a new server thread. */
pthread_create(&new->tid, NULL, tftpd_receive_request,
(void *)new);
}
else
{
pthread_mutex_unlock(&stdin_mutex);
if (tftpd_list_num_of_thread() == 0)
{
run = 0;
if (tftpd_daemon || (tftpd_timeout == 0))
logger(LOG_NOTICE, "Terminating");
else
logger(LOG_NOTICE,
"Terminating after timeout of %d seconds",
tftpd_timeout);
}
}
}
close(0);
close(1);
close(2);
/* stop collecting stats and print them*/
stats_end();
stats_print();
/* some cleaning */
if (log_file)
free(log_file);
logger(LOG_NOTICE, "Main thread exiting");
close_logger();
exit(0);
}
/*
* This function handles the initial connection with a client. It reads
* the request from stdin and then release the stdin_mutex, so the main
* thread may listen for new clients. After that, we process options and
* call the sending or receiving function.
*
* arg is a thread_data structure pointer for that thread.
*/
void *tftpd_receive_request(void *arg)
{
struct thread_data *data = (struct thread_data *)arg;
int retval; /* hold return value for testing */
int data_size; /* returned size by recvfrom */
char string[MAXLEN]; /* hold the string we pass to the logger */
int num_of_threads;
int abort = 0; /* 1 if we need to abort because the maximum
number of threads have been reached*/
socklen_t len = sizeof(struct sockaddr);
/* Detach ourself. That way the main thread does not have to
* wait for us with pthread_join. */
pthread_detach(pthread_self());
/* Verify the number of threads */
if ((num_of_threads = tftpd_list_num_of_thread()) >= tftpd_max_thread)
abort = 1;
/* Read the first packet from stdin. */
data_size = data->data_buffer_size;
retval = tftp_get_packet(0, &data->client_info->client, data->timeout,
&data_size, data->data_buffer);
/* Add this new thread structure to the list. */
if (!abort)
stats_new_thread(tftpd_list_add(data));
/* now unlock stdin */
pthread_mutex_unlock(&stdin_mutex);
/* if the maximum number of thread is reached, too bad we abort. */
if (abort)
{
stats_abort_locked();
logger(LOG_INFO, "Maximum number of threads reached: %d",
num_of_threads);
}
else
{
/* open a socket for client communication */
data->sockfd = socket(PF_INET, SOCK_DGRAM, 0);
/* bind the socket to the interface */
getsockname(0, (struct sockaddr *)&(data->sa_in), &len);
data->sa_in.sin_port = 0;
bind(data->sockfd, (struct sockaddr *)&(data->sa_in), len);
getsockname(data->sockfd, (struct sockaddr *)&(data->sa_in), &len);
connect(data->sockfd, (struct sockaddr *)&data->client_info->client,
sizeof(data->client_info->client));
/* read options from request */
pthread_mutex_lock(&data->opt_mutex);
opt_parse_request(data->data_buffer, data_size,
data->tftp_options);
pthread_mutex_unlock(&data->opt_mutex);
opt_request_to_string(data->tftp_options, string, MAXLEN);
/* Analyse the request. */
switch (retval)
{
case GET_RRQ:
logger(LOG_NOTICE, "Serving %s to %s",
data->tftp_options[OPT_FILENAME].value,
inet_ntoa(data->client_info->client.sin_addr));
logger(LOG_DEBUG, "reveived RRQ <%s>", string);
if (tftpd_send_file(data) == OK)
stats_send_locked();
else
stats_err_locked();
break;
case GET_WRQ:
logger(LOG_NOTICE, "Fetching from %s to %s",
inet_ntoa(data->client_info->client.sin_addr),
data->tftp_options[OPT_FILENAME].value);
logger(LOG_DEBUG, "received WRQ <%s>", string);
if (tftpd_receive_file(data) == OK)
stats_recv_locked();
else
stats_err_locked();
break;
default:
logger(LOG_NOTICE, "Invalid request <%d> from %s",
retval, inet_ntoa(data->client_info->client.sin_addr));
tftp_send_error(data->sockfd, &data->client_info->client,
EBADOP);
logger(LOG_DEBUG, "sent ERROR <CODE: %s msg: %d,>", EBADOP,
tftp_errmsg[EBADOP]);
stats_err_locked();
}
}
/* make sure all data is sent to the network */
if (data->sockfd)
{
fsync(data->sockfd);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -