| /* Copyright 1999 Peter Schlaile. |
| * Copyright 1999-2005,2007-2009 Alain Knaff. |
| * This file is part of mtools. |
| * |
| * Mtools is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * Mtools is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with Mtools. If not, see <http://www.gnu.org/licenses/>. |
| * |
| * the floppyd daemon running on the local X-Server |
| * |
| * written by: |
| * |
| * Peter Schlaile |
| * |
| * [email protected] |
| * |
| * Large parts of the network code shamelessly stolen from |
| * transproxy by John Saunders <[email protected]> |
| * |
| * Rewritten in C by Alain Knaff. Apparently C++ is still not as |
| * portable as C. */ |
| |
| #define DEBUG 0 |
| |
| #include "sysincludes.h" |
| #include "llong.h" |
| |
| #ifdef USE_FLOPPYD |
| |
| #define USE_FLOPPYD_BUFFERED_IO 1 |
| |
| #include "sysincludes.h" |
| #include "grp.h" |
| #include <X11/Xlib.h> |
| #include <X11/Xauth.h> |
| |
| #include "floppyd_io.h" |
| |
| #ifndef SIGCLD |
| #define SIGCLD SIGCHLD |
| #endif |
| |
| /* For Linux 1.2.13 */ |
| #ifndef SOMAXCONN |
| #define SOMAXCONN 5 |
| #endif |
| |
| /* |
| To compile: |
| |
| gcc -Wall floppyd.cpp -o floppyd -lX11 |
| |
| floppyd |
| |
| Communication to the clients works the following way: |
| |
| Client sends his protocol-version. If the version between server and client |
| differ: bail out. |
| |
| After that,we send our .Xauthority-file (a maximum of MAX_XAUTHORITY_LENGTH |
| Bytes long) to the server. |
| |
| The server then checks, if it already has a .Xauthority file. If so |
| it is interpreted as LOCK-File for the floppy-device and the communication |
| gets terminated. |
| |
| (What if we have 2 floppy devices? Well. Two floppy users with different |
| home-directories should work nicely...) |
| |
| Now, the data is written to the .Xauthority file. Then we try to open |
| a connection to the local X-Server. If this fails -> bail out. |
| |
| *** |
| |
| The data packets are built as follows: |
| |
| Base-packets: 1 Dword length, then data. |
| length is in Network-Byte order. (4 Bytes) |
| |
| Commands are: 1. Packet Opcode (length 1), 1. Data packet as parameter. |
| |
| Return: 1.Packet: 1. Dword: Bytes processed, 2. Dword: Error-Code |
| |
| *** |
| |
| TODO: |
| * Implement some IOCTL calls to format floppy disks or so... |
| * Read is somewhat dirty implemented. Tries multiple times to |
| read the expected bytes from the socket stream. Don't know |
| why this is necessary. Maybe the socket stream is nonblocking |
| or something IT SHOULD NOT BE! |
| |
| */ |
| |
| |
| #define MAX_XAUTHORITY_LENGTH 3000 |
| #define MAX_DATA_REQUEST 3000000 |
| #define BUFFERED_IO_SIZE 16348 |
| |
| unsigned int mtools_lock_timeout=30; |
| |
| void serve_client(int sock, const char *const*device_name, unsigned int n_dev, |
| int close_stderr); |
| |
| |
| #ifdef USE_FLOPPYD_BUFFERED_IO |
| typedef struct io_buffer { |
| Byte out_buffer[BUFFERED_IO_SIZE]; |
| Byte in_buffer[BUFFERED_IO_SIZE]; |
| |
| size_t in_valid; |
| size_t in_start; |
| size_t out_valid; |
| |
| int handle; |
| } *io_buffer; |
| |
| static io_buffer new_io_buffer (int _handle) { |
| io_buffer buffer; |
| |
| buffer = New(struct io_buffer); |
| |
| buffer->handle = _handle; |
| buffer->in_valid = buffer->in_start = 0; |
| buffer->out_valid = 0; |
| return buffer; |
| } |
| |
| |
| static void flush(io_buffer buffer) { |
| if (buffer->out_valid) { |
| if(write(buffer->handle, buffer->out_buffer, buffer->out_valid) < 0) { |
| perror("floppyd flush"); |
| } |
| buffer->out_valid = 0; |
| } |
| } |
| |
| static void free_io_buffer(io_buffer buffer) { |
| flush(buffer); |
| free(buffer); |
| } |
| |
| |
| static size_t buf_read (io_buffer buf, Byte* buffer, size_t nbytes) { |
| size_t ret; |
| |
| if (nbytes <= buf->in_valid) { |
| memcpy(buffer, buf->in_buffer+buf->in_start, nbytes); |
| buf->in_valid -= nbytes; |
| buf->in_start += nbytes; |
| ret = nbytes; |
| } else { |
| if (buf->in_valid) |
| memcpy(buffer, buf->in_buffer+buf->in_start, |
| buf->in_valid); |
| nbytes -= buf->in_valid; |
| buffer += buf->in_valid; |
| if (nbytes > BUFFERED_IO_SIZE) { |
| ssize_t rval = read(buf->handle, buffer, nbytes); |
| if (rval >= 0) { |
| ret = (size_t) rval + buf->in_valid; |
| } else { |
| perror("read error"); |
| exit(1); |
| } |
| buf->in_valid = buf->in_start = 0; |
| } else { |
| ssize_t rval = read(buf->handle, buf->in_buffer, |
| BUFFERED_IO_SIZE); |
| if (rval >= 0) { |
| if (rval < (ssize_t) nbytes) { |
| memcpy(buffer, buf->in_buffer, |
| (size_t) rval); |
| ret = (size_t) rval + buf->in_valid; |
| buf->in_valid = buf->in_start = 0; |
| } else { |
| size_t a; |
| memcpy(buffer, buf->in_buffer, nbytes); |
| buf->in_start = nbytes; |
| a = buf->in_valid; |
| buf->in_valid = (size_t) rval-nbytes; |
| ret = a + nbytes; |
| } |
| } else { |
| perror("read error"); |
| exit(1); |
| } |
| } |
| } |
| return ret; |
| } |
| |
| static ssize_t buf_write(io_buffer buf, void* buffer, size_t nbytes) { |
| if (buf->out_valid + nbytes > BUFFERED_IO_SIZE) { |
| flush(buf); |
| return write(buf->handle, buffer, nbytes); |
| } |
| memcpy(buf->out_buffer+buf->out_valid, buffer, nbytes); |
| buf->out_valid += nbytes; |
| return (ssize_t) nbytes; |
| } |
| |
| |
| |
| #else |
| |
| typedef int io_buffer; |
| |
| io_buffer new_io_buffer (int handle) { |
| return handle; |
| } |
| |
| |
| size_t buf_read (io_buffer handle, Byte* buffer, size_t nbytes) { |
| return (read(handle, buffer, nbytes)); |
| } |
| |
| ssize_t buf_write(io_buffer handle, void* buffer, size_t nbytes) { |
| return (write(handle, buffer, nbytes)); |
| } |
| |
| |
| void free_io_buffer(io_buffer buffer) { } |
| |
| |
| void flush(io_buffer buffer) { } |
| |
| #endif |
| |
| typedef struct Packet { |
| Byte* data; |
| Dword len; |
| Dword alloc_size; |
| } *Packet; |
| |
| #include "byte_dword.h" |
| |
| static Dword read_dword(io_buffer fp) |
| { |
| Byte val[4]; |
| if (buf_read(fp, val, 4) < 4) { |
| return 0xffffffff; |
| } |
| |
| return byte2dword(val); |
| } |
| |
| static void write_dword(io_buffer fp, Dword parm) |
| { |
| Byte val[4]; |
| |
| dword2byte(parm, val); |
| |
| buf_write(fp, val,4); |
| } |
| |
| |
| static Packet newPacket(void) |
| { |
| Packet packet; |
| |
| packet = New(struct Packet); |
| packet->data = NULL; |
| packet->len = packet->alloc_size = 0; |
| return packet; |
| } |
| |
| |
| static void destroyPacket(Packet packet) |
| { |
| if(packet->data) |
| free(packet->data); |
| free(packet); |
| } |
| |
| static void kill_packet(Packet packet) |
| { |
| if(packet->data) |
| free(packet->data); |
| packet->data = NULL; |
| packet->len = 0; |
| packet->alloc_size = 0; |
| } |
| |
| static void make_new(Packet packet, Dword l) |
| { |
| if (l < packet->alloc_size) { |
| packet->len = l; |
| return; |
| } |
| kill_packet(packet); |
| packet->len = packet->alloc_size = l; |
| packet->data = malloc(l); |
| memset(packet->data, 0, l); |
| } |
| |
| static char send_packet(Packet packet, io_buffer fp) |
| { |
| if (packet->data) { |
| write_dword(fp, packet->len); |
| buf_write(fp, packet->data, packet->len); |
| flush(fp); |
| #if DEBUG |
| fprintf(stderr, "send_packet(): Size: %li\n", packet->len); |
| #endif |
| |
| #if DEBUG |
| fprintf(stderr, "send_packet(): "); |
| for (int i = 0; i < packet->len; i++) { |
| fprintf(stderr, "%d ", packet->data[i]); |
| } |
| fprintf(stderr, "\n"); |
| #endif |
| |
| } |
| return (packet->data != NULL); |
| } |
| |
| static char recv_packet(Packet packet, io_buffer fp, Dword maxlength) |
| { |
| Dword start; |
| size_t l; |
| Dword length = read_dword(fp); |
| #if DEBUG |
| fprintf(stderr, "recv_packet(): Size: %li\n", length); |
| #endif |
| if (length > maxlength || length == 0xffffffff ) { |
| return 0; |
| } |
| make_new(packet, length); |
| l = 0; |
| for (start = 0; start < length; start += l) { |
| l = buf_read(fp, packet->data+start, length-start); |
| if (l == 0) { |
| return 0; |
| } |
| } |
| if (packet->len == 0) { |
| return 0; |
| } |
| #if DEBUG |
| fprintf(stderr, "*** read: %li\n", packet->len); |
| #endif |
| |
| #if DEBUG |
| fprintf(stderr, "recv_packet(): "); |
| for (i = 0; i < packet->len; i++) { |
| fprintf(stderr, "%d ", packet->data[i]); |
| } |
| fprintf(stderr, "\n"); |
| #endif |
| return 1; |
| } |
| |
| static ssize_t read_packet(Packet packet, int fd, Dword length) { |
| ssize_t ret; |
| make_new(packet, length); |
| ret = read(fd, packet->data, packet->len); |
| if(ret < 0) |
| return ret; |
| packet->len = (Dword) ret; |
| return 0; |
| } |
| |
| static int write_packet(Packet packet, int fd) { |
| return (int)write(fd, packet->data, packet->len); |
| } |
| |
| static void put_dword(Packet packet, int my_index, Dword val) { |
| dword2byte(val, packet->data+my_index); |
| } |
| |
| static void put_qword(Packet packet, int my_index, Qword val) { |
| qword2byte(val, packet->data+my_index); |
| } |
| |
| static Dword get_dword(Packet packet, int my_index) { |
| return byte2dword(packet->data+my_index); |
| } |
| |
| static Qword get_qword(Packet packet, int my_index) { |
| return byte2qword(packet->data+my_index); |
| } |
| |
| static Dword get_length(Packet packet) { |
| return packet->len; |
| } |
| |
| static int eat(unsigned char **ptr, size_t *len, unsigned char c) { |
| /* remove length + size code + terminating 0 */ |
| if (*len < c + 3u) |
| return -1; |
| (*ptr) += c + 2; |
| (*len) -= c + 2; |
| return 0; |
| } |
| |
| static const char *dispName; |
| |
| static char XAUTHORITY[]="XAUTHORITY"; |
| |
| static char do_auth(io_buffer sock, unsigned int *version) |
| { |
| int fd; |
| Display* displ; |
| Packet proto_version = newPacket(); |
| Packet mit_cookie; |
| unsigned char *ptr; |
| size_t len; |
| |
| char authFile[41]="/tmp/floppyd.XXXXXX"; |
| unsigned char template[4096]; |
| |
| Packet reply = newPacket(); |
| |
| make_new(reply, 4); |
| |
| if (!recv_packet(proto_version, sock, 4)) { |
| put_dword(reply, 0, AUTH_PACKETOVERSIZE); |
| send_packet(reply, sock); |
| destroyPacket(reply); |
| destroyPacket(proto_version); |
| return 0; |
| } |
| |
| *version = get_dword(proto_version, 0); |
| if (*version > FLOPPYD_PROTOCOL_VERSION || |
| *version < FLOPPYD_PROTOCOL_VERSION_OLD) { |
| /* fail if client requests a newer version than us */ |
| put_dword(reply, 0, AUTH_WRONGVERSION); |
| send_packet(reply, sock); |
| destroyPacket(reply); |
| destroyPacket(proto_version); |
| return 0; |
| } |
| |
| if(*version == FLOPPYD_PROTOCOL_VERSION_OLD) { |
| put_dword(reply, 0, AUTH_SUCCESS); |
| } else { |
| Dword cap = FLOPPYD_CAP_EXPLICIT_OPEN; |
| if(sizeof(mt_off_t) >= 8) { |
| cap |= FLOPPYD_CAP_LARGE_SEEK; |
| } |
| make_new(reply, 12); |
| put_dword(reply, 0, AUTH_SUCCESS); |
| put_dword(reply, 4, FLOPPYD_PROTOCOL_VERSION); |
| put_dword(reply, 8, cap); |
| } |
| send_packet(reply, sock); |
| destroyPacket(proto_version); |
| |
| make_new(reply, 4); |
| mit_cookie = newPacket(); |
| if (!recv_packet(mit_cookie, sock, MAX_XAUTHORITY_LENGTH)) { |
| put_dword(reply, 0, AUTH_PACKETOVERSIZE); |
| send_packet(reply, sock); |
| destroyPacket(reply); |
| destroyPacket(mit_cookie); |
| return 0; |
| } |
| |
| umask(077); |
| fd = mkstemp(authFile); |
| if(fd == -1) { |
| /* Different error than file exists */ |
| put_dword(reply, 0, AUTH_DEVLOCKED); |
| send_packet(reply, sock); |
| close(fd); |
| destroyPacket(reply); |
| destroyPacket(mit_cookie); |
| return 0; |
| } |
| #ifdef HAVE_SETENV |
| setenv(XAUTHORITY, authFile, 1); |
| #else |
| { |
| char *buffer=malloc(strlen(XAUTHORITY)+strlen(authFile)+2); |
| strcpy(buffer, XAUTHORITY); |
| strcat(buffer, "="); |
| strcat(buffer, authFile); |
| putenv(buffer); |
| } |
| #endif |
| |
| ptr = template; |
| ptr[4095] = 0; |
| *ptr++ = 1; |
| *ptr++ = 0; |
| *ptr++ = 0; |
| gethostname((char*)ptr+1, 4088); |
| len = strlen((char*)ptr+1); |
| *ptr++ = (unsigned char) len; |
| ptr += len; |
| *ptr++ = 0; |
| *ptr++ = 1; |
| *ptr++ = '0'; /* Display number */ |
| *ptr++ = '\0'; |
| |
| if(write(fd, template, len+8) < (ssize_t) (len + 8)) { |
| close(fd); |
| return 0; |
| } |
| ptr = mit_cookie->data; |
| len = mit_cookie->len; |
| |
| if (eat(&ptr,&len,1) || /* the "type" */ |
| eat(&ptr,&len,*ptr) || /* the hostname */ |
| eat(&ptr,&len,*ptr)) { /* the display number */ |
| destroyPacket(mit_cookie); |
| unlink(XauFileName()); |
| put_dword(reply, 0, AUTH_BADPACKET); |
| send_packet(reply, sock); |
| destroyPacket(reply); |
| return 0; |
| } |
| |
| if(write(fd, ptr, len) < (ssize_t) len) { |
| close(fd); |
| return 0; |
| } |
| close(fd); |
| |
| destroyPacket(mit_cookie); |
| |
| displ = XOpenDisplay(dispName); |
| if (!displ) { |
| unlink(XauFileName()); |
| put_dword(reply, 0, AUTH_AUTHFAILED); |
| send_packet(reply, sock); |
| destroyPacket(reply); |
| return 0; |
| } |
| XCloseDisplay(displ); |
| |
| put_dword(reply, 0, AUTH_SUCCESS); |
| send_packet(reply, sock); |
| destroyPacket(reply); |
| unlink(XauFileName()); |
| return 1; |
| } |
| |
| /* |
| * Return the port number, in network order, of the specified service. |
| */ |
| static uint16_t getportnum(char *portnum) |
| { |
| char *digits = portnum; |
| struct servent *serv; |
| uint16_t port; |
| |
| for (port = 0; isdigit(*digits); ++digits) |
| { |
| port = (port * 10) + (uint8_t)(*digits - '0'); |
| } |
| |
| if ((*digits != '\0') || (port <= 0)) |
| { |
| if ((serv = getservbyname(portnum, "tcp")) != NULL) |
| { |
| port = ntohs((uint16_t)serv->s_port); |
| } |
| else |
| { |
| port = 0; |
| } |
| endservent(); |
| } |
| |
| #if DEBUG |
| fprintf(stderr, "Port lookup %s -> %hd\n", portnum, port); |
| #endif |
| |
| return (port); |
| } |
| |
| /* |
| * Return the IP address of the specified host. |
| */ |
| static in_addr_t getipaddress(char *ipaddr) |
| { |
| struct hostent *host; |
| in_addr_t ip; |
| |
| if (((ip = inet_addr(ipaddr)) == INADDR_NONE) |
| && |
| (strcmp(ipaddr, "255.255.255.255") != 0)) |
| { |
| if ((host = gethostbyname(ipaddr)) != NULL) |
| { |
| memcpy(&ip, host->h_addr, sizeof(ip)); |
| } |
| endhostent(); |
| } |
| |
| #if DEBUG |
| fprintf(stderr, "IP lookup %s -> 0x%08lx\n", ipaddr, ip); |
| #endif |
| |
| return (ip); |
| } |
| |
| /* |
| * Find the userid of the specified user. |
| */ |
| static uid_t getuserid(char *user) |
| { |
| struct passwd *pw; |
| uid_t uid; |
| |
| if ((pw = getpwnam(user)) != NULL) |
| { |
| uid = pw->pw_uid; |
| } |
| else if (*user == '#') |
| { |
| uid = (uid_t)atoi(&user[1]); |
| } |
| else |
| { |
| #ifdef HAVE_GETUSERID |
| id = getuserid("nobody"); |
| #else |
| uid = 65535; |
| #endif |
| } |
| |
| #if DEBUG |
| fprintf(stderr, "User lookup %s -> %d\n", user, uid); |
| #endif |
| |
| endpwent(); |
| |
| return (uid); |
| } |
| |
| /* |
| * Find the groupid of the specified user. |
| */ |
| static uid_t getgroupid(uid_t uid) |
| { |
| struct passwd *pw; |
| gid_t gid; |
| |
| if ((pw = getpwuid(uid)) != NULL) |
| { |
| gid = pw->pw_gid; |
| } |
| else |
| { |
| #ifdef HAVE_GETGROUPID |
| id = getgroupid(uid); |
| #else |
| gid = 65535; |
| #endif |
| } |
| |
| #if DEBUG |
| fprintf(stderr, "Group lookup %d -> %d\n", uid, gid); |
| #endif |
| |
| endpwent(); |
| |
| return (gid); |
| } |
| |
| /* |
| * Bind to the specified ip and port. |
| */ |
| static int bind_to_port(in_addr_t bind_ip, uint16_t bind_port) |
| { |
| struct sockaddr_in addr; |
| int sock; |
| |
| /* |
| * Allocate a socket. |
| */ |
| if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) |
| { |
| perror("socket()"); |
| exit(1); |
| } |
| |
| /* |
| * Set the SO_REUSEADDR option for debugging. |
| */ |
| { |
| int on = 1; |
| if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, |
| (char *)&on, sizeof(on)) < 0) { |
| perror("setsockopt"); |
| exit(1); |
| } |
| } |
| |
| /* |
| * Set the address to listen to. |
| */ |
| addr.sin_family = AF_INET; |
| addr.sin_port = htons(bind_port); |
| addr.sin_addr.s_addr = bind_ip; |
| |
| /* |
| * Bind our socket to the above address. |
| */ |
| if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) |
| { |
| perror("bind()"); |
| exit(1); |
| } |
| |
| /* |
| * Establish a large listen backlog. |
| */ |
| if (listen(sock, SOMAXCONN) < 0) |
| { |
| perror("listen()"); |
| exit(1); |
| } |
| |
| return (sock); |
| } |
| |
| static int sockethandle_now = -1; |
| |
| /* |
| * Catch alarm signals and exit. |
| */ |
| static void alarm_signal(int a UNUSEDP) NORETURN; |
| static void alarm_signal(int a UNUSEDP) |
| { |
| if (sockethandle_now != -1) { |
| close(sockethandle_now); |
| sockethandle_now = -1; |
| unlink(XauFileName()); |
| } |
| exit(1); |
| } |
| |
| |
| /* |
| * This is the main loop when running as a server. |
| */ |
| static void server_main_loop(int sock, const char *const*device_name, |
| unsigned int n_dev) NORETURN; |
| static void server_main_loop(int sock, const char *const*device_name, |
| unsigned int n_dev) |
| { |
| struct sockaddr_in addr; |
| unsigned int len; |
| |
| /* |
| * Ignore dead servers so no zombies should be left hanging. |
| */ |
| signal(SIGCLD, SIG_IGN); |
| |
| for (;;) { |
| int new_sock; |
| /* |
| * Accept an incoming connection. |
| */ |
| len = sizeof(addr); |
| while ((new_sock = accept(sock, (struct sockaddr *)&addr, &len)) < 0){} |
| |
| /* |
| * Create a new process to handle the connection. |
| */ |
| #if DEBUG == 0 |
| switch (fork()) { |
| case -1: |
| /* |
| * Under load conditions just ignore new connections. |
| */ |
| break; |
| |
| case 0: |
| /* |
| * Start the proxy work in the new socket. |
| */ |
| #endif |
| serve_client(new_sock, device_name, n_dev, 0); |
| exit(0); |
| #if DEBUG == 0 |
| } |
| #endif |
| /* |
| * Close the socket as the child does the handling. |
| */ |
| close(new_sock); |
| new_sock = -1; |
| } |
| } |
| |
| /* |
| * Print some basic help information. |
| */ |
| static void usage(char *prog, const char *opt, int ret) NORETURN; |
| static void usage(char *prog, const char *opt, int ret) |
| { |
| if (opt) |
| { |
| fprintf(stderr, "%s: %s\n", prog, opt); |
| } |
| fprintf(stderr, "usage: %s [-s port [-r user] [-b ipaddr]] devicename [Names of local host]\n", |
| prog); |
| fprintf(stderr, " -d Run as a server (default port 5703 + DISPLAY)\n"); |
| fprintf(stderr, " -s port Run as a server bound to the specified port.\n"); |
| fprintf(stderr, " -r user Run as the specified user in server mode.\n"); |
| fprintf(stderr, " -b ipaddr Bind to the specified ipaddr in server mode.\n"); |
| fprintf(stderr, " -l Do not attempt to connect to localhost:0 to validate connection\n"); |
| exit(ret); |
| } |
| |
| |
| static char *makeDisplayName(int dispNr) |
| { |
| char result[80]; |
| sprintf(result, ":%d.0", dispNr); |
| return strdup(result); |
| } |
| |
| int main (int argc, char** argv) |
| { |
| int sockfd = 0; |
| int arg; |
| int run_as_server = 0; |
| in_addr_t bind_ip = INADDR_ANY; |
| uint16_t bind_port = 0; |
| uid_t run_uid = 65535; |
| gid_t run_gid = 65535; |
| char* username = strdup("nobody"); |
| int sock; |
| |
| const char *const* device_name = NULL; |
| const char *floppy0 = "/dev/fd0"; |
| unsigned int n_dev; |
| |
| |
| /* |
| * Parse the command line arguments. |
| */ |
| if(argc > 1 && !strcmp(argv[0], "--help")) |
| usage(argv[0], NULL, 0); |
| while ((arg = getopt(argc, argv, "ds:r:b:x:h")) != EOF) |
| { |
| switch (arg) |
| { |
| case 'd': |
| run_as_server = 1; |
| break; |
| case 's': |
| run_as_server = 1; |
| bind_port = getportnum(optarg); |
| break; |
| |
| case 'r': |
| free(username); username = strdup(optarg); |
| run_uid = getuserid(optarg); |
| run_gid = getgroupid(run_uid); |
| break; |
| |
| case 'b': |
| run_as_server = 1; |
| bind_ip = getipaddress(optarg); |
| break; |
| case 'x': |
| dispName = strdup(optarg); |
| break; |
| |
| case 'h': |
| usage(argv[0], NULL, 0); |
| case '?': |
| usage(argv[0], NULL, 1); |
| } |
| } |
| |
| if(optind < argc) { |
| device_name = (const char * const *) argv + optind; |
| n_dev = (unsigned int) (argc - optind); |
| } else { |
| device_name = &floppy0; |
| n_dev = 1; |
| } |
| |
| if(dispName == NULL) |
| dispName = getenv("DISPLAY"); |
| if(dispName==NULL && bind_port != 0) |
| dispName=makeDisplayName((unsigned short)(bind_port - 5703)); |
| if(dispName==NULL) |
| dispName=":0"; |
| |
| if(bind_port == 0) { |
| char *p = strchr(dispName,':'); |
| bind_port = FLOPPYD_DEFAULT_PORT; |
| if(p != NULL) |
| bind_port += atoi(p+1); |
| } |
| |
| if(!run_as_server) { |
| struct sockaddr_in addr; |
| unsigned int len = sizeof(addr); |
| |
| /* try to find out port that we are connected to */ |
| if(getsockname(0, (struct sockaddr*) &addr, &len) >= 0 && |
| len == sizeof(addr)) { |
| bind_port = ntohs(addr.sin_port); |
| } |
| } |
| |
| umask(0077); |
| |
| /* |
| * Test to make sure required args were provided and are valid. |
| */ |
| if (run_as_server && (bind_ip == INADDR_NONE)) { |
| usage(argv[0], "The server ipaddr is invalid.", 1); |
| } |
| if (run_as_server && (bind_port == 0)) { |
| usage(argv[0], "No server port was specified (or it was invalid).", 1); |
| } |
| |
| |
| /* |
| * See if we should run as a server. |
| */ |
| if (run_as_server) { |
| /* |
| * Start by binding to the port, the child inherits this socket. |
| */ |
| sock = bind_to_port(bind_ip, bind_port); |
| |
| /* |
| * Start a server process. When DEBUG is defined, just run |
| * in the foreground. |
| */ |
| #if DEBUG |
| switch (0) |
| #else |
| switch (fork()) |
| #endif |
| { |
| case -1: |
| perror("fork()"); |
| exit(1); |
| |
| case 0: |
| /* |
| * Ignore some signals. |
| */ |
| signal(SIGHUP, SIG_IGN); |
| #if DEBUG |
| signal(SIGINT, SIG_IGN); |
| #endif |
| signal(SIGQUIT, SIG_IGN); |
| signal(SIGTSTP, SIG_IGN); |
| signal(SIGCONT, SIG_IGN); |
| signal(SIGPIPE, alarm_signal); |
| /*signal(SIGALRM, alarm_signal);*/ |
| |
| /* |
| * Drop back to an untrusted user. |
| */ |
| setgid(run_gid); |
| initgroups(username, run_gid); |
| setuid(run_uid); |
| |
| /* |
| * Start a new session and group. |
| */ |
| setsid(); |
| #ifdef HAVE_SETPGRP |
| #ifdef SETPGRP_VOID |
| setpgrp(); |
| #else |
| setpgrp(0,0); |
| #endif |
| #endif |
| #if DEBUG |
| close(2); |
| open("/dev/null", O_WRONLY); |
| #endif |
| /* |
| * Handle the server main loop. |
| */ |
| server_main_loop(sock, device_name, |
| n_dev); |
| } |
| |
| /* |
| * Parent exits at this stage. |
| */ |
| exit(0); |
| } |
| |
| signal(SIGHUP, alarm_signal); |
| #if DEBUG == 0 |
| signal(SIGINT, alarm_signal); |
| #endif |
| signal(SIGQUIT, alarm_signal); |
| signal(SIGTERM, alarm_signal); |
| signal(SIGTSTP, SIG_IGN); |
| signal(SIGCONT, SIG_IGN); |
| signal(SIGPIPE, alarm_signal); |
| /*signal(SIGALRM, alarm_signal);*/ |
| |
| /* Starting from inetd */ |
| |
| serve_client(sockfd, device_name, n_dev, 1); |
| return 0; |
| } |
| |
| static void send_reply(int rval, io_buffer sock, Dword len) { |
| Packet reply = newPacket(); |
| |
| make_new(reply, 8); |
| put_dword(reply, 0, len); |
| if (rval == -1) { |
| put_dword(reply, 4, 0); |
| } else { |
| put_dword(reply, 4, (Dword) errno); |
| } |
| send_packet(reply, sock); |
| destroyPacket(reply); |
| } |
| |
| static void send_reply64(int rval, io_buffer sock, mt_off_t len) { |
| Packet reply = newPacket(); |
| |
| make_new(reply, 12); |
| put_qword(reply, 0, (Qword) len); |
| if (rval == -1) { |
| put_dword(reply, 8, 0); |
| } else { |
| put_dword(reply, 8, (Dword) errno); |
| } |
| send_packet(reply, sock); |
| destroyPacket(reply); |
| } |
| |
| static void cleanup(int x UNUSEDP) NORETURN; |
| static void cleanup(int x UNUSEDP) { |
| unlink(XauFileName()); |
| exit(-1); |
| } |
| |
| #include "lockdev.h" |
| |
| void serve_client(int sockhandle, const char *const*device_name, |
| unsigned int n_dev, int close_stderr) { |
| Packet opcode; |
| Packet parm; |
| |
| int readOnly; |
| int devFd; |
| io_buffer sock; |
| int stopLoop; |
| unsigned int version; |
| int needSendReply=0; |
| int rval=0; |
| |
| /* |
| * Set the keepalive socket option to on. |
| */ |
| { |
| int on = 1; |
| if(setsockopt(sockhandle, SOL_SOCKET, |
| SO_KEEPALIVE, (char *)&on, sizeof(on)) < 0) { |
| perror("setsockopt"); |
| exit(1); |
| } |
| |
| } |
| |
| |
| #if DEBUG == 0 |
| if(close_stderr) { |
| close(2); |
| open("/dev/null", O_WRONLY); |
| } |
| #endif |
| |
| sock = new_io_buffer(sockhandle); |
| |
| /* |
| * Allow 60 seconds for any activity. |
| */ |
| alarm(60); |
| |
| version = 0; |
| if (!do_auth(sock, &version)) { |
| free_io_buffer(sock); |
| return; |
| } |
| alarm(0); |
| |
| |
| signal(SIGTERM, cleanup); |
| signal(SIGALRM, cleanup); |
| |
| |
| |
| sockethandle_now = sockhandle; |
| |
| |
| opcode = newPacket(); |
| parm = newPacket(); |
| |
| devFd = -1; |
| readOnly = 1; |
| |
| stopLoop = 0; |
| if(version == FLOPPYD_PROTOCOL_VERSION_OLD) { |
| /* old protocol */ |
| readOnly = 0; |
| devFd = open(device_name[0], O_RDWR|O_LARGEFILE); |
| |
| if (devFd < 0) { |
| readOnly = 1; |
| devFd = open(device_name[0], |
| O_RDONLY|O_LARGEFILE); |
| } |
| if(devFd < 0) { |
| send_reply(0, sock, devFd >= 0 ? 0 : DWORD_ERR); |
| stopLoop = 1; |
| } |
| lock_dev(devFd, !readOnly, NULL); |
| } |
| |
| |
| while(!stopLoop) { |
| uint32_t dev_nr = 0; |
| /* |
| * Allow 60 seconds for any activity. |
| */ |
| /*alarm(60);*/ |
| |
| if (!recv_packet(opcode, sock, 1)) { |
| break; |
| } |
| /* if(opcode->data[0] != OP_CLOSE)*/ |
| recv_packet(parm, sock, MAX_DATA_REQUEST); |
| |
| /* on its own, floppyd does several small writes, |
| * running into the performance issue described in |
| * https://eklitzke.org/the-caveats-of-tcp-nodelay |
| * Cork fixes this by stalling the small writes until |
| * floppyd has assembled its entire message, thus |
| * preventing the bad interaction between Nagle's |
| * algorithm and Linux' delayed ACKs. Another fix |
| * would be to not send the small batches immediately, |
| * but instead keep them around and submit them to the |
| * kernel in one go using writev. However, writev is |
| * not available everywhere |
| */ |
| cork(sock->handle, 1); |
| |
| switch(opcode->data[0]) { |
| case OP_OPRO: |
| if(get_length(parm) >= 4) |
| dev_nr = get_dword(parm,0); |
| else |
| dev_nr = 0; |
| if(dev_nr >= n_dev) { |
| send_reply(0, sock, DWORD_ERR); |
| break; |
| } |
| |
| devFd = open(device_name[dev_nr], |
| O_RDONLY | O_LARGEFILE); |
| #if DEBUG |
| fprintf(stderr, "Device opened\n"); |
| #endif |
| if(devFd >= 0 && lock_dev(devFd, 0, NULL)) { |
| send_reply(0, sock, DWORD_ERR); |
| break; |
| } |
| send_reply(0, sock, |
| devFd >= 0 ? 0 : DWORD_ERR); |
| readOnly = 1; |
| break; |
| case OP_OPRW: |
| if(get_length(parm) >= 4) |
| dev_nr = get_dword(parm,0); |
| else |
| dev_nr = 0; |
| if(dev_nr >= n_dev) { |
| send_reply(0, sock, DWORD_ERR); |
| break; |
| } |
| devFd = open(device_name[dev_nr], O_RDWR); |
| if(devFd >= 0 && lock_dev(devFd, 1, NULL)) { |
| send_reply(0, sock, DWORD_ERR); |
| break; |
| } |
| send_reply(0, sock, |
| devFd >= 0 ? 0 : DWORD_ERR); |
| readOnly = 0; |
| break; |
| case OP_READ: |
| #if DEBUG |
| fprintf(stderr, "READ:\n"); |
| #endif |
| if(read_packet(parm, devFd, |
| get_dword(parm, 0)) < 0) |
| send_reply(devFd, sock, DWORD_ERR); |
| else { |
| send_reply(devFd, sock, |
| get_length(parm)); |
| send_packet(parm, sock); |
| } |
| break; |
| case OP_WRITE: |
| #if DEBUG |
| fprintf(stderr, "WRITE:\n"); |
| #endif |
| if(readOnly) { |
| errno = -EROFS; |
| rval = -1; |
| } else { |
| rval = write_packet(parm, devFd); |
| } |
| send_reply(devFd, sock, (Dword) rval); |
| break; |
| case OP_SEEK: |
| #if DEBUG |
| fprintf(stderr, "SEEK:\n"); |
| #endif |
| |
| lseek(devFd, |
| (off_t) get_dword(parm, 0), |
| (int) get_dword(parm, 4)); |
| send_reply(devFd, |
| sock, |
| (Dword) lseek(devFd, 0, SEEK_CUR)); |
| break; |
| case OP_SEEK64: |
| if(sizeof(mt_off_t) < 8) { |
| #if DEBUG |
| fprintf(stderr, "64 bit requested where not available!\n"); |
| #endif |
| errno = EINVAL; |
| send_reply(devFd, sock, DWORD_ERR); |
| break; |
| } |
| #if DEBUG |
| fprintf(stderr, "SEEK64:\n"); |
| #endif |
| mt_lseek(devFd, |
| (mt_off_t) get_qword(parm,0), |
| (int) get_dword(parm,8)); |
| send_reply64(devFd, |
| sock, |
| mt_lseek(devFd, 0, SEEK_CUR)); |
| break; |
| case OP_FLUSH: |
| #if DEBUG |
| fprintf(stderr, "FLUSH:\n"); |
| #endif |
| fsync(devFd); |
| send_reply(devFd, sock, 0); |
| break; |
| case OP_CLOSE: |
| #if DEBUG |
| fprintf(stderr, "CLOSE:\n"); |
| #endif |
| |
| close(devFd); |
| needSendReply = 1; |
| rval = devFd; |
| devFd = -1; |
| stopLoop = 1; |
| break; |
| case OP_IOCTL: |
| /* Unimplemented for now... */ |
| break; |
| default: |
| #if DEBUG |
| fprintf(stderr, "Invalid Opcode!\n"); |
| #endif |
| errno = EINVAL; |
| send_reply(devFd, sock, DWORD_ERR); |
| break; |
| } |
| cork(sock->handle, 0); |
| kill_packet(parm); |
| alarm(0); |
| } |
| |
| |
| |
| #if DEBUG |
| fprintf(stderr, "Closing down...\n"); |
| #endif |
| |
| if (devFd >= 0) { |
| close(devFd); |
| devFd = -1; |
| } |
| |
| free_io_buffer(sock); |
| |
| /* remove "Lock"-File */ |
| unlink(XauFileName()); |
| |
| if(needSendReply) |
| send_reply(rval, sock, 0); |
| destroyPacket(opcode); |
| destroyPacket(parm); |
| } |
| |
| #else |
| #include <stdio.h> |
| |
| int main(int argc, char **argv) |
| { |
| puts("Floppyd support not included!"); |
| return -1; |
| } |
| |
| #endif |