It calls ftp multiple times to find the relative speed and latency of a
PKG_PATH mirrors found in /etc/examples/pkg.conf and if it is run as root,
provides automatic, fine-grained excision of installpath options and
attributed comments. It is pledged in many places, provides privilege
separation, but I run 5.9 and a glitch that is fixed in current forbids
using NOTE_EXIT in kqueue while using setuid executables. I need NOTE_EXIT
to time how long it takes for ftp to run and to kill it if it takes too
long. It is a fun little utility!
/*
 * Copyright (c) 2016 Luke N. Small
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */


/*
 * indent pkg_ping2.c -bap -br -ce -ci4 -cli0 -d0 -di0 -i8 -ip -l79 -nbc -ncdb -ndj -ei -nfc1 -nlp -npcs -psl -sc -sob
 */


#define EVENT_NOPOLL
#define EVENT_NOSELECT

#include <err.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/event.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include <unistd.h>
#include <sys/wait.h>

struct mirror_st {
	char *label;
	char *mirror;
	double diff;
};

static int
ftp_cmp(const void *a, const void *b)
{
	struct mirror_st **one;
	struct mirror_st **two;

	one = (struct mirror_st **) a;
	two = (struct mirror_st **) b;

	if ((*one)->diff < (*two)->diff)
		return -1;
	if ((*one)->diff > (*two)->diff)
		return 1;
	return 0;
}

static int
label_cmp(const void *a, const void *b)
{
	struct mirror_st **one;
	struct mirror_st **two;
	int8_t temp;

	one = (struct mirror_st **) a;
	two = (struct mirror_st **) b;

	/* list the USA mirrors first, it will subsort correctly */
	temp = !strncmp("USA", (*one)->label + strlen((*one)->label) - 3, 3);
	if (temp != !strncmp("USA", (*two)->label + strlen((*two)->label) - 3, 3)) {
		if (temp)
			return -1;
		return 1;
	}
	return strcmp((*one)->label, (*two)->label);
}


static double
get_time_diff(struct timeval a, struct timeval b)
{
	long sec;
	long usec;
	sec = b.tv_sec - a.tv_sec;
	usec = b.tv_usec - a.tv_usec;
	if (usec < 0) {
		--sec;
		usec += 1000000;
	}
	return sec + ((double) usec / 1000000.0);
}

__dead void
manpage(char *a)
{
	errx(1, "%s [-n maximum_mirrors_written] [-s timeout (floating-point)]", a);
}

int
main(int argc, char *argv[])
{
	if (pledge("stdio wpath cpath rpath proc exec id getpw", NULL) == -1)
		err(EXIT_FAILURE, "pledge");

	extern char *malloc_options;
	malloc_options = (char *)"AFJHSUX";

	pid_t ftp_pid, write_pid;
	int parent_to_write[2];
	char letter;
	double s;
	int kq, i, pos, num, c, n, array_max, array_length;
	FILE *input, *pkgRead;
	struct utsname name;
	struct mirror_st **array;
	struct kevent ke;




	pkgRead = fopen("/etc/pkg.conf", "r");

	array_max = 300;

	if ((array = calloc(array_max, sizeof(struct mirror_st *))) == NULL)
		errx(1, "calloc failed.");

	s = 5;
	n = 5000;

	if (uname(&name) == -1)
		err(1, NULL);

	if (argc > 1) {
		if (argc % 2 == 0)
			manpage(argv[0]);

		for (pos = 1; pos < argc; ++pos) {
			if (strlen(argv[pos]) != 2)
				manpage(argv[0]);

			if (!strcmp(argv[pos], "-s")) {
				++pos;
				c = -1;
				i = 0;
				while ((letter = argv[pos][++c]) != '\0') {
					if (letter == '.')
						++i;

					if (((letter < '0' || letter > '9')
						&& letter != '.') || i > 1) {

						if (letter == '-')
							errx(1, "No negative numbers.");
						errx(1, "Incorrect floating point format.");
					}
				}
				errno = 0;
				strtod(argv[pos], NULL);
				if (errno == ERANGE)
					err(1, NULL);
				if ((s = strtod(argv[pos], NULL)) > 1000.0)
					errx(1, "-s should <= 1000");
			} else if (!strcmp(argv[pos], "-n")) {
				++pos;
				if (strlen(argv[pos]) > 3)
					errx(1, "Integer should be <= 3 digits long.");
				c = -1;
				n = 0;
				while ((letter = argv[pos][++c]) != '\0') {
					if (letter < '0' || letter > '9') {
						if (letter == '.')
							errx(1, "No decimal points.");
						if (letter == '-')
							errx(1, "No negative #.");
						errx(1, "Incorrect integer format.");
					}
					n = n * 10 + (int) (letter - '0');
				}
			} else
				manpage(argv[0]);
		}
	}
	if (pipe(parent_to_write) == -1)
		err(1, "pipe");

	write_pid = fork();
	if (write_pid == (pid_t) 0) {
		if (getuid() == 0) {
			if (pledge("stdio wpath cpath rpath", NULL) == -1) {
				printf("pledge\n");
				_exit(EXIT_FAILURE);
			}
		} else {
			if (pledge("stdio rpath", NULL) == -1) {
				printf("pledge\n");
				_exit(EXIT_FAILURE);
			}
		}
		close(parent_to_write[1]);
		dup2(parent_to_write[0], STDIN_FILENO);
		kq = kqueue();

		EV_SET(&ke, parent_to_write[0], EVFILT_READ, EV_ADD | EV_ONESHOT,
		    0, 0, NULL);
		if (kevent(kq, &ke, 1, NULL, 0, NULL) == -1) {
			printf("parent_to_write kevent register fail.\n");
			_exit(1);
		}
		i = kevent(kq, NULL, 0, &ke, 1, NULL);
		if (i == -1) {
			printf("parent_to_write pipe failed.\n");
			_exit(1);
		}
		if (i == 0) {
			printf("parent_to_write pipe signal received.\n");
			_exit(1);
		}
		if (ke.flags & EV_EOF) {
			printf("EV_EOF\n");
			_exit(1);
		}
		input = fdopen(parent_to_write[0], "r");
		if (input == NULL) {
			printf("input = fdopen (parent_to_write[0], \"r\") failed.\n");
			_exit(1);
		}
		FILE *PkgWrite = NULL;

		if (getuid() == 0) {
			if (pledge("stdio wpath cpath", NULL) == -1) {
				printf("pledge\n");
				_exit(EXIT_FAILURE);
			}
			PkgWrite = fopen("/etc/pkg.conf", "w");
		}
		if (pledge("stdio", NULL) == -1) {
			printf("pledge\n");
			_exit(EXIT_FAILURE);
		}
		printf("\n\n");
		if (PkgWrite != NULL) {
			printf("Edit out all PKG_PATH environment variable exports ");
			printf("and run \"unset PKG_PATH\".\n\n");
			printf("/etc/pkg.conf:\n");
			while ((c = getc(input)) != EOF) {
				printf("%c", c);
				putc(c, PkgWrite);
			}
			fclose(input);
			fclose(PkgWrite);
			printf("\n");
		} else {
			printf("This could have been the contents of /etc/pkg.conf");
			printf(" (run as superuser):\n");
			while ((c = getc(input)) != EOF)
				printf("%c", c);
			printf("\n");
		}

		if (argc == 1) {
			printf("%s [-n maximum_mirrors_written]", argv[0]);
			printf(" [-s timeout (floating-point)]\n");
		}
		_exit(0);
	}
	if (write_pid == -1)
		err(1, "fork");

	close(parent_to_write[0]);
	//~setuid(1000);
	//~seteuid(1000);
	//~setgid(1000);
	//~setegid(1000);
	//~printf("%u %u\n", getuid(), geteuid());

	if (pledge("stdio proc exec rpath", NULL) == -1)
		err(EXIT_FAILURE, "pledge");


	struct timespec timeout;

	timeout.tv_sec = (int) s;
	timeout.tv_nsec = (int) ((s - (double) timeout.tv_sec) * 1000000000);

	kq = kqueue();
	if (kq == -1)
		errx(1, "kq!");

	input = fopen("/etc/examples/pkg.conf", "r");
	if (input == NULL)
		err(1, "fopen");

	if (pledge("stdio proc exec", NULL) == -1)
		err(EXIT_FAILURE, "pledge");

	char line[array_max];
	int space = 0;
	num = 0;
	pos = 0;
	array_length = 0;
	array[array_length] = malloc(sizeof(struct mirror_st));
	if (array[array_length] == NULL) {
		errx(1, "malloc failed");
	}
	/* clear out the header and the '#' following it */
	while ((c = getc(input)) != EOF) {
		if (c == '\n') {
			if ((c = getc(input)) == '\n') {
				c = getc(input);
				if (c == EOF)
					err(1, "EOF");
				break;
			}
		} else if (c == EOF)
			err(1, "EOF");
	}

	while ((c = getc(input)) != EOF) {
		if (pos >= array_max) {
			errx(1, "line[] got too long!");
		}
		if (num == 0) {
			if (c != '\n')
				line[pos++] = c;
			else {
				array[array_length]->label = malloc(pos);
				if (array[array_length]->label == NULL) {
					errx(1, "malloc failed.");
				}
				strlcpy(array[array_length]->label, line + 1, pos);

				pos = 0;
				num = 1;
				space = 0;
			}
		} else {
			if (space < 2) {
				if (c == ' ')
					++space;
				continue;
			}
			if (c != '\n')
				line[pos++] = c;
			else {
				line[pos++] = '\0';

				array[array_length]->mirror = malloc(pos);
				if (array[array_length]->mirror == NULL) {
					errx(1, "malloc failed.");
				}
				strlcpy(array[array_length]->mirror,
				    line, pos);


				if (++array_length > array_max) {
					array_max += 100;
					array = reallocarray(array, array_max,
					    sizeof(struct mirror_st));

					if (array == NULL)
						err(1, "reallocarray");
				}
				array[array_length]
				    = (struct mirror_st*)calloc(1, sizeof(struct mirror_st));

				if (array[array_length] == NULL) {
					errx(1, "calloc failed.");
				}
				pos = 0;
				num = 0;
				space = 0;

				if ((c = getc(input)) == '\n') {
					c = getc(input);
					if (c == EOF)
						break;
					continue;
				}
				if (c == EOF)
					break;

				pos = strlen(array[array_length - 1]->label) + 1;
				array[array_length]->label = malloc(pos);
				if (array[array_length]->label == NULL) {
					errx(1, "malloc failed.");
				}
				strlcpy(array[array_length]->label,
				    array[array_length - 1]->label, pos);
				num = 1;
				pos = 0;
			}
		}
	}

	fclose(input);

	if (pledge("stdio proc exec", NULL) == -1)
		err(EXIT_FAILURE, "pledge");


	if (array_length <= 1)
		errx(1, "No mirrors found.");
		
	if (array[array_length]->label)
		free(array[array_length]->label);
	free(array[array_length]);

	int mirror_num = array_length;


	char *ftp_file;

	qsort(array, mirror_num, sizeof(struct mirror_st *), label_cmp);

	for (c = 0; c < mirror_num; ++c) {

		pos = strlcpy(line, array[c]->mirror, array_max) + 1;

		if (!strncmp(line, "http://";, 7));
		else if ((pos + 7) < array_max) {
			memmove(line + 7, line, pos);
			pos += 7;
			memcpy(line, "http://";, 7);
		} else
			err(1, "line got too long");

		if (pos >= 16) {
			if (!strncmp(line + pos - 16, "%c/packages/%a/", 15)) {
				line[pos - 16] = '\0';
				pos -= 15;
			}
		}
		if (line[pos - 2] != '/') {
			if (pos + 13 >= array_max)
				err(1, "line got too long");
			else {
				strlcpy(line + pos - 1, "/pub/OpenBSD/", 13 + 1);
				pos += 13;
			}
		}
		pos += strlen(name.release) + strlen("/ANNOUNCEMENT");

		ftp_file = malloc(pos);
		if (ftp_file == NULL)
			errx(1, "malloc failed.");

		strlcpy(ftp_file, line, pos);
		strlcat(ftp_file, name.release, pos);
		strlcat(ftp_file, "/ANNOUNCEMENT", pos);

		printf("\n%d : %s  :  %s\n", mirror_num - c,
		    array[c]->label, ftp_file);




		ftp_pid = fork();
		if (ftp_pid == -1)
			err(1, "fork");

		if (ftp_pid == (pid_t) 0) {
			execl("/usr/bin/ftp", "ftp", "-Vmo", "/dev/null",
			    ftp_file, NULL);
			errx(1, "ftp execl() failed.");
		}
		EV_SET(&ke, ftp_pid, EVFILT_PROC, EV_ADD | EV_ONESHOT,
		    NOTE_EXIT, 0, NULL);
		if (kevent(kq, &ke, 1, NULL, 0, NULL) == -1) {
			kill(ftp_pid, SIGKILL);
			err(EXIT_FAILURE, "kevent register fail.");
		}
		array[c]->diff = 0;
		struct timeval tv_start, tv_end;
		gettimeofday(&tv_start, NULL);

		/* Loop until ftp() is dead and 'ke' is populated */
		for (;;) {
			i = kevent(kq, NULL, 0, &ke, 1, &timeout);
			if (i == -1) {
				kill(ftp_pid, SIGKILL);
				errx(1, "kevent");
			}
			if (i == 0) {
				printf("\nTimeout\n");
				kill(ftp_pid, SIGKILL);
				array[c]->diff = s;
			} else
				break;
		}

		free(ftp_file);

		if (ke.data == 0) {
			gettimeofday(&tv_end, NULL);
			array[c]->diff = get_time_diff(tv_start, tv_end);
			printf("%f\n", array[c]->diff);
		} else if (array[c]->diff == 0) {
			array[c]->diff = s + 1;
			printf("Download Error\n");
		}
	}

	if (pledge("stdio", NULL) == -1)
		err(EXIT_FAILURE, "pledge");

	qsort(array, mirror_num, sizeof(struct mirror_st *), ftp_cmp);

	printf("\n\n");

	for (c = mirror_num - 1; c >= 0; --c) {

		if (array[c]->diff < s) {
			if (c > (n - 1)) {
				printf("%d : %s:\n\t%s : ", c + 1, array[c]->label, array[c]->mirror);
				printf("%f\n\n", array[c]->diff);
			}
		} else if (array[c]->diff == s) {
			printf("%d : %s:\n\t%s : ", c + 1, array[c]->label, array[c]->mirror);
			printf("Timeout\n\n");
		} else {
			printf("%d : %s:\n\t%s : ", c + 1, array[c]->label, array[c]->mirror);
			printf("Download Error\n\n");
		}
	}

	if (array[0]->diff >= s)
		errx(1, "No mirrors found within timeout period.");

	char *buf;
	int lines;
	int total_length;
	int copy;
	int j;

	if (n < mirror_num)
		mirror_num = n;

	if (mirror_num == 0)
		return 0;

	total_length = 1 + 1;


	for (pos = 0; pos < mirror_num; ++pos) {
		total_length += 1 + 2 + strlen(array[pos]->label) + 3 + 12;
		total_length += 1 + strlen("installpath += ");
		total_length += strlen(array[pos]->mirror) + 1;
	}

	if (pkgRead == NULL) {
		buf = (char *) malloc(total_length + 1);
		if (buf == NULL)
			errx(1, "malloc failed.");
	} else {
		fseek(pkgRead, 0, SEEK_END);
		num = ftell(pkgRead);
		total_length += num;
		fseek(pkgRead, 0, SEEK_SET);
		buf = (char *) malloc(total_length + 11 + 1);
		if (buf == NULL)
			err(1, "malloc failed.");
		fread(buf, 1, num, pkgRead);
		fclose(pkgRead);

		lines = 0;
		for (c = 0; c < num; ++c) {
			if (buf[c] == '\n')
				++lines;
		}

/* erase installpath lines from buf: length 'c' into buf: length 'copy' */
		copy = 0;
		pos = 0;
		for (c = 0; c < num; ++c) {
			if (buf[c] == '\n') {
				--lines;
				if (pos == 0)
					continue;
				pos = 0;
			} else if (pos++ == 0) {
				if (!strncmp(buf + c, "installpath", 11) || buf[c] == '#') {
					if (lines) {
						if (buf[c] == '#') {
							i = c;
							j = lines;
							for (;;) {
								while (buf[++i] != '\n');
								--j;
								while ((buf[i] == ' ') && (i < num))
									++i;
								if (i + 1 >= num)
									goto leap;
								if (buf[++i] == '\n')
									goto leap;
								while ((buf[i] == ' ') && (i < num))
									++i;
								if (i == num)
									goto leap;
								if (buf[i] == '#') {
									if (j)
										continue;
									goto leap;
								}
								if (strncmp(buf + i, "installpath", 11))
									goto leap;
								if (!j)
									goto leave;
								lines = j;
								c = i + 11;
								break;
							}
						}
						while (buf[++c] != '\n');
						--lines;
						pos = 0;
						continue;
					} else
						break;
				} else if ((pos == 1) && (buf[c] == ' ')) {
					pos = 0;
					continue;
				}
			}
	leap:
			buf[copy++] = buf[c];
		}
	}

leave:

	buf[copy] = '\0';
	if ((buf[copy - 1] != '\n') && copy)
		strlcat(buf, "\n", total_length);

	for (pos = 0; pos < mirror_num; ++pos) {

		/* Eliminates dowload error and timeout mirrors */
		if (array[pos]->diff >= s)
			break;

		strlcat(buf, "\n# ", total_length);
		strlcat(buf, array[pos]->label, total_length);
		strlcat(buf, " : ", total_length);
		copy = -1;
		while (buf[++copy] != '\0');
		snprintf(buf + copy, 12, "%f", array[pos]->diff);
		strlcat(buf, "\ninstallpath ", total_length);
		if (pos == 0)
			strlcat(buf, "= ", total_length);
		else
			strlcat(buf, "+= ", total_length);
		strlcat(buf, array[pos]->mirror, total_length);
		strlcat(buf, "\n", total_length);
	}

	c = dup2(parent_to_write[1], STDOUT_FILENO);
	if (c == -1)
		err(1, "dup2");

	printf("%s", buf);

	wait(&c);

	return c;
}

Reply via email to