about summary refs log tree commit diff stats
path: root/nbtpd/handlers.c
diff options
context:
space:
mode:
Diffstat (limited to 'nbtpd/handlers.c')
-rw-r--r--nbtpd/handlers.c535
1 files changed, 535 insertions, 0 deletions
diff --git a/nbtpd/handlers.c b/nbtpd/handlers.c
new file mode 100644
index 0000000..d42382e
--- /dev/null
+++ b/nbtpd/handlers.c
@@ -0,0 +1,535 @@
+#ifdef __linux__
+#define _POSIX_C_SOURCE 200809L
+#define _DEFAULT_SOURCE
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <syslog.h>
+#include <limits.h>
+#include <errno.h>
+#include <unistd.h>
+#ifdef __FreeBSD__
+#include <sys/param.h>
+#endif
+
+#include "handlers.h"
+#include "packet.h"
+#include "netascii.h"
+
+inline nbd_opmode get_mode(char *mode) {
+	nbd_opmode opmode;
+	if (strncmp(mode, (char *)&"netascii", NBD_NBTPD_ARGS_MODE_MAX) == 0) {
+		opmode = NETASCII;
+	} else if (strncmp(mode, (char *)&"octet", NBD_NBTPD_ARGS_MODE_MAX) == 0) {
+		opmode = OCTET;
+	} else if (strncmp(mode, (char *)&"mail", NBD_NBTPD_ARGS_MODE_MAX) == 0) {
+		opmode = MAIL;
+	} else {
+		opmode = NOT_FOUND;
+	}
+	return opmode;
+}
+
+int makesock(nbd_nbtpd_args *argptr) {
+	int s = socket(AF_INET, SOCK_DGRAM, 0);
+	if (s <= 0) {
+		syslog(LOG_ERR, "unable to define socket: %s", strerror(errno));
+		return -1;
+	}
+	struct timeval timeout = { 30, 0 };
+	if (setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) {
+		syslog(LOG_ERR, "unable to set socket timeout: %s", strerror(errno));
+		close(s);
+		return -1;
+	}
+	if (connect(s, (struct sockaddr *)&(argptr->client), sizeof(struct sockaddr)) < 0) {
+		syslog(
+			LOG_ERR,
+			"unable to connect to client %s: %s",
+			inet_ntoa(argptr->client.sin_addr),
+			strerror(errno)
+		);
+		close(s);
+		return -1;
+	}
+	return s;
+}
+
+char *checkpath(nbd_nbtpd_args *argptr, uint8_t read) {
+	char *fname = NULL, *wd = NULL, *path = NULL, *ptr = NULL;
+	char **parts = NULL;
+	if (!is_netascii_str((char *)&argptr->path)) {
+		argptr->err = 1;
+		goto cleanup;
+	}
+	if (read) {
+		fname = realpath((char *)&argptr->path, NULL);
+	} else {
+		//TODO: figure out how to canonicalize non-existent path
+		char *tok = NULL;
+		parts = malloc(sizeof(parts) * NBD_NBTPD_ARGS_PATH_MAX);
+		if (parts == NULL) {
+			goto cleanup;
+		}
+		memset(parts, '\0', sizeof(parts) * NBD_NBTPD_ARGS_PATH_MAX);
+		ptr = (char *)&argptr->path;
+		for (int i = 0; ((tok = strsep(&ptr, "/")) != NULL); i++ ) {
+			parts[i] = tok;
+		}
+		path = malloc(NBD_NBTPD_ARGS_PATH_MAX);
+		if (path == NULL) {
+			goto cleanup;
+		}
+		memset(path, '\0', NBD_NBTPD_ARGS_PATH_MAX);
+		int z = 0;
+		for (int i = 0; parts[i] != NULL; i++) {
+			if (strncmp(parts[i], "..", 2) == 0) {
+				continue;
+			}
+			if (strncmp(parts[i], ".", NBD_NBTPD_ARGS_PATH_MAX) == 0) {
+				continue;
+			}
+			for (int x = 0; parts[i][x] != '\0'; x++) {
+				if (z < NBD_NBTPD_ARGS_PATH_MAX) {
+					path[z++] = parts[i][x];
+				} else {
+					goto cleanup;
+				}
+			}
+			if (z < NBD_NBTPD_ARGS_PATH_MAX) {
+				path[z++] = '/';
+			} else {
+				goto cleanup;
+			}
+		}
+		// erase trailing slash
+		path[z - 1] = '\0';
+		fname = strdup(path);
+	}
+	if (fname == NULL) {
+		syslog(LOG_ERR, "unable to get real path: %s", strerror(errno));
+		argptr->err = 1;
+		goto cleanup;
+	}
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * POSIX does not define what happens when getcwd() is given NULL.     *
+ * This means that illumos libc and GNU libc both return a newly       *
+ * malloc()'d string, while FreeBSD libc does not.  Also, FreeBSD uses *
+ * constant MAXPATHLEN defined in <sys/param.h> rather than PATH_MAX.  *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+#ifdef __FreeBSD__
+	wd = malloc(MAXPATHLEN);
+	if (wd == NULL) {
+		syslog(LOG_ERR, "unable to allocate PATH_MAX memory: %s", strerror(errno));
+		argptr->err = 0;
+		goto cleanup;
+	}
+	if (getcwd(wd, MAXPATHLEN) == NULL) {
+		argptr->err = 0;
+		goto cleanup;
+	}
+#else
+	if ((wd = getcwd(NULL, PATH_MAX)) == NULL) {
+		argptr->err = 0;
+		goto cleanup;
+	}
+#endif
+	syslog(LOG_DEBUG, "cwd: %s :: realpath: %s", wd, fname);
+	if (read) {
+		if (strncmp(wd, fname, strlen(wd))) {
+			syslog(
+				LOG_ERR,
+				"%s:%d requested invalid file %s",
+				inet_ntoa(argptr->client.sin_addr),
+				ntohs(argptr->client.sin_port),
+				fname
+			);
+			argptr->err = 2;
+			goto cleanup;
+		}
+	} else {
+		strcat(wd, "/");
+		strcat(wd, fname);
+		char *intermediate = wd;
+		wd = fname;
+		fname = intermediate;
+	}
+cleanup:
+	free(wd);
+	free(path);
+	free(parts);
+	return fname;
+}
+
+ssize_t senderror(int s, nbd_nbtpd_args *argptr) {
+	size_t buflen = 4 + strlen(nbd_tftp_error_to_message((nbd_tftp_ecode)argptr->err));
+	char *buf = nbd_tftp_ser_error_from_code((nbd_tftp_ecode)argptr->err);
+	ssize_t sb = send(s, buf, buflen, 0);
+	free(buf);
+	return sb;
+}
+
+void *read_req_resp(void *args) {
+	char *fname = NULL, *buf = NULL, *rxb = NULL;
+	FILE *fp = NULL;
+	nbd_nbtpd_args *argptr = (nbd_nbtpd_args *)args;
+	syslog(
+		LOG_DEBUG,
+		"%s:%d is requesting file %s",
+		inet_ntoa(argptr->client.sin_addr),
+		ntohs(argptr->client.sin_port),
+		argptr->path
+	);
+	fname = checkpath(argptr, 1);
+	if (fname == NULL) {
+		goto pre_socket;
+	}
+	if (!is_netascii_str((char *)&(argptr->mode))) {
+		syslog(
+			LOG_ERR,
+			"%s:%d mode is invalid %s",
+			inet_ntoa(argptr->client.sin_addr),
+			ntohs(argptr->client.sin_port),
+			argptr->mode
+		);
+		argptr->err = 4;
+		goto pre_socket;
+	}
+	nbd_opmode opmode = get_mode((char *)&(argptr->mode));
+	if ((opmode != NETASCII) && (opmode != OCTET)) {
+		argptr->err = 4;
+		goto pre_socket;
+	}
+	fp = fopen(fname, "r");
+	if (fp == NULL) {
+		syslog(
+			LOG_ERR,
+			"failed to open file for reading: %s",
+			strerror(errno)
+		);
+		argptr->err = 2;
+		goto pre_socket;
+	}
+	buf = malloc(512);
+	rxb = malloc(256);
+	if ((buf == NULL) || (rxb == NULL)) {
+		syslog(
+			LOG_CRIT,
+			"failed to allocate memory: %s",
+			strerror(errno)
+		);
+		argptr->err = 0;
+		goto pre_socket;
+	}
+	int s = makesock(argptr);
+	if (s <= 0) {
+		goto cleanup;
+	}
+	uint8_t rxon = 0, lon = 1;
+	uint16_t bnum = 0;
+	while (lon) {
+		memset(buf, '\0', 512);
+		uint8_t verif = 0;
+		uint8_t vcount = 0;
+		size_t num_read = fread(buf, 1, 512, fp);
+		if (((num_read < 512) && (!feof(fp))) || (vcount > 5)) {
+			argptr->err = 0;
+			senderror(s, argptr);
+			goto cleanup;
+		}
+		if ((opmode == NETASCII) && (!is_netascii_buf(buf, num_read))) {
+			syslog(
+				LOG_ERR,
+				"%s:%d: requested file is not netascii compliant.",
+				inet_ntoa(argptr->client.sin_addr),
+				ntohs(argptr->client.sin_port)
+			);
+			argptr->err = 5;
+			senderror(s, argptr);
+			goto cleanup;
+		}
+		char *packet = nbd_tftp_ser_data_from_parts(++bnum, buf, num_read);
+		while (!verif) {
+			syslog(LOG_DEBUG, "sending block number %d to %s:%d",
+				bnum,
+				inet_ntoa(argptr->client.sin_addr),
+				htons(argptr->client.sin_port)
+			);
+			if (send(s, packet, num_read + 4, 0) < 0) {
+				syslog(
+					LOG_ERR,
+					"unable to send data to %s: %s",
+					inet_ntoa(argptr->client.sin_addr),
+					strerror(errno)
+				);
+				continue;
+			}
+			rxon = 1;
+			uint8_t rxcount = 0;
+			while (rxon) {
+				syslog(LOG_DEBUG, "waiting for ack %d from %s:%d",
+					bnum,
+					inet_ntoa(argptr->client.sin_addr),
+					htons(argptr->client.sin_port)
+				);
+				memset(rxb, '\0', 256);
+				ssize_t rcv = recv(s, rxb, 256, 0);
+				syslog(LOG_DEBUG, "recv'd %ld bytes from %s:%d",
+					rcv,
+					inet_ntoa(argptr->client.sin_addr),
+					htons(argptr->client.sin_port)
+				);
+				if (rcv == 4) {
+					nbd_tftp_packet_ack ack = nbd_tftp_de_ack(rxb, rcv);
+					if (ack.opcode == 5) {
+						goto socket_clean;
+					}
+					syslog(LOG_DEBUG, "%s:%d ack'd with block number: %d (expect %d)",
+						inet_ntoa(argptr->client.sin_addr),
+						htons(argptr->client.sin_port),
+						ack.block_num,
+						bnum
+					);
+					if (ack.block_num == bnum) {
+						rxon = 0;
+						verif = 1;
+						break;
+					}
+					if (ack.block_num == (bnum - 1)) {
+						rxon = 0;
+						vcount++;
+						break;
+					}
+				}
+				if (++rxcount > 30) {
+					argptr->err = 0;
+					senderror(s, argptr);
+					goto cleanup;
+				}
+				if (rcv > 4) {
+					if ((((uint16_t)buf[0] << 8) + buf[1]) == 5) {
+						goto socket_clean;
+					}
+				}
+			}
+		}
+		free(packet);
+		if (num_read < 512) {
+			lon = 0;
+		}
+	}
+socket_clean:
+	close(s);
+cleanup:
+	fclose(fp);
+	free(buf);
+	free(rxb);
+	free(fname);
+	free(args);
+	return (void *)NULL;
+pre_socket:
+	if (fp != NULL) {
+		fclose(fp);
+	}
+	free(buf);
+	free(rxb);
+	free(fname);
+	return nbd_nbtpd_resp_error(args);
+}
+
+void *write_req_resp(void *args) {
+	char *fname = NULL, *rxb = NULL;
+	FILE *fp = NULL;
+	nbd_nbtpd_args *argptr = (nbd_nbtpd_args *)args;
+	syslog(
+		LOG_DEBUG,
+		"%s:%d is trying to write file %s",
+		inet_ntoa(argptr->client.sin_addr),
+		ntohs(argptr->client.sin_port),
+		argptr->path
+	);
+	fname = checkpath(argptr, 0);
+	if (fname == NULL) {
+		syslog(LOG_ERR, "fname is NULL");
+		goto pre_socket;
+	}
+	if (!is_netascii_str((char *)&(argptr->mode))) {
+		syslog(
+			LOG_ERR,
+			"%s:%d mode is invalid %s",
+			inet_ntoa(argptr->client.sin_addr),
+			ntohs(argptr->client.sin_port),
+			argptr->mode
+		);
+		argptr->err = 4;
+		goto pre_socket;
+	}
+	nbd_opmode opmode = get_mode((char *)&(argptr->mode));
+	if ((opmode != NETASCII) && (opmode != OCTET)) {
+		syslog(LOG_ERR, "%s:%d mode is not supported.", inet_ntoa(argptr->client.sin_addr), ntohs(argptr->client.sin_port));
+		argptr->err = 4;
+		goto pre_socket;
+	}
+	fp = fopen(fname, "w");
+	if (fp == NULL) {
+		syslog(
+			LOG_ERR,
+			"failed to open file for writing: %s",
+			strerror(errno)
+		);
+		argptr->err = 2;
+		goto pre_socket;
+	}
+	rxb = malloc(768); // 768 is 1.5x expected block size
+	if (rxb == NULL) {
+		syslog(
+			LOG_CRIT,
+			"failed to allocate memory: %s",
+			strerror(errno)
+		);
+		argptr->err = 0;
+		goto pre_socket;
+	}
+	int s = makesock(argptr);
+	if (s <= 0) {
+		goto cleanup;
+	}
+	uint8_t rxon = 0, lon = 1;
+	uint16_t bnum = 0;
+	ssize_t rcv = 516;
+	while (lon) {
+		uint8_t verif = 0;
+		uint8_t vcount = 0;
+		char *packet = nbd_tftp_ser_ack_from_block_num(bnum++);
+		while (!verif) {
+			syslog(LOG_DEBUG, "sending ack number %d to %s:%d",
+				bnum,
+				inet_ntoa(argptr->client.sin_addr),
+				htons(argptr->client.sin_port)
+			);
+			if (send(s, packet, 4, 0) < 0) {
+				syslog(
+					LOG_ERR,
+					"unable to send ack to %s:%d: %s",
+					inet_ntoa(argptr->client.sin_addr),
+					htons(argptr->client.sin_port),
+					strerror(errno)
+				);
+				continue;
+			}
+			if ((rcv < 516) || (vcount > 5)) {
+				lon = 0;
+				break;
+			}
+			rxon = 1;
+			uint8_t rxcount = 0;
+			while (rxon) {
+				syslog(LOG_DEBUG, "waiting for data from %s:%d",
+					inet_ntoa(argptr->client.sin_addr),
+					htons(argptr->client.sin_port)
+				);
+				memset(rxb, '\0', 768);
+				rcv = recv(s, rxb, 768, 0);
+				syslog(LOG_DEBUG, "recv'd %ld bytes from %s:%d",
+					rcv,
+					inet_ntoa(argptr->client.sin_addr),
+					htons(argptr->client.sin_port)
+				);
+				if (rcv >= 4) {
+					nbd_tftp_packet_data data = nbd_tftp_de_data(rxb, rcv);
+					if (data.opcode == 5) {
+						free(data.data);
+						goto cleanup;
+					}
+					syslog(
+						LOG_DEBUG,
+						"%s:%d sent block number: %d (expect %d)",
+						inet_ntoa(argptr->client.sin_addr),
+						htons(argptr->client.sin_port),
+						data.block_num,
+						bnum
+					);
+					if (data.block_num == bnum) {
+						rxon = 0;
+						verif = 1;
+						if (data.datalen > 0) {
+							if ((opmode == NETASCII) && (!is_netascii_buf(data.data, data.datalen))) {
+								syslog(
+									LOG_ERR,
+									"%s:%d: requested file is not netascii compliant.",
+									inet_ntoa(argptr->client.sin_addr),
+									ntohs(argptr->client.sin_port)
+								);
+								argptr->err = 5;
+								senderror(s, argptr);
+								free(data.data);
+								goto cleanup;
+							}
+							if (fwrite(data.data, 1, data.datalen, fp) < data.datalen) {
+								syslog(LOG_ERR, "filewrite failed for %s: %s", fname, strerror(errno));
+								argptr->err = 0;
+								senderror(s, argptr);
+								free(data.data);
+								goto clean_socket;
+							}
+						}
+						free(data.data);
+						break;
+					}
+					if (data.block_num == (bnum - 1)) {
+						rxon = 0;
+						vcount++;
+						free(data.data);
+						break;
+					}
+					free(data.data);
+				}
+				if (++rxcount > 30) {
+					syslog(LOG_ERR, "retry count exceeded");
+					argptr->err = 0;
+					senderror(s, argptr);
+					goto clean_socket;
+				}
+			}
+		}
+		free(packet);
+	}
+clean_socket:
+	close(s);
+cleanup:
+	fclose(fp);
+	free(rxb);
+	free(fname);
+	free(args);
+	return (void *)NULL;
+pre_socket:
+	if (fp != NULL) {
+		fclose(fp);
+	}
+	free(rxb);
+	free(fname);
+	return nbd_nbtpd_resp_error(args);
+}
+
+void *nbd_nbtpd_resp_error(void *args) {
+	nbd_nbtpd_args *argptr = (nbd_nbtpd_args *)args;
+	int s = makesock(argptr);
+	if (s <= 0) {
+		syslog(
+			LOG_ERR,
+			"cannot send error to %s:%d",
+			inet_ntoa(argptr->client.sin_addr),
+			ntohs(argptr->client.sin_port)
+		);
+		goto cleanup;
+	}
+	senderror(s, argptr);
+cleanup:
+	free(args);
+	return (void *)NULL;
+}
+