#include #include #include #include #include #include #include #include #include #include #include #include #include #define SEND_TIMEOUT 1 #define RECV_TIMEOUT 2 #define MSG1 "Server is very busy.\n" #define MSG2 "File is very large.\n" #define MSG3 "No such file.\n" #define MSG4 "No namespace available.\n" #ifndef VERSION #define VERSION "1.2" #endif size_t max_clients = 15; size_t max_size = 1; size_t buf_size = 32000; size_t name_len = 15; char *dir = "."; char *prog_name; char *motd; int ifd; size_t ifd_clients; int ofd; size_t ofd_clients; enum { OUTPUT, INPUT }; enum { WRITE, SEND }; int new_server(const int port, const int backlog) { int listen_fd = socket(AF_INET, SOCK_STREAM, 0); if (listen_fd < 0) return -1; struct timeval tout = {.tv_sec = RECV_TIMEOUT, .tv_usec = 0}; if (setsockopt(listen_fd, SOL_SOCKET, SO_RCVTIMEO, &tout, sizeof(tout)) < 0) goto NS_ERROR; tout.tv_sec = SEND_TIMEOUT; if (setsockopt(listen_fd, SOL_SOCKET, SO_SNDTIMEO, &tout, sizeof(tout)) < 0) goto NS_ERROR; int reuse = 1; if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEPORT, &reuse, sizeof(reuse)) < 0) goto NS_ERROR; struct sockaddr_in serv_addr; serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(port); if (bind(listen_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) goto NS_ERROR; if (listen(listen_fd, backlog) < 0) goto NS_ERROR; return listen_fd; NS_ERROR: close(listen_fd); return -1; } char *rand_string(void) { char abc[] = "asdfghjklzxcvbnmqwertyuiopQWERTYUIOPASDFGHJKLZXCVBNM1234567890"; char *res = malloc(name_len + 1); if (res == NULL) return NULL; for (size_t i = 0; i < name_len; i++) res[i] = abc[rand() % (sizeof(abc) - 1)]; res[name_len] = '\0'; return res; } char *rand_name(void) { for (int i = 0; i < 1000; i++) { char *filename = rand_string(); if (filename == NULL) continue; struct stat sb; if (stat(filename, &sb) < 0) return filename; free(filename); } return NULL; } int send_str(int fd, char *str, ssize_t size, int flag) { while (size) { ssize_t ret = 0; if (flag) ret = send(fd, str, size, 0); else ret = write(fd, str, size); if (ret < 0 && (errno != EAGAIN || errno != EINTR)) return 1; else if (ret == 0) return 0; size -= ret; str += ret; } return 0; } void upload(int cfd, pid_t par) { int ret = -1; int ffd = -1; char *buf = NULL; /* Send and create filename */ char *filename = rand_name(); if (filename == NULL) { send(cfd, MSG4, sizeof(MSG4), 0); goto UPL_ERROR; } if (send(cfd, filename, strlen(filename), 0) < 0) goto UPL_ERROR; if (send(cfd, "\n", 1, 0) < 0) goto UPL_ERROR; ffd = open(filename, O_CREAT | O_WRONLY, 0666); if (ffd < 0) goto UPL_ERROR; /* Sending */ buf = malloc(buf_size + 1); if (buf == NULL) goto UPL_ERROR; size_t tbytes = 0; while (1) { ssize_t rbytes = recv(cfd, buf, buf_size, 0); if (rbytes < 0 && (errno != EAGAIN || errno != EINTR)) goto UPL_ERROR; else if (rbytes == 0) break; tbytes += (size_t)rbytes; if (tbytes / 1024 / 1024 >= max_size) { send_str(cfd, MSG2, sizeof(MSG2), WRITE); break; } if (send_str(ffd, buf, rbytes, WRITE)) goto UPL_ERROR; } ret = 0; /* Clear */ UPL_ERROR: if (buf) free(buf); if (ffd != -1) close(ffd); if (ret == -1) fprintf(stderr, "%s: input thread: %s: %s\n", prog_name, (filename) ? filename : "NULL", strerror(errno)); if (filename) free(filename); close(cfd); kill(par, SIGUSR1); exit(0); } void load(int cfd, pid_t par) { int ret = -1; int ffd = -1; char *buf = NULL; /* For INFO cmd */ name_len += 4; /* Get filename */ char *filename = malloc(name_len + 1); if (filename == NULL) goto LD_ERROR; ssize_t rbytes = recv(cfd, filename, name_len, 0); if (rbytes <= 0) { if (rbytes == 0) ret = 0; goto LD_ERROR; } filename[rbytes] = '\0'; char *ptr = strchr(filename, '\n'); if (ptr) *ptr = '\0'; if (strstr(filename, ".") || strstr(filename, "/")) goto LD_ERROR; /* INFO */ if (!strcmp(filename, "INFO")) { char msg[336]; int ret2 = snprintf(msg, sizeof(msg), "Max file size: %zuMB\nMax clients in thread: %zu\nBuffer size: %zu\nMOTD: %s\n", max_size, max_clients, buf_size, (motd) ? motd : ""); if (send_str(cfd, msg, (ssize_t)ret2, SEND)) goto LD_ERROR; ret = 0; goto LD_ERROR; } /* Open */ ffd = open(filename, O_RDONLY); if (ffd < 0) { send_str(cfd, MSG3, sizeof(MSG3), SEND); goto LD_ERROR; } /* Sending */ buf = malloc(buf_size + 1); if (buf == NULL) goto LD_ERROR; while (1) { rbytes = read(ffd, buf, buf_size); if (rbytes < 0 && (errno != EAGAIN || errno != EINTR)) goto LD_ERROR; else if (rbytes == 0) break; if (send_str(cfd, buf, rbytes, SEND)) goto LD_ERROR; } ret = 0; /* Clear */ LD_ERROR: if (buf) free(buf); if (ffd != -1) close(ffd); if (ret == -1) fprintf(stderr, "%s: output thread: %s: %s\n", prog_name, (filename) ? filename : "NULL", strerror(errno)); if (filename) free(filename); sleep(1); close(cfd); kill(par, SIGUSR2); exit(0); } void sig_handler(int sig) { if (sig == SIGUSR1) { if (ifd_clients) ifd_clients--; } else if (sig == SIGUSR2) { if (ofd_clients) ofd_clients--; } else { close(ifd); close(ofd); exit(0); } } void Proc(int fd, int flag) { static struct sockaddr_in cli_addr; socklen_t length = sizeof(cli_addr); pid_t par = getpid(); while (1) { int cfd = accept(fd, (struct sockaddr *)&cli_addr, &length); if (cfd < 0) continue; if (flag == INPUT && ifd_clients < max_clients) { ifd_clients++; if (fork() == 0) { close(fd); upload(cfd, par); } } else if (flag == OUTPUT && ofd_clients < max_clients) { ofd_clients++; if (fork() == 0) { close(fd); load(cfd, par); } } else send(cfd, MSG1, sizeof(MSG1), 0); close(cfd); } } int main(int argc, char **argv) { srand(time(NULL)); prog_name = argv[0]; signal(SIGUSR1, sig_handler); signal(SIGUSR2, sig_handler); signal(SIGINT, sig_handler); signal(SIGTERM, sig_handler); signal(SIGCHLD, SIG_IGN); int backlog = 1; int iport = 8080; int oport = 8081; int opt; while ((opt = getopt(argc, argv, "i:o:m:d:s:n:b:t:l:V")) != -1) { switch (opt) { case 'i': iport = atoi(optarg); break; case 'o': oport = atoi(optarg); break; case 'm': sscanf(optarg, "%zu", &max_clients); break; case 'd': dir = optarg; break; case 's': sscanf(optarg, "%zu", &max_size); break; case 'n': sscanf(optarg, "%zu", &name_len); break; case 'b': sscanf(optarg, "%zu", &buf_size); break; case 't': if (strlen(optarg) > 256) { fprintf(stderr, "%s: MOTD very long\n", prog_name); return 1; } motd = optarg; break; case 'l': backlog = atoi(optarg); break; case 'V': puts("Version: " VERSION "\nWritten under WTFPL License."); return 0; default: printf("%s [iomdsnbtlV] - Simple file sharing server\n\t-i NUM Input port\tdefault: %d\n\t-o NUM Output port\tdefault: %d\n\t-m NUM Max clients\tdefault: %zu\n\t-d STR Working dir\tdefault: %s\n\t-s NUM Max file size\tdefault: %zuMB\n\t-n NUM Filename length\tdefault: %zu\n\t-b NUM Buffer size\tdefault: %zuB\n\t-t STR Set MOTD\n\t-l listen() backlog\tdefault: %d\n\t-V Print version and license\n", prog_name, iport, oport, max_clients, dir, max_size, name_len, buf_size, backlog); return 0; } } if (chdir(dir) < 0) { fprintf(stderr, "%s: chdir: %s\n", prog_name, strerror(errno)); return 1; } ifd = new_server(iport, backlog); if (ifd == -1) { fprintf(stderr, "%s: new_server: input socket: %s\n", prog_name, strerror(errno)); return 1; } ofd = new_server(oport, backlog); if (ofd == -1) { fprintf(stderr, "%s: new_server: output socket: %s\n", prog_name, strerror(errno)); return 1; } if (fork() == 0) Proc(ofd, OUTPUT); else Proc(ifd, INPUT); return 0; }