Index: head/usr.sbin/autofs/common.c =================================================================== --- head/usr.sbin/autofs/common.c (revision 303526) +++ head/usr.sbin/autofs/common.c (revision 303527) @@ -1,1224 +1,1223 @@ /*- * Copyright (c) 2014 The FreeBSD Foundation * All rights reserved. * * This software was developed by Edward Tomasz Napierala under sponsorship * from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#define _WITH_GETLINE #include #include #include #include #include "autofs_ioctl.h" #include "common.h" extern FILE *yyin; extern char *yytext; extern int yylex(void); static void parse_master_yyin(struct node *root, const char *master); static void parse_map_yyin(struct node *parent, const char *map, const char *executable_key); char * checked_strdup(const char *s) { char *c; assert(s != NULL); c = strdup(s); if (c == NULL) log_err(1, "strdup"); return (c); } /* * Concatenate two strings, inserting separator between them, unless not needed. */ char * concat(const char *s1, char separator, const char *s2) { char *result; char s1last, s2first; int ret; if (s1 == NULL) s1 = ""; if (s2 == NULL) s2 = ""; if (s1[0] == '\0') s1last = '\0'; else s1last = s1[strlen(s1) - 1]; s2first = s2[0]; if (s1last == separator && s2first == separator) { /* * If s1 ends with the separator and s2 begins with * it - skip the latter; otherwise concatenating "/" * and "/foo" would end up returning "//foo". */ ret = asprintf(&result, "%s%s", s1, s2 + 1); } else if (s1last == separator || s2first == separator || s1[0] == '\0' || s2[0] == '\0') { ret = asprintf(&result, "%s%s", s1, s2); } else { ret = asprintf(&result, "%s%c%s", s1, separator, s2); } if (ret < 0) log_err(1, "asprintf"); //log_debugx("%s: got %s and %s, returning %s", __func__, s1, s2, result); return (result); } void create_directory(const char *path) { char *component, *copy, *tofree, *partial, *tmp; int error; assert(path[0] == '/'); /* * +1 to skip the leading slash. */ copy = tofree = checked_strdup(path + 1); partial = checked_strdup(""); for (;;) { component = strsep(©, "/"); if (component == NULL) break; tmp = concat(partial, '/', component); free(partial); partial = tmp; //log_debugx("creating \"%s\"", partial); error = mkdir(partial, 0755); if (error != 0 && errno != EEXIST) { log_warn("cannot create %s", partial); return; } } free(tofree); } struct node * node_new_root(void) { struct node *n; n = calloc(1, sizeof(*n)); if (n == NULL) log_err(1, "calloc"); // XXX n->n_key = checked_strdup("/"); n->n_options = checked_strdup(""); TAILQ_INIT(&n->n_children); return (n); } struct node * node_new(struct node *parent, char *key, char *options, char *location, const char *config_file, int config_line) { struct node *n; n = calloc(1, sizeof(*n)); if (n == NULL) log_err(1, "calloc"); TAILQ_INIT(&n->n_children); assert(key != NULL); assert(key[0] != '\0'); n->n_key = key; if (options != NULL) n->n_options = options; else n->n_options = strdup(""); n->n_location = location; assert(config_file != NULL); n->n_config_file = config_file; assert(config_line >= 0); n->n_config_line = config_line; assert(parent != NULL); n->n_parent = parent; TAILQ_INSERT_TAIL(&parent->n_children, n, n_next); return (n); } struct node * node_new_map(struct node *parent, char *key, char *options, char *map, const char *config_file, int config_line) { struct node *n; n = calloc(1, sizeof(*n)); if (n == NULL) log_err(1, "calloc"); TAILQ_INIT(&n->n_children); assert(key != NULL); assert(key[0] != '\0'); n->n_key = key; if (options != NULL) n->n_options = options; else n->n_options = strdup(""); n->n_map = map; assert(config_file != NULL); n->n_config_file = config_file; assert(config_line >= 0); n->n_config_line = config_line; assert(parent != NULL); n->n_parent = parent; TAILQ_INSERT_TAIL(&parent->n_children, n, n_next); return (n); } static struct node * node_duplicate(const struct node *o, struct node *parent) { const struct node *child; struct node *n; if (parent == NULL) parent = o->n_parent; n = node_new(parent, o->n_key, o->n_options, o->n_location, o->n_config_file, o->n_config_line); TAILQ_FOREACH(child, &o->n_children, n_next) node_duplicate(child, n); return (n); } static void node_delete(struct node *n) { struct node *child, *tmp; assert (n != NULL); TAILQ_FOREACH_SAFE(child, &n->n_children, n_next, tmp) node_delete(child); if (n->n_parent != NULL) TAILQ_REMOVE(&n->n_parent->n_children, n, n_next); free(n); } /* * Move (reparent) node 'n' to make it sibling of 'previous', placed * just after it. */ static void node_move_after(struct node *n, struct node *previous) { TAILQ_REMOVE(&n->n_parent->n_children, n, n_next); n->n_parent = previous->n_parent; TAILQ_INSERT_AFTER(&previous->n_parent->n_children, previous, n, n_next); } static void node_expand_includes(struct node *root, bool is_master) { struct node *n, *n2, *tmp, *tmp2, *tmproot; int error; TAILQ_FOREACH_SAFE(n, &root->n_children, n_next, tmp) { if (n->n_key[0] != '+') continue; error = access(AUTO_INCLUDE_PATH, F_OK); if (error != 0) { log_errx(1, "directory services not configured; " "%s does not exist", AUTO_INCLUDE_PATH); } /* * "+1" to skip leading "+". */ yyin = auto_popen(AUTO_INCLUDE_PATH, n->n_key + 1, NULL); assert(yyin != NULL); tmproot = node_new_root(); if (is_master) parse_master_yyin(tmproot, n->n_key); else parse_map_yyin(tmproot, n->n_key, NULL); error = auto_pclose(yyin); yyin = NULL; if (error != 0) { log_errx(1, "failed to handle include \"%s\"", n->n_key); } /* * Entries to be included are now in tmproot. We need to merge * them with the rest, preserving their place and ordering. */ TAILQ_FOREACH_REVERSE_SAFE(n2, &tmproot->n_children, nodehead, n_next, tmp2) { node_move_after(n2, n); } node_delete(n); node_delete(tmproot); } } static char * expand_ampersand(char *string, const char *key) { char c, *expanded; int i, ret, before_len = 0; bool backslashed = false; assert(key[0] != '\0'); expanded = checked_strdup(string); for (i = 0; string[i] != '\0'; i++) { c = string[i]; if (c == '\\' && backslashed == false) { backslashed = true; continue; } if (backslashed) { backslashed = false; continue; } backslashed = false; if (c != '&') continue; /* * The 'before_len' variable contains the number * of characters before the '&'. */ before_len = i; //assert(i + 1 < (int)strlen(string)); ret = asprintf(&expanded, "%.*s%s%s", before_len, string, key, string + before_len + 1); if (ret < 0) log_err(1, "asprintf"); //log_debugx("\"%s\" expanded with key \"%s\" to \"%s\"", // string, key, expanded); /* * Figure out where to start searching for next variable. */ string = expanded; i = before_len + strlen(key); backslashed = false; //assert(i < (int)strlen(string)); } return (expanded); } /* * Expand "&" in n_location. If the key is NULL, try to use * key from map entries themselves. Keep in mind that maps * consist of tho levels of node structures, the key is one * level up. * * Variant with NULL key is for "automount -LL". */ void node_expand_ampersand(struct node *n, const char *key) { struct node *child; if (n->n_location != NULL) { if (key == NULL) { if (n->n_parent != NULL && strcmp(n->n_parent->n_key, "*") != 0) { n->n_location = expand_ampersand(n->n_location, n->n_parent->n_key); } } else { n->n_location = expand_ampersand(n->n_location, key); } } TAILQ_FOREACH(child, &n->n_children, n_next) node_expand_ampersand(child, key); } /* * Expand "*" in n_key. */ void node_expand_wildcard(struct node *n, const char *key) { struct node *child, *expanded; assert(key != NULL); if (strcmp(n->n_key, "*") == 0) { expanded = node_duplicate(n, NULL); expanded->n_key = checked_strdup(key); node_move_after(expanded, n); } TAILQ_FOREACH(child, &n->n_children, n_next) node_expand_wildcard(child, key); } int node_expand_defined(struct node *n) { struct node *child; int error, cumulated_error = 0; if (n->n_location != NULL) { n->n_location = defined_expand(n->n_location); if (n->n_location == NULL) { log_warnx("failed to expand location for %s", node_path(n)); return (EINVAL); } } TAILQ_FOREACH(child, &n->n_children, n_next) { error = node_expand_defined(child); if (error != 0 && cumulated_error == 0) cumulated_error = error; } return (cumulated_error); } static bool node_is_direct_key(const struct node *n) { if (n->n_parent != NULL && n->n_parent->n_parent == NULL && strcmp(n->n_key, "/-") == 0) { return (true); } return (false); } bool node_is_direct_map(const struct node *n) { for (;;) { assert(n->n_parent != NULL); if (n->n_parent->n_parent == NULL) break; n = n->n_parent; } return (node_is_direct_key(n)); } bool node_has_wildcards(const struct node *n) { const struct node *child; TAILQ_FOREACH(child, &n->n_children, n_next) { if (strcmp(child->n_key, "*") == 0) return (true); } return (false); } static void node_expand_maps(struct node *n, bool indirect) { struct node *child, *tmp; TAILQ_FOREACH_SAFE(child, &n->n_children, n_next, tmp) { if (node_is_direct_map(child)) { if (indirect) continue; } else { if (indirect == false) continue; } /* * This is the first-level map node; the one that contains * the key and subnodes with mountpoints and actual map names. */ if (child->n_map == NULL) continue; if (indirect) { log_debugx("map \"%s\" is an indirect map, parsing", child->n_map); } else { log_debugx("map \"%s\" is a direct map, parsing", child->n_map); } parse_map(child, child->n_map, NULL, NULL); } } static void node_expand_direct_maps(struct node *n) { node_expand_maps(n, false); } void node_expand_indirect_maps(struct node *n) { node_expand_maps(n, true); } static char * node_path_x(const struct node *n, char *x) { char *path; if (n->n_parent == NULL) return (x); /* * Return "/-" for direct maps only if we were asked for path * to the "/-" node itself, not to any of its subnodes. */ if (node_is_direct_key(n) && x[0] != '\0') return (x); assert(n->n_key[0] != '\0'); path = concat(n->n_key, '/', x); free(x); return (node_path_x(n->n_parent, path)); } /* * Return full path for node, consisting of concatenated * paths of node itself and all its parents, up to the root. */ char * node_path(const struct node *n) { char *path; size_t len; path = node_path_x(n, checked_strdup("")); /* * Strip trailing slash, unless the whole path is "/". */ len = strlen(path); if (len > 1 && path[len - 1] == '/') path[len - 1] = '\0'; return (path); } static char * node_options_x(const struct node *n, char *x) { char *options; if (n == NULL) return (x); options = concat(x, ',', n->n_options); free(x); return (node_options_x(n->n_parent, options)); } /* * Return options for node, consisting of concatenated * options from the node itself and all its parents, * up to the root. */ char * node_options(const struct node *n) { return (node_options_x(n, checked_strdup(""))); } static void node_print_indent(const struct node *n, const char *cmdline_options, int indent) { const struct node *child, *first_child; char *path, *options, *tmp; path = node_path(n); tmp = node_options(n); options = concat(cmdline_options, ',', tmp); free(tmp); /* * Do not show both parent and child node if they have the same * mountpoint; only show the child node. This means the typical, * "key location", map entries are shown in a single line; * the "key mountpoint1 location2 mountpoint2 location2" entries * take multiple lines. */ first_child = TAILQ_FIRST(&n->n_children); if (first_child == NULL || TAILQ_NEXT(first_child, n_next) != NULL || strcmp(path, node_path(first_child)) != 0) { assert(n->n_location == NULL || n->n_map == NULL); printf("%*.s%-*s %s%-*s %-*s # %s map %s at %s:%d\n", indent, "", 25 - indent, path, options[0] != '\0' ? "-" : " ", 20, options[0] != '\0' ? options : "", 20, n->n_location != NULL ? n->n_location : n->n_map != NULL ? n->n_map : "", node_is_direct_map(n) ? "direct" : "indirect", indent == 0 ? "referenced" : "defined", n->n_config_file, n->n_config_line); } free(path); free(options); TAILQ_FOREACH(child, &n->n_children, n_next) node_print_indent(child, cmdline_options, indent + 2); } /* * Recursively print node with all its children. The cmdline_options * argument is used for additional options to be prepended to all the * others - usually those are the options passed by command line. */ void node_print(const struct node *n, const char *cmdline_options) { const struct node *child; TAILQ_FOREACH(child, &n->n_children, n_next) node_print_indent(child, cmdline_options, 0); } static struct node * node_find_x(struct node *node, const char *path) { struct node *child, *found; char *tmp; size_t tmplen; //log_debugx("looking up %s in %s", path, node_path(node)); if (!node_is_direct_key(node)) { tmp = node_path(node); tmplen = strlen(tmp); if (strncmp(tmp, path, tmplen) != 0) { free(tmp); return (NULL); } if (path[tmplen] != '/' && path[tmplen] != '\0') { /* * If we have two map entries like 'foo' and 'foobar', make * sure the search for 'foobar' won't match 'foo' instead. */ free(tmp); return (NULL); } free(tmp); } TAILQ_FOREACH(child, &node->n_children, n_next) { found = node_find_x(child, path); if (found != NULL) return (found); } if (node->n_parent == NULL || node_is_direct_key(node)) return (NULL); return (node); } struct node * node_find(struct node *root, const char *path) { struct node *node; assert(root->n_parent == NULL); node = node_find_x(root, path); if (node != NULL) assert(node != root); return (node); } /* * Canonical form of a map entry looks like this: * * key [-options] [ [/mountpoint] [-options2] location ... ] * * Entries for executable maps are slightly different, as they * lack the 'key' field and are always single-line; the key field * for those maps is taken from 'executable_key' argument. * * We parse it in such a way that a map always has two levels - first * for key, and the second, for the mountpoint. */ static void parse_map_yyin(struct node *parent, const char *map, const char *executable_key) { char *key = NULL, *options = NULL, *mountpoint = NULL, *options2 = NULL, *location = NULL; int ret; struct node *node; lineno = 1; if (executable_key != NULL) key = checked_strdup(executable_key); for (;;) { ret = yylex(); if (ret == 0 || ret == NEWLINE) { /* * In case of executable map, the key is always * non-NULL, even if the map is empty. So, make sure * we don't fail empty maps here. */ if ((key != NULL && executable_key == NULL) || options != NULL) { log_errx(1, "truncated entry at %s, line %d", map, lineno); } if (ret == 0 || executable_key != NULL) { /* * End of file. */ break; } else { key = options = NULL; continue; } } if (key == NULL) { key = checked_strdup(yytext); if (key[0] == '+') { node_new(parent, key, NULL, NULL, map, lineno); key = options = NULL; continue; } continue; } else if (yytext[0] == '-') { if (options != NULL) { log_errx(1, "duplicated options at %s, line %d", map, lineno); } /* * +1 to skip leading "-". */ options = checked_strdup(yytext + 1); continue; } /* * We cannot properly handle a situation where the map key * is "/". Ignore such entries. * * XXX: According to Piete Brooks, Linux automounter uses * "/" as a wildcard character in LDAP maps. Perhaps * we should work around this braindamage by substituting * "*" for "/"? */ if (strcmp(key, "/") == 0) { log_warnx("nonsensical map key \"/\" at %s, line %d; " "ignoring map entry ", map, lineno); /* * Skip the rest of the entry. */ do { ret = yylex(); } while (ret != 0 && ret != NEWLINE); key = options = NULL; continue; } //log_debugx("adding map node, %s", key); node = node_new(parent, key, options, NULL, map, lineno); key = options = NULL; for (;;) { if (yytext[0] == '/') { if (mountpoint != NULL) { log_errx(1, "duplicated mountpoint " "in %s, line %d", map, lineno); } if (options2 != NULL || location != NULL) { log_errx(1, "mountpoint out of order " "in %s, line %d", map, lineno); } mountpoint = checked_strdup(yytext); goto again; } if (yytext[0] == '-') { if (options2 != NULL) { log_errx(1, "duplicated options " "in %s, line %d", map, lineno); } if (location != NULL) { log_errx(1, "options out of order " "in %s, line %d", map, lineno); } options2 = checked_strdup(yytext + 1); goto again; } if (location != NULL) { log_errx(1, "too many arguments " "in %s, line %d", map, lineno); } /* * If location field starts with colon, e.g. ":/dev/cd0", * then strip it. */ if (yytext[0] == ':') { location = checked_strdup(yytext + 1); if (location[0] == '\0') { log_errx(1, "empty location in %s, " "line %d", map, lineno); } } else { location = checked_strdup(yytext); } if (mountpoint == NULL) mountpoint = checked_strdup("/"); if (options2 == NULL) options2 = checked_strdup(""); #if 0 log_debugx("adding map node, %s %s %s", mountpoint, options2, location); #endif node_new(node, mountpoint, options2, location, map, lineno); mountpoint = options2 = location = NULL; again: ret = yylex(); if (ret == 0 || ret == NEWLINE) { if (mountpoint != NULL || options2 != NULL || location != NULL) { log_errx(1, "truncated entry " "in %s, line %d", map, lineno); } break; } } } } /* * Parse output of a special map called without argument. It is a list * of keys, separated by newlines. They can contain whitespace, so use * getline(3) instead of lexer used for maps. */ static void parse_map_keys_yyin(struct node *parent, const char *map) { char *line = NULL, *key; size_t linecap = 0; ssize_t linelen; lineno = 1; for (;;) { linelen = getline(&line, &linecap, yyin); if (linelen < 0) { /* * End of file. */ break; } if (linelen <= 1) { /* * Empty line, consisting of just the newline. */ continue; } /* * "-1" to strip the trailing newline. */ key = strndup(line, linelen - 1); log_debugx("adding key \"%s\"", key); node_new(parent, key, NULL, NULL, map, lineno); lineno++; } free(line); } static bool file_is_executable(const char *path) { struct stat sb; int error; error = stat(path, &sb); if (error != 0) log_err(1, "cannot stat %s", path); if ((sb.st_mode & S_IXUSR) || (sb.st_mode & S_IXGRP) || (sb.st_mode & S_IXOTH)) return (true); return (false); } /* * Parse a special map, e.g. "-hosts". */ static void parse_special_map(struct node *parent, const char *map, const char *key) { char *path; int error, ret; assert(map[0] == '-'); /* * +1 to skip leading "-" in map name. */ ret = asprintf(&path, "%s/special_%s", AUTO_SPECIAL_PREFIX, map + 1); if (ret < 0) log_err(1, "asprintf"); yyin = auto_popen(path, key, NULL); assert(yyin != NULL); if (key == NULL) { parse_map_keys_yyin(parent, map); } else { parse_map_yyin(parent, map, key); } error = auto_pclose(yyin); yyin = NULL; if (error != 0) log_errx(1, "failed to handle special map \"%s\"", map); node_expand_includes(parent, false); node_expand_direct_maps(parent); free(path); } /* * Retrieve and parse map from directory services, e.g. LDAP. * Note that it is different from executable maps, in that * the include script outputs the whole map to standard output * (as opposed to executable maps that only output a single * entry, without the key), and it takes the map name as an * argument, instead of key. */ static void parse_included_map(struct node *parent, const char *map) { int error; assert(map[0] != '-'); assert(map[0] != '/'); error = access(AUTO_INCLUDE_PATH, F_OK); if (error != 0) { log_errx(1, "directory services not configured;" " %s does not exist", AUTO_INCLUDE_PATH); } yyin = auto_popen(AUTO_INCLUDE_PATH, map, NULL); assert(yyin != NULL); parse_map_yyin(parent, map, NULL); error = auto_pclose(yyin); yyin = NULL; if (error != 0) log_errx(1, "failed to handle remote map \"%s\"", map); node_expand_includes(parent, false); node_expand_direct_maps(parent); } void parse_map(struct node *parent, const char *map, const char *key, bool *wildcards) { char *path = NULL; int error, ret; bool executable; assert(map != NULL); assert(map[0] != '\0'); log_debugx("parsing map \"%s\"", map); if (wildcards != NULL) *wildcards = false; if (map[0] == '-') { if (wildcards != NULL) *wildcards = true; return (parse_special_map(parent, map, key)); } if (map[0] == '/') { path = checked_strdup(map); } else { ret = asprintf(&path, "%s/%s", AUTO_MAP_PREFIX, map); if (ret < 0) log_err(1, "asprintf"); log_debugx("map \"%s\" maps to \"%s\"", map, path); /* * See if the file exists. If not, try to obtain the map * from directory services. */ error = access(path, F_OK); if (error != 0) { log_debugx("map file \"%s\" does not exist; falling " "back to directory services", path); return (parse_included_map(parent, map)); } } executable = file_is_executable(path); if (executable) { log_debugx("map \"%s\" is executable", map); if (wildcards != NULL) *wildcards = true; if (key != NULL) { yyin = auto_popen(path, key, NULL); } else { yyin = auto_popen(path, NULL); } assert(yyin != NULL); } else { yyin = fopen(path, "r"); if (yyin == NULL) log_err(1, "unable to open \"%s\"", path); } free(path); path = NULL; parse_map_yyin(parent, map, executable ? key : NULL); if (executable) { error = auto_pclose(yyin); yyin = NULL; if (error != 0) { log_errx(1, "failed to handle executable map \"%s\"", map); } } else { fclose(yyin); } yyin = NULL; log_debugx("done parsing map \"%s\"", map); node_expand_includes(parent, false); node_expand_direct_maps(parent); } static void parse_master_yyin(struct node *root, const char *master) { char *mountpoint = NULL, *map = NULL, *options = NULL; int ret; /* * XXX: 1 gives incorrect values; wtf? */ lineno = 0; for (;;) { ret = yylex(); if (ret == 0 || ret == NEWLINE) { if (mountpoint != NULL) { //log_debugx("adding map for %s", mountpoint); node_new_map(root, mountpoint, options, map, master, lineno); } if (ret == 0) { break; } else { mountpoint = map = options = NULL; continue; } } if (mountpoint == NULL) { mountpoint = checked_strdup(yytext); } else if (map == NULL) { map = checked_strdup(yytext); } else if (options == NULL) { /* * +1 to skip leading "-". */ options = checked_strdup(yytext + 1); } else { log_errx(1, "too many arguments at %s, line %d", master, lineno); } } } void parse_master(struct node *root, const char *master) { log_debugx("parsing auto_master file at \"%s\"", master); yyin = fopen(master, "r"); if (yyin == NULL) err(1, "unable to open %s", master); parse_master_yyin(root, master); fclose(yyin); yyin = NULL; log_debugx("done parsing \"%s\"", master); node_expand_includes(root, true); node_expand_direct_maps(root); } /* * Two things daemon(3) does, that we actually also want to do * when running in foreground, is closing the stdin and chdiring * to "/". This is what we do here. */ void lesser_daemon(void) { int error, fd; error = chdir("/"); if (error != 0) log_warn("chdir"); fd = open(_PATH_DEVNULL, O_RDWR, 0); if (fd < 0) { log_warn("cannot open %s", _PATH_DEVNULL); return; } error = dup2(fd, STDIN_FILENO); if (error != 0) log_warn("dup2"); error = close(fd); if (error != 0) { /* Bloody hell. */ log_warn("close"); } } int main(int argc, char **argv) { char *cmdname; if (argv[0] == NULL) log_errx(1, "NULL command name"); cmdname = basename(argv[0]); if (strcmp(cmdname, "automount") == 0) return (main_automount(argc, argv)); else if (strcmp(cmdname, "automountd") == 0) return (main_automountd(argc, argv)); else if (strcmp(cmdname, "autounmountd") == 0) return (main_autounmountd(argc, argv)); else log_errx(1, "binary name should be either \"automount\", " "\"automountd\", or \"autounmountd\""); } Index: head/usr.sbin/pkg/pkg.c =================================================================== --- head/usr.sbin/pkg/pkg.c (revision 303526) +++ head/usr.sbin/pkg/pkg.c (revision 303527) @@ -1,1109 +1,1108 @@ /*- * Copyright (c) 2012-2014 Baptiste Daroussin * Copyright (c) 2013 Bryan Drewery * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include -#define _WITH_GETLINE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dns_utils.h" #include "config.h" struct sig_cert { char *name; unsigned char *sig; int siglen; unsigned char *cert; int certlen; bool trusted; }; struct pubkey { unsigned char *sig; int siglen; }; typedef enum { HASH_UNKNOWN, HASH_SHA256, } hash_t; struct fingerprint { hash_t type; char *name; char hash[BUFSIZ]; STAILQ_ENTRY(fingerprint) next; }; STAILQ_HEAD(fingerprint_list, fingerprint); static int extract_pkg_static(int fd, char *p, int sz) { struct archive *a; struct archive_entry *ae; char *end; int ret, r; ret = -1; a = archive_read_new(); if (a == NULL) { warn("archive_read_new"); return (ret); } archive_read_support_filter_all(a); archive_read_support_format_tar(a); if (lseek(fd, 0, 0) == -1) { warn("lseek"); goto cleanup; } if (archive_read_open_fd(a, fd, 4096) != ARCHIVE_OK) { warnx("archive_read_open_fd: %s", archive_error_string(a)); goto cleanup; } ae = NULL; while ((r = archive_read_next_header(a, &ae)) == ARCHIVE_OK) { end = strrchr(archive_entry_pathname(ae), '/'); if (end == NULL) continue; if (strcmp(end, "/pkg-static") == 0) { r = archive_read_extract(a, ae, ARCHIVE_EXTRACT_OWNER | ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_ACL | ARCHIVE_EXTRACT_FFLAGS | ARCHIVE_EXTRACT_XATTR); strlcpy(p, archive_entry_pathname(ae), sz); break; } } if (r == ARCHIVE_OK) ret = 0; else warnx("failed to extract pkg-static: %s", archive_error_string(a)); cleanup: archive_read_free(a); return (ret); } static int install_pkg_static(const char *path, const char *pkgpath, bool force) { int pstat; pid_t pid; switch ((pid = fork())) { case -1: return (-1); case 0: if (force) execl(path, "pkg-static", "add", "-f", pkgpath, (char *)NULL); else execl(path, "pkg-static", "add", pkgpath, (char *)NULL); _exit(1); default: break; } while (waitpid(pid, &pstat, 0) == -1) if (errno != EINTR) return (-1); if (WEXITSTATUS(pstat)) return (WEXITSTATUS(pstat)); else if (WIFSIGNALED(pstat)) return (128 & (WTERMSIG(pstat))); return (pstat); } static int fetch_to_fd(const char *url, char *path) { struct url *u; struct dns_srvinfo *mirrors, *current; struct url_stat st; FILE *remote; /* To store _https._tcp. + hostname + \0 */ int fd; int retry, max_retry; ssize_t r; char buf[10240]; char zone[MAXHOSTNAMELEN + 13]; static const char *mirror_type = NULL; max_retry = 3; current = mirrors = NULL; remote = NULL; if (mirror_type == NULL && config_string(MIRROR_TYPE, &mirror_type) != 0) { warnx("No MIRROR_TYPE defined"); return (-1); } if ((fd = mkstemp(path)) == -1) { warn("mkstemp()"); return (-1); } retry = max_retry; if ((u = fetchParseURL(url)) == NULL) { warn("fetchParseURL('%s')", url); return (-1); } while (remote == NULL) { if (retry == max_retry) { if (strcmp(u->scheme, "file") != 0 && strcasecmp(mirror_type, "srv") == 0) { snprintf(zone, sizeof(zone), "_%s._tcp.%s", u->scheme, u->host); mirrors = dns_getsrvinfo(zone); current = mirrors; } } if (mirrors != NULL) { strlcpy(u->host, current->host, sizeof(u->host)); u->port = current->port; } remote = fetchXGet(u, &st, ""); if (remote == NULL) { --retry; if (retry <= 0) goto fetchfail; if (mirrors == NULL) { sleep(1); } else { current = current->next; if (current == NULL) current = mirrors; } } } while ((r = fread(buf, 1, sizeof(buf), remote)) > 0) { if (write(fd, buf, r) != r) { warn("write()"); goto fetchfail; } } if (r != 0) { warn("An error occurred while fetching pkg(8)"); goto fetchfail; } if (ferror(remote)) goto fetchfail; goto cleanup; fetchfail: if (fd != -1) { close(fd); fd = -1; unlink(path); } cleanup: if (remote != NULL) fclose(remote); return fd; } static struct fingerprint * parse_fingerprint(ucl_object_t *obj) { const ucl_object_t *cur; ucl_object_iter_t it = NULL; const char *function, *fp, *key; struct fingerprint *f; hash_t fct = HASH_UNKNOWN; function = fp = NULL; while ((cur = ucl_iterate_object(obj, &it, true))) { key = ucl_object_key(cur); if (cur->type != UCL_STRING) continue; if (strcasecmp(key, "function") == 0) { function = ucl_object_tostring(cur); continue; } if (strcasecmp(key, "fingerprint") == 0) { fp = ucl_object_tostring(cur); continue; } } if (fp == NULL || function == NULL) return (NULL); if (strcasecmp(function, "sha256") == 0) fct = HASH_SHA256; if (fct == HASH_UNKNOWN) { warnx("Unsupported hashing function: %s", function); return (NULL); } f = calloc(1, sizeof(struct fingerprint)); f->type = fct; strlcpy(f->hash, fp, sizeof(f->hash)); return (f); } static void free_fingerprint_list(struct fingerprint_list* list) { struct fingerprint *fingerprint, *tmp; STAILQ_FOREACH_SAFE(fingerprint, list, next, tmp) { free(fingerprint->name); free(fingerprint); } free(list); } static struct fingerprint * load_fingerprint(const char *dir, const char *filename) { ucl_object_t *obj = NULL; struct ucl_parser *p = NULL; struct fingerprint *f; char path[MAXPATHLEN]; f = NULL; snprintf(path, MAXPATHLEN, "%s/%s", dir, filename); p = ucl_parser_new(0); if (!ucl_parser_add_file(p, path)) { warnx("%s: %s", path, ucl_parser_get_error(p)); ucl_parser_free(p); return (NULL); } obj = ucl_parser_get_object(p); if (obj->type == UCL_OBJECT) f = parse_fingerprint(obj); if (f != NULL) f->name = strdup(filename); ucl_object_unref(obj); ucl_parser_free(p); return (f); } static struct fingerprint_list * load_fingerprints(const char *path, int *count) { DIR *d; struct dirent *ent; struct fingerprint *finger; struct fingerprint_list *fingerprints; *count = 0; fingerprints = calloc(1, sizeof(struct fingerprint_list)); if (fingerprints == NULL) return (NULL); STAILQ_INIT(fingerprints); if ((d = opendir(path)) == NULL) { free(fingerprints); return (NULL); } while ((ent = readdir(d))) { if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) continue; finger = load_fingerprint(path, ent->d_name); if (finger != NULL) { STAILQ_INSERT_TAIL(fingerprints, finger, next); ++(*count); } } closedir(d); return (fingerprints); } static void sha256_hash(unsigned char hash[SHA256_DIGEST_LENGTH], char out[SHA256_DIGEST_LENGTH * 2 + 1]) { int i; for (i = 0; i < SHA256_DIGEST_LENGTH; i++) sprintf(out + (i * 2), "%02x", hash[i]); out[SHA256_DIGEST_LENGTH * 2] = '\0'; } static void sha256_buf(char *buf, size_t len, char out[SHA256_DIGEST_LENGTH * 2 + 1]) { unsigned char hash[SHA256_DIGEST_LENGTH]; SHA256_CTX sha256; out[0] = '\0'; SHA256_Init(&sha256); SHA256_Update(&sha256, buf, len); SHA256_Final(hash, &sha256); sha256_hash(hash, out); } static int sha256_fd(int fd, char out[SHA256_DIGEST_LENGTH * 2 + 1]) { int my_fd; FILE *fp; char buffer[BUFSIZ]; unsigned char hash[SHA256_DIGEST_LENGTH]; size_t r; int ret; SHA256_CTX sha256; my_fd = -1; fp = NULL; r = 0; ret = 1; out[0] = '\0'; /* Duplicate the fd so that fclose(3) does not close it. */ if ((my_fd = dup(fd)) == -1) { warnx("dup"); goto cleanup; } if ((fp = fdopen(my_fd, "rb")) == NULL) { warnx("fdopen"); goto cleanup; } SHA256_Init(&sha256); while ((r = fread(buffer, 1, BUFSIZ, fp)) > 0) SHA256_Update(&sha256, buffer, r); if (ferror(fp) != 0) { warnx("fread"); goto cleanup; } SHA256_Final(hash, &sha256); sha256_hash(hash, out); ret = 0; cleanup: if (fp != NULL) fclose(fp); else if (my_fd != -1) close(my_fd); (void)lseek(fd, 0, SEEK_SET); return (ret); } static EVP_PKEY * load_public_key_file(const char *file) { EVP_PKEY *pkey; BIO *bp; char errbuf[1024]; bp = BIO_new_file(file, "r"); if (!bp) errx(EXIT_FAILURE, "Unable to read %s", file); if ((pkey = PEM_read_bio_PUBKEY(bp, NULL, NULL, NULL)) == NULL) warnx("ici: %s", ERR_error_string(ERR_get_error(), errbuf)); BIO_free(bp); return (pkey); } static EVP_PKEY * load_public_key_buf(const unsigned char *cert, int certlen) { EVP_PKEY *pkey; BIO *bp; char errbuf[1024]; bp = BIO_new_mem_buf(__DECONST(void *, cert), certlen); if ((pkey = PEM_read_bio_PUBKEY(bp, NULL, NULL, NULL)) == NULL) warnx("%s", ERR_error_string(ERR_get_error(), errbuf)); BIO_free(bp); return (pkey); } static bool rsa_verify_cert(int fd, const char *sigfile, const unsigned char *key, int keylen, unsigned char *sig, int siglen) { EVP_MD_CTX *mdctx; EVP_PKEY *pkey; char sha256[(SHA256_DIGEST_LENGTH * 2) + 2]; char errbuf[1024]; bool ret; pkey = NULL; mdctx = NULL; ret = false; SSL_load_error_strings(); /* Compute SHA256 of the package. */ if (lseek(fd, 0, 0) == -1) { warn("lseek"); goto cleanup; } if ((sha256_fd(fd, sha256)) == -1) { warnx("Error creating SHA256 hash for package"); goto cleanup; } if (sigfile != NULL) { if ((pkey = load_public_key_file(sigfile)) == NULL) { warnx("Error reading public key"); goto cleanup; } } else { if ((pkey = load_public_key_buf(key, keylen)) == NULL) { warnx("Error reading public key"); goto cleanup; } } /* Verify signature of the SHA256(pkg) is valid. */ if ((mdctx = EVP_MD_CTX_create()) == NULL) { warnx("%s", ERR_error_string(ERR_get_error(), errbuf)); goto error; } if (EVP_DigestVerifyInit(mdctx, NULL, EVP_sha256(), NULL, pkey) != 1) { warnx("%s", ERR_error_string(ERR_get_error(), errbuf)); goto error; } if (EVP_DigestVerifyUpdate(mdctx, sha256, strlen(sha256)) != 1) { warnx("%s", ERR_error_string(ERR_get_error(), errbuf)); goto error; } if (EVP_DigestVerifyFinal(mdctx, sig, siglen) != 1) { warnx("%s", ERR_error_string(ERR_get_error(), errbuf)); goto error; } ret = true; printf("done\n"); goto cleanup; error: printf("failed\n"); cleanup: if (pkey) EVP_PKEY_free(pkey); if (mdctx) EVP_MD_CTX_destroy(mdctx); ERR_free_strings(); return (ret); } static struct pubkey * read_pubkey(int fd) { struct pubkey *pk; struct sbuf *sig; char buf[4096]; int r; if (lseek(fd, 0, 0) == -1) { warn("lseek"); return (NULL); } sig = sbuf_new_auto(); while ((r = read(fd, buf, sizeof(buf))) >0) { sbuf_bcat(sig, buf, r); } sbuf_finish(sig); pk = calloc(1, sizeof(struct pubkey)); pk->siglen = sbuf_len(sig); pk->sig = calloc(1, pk->siglen); memcpy(pk->sig, sbuf_data(sig), pk->siglen); sbuf_delete(sig); return (pk); } static struct sig_cert * parse_cert(int fd) { int my_fd; struct sig_cert *sc; FILE *fp; struct sbuf *buf, *sig, *cert; char *line; size_t linecap; ssize_t linelen; buf = NULL; my_fd = -1; sc = NULL; line = NULL; linecap = 0; if (lseek(fd, 0, 0) == -1) { warn("lseek"); return (NULL); } /* Duplicate the fd so that fclose(3) does not close it. */ if ((my_fd = dup(fd)) == -1) { warnx("dup"); return (NULL); } if ((fp = fdopen(my_fd, "rb")) == NULL) { warn("fdopen"); close(my_fd); return (NULL); } sig = sbuf_new_auto(); cert = sbuf_new_auto(); while ((linelen = getline(&line, &linecap, fp)) > 0) { if (strcmp(line, "SIGNATURE\n") == 0) { buf = sig; continue; } else if (strcmp(line, "CERT\n") == 0) { buf = cert; continue; } else if (strcmp(line, "END\n") == 0) { break; } if (buf != NULL) sbuf_bcat(buf, line, linelen); } fclose(fp); /* Trim out unrelated trailing newline */ sbuf_setpos(sig, sbuf_len(sig) - 1); sbuf_finish(sig); sbuf_finish(cert); sc = calloc(1, sizeof(struct sig_cert)); sc->siglen = sbuf_len(sig); sc->sig = calloc(1, sc->siglen); memcpy(sc->sig, sbuf_data(sig), sc->siglen); sc->certlen = sbuf_len(cert); sc->cert = strdup(sbuf_data(cert)); sbuf_delete(sig); sbuf_delete(cert); return (sc); } static bool verify_pubsignature(int fd_pkg, int fd_sig) { struct pubkey *pk; const char *pubkey; bool ret; pk = NULL; pubkey = NULL; ret = false; if (config_string(PUBKEY, &pubkey) != 0) { warnx("No CONFIG_PUBKEY defined"); goto cleanup; } if ((pk = read_pubkey(fd_sig)) == NULL) { warnx("Error reading signature"); goto cleanup; } /* Verify the signature. */ printf("Verifying signature with public key %s... ", pubkey); if (rsa_verify_cert(fd_pkg, pubkey, NULL, 0, pk->sig, pk->siglen) == false) { fprintf(stderr, "Signature is not valid\n"); goto cleanup; } ret = true; cleanup: if (pk) { free(pk->sig); free(pk); } return (ret); } static bool verify_signature(int fd_pkg, int fd_sig) { struct fingerprint_list *trusted, *revoked; struct fingerprint *fingerprint; struct sig_cert *sc; bool ret; int trusted_count, revoked_count; const char *fingerprints; char path[MAXPATHLEN]; char hash[SHA256_DIGEST_LENGTH * 2 + 1]; sc = NULL; trusted = revoked = NULL; ret = false; /* Read and parse fingerprints. */ if (config_string(FINGERPRINTS, &fingerprints) != 0) { warnx("No CONFIG_FINGERPRINTS defined"); goto cleanup; } snprintf(path, MAXPATHLEN, "%s/trusted", fingerprints); if ((trusted = load_fingerprints(path, &trusted_count)) == NULL) { warnx("Error loading trusted certificates"); goto cleanup; } if (trusted_count == 0 || trusted == NULL) { fprintf(stderr, "No trusted certificates found.\n"); goto cleanup; } snprintf(path, MAXPATHLEN, "%s/revoked", fingerprints); if ((revoked = load_fingerprints(path, &revoked_count)) == NULL) { warnx("Error loading revoked certificates"); goto cleanup; } /* Read certificate and signature in. */ if ((sc = parse_cert(fd_sig)) == NULL) { warnx("Error parsing certificate"); goto cleanup; } /* Explicitly mark as non-trusted until proven otherwise. */ sc->trusted = false; /* Parse signature and pubkey out of the certificate */ sha256_buf(sc->cert, sc->certlen, hash); /* Check if this hash is revoked */ if (revoked != NULL) { STAILQ_FOREACH(fingerprint, revoked, next) { if (strcasecmp(fingerprint->hash, hash) == 0) { fprintf(stderr, "The package was signed with " "revoked certificate %s\n", fingerprint->name); goto cleanup; } } } STAILQ_FOREACH(fingerprint, trusted, next) { if (strcasecmp(fingerprint->hash, hash) == 0) { sc->trusted = true; sc->name = strdup(fingerprint->name); break; } } if (sc->trusted == false) { fprintf(stderr, "No trusted fingerprint found matching " "package's certificate\n"); goto cleanup; } /* Verify the signature. */ printf("Verifying signature with trusted certificate %s... ", sc->name); if (rsa_verify_cert(fd_pkg, NULL, sc->cert, sc->certlen, sc->sig, sc->siglen) == false) { fprintf(stderr, "Signature is not valid\n"); goto cleanup; } ret = true; cleanup: if (trusted) free_fingerprint_list(trusted); if (revoked) free_fingerprint_list(revoked); if (sc) { free(sc->cert); free(sc->sig); free(sc->name); free(sc); } return (ret); } static int bootstrap_pkg(bool force) { int fd_pkg, fd_sig; int ret; char url[MAXPATHLEN]; char tmppkg[MAXPATHLEN]; char tmpsig[MAXPATHLEN]; const char *packagesite; const char *signature_type; char pkgstatic[MAXPATHLEN]; fd_sig = -1; ret = -1; if (config_string(PACKAGESITE, &packagesite) != 0) { warnx("No PACKAGESITE defined"); return (-1); } if (config_string(SIGNATURE_TYPE, &signature_type) != 0) { warnx("Error looking up SIGNATURE_TYPE"); return (-1); } printf("Bootstrapping pkg from %s, please wait...\n", packagesite); /* Support pkg+http:// for PACKAGESITE which is the new format in 1.2 to avoid confusion on why http://pkg.FreeBSD.org has no A record. */ if (strncmp(URL_SCHEME_PREFIX, packagesite, strlen(URL_SCHEME_PREFIX)) == 0) packagesite += strlen(URL_SCHEME_PREFIX); snprintf(url, MAXPATHLEN, "%s/Latest/pkg.txz", packagesite); snprintf(tmppkg, MAXPATHLEN, "%s/pkg.txz.XXXXXX", getenv("TMPDIR") ? getenv("TMPDIR") : _PATH_TMP); if ((fd_pkg = fetch_to_fd(url, tmppkg)) == -1) goto fetchfail; if (signature_type != NULL && strcasecmp(signature_type, "NONE") != 0) { if (strcasecmp(signature_type, "FINGERPRINTS") == 0) { snprintf(tmpsig, MAXPATHLEN, "%s/pkg.txz.sig.XXXXXX", getenv("TMPDIR") ? getenv("TMPDIR") : _PATH_TMP); snprintf(url, MAXPATHLEN, "%s/Latest/pkg.txz.sig", packagesite); if ((fd_sig = fetch_to_fd(url, tmpsig)) == -1) { fprintf(stderr, "Signature for pkg not " "available.\n"); goto fetchfail; } if (verify_signature(fd_pkg, fd_sig) == false) goto cleanup; } else if (strcasecmp(signature_type, "PUBKEY") == 0) { snprintf(tmpsig, MAXPATHLEN, "%s/pkg.txz.pubkeysig.XXXXXX", getenv("TMPDIR") ? getenv("TMPDIR") : _PATH_TMP); snprintf(url, MAXPATHLEN, "%s/Latest/pkg.txz.pubkeysig", packagesite); if ((fd_sig = fetch_to_fd(url, tmpsig)) == -1) { fprintf(stderr, "Signature for pkg not " "available.\n"); goto fetchfail; } if (verify_pubsignature(fd_pkg, fd_sig) == false) goto cleanup; } else { warnx("Signature type %s is not supported for " "bootstrapping.", signature_type); goto cleanup; } } if ((ret = extract_pkg_static(fd_pkg, pkgstatic, MAXPATHLEN)) == 0) ret = install_pkg_static(pkgstatic, tmppkg, force); goto cleanup; fetchfail: warnx("Error fetching %s: %s", url, fetchLastErrString); fprintf(stderr, "A pre-built version of pkg could not be found for " "your system.\n"); fprintf(stderr, "Consider changing PACKAGESITE or installing it from " "ports: 'ports-mgmt/pkg'.\n"); cleanup: if (fd_sig != -1) { close(fd_sig); unlink(tmpsig); } if (fd_pkg != -1) { close(fd_pkg); unlink(tmppkg); } return (ret); } static const char confirmation_message[] = "The package management tool is not yet installed on your system.\n" "Do you want to fetch and install it now? [y/N]: "; static const char non_interactive_message[] = "The package management tool is not yet installed on your system.\n" "Please set ASSUME_ALWAYS_YES=yes environment variable to be able to bootstrap " "in non-interactive (stdin not being a tty)\n"; static int pkg_query_yes_no(void) { int ret, c; c = getchar(); if (c == 'y' || c == 'Y') ret = 1; else ret = 0; while (c != '\n' && c != EOF) c = getchar(); return (ret); } static int bootstrap_pkg_local(const char *pkgpath, bool force) { char path[MAXPATHLEN]; char pkgstatic[MAXPATHLEN]; const char *signature_type; int fd_pkg, fd_sig, ret; fd_sig = -1; ret = -1; fd_pkg = open(pkgpath, O_RDONLY); if (fd_pkg == -1) err(EXIT_FAILURE, "Unable to open %s", pkgpath); if (config_string(SIGNATURE_TYPE, &signature_type) != 0) { warnx("Error looking up SIGNATURE_TYPE"); goto cleanup; } if (signature_type != NULL && strcasecmp(signature_type, "NONE") != 0) { if (strcasecmp(signature_type, "FINGERPRINTS") == 0) { snprintf(path, sizeof(path), "%s.sig", pkgpath); if ((fd_sig = open(path, O_RDONLY)) == -1) { fprintf(stderr, "Signature for pkg not " "available.\n"); goto cleanup; } if (verify_signature(fd_pkg, fd_sig) == false) goto cleanup; } else if (strcasecmp(signature_type, "PUBKEY") == 0) { snprintf(path, sizeof(path), "%s.pubkeysig", pkgpath); if ((fd_sig = open(path, O_RDONLY)) == -1) { fprintf(stderr, "Signature for pkg not " "available.\n"); goto cleanup; } if (verify_pubsignature(fd_pkg, fd_sig) == false) goto cleanup; } else { warnx("Signature type %s is not supported for " "bootstrapping.", signature_type); goto cleanup; } } if ((ret = extract_pkg_static(fd_pkg, pkgstatic, MAXPATHLEN)) == 0) ret = install_pkg_static(pkgstatic, pkgpath, force); cleanup: close(fd_pkg); if (fd_sig != -1) close(fd_sig); return (ret); } int main(int argc, char *argv[]) { char pkgpath[MAXPATHLEN]; const char *pkgarg; bool bootstrap_only, force, yes; bootstrap_only = false; force = false; pkgarg = NULL; yes = false; snprintf(pkgpath, MAXPATHLEN, "%s/sbin/pkg", getenv("LOCALBASE") ? getenv("LOCALBASE") : _LOCALBASE); if (argc > 1 && strcmp(argv[1], "bootstrap") == 0) { bootstrap_only = true; if (argc == 3 && strcmp(argv[2], "-f") == 0) force = true; } if ((bootstrap_only && force) || access(pkgpath, X_OK) == -1) { /* * To allow 'pkg -N' to be used as a reliable test for whether * a system is configured to use pkg, don't bootstrap pkg * when that argument is given as argv[1]. */ if (argv[1] != NULL && strcmp(argv[1], "-N") == 0) errx(EXIT_FAILURE, "pkg is not installed"); config_init(); if (argc > 1 && strcmp(argv[1], "add") == 0) { if (argc > 2 && strcmp(argv[2], "-f") == 0) { force = true; pkgarg = argv[3]; } else pkgarg = argv[2]; if (pkgarg == NULL) { fprintf(stderr, "Path to pkg.txz required\n"); exit(EXIT_FAILURE); } if (access(pkgarg, R_OK) == -1) { fprintf(stderr, "No such file: %s\n", pkgarg); exit(EXIT_FAILURE); } if (bootstrap_pkg_local(pkgarg, force) != 0) exit(EXIT_FAILURE); exit(EXIT_SUCCESS); } /* * Do not ask for confirmation if either of stdin or stdout is * not tty. Check the environment to see if user has answer * tucked in there already. */ config_bool(ASSUME_ALWAYS_YES, &yes); if (!yes) { if (!isatty(fileno(stdin))) { fprintf(stderr, non_interactive_message); exit(EXIT_FAILURE); } printf("%s", confirmation_message); if (pkg_query_yes_no() == 0) exit(EXIT_FAILURE); } if (bootstrap_pkg(force) != 0) exit(EXIT_FAILURE); config_finish(); if (bootstrap_only) exit(EXIT_SUCCESS); } else if (bootstrap_only) { printf("pkg already bootstrapped at %s\n", pkgpath); exit(EXIT_SUCCESS); } execv(pkgpath, argv); /* NOT REACHED */ return (EXIT_FAILURE); } Index: head/usr.sbin/pw/pw.h =================================================================== --- head/usr.sbin/pw/pw.h (revision 303526) +++ head/usr.sbin/pw/pw.h (revision 303527) @@ -1,107 +1,106 @@ /*- * Copyright (C) 1996 * David L. Nugent. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #include -#define _WITH_GETLINE #include #include #include #include "pwupd.h" enum _mode { M_ADD, M_DELETE, M_UPDATE, M_PRINT, M_NEXT, M_LOCK, M_UNLOCK, M_NUM }; enum _which { W_USER, W_GROUP, W_NUM }; #define _DEF_DIRMODE (S_IRWXU | S_IRWXG | S_IRWXO) #define _PATH_PW_CONF "/etc/pw.conf" #define _UC_MAXLINE 1024 #define _UC_MAXSHELLS 32 struct userconf *get_userconfig(const char *cfg); struct userconf *read_userconfig(char const * file); int write_userconfig(struct userconf *cnf, char const * file); int pw_group_add(int argc, char **argv, char *name); int pw_group_del(int argc, char **argv, char *name); int pw_group_mod(int argc, char **argv, char *name); int pw_group_next(int argc, char **argv, char *name); int pw_group_show(int argc, char **argv, char *name); int pw_user_add(int argc, char **argv, char *name); int pw_user_add(int argc, char **argv, char *name); int pw_user_add(int argc, char **argv, char *name); int pw_user_add(int argc, char **argv, char *name); int pw_user_del(int argc, char **argv, char *name); int pw_user_lock(int argc, char **argv, char *name); int pw_user_mod(int argc, char **argv, char *name); int pw_user_next(int argc, char **argv, char *name); int pw_user_show(int argc, char **argv, char *name); int pw_user_unlock(int argc, char **argv, char *name); int pw_groupnext(struct userconf *cnf, bool quiet); char *pw_checkname(char *name, int gecos); uintmax_t pw_checkid(char *nptr, uintmax_t maxval); int pw_checkfd(char *nptr); int addnispwent(const char *path, struct passwd *pwd); int delnispwent(const char *path, const char *login); int chgnispwent(const char *path, const char *login, struct passwd *pwd); int groupadd(struct userconf *, char *name, gid_t id, char *members, int fd, bool dryrun, bool pretty, bool precrypted); int nis_update(void); int boolean_val(char const * str, int dflt); int passwd_val(char const * str, int dflt); char const *boolean_str(int val); char *newstr(char const * p); void pw_log(struct userconf * cnf, int mode, int which, char const * fmt,...) __printflike(4, 5); char *pw_pwcrypt(char *password); extern const char *Modes[]; extern const char *Which[]; uintmax_t strtounum(const char * __restrict, uintmax_t, uintmax_t, const char ** __restrict); Index: head/usr.sbin/pw/pw_vpw.c =================================================================== --- head/usr.sbin/pw/pw_vpw.c (revision 303526) +++ head/usr.sbin/pw/pw_vpw.c (revision 303527) @@ -1,204 +1,203 @@ /*- * Copyright (C) 1996 * David L. Nugent. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ #ifndef lint static const char rcsid[] = "$FreeBSD$"; #endif /* not lint */ #include #include #include -#define _WITH_GETLINE #include #include #include #include #include "pwupd.h" static FILE * pwd_fp = NULL; void vendpwent(void) { if (pwd_fp != NULL) { fclose(pwd_fp); pwd_fp = NULL; } } void vsetpwent(void) { vendpwent(); } static struct passwd * vnextpwent(char const *nam, uid_t uid, int doclose) { struct passwd *pw; char *line; size_t linecap; ssize_t linelen; pw = NULL; line = NULL; linecap = 0; if (pwd_fp != NULL || (pwd_fp = fopen(getpwpath(_MASTERPASSWD), "r")) != NULL) { while ((linelen = getline(&line, &linecap, pwd_fp)) > 0) { /* Skip comments and empty lines */ if (*line == '\n' || *line == '#') continue; /* trim latest \n */ if (line[linelen - 1 ] == '\n') line[linelen - 1] = '\0'; pw = pw_scan(line, PWSCAN_MASTER); if (pw == NULL) errx(EXIT_FAILURE, "Invalid user entry in '%s':" " '%s'", getpwpath(_MASTERPASSWD), line); if (uid != (uid_t)-1) { if (uid == pw->pw_uid) break; } else if (nam != NULL) { if (strcmp(nam, pw->pw_name) == 0) break; } else break; free(pw); pw = NULL; } if (doclose) vendpwent(); } free(line); return (pw); } struct passwd * vgetpwent(void) { return vnextpwent(NULL, -1, 0); } struct passwd * vgetpwuid(uid_t uid) { return vnextpwent(NULL, uid, 1); } struct passwd * vgetpwnam(const char * nam) { return vnextpwent(nam, -1, 1); } static FILE * grp_fp = NULL; void vendgrent(void) { if (grp_fp != NULL) { fclose(grp_fp); grp_fp = NULL; } } RET_SETGRENT vsetgrent(void) { vendgrent(); #if defined(__FreeBSD__) return 0; #endif } static struct group * vnextgrent(char const *nam, gid_t gid, int doclose) { struct group *gr; char *line; size_t linecap; ssize_t linelen; gr = NULL; line = NULL; linecap = 0; if (grp_fp != NULL || (grp_fp = fopen(getgrpath(_GROUP), "r")) != NULL) { while ((linelen = getline(&line, &linecap, grp_fp)) > 0) { /* Skip comments and empty lines */ if (*line == '\n' || *line == '#') continue; /* trim latest \n */ if (line[linelen - 1 ] == '\n') line[linelen - 1] = '\0'; gr = gr_scan(line); if (gr == NULL) errx(EXIT_FAILURE, "Invalid group entry in '%s':" " '%s'", getgrpath(_GROUP), line); if (gid != (gid_t)-1) { if (gid == gr->gr_gid) break; } else if (nam != NULL) { if (strcmp(nam, gr->gr_name) == 0) break; } else break; free(gr); gr = NULL; } if (doclose) vendgrent(); } free(line); return (gr); } struct group * vgetgrent(void) { return vnextgrent(NULL, -1, 0); } struct group * vgetgrgid(gid_t gid) { return vnextgrent(NULL, gid, 1); } struct group * vgetgrnam(const char * nam) { return vnextgrent(nam, -1, 1); } Index: head/usr.sbin/services_mkdb/services_mkdb.c =================================================================== --- head/usr.sbin/services_mkdb/services_mkdb.c (revision 303526) +++ head/usr.sbin/services_mkdb/services_mkdb.c (revision 303527) @@ -1,463 +1,462 @@ /* $NetBSD: services_mkdb.c,v 1.14 2008/04/28 20:24:17 martin Exp $ */ /*- * Copyright (c) 1999 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Luke Mewburn and Christos Zoulas. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include -#define _WITH_GETLINE #include #include #include #include #include #include #include #include #include "extern.h" static char tname[MAXPATHLEN]; #define PMASK 0xffff #define PROTOMAX 5 static void add(DB *, StringList *, size_t, const char *, size_t *, int); static StringList ***parseservices(const char *, StringList *); static void cleanup(void); static void store(DB *, DBT *, DBT *, int); static void killproto(DBT *); static char *getstring(const char *, size_t, char **, const char *); static size_t getprotoindex(StringList *, const char *); static const char *getprotostr(StringList *, size_t); static const char *mkaliases(StringList *, char *, size_t); static void usage(void); HASHINFO hinfo = { .bsize = 256, .ffactor = 4, .nelem = 32768, .cachesize = 1024, .hash = NULL, .lorder = 0 }; int main(int argc, char *argv[]) { DB *db; int ch; const char *fname = _PATH_SERVICES; const char *dbname = _PATH_SERVICES_DB; int warndup = 1; int unique = 0; int otherflag = 0; int byteorder = 0; size_t cnt = 0; StringList *sl, ***svc; size_t port, proto; char *dbname_dir, *dbname_dirbuf; int dbname_dir_fd = -1; setprogname(argv[0]); while ((ch = getopt(argc, argv, "blo:qu")) != -1) switch (ch) { case 'b': case 'l': if (byteorder != 0) usage(); byteorder = ch == 'b' ? 4321 : 1234; break; case 'q': otherflag = 1; warndup = 0; break; case 'o': otherflag = 1; dbname = optarg; break; case 'u': unique++; break; case '?': default: usage(); } argc -= optind; argv += optind; if (argc > 1 || (unique && otherflag)) usage(); if (argc == 1) fname = argv[0]; /* Set byte order. */ hinfo.lorder = byteorder; if (unique) uniq(fname); svc = parseservices(fname, sl = sl_init()); if (atexit(cleanup)) err(1, "Cannot install exit handler"); (void)snprintf(tname, sizeof(tname), "%s.tmp", dbname); db = dbopen(tname, O_RDWR | O_CREAT | O_EXCL, (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH), DB_HASH, &hinfo); if (!db) err(1, "Error opening temporary database `%s'", tname); for (port = 0; port < PMASK + 1; port++) { if (svc[port] == NULL) continue; for (proto = 0; proto < PROTOMAX; proto++) { StringList *s; if ((s = svc[port][proto]) == NULL) continue; add(db, s, port, getprotostr(sl, proto), &cnt, warndup); } free(svc[port]); } free(svc); sl_free(sl, 1); if ((db->close)(db)) err(1, "Error closing temporary database `%s'", tname); /* * Make sure file is safe on disk. To improve performance we will call * fsync() to the directory where file lies */ if (rename(tname, dbname) == -1 || (dbname_dirbuf = strdup(dbname)) == NULL || (dbname_dir = dirname(dbname_dirbuf)) == NULL || (dbname_dir_fd = open(dbname_dir, O_RDONLY|O_DIRECTORY)) == -1 || fsync(dbname_dir_fd) != 0) { if (dbname_dir_fd != -1) close(dbname_dir_fd); err(1, "Cannot rename `%s' to `%s'", tname, dbname); } if (dbname_dir_fd != -1) close(dbname_dir_fd); return 0; } static void add(DB *db, StringList *sl, size_t port, const char *proto, size_t *cnt, int warndup) { size_t i; char keyb[BUFSIZ], datab[BUFSIZ], abuf[BUFSIZ]; DBT data, key; key.data = keyb; data.data = datab; #ifdef DEBUG (void)printf("add %s %zu %s [ ", sl->sl_str[0], port, proto); for (i = 1; i < sl->sl_cur; i++) (void)printf("%s ", sl->sl_str[i]); (void)printf("]\n"); #endif /* key `indirect key', data `full line' */ data.size = snprintf(datab, sizeof(datab), "%zu", (*cnt)++) + 1; key.size = snprintf(keyb, sizeof(keyb), "%s %zu/%s %s", sl->sl_str[0], port, proto, mkaliases(sl, abuf, sizeof(abuf))) + 1; store(db, &data, &key, warndup); /* key `\377port/proto', data = `indirect key' */ key.size = snprintf(keyb, sizeof(keyb), "\377%zu/%s", port, proto) + 1; store(db, &key, &data, warndup); /* key `\377port', data = `indirect key' */ killproto(&key); store(db, &key, &data, warndup); /* add references for service and all aliases */ for (i = 0; i < sl->sl_cur; i++) { /* key `\376service/proto', data = `indirect key' */ key.size = snprintf(keyb, sizeof(keyb), "\376%s/%s", sl->sl_str[i], proto) + 1; store(db, &key, &data, warndup); /* key `\376service', data = `indirect key' */ killproto(&key); store(db, &key, &data, warndup); } sl_free(sl, 1); } static StringList *** parseservices(const char *fname, StringList *sl) { ssize_t len; size_t linecap, line, pindex; FILE *fp; StringList ***svc, *s; char *p, *ep; if ((fp = fopen(fname, "r")) == NULL) err(1, "Cannot open `%s'", fname); line = linecap = 0; if ((svc = calloc(PMASK + 1, sizeof(StringList **))) == NULL) err(1, "Cannot allocate %zu bytes", (size_t)(PMASK + 1)); p = NULL; while ((len = getline(&p, &linecap, fp)) != -1) { char *name, *port, *proto, *aliases, *cp, *alias; unsigned long pnum; line++; if (len == 0) continue; if (p[len - 1] == '\n') p[len - 1] = '\0'; for (cp = p; *cp && isspace((unsigned char)*cp); cp++) continue; if (*cp == '\0' || *cp == '#') continue; if ((name = getstring(fname, line, &cp, "name")) == NULL) continue; if ((port = getstring(fname, line, &cp, "port")) == NULL) continue; if (cp) { for (aliases = cp; *cp && *cp != '#'; cp++) continue; if (*cp) *cp = '\0'; } else aliases = NULL; proto = strchr(port, '/'); if (proto == NULL || proto[1] == '\0') { warnx("%s, %zu: no protocol found", fname, line); continue; } *proto++ = '\0'; errno = 0; pnum = strtoul(port, &ep, 0); if (*port == '\0' || *ep != '\0') { warnx("%s, %zu: invalid port `%s'", fname, line, port); continue; } if ((errno == ERANGE && pnum == ULONG_MAX) || pnum > PMASK) { warnx("%s, %zu: port too big `%s'", fname, line, port); continue; } if (svc[pnum] == NULL) { svc[pnum] = calloc(PROTOMAX, sizeof(StringList *)); if (svc[pnum] == NULL) err(1, "Cannot allocate %zu bytes", (size_t)PROTOMAX); } pindex = getprotoindex(sl, proto); if (svc[pnum][pindex] == NULL) s = svc[pnum][pindex] = sl_init(); else s = svc[pnum][pindex]; /* build list of aliases */ if (sl_find(s, name) == NULL) { char *p2; if ((p2 = strdup(name)) == NULL) err(1, "Cannot copy string"); (void)sl_add(s, p2); } if (aliases) { while ((alias = strsep(&aliases, " \t")) != NULL) { if (alias[0] == '\0') continue; if (sl_find(s, alias) == NULL) { char *p2; if ((p2 = strdup(alias)) == NULL) err(1, "Cannot copy string"); (void)sl_add(s, p2); } } } } (void)fclose(fp); return svc; } /* * cleanup(): Remove temporary files upon exit */ static void cleanup(void) { if (tname[0]) (void)unlink(tname); } static char * getstring(const char *fname, size_t line, char **cp, const char *tag) { char *str; while ((str = strsep(cp, " \t")) != NULL && *str == '\0') continue; if (str == NULL) warnx("%s, %zu: no %s found", fname, line, tag); return str; } static void killproto(DBT *key) { char *p, *d = key->data; if ((p = strchr(d, '/')) == NULL) abort(); *p++ = '\0'; key->size = p - d; } static void store(DB *db, DBT *key, DBT *data, int warndup) { #ifdef DEBUG int k = key->size - 1; int d = data->size - 1; (void)printf("store [%*.*s] [%*.*s]\n", k, k, (char *)key->data + 1, d, d, (char *)data->data + 1); #endif switch ((db->put)(db, key, data, R_NOOVERWRITE)) { case 0: break; case 1: if (warndup) warnx("duplicate service `%s'", &((char *)key->data)[1]); break; case -1: err(1, "put"); break; default: abort(); break; } } static size_t getprotoindex(StringList *sl, const char *str) { size_t i; char *p; for (i= 0; i < sl->sl_cur; i++) if (strcmp(sl->sl_str[i], str) == 0) return i; if (i == PROTOMAX) errx(1, "Ran out of protocols adding `%s';" " recompile with larger PROTOMAX", str); if ((p = strdup(str)) == NULL) err(1, "Cannot copy string"); (void)sl_add(sl, p); return i; } static const char * getprotostr(StringList *sl, size_t i) { assert(i < sl->sl_cur); return sl->sl_str[i]; } static const char * mkaliases(StringList *sl, char *buf, size_t len) { size_t nc, i, pos; buf[0] = 0; for (i = 1, pos = 0; i < sl->sl_cur; i++) { nc = strlcpy(buf + pos, sl->sl_str[i], len); if (nc >= len) goto out; pos += nc; len -= nc; nc = strlcpy(buf + pos, " ", len); if (nc >= len) goto out; pos += nc; len -= nc; } return buf; out: warn("aliases for `%s' truncated", sl->sl_str[0]); return buf; } static void usage(void) { (void)fprintf(stderr, "Usage:\t%s [-b | -l] [-q] [-o ] []\n" "\t%s -u []\n", getprogname(), getprogname()); exit(1); }