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; }