I had one before that read openbsd.org/ftp.html (which is insecure because
it gets sets a mirror from data from an unencrypted connection), but I
changed it to read the /etc/examples/pkg.conf file so that there is a more
secure method. I pledged it. I tried to do pledge and setuid, but a glitch
that presumably is fixed in -current (I run 5.9) prevents me from doing so
(I use kqueue with NOTE_EXIT). I commented the setuid part. I fork() and
pipe to a process early in the execution for privilege separation. I do
more fine-grained comment and installpath editing on /etc/pkg.conf any
comments not immediately preceding installpath values are remain in the
file.
The option -s and allows floating point timeout for a test. I like to put
-s .3 with 5Mb/s download.
The option -n limits the number of mirrors written.
It downloads the ANNOUNCEMENT file from each mirror, which is both small
and has the same name for every release.
I suspect it still wouldn't be accepted in any form for the base, but is
there any reason I couldn't make a package, even though I have no idea how.
It doesn't even require any libraries or dependencies. optimizing it is
ridiculous. It spends a monumental time on its executions of ftp and hardly
any time on anything else. making the -s value small, makes it execute
pretty fast.
/*
* 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");
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)) > 100000000.0)
errx(1, "-s should <= 100000000");
} 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 cpath 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 cpath 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");
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]
= malloc(sizeof(struct mirror_st));
if (array[array_length] == NULL) {
errx(1, "malloc 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.");
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;
}