struct result selection_add_item(struct selection *selection, char const *description, selection_action_fn *action) { if (!selection) return result_set_system_error(EINVAL); if (!description) return result_set_system_error(EINVAL); if (!description[0]) return result_set_system_error(EINVAL); int index = selection->items_count; int null_index = index + 1; ++selection->items_count; selection->items = reallocarray_or_die(selection->items, selection->items_count + 1, sizeof(ITEM *)); char name[] = "1"; name[0] += index; char *name_dup = strdup_or_die(name); char *description_dup = strdup_or_die(description); selection->items[index] = new_item(name_dup, description_dup); if (!selection->items[index]) { free_or_die(description_dup); free_or_die(name_dup); return result_system_error(); } struct selection_item *selection_item = calloc_or_die(1, sizeof(struct selection_item)); selection_item->action = action; set_item_userptr(selection->items[index], selection_item); selection->items[null_index] = NULL; return result_success(); }
/* add a bundle_result to the results list */ static void add_bundle_file_result(char *bundlename, char *filename, double score) { struct bundle_result *bundle = NULL; struct file_result *file; struct list *ptr; ptr = results; while (ptr) { struct bundle_result *item; item = ptr->data; if (strcmp(item->bundle_name, bundlename) == 0) { bundle = item; break; } ptr = ptr->next; } if (!bundle) { bundle = calloc(sizeof(struct bundle_result), 1); ON_NULL_ABORT(bundle); results = list_append_data(results, bundle); strncpy(bundle->bundle_name, bundlename, BUNDLE_NAME_MAXLEN - 1); /* record if the bundle is tracked on the system */ bundle->is_tracked = is_tracked_bundle(bundlename); } file = calloc(sizeof(struct file_result), 1); ON_NULL_ABORT(file); file->filename = strdup_or_die(filename); bundle->files = list_append_data(bundle->files, file); file->score = score; bundle->score += score; }
/********************************************************************* * * Function : parse_forwarder_address * * Description : Parse out the host and port from a forwarder address. * * Parameters : * 1 : address = The forwarder address to parse. * 2 : hostname = Used to return the hostname. NULL on error. * 3 : port = Used to return the port. Untouched if no port * is specified. * * Returns : JB_ERR_OK on success * JB_ERR_MEMORY on out of memory * JB_ERR_PARSE on malformed address. * *********************************************************************/ jb_err parse_forwarder_address(char *address, char **hostname, int *port) { char *p = address; if ((*address == '[') && (NULL == strchr(address, ']'))) { /* XXX: Should do some more validity checks here. */ return JB_ERR_PARSE; } *hostname = strdup_or_die(address); if ((**hostname == '[') && (NULL != (p = strchr(*hostname, ']')))) { *p++ = '\0'; memmove(*hostname, (*hostname + 1), (size_t)(p - *hostname)); if (*p == ':') { *port = (int)strtol(++p, NULL, 0); } } else if (NULL != (p = strchr(*hostname, ':'))) { *p++ = '\0'; *port = (int)strtol(p, NULL, 0); } return JB_ERR_OK; }
/********************************************************************* * * Function : compile_host_pattern * * Description : Parses and "compiles" an old-school host pattern. * * Parameters : * 1 : url = Target pattern_spec to be filled in. * 2 : host_pattern = Host pattern to parse. * * Returns : JB_ERR_OK - Success * JB_ERR_PARSE - Cannot parse regex * *********************************************************************/ static jb_err compile_host_pattern(struct pattern_spec *url, const char *host_pattern) { char *v[150]; size_t size; char *p; /* * Parse domain part */ if (host_pattern[strlen(host_pattern) - 1] == '.') { url->pattern.url_spec.unanchored |= ANCHOR_RIGHT; } if (host_pattern[0] == '.') { url->pattern.url_spec.unanchored |= ANCHOR_LEFT; } /* * Split domain into components */ url->pattern.url_spec.dbuffer = strdup_or_die(host_pattern); /* * Map to lower case */ for (p = url->pattern.url_spec.dbuffer; *p ; p++) { *p = (char)privoxy_tolower(*p); } /* * Split the domain name into components */ url->pattern.url_spec.dcount = ssplit(url->pattern.url_spec.dbuffer, ".", v, SZ(v)); if (url->pattern.url_spec.dcount < 0) { free_pattern_spec(url); return JB_ERR_PARSE; } else if (url->pattern.url_spec.dcount != 0) { /* * Save a copy of the pointers in dvec */ size = (size_t)url->pattern.url_spec.dcount * sizeof(*url->pattern.url_spec.dvec); url->pattern.url_spec.dvec = malloc_or_die(size); memcpy(url->pattern.url_spec.dvec, v, size); } /* * else dcount == 0 in which case we needn't do anything, * since dvec will never be accessed and the pattern will * match all domains. */ return JB_ERR_OK; }
char * area_alloc_description(struct area const *area) { int width = area->box.size.width * 10; int length = area->box.size.length * 10; int level = area->box.origin.z; char *description; switch (area->type) { case area_type_chamber: description = str_alloc_formatted("%u' x %u' chamber", width, length); break; case area_type_intersection: description = strdup_or_die("intersection"); break; case area_type_passage: { enum orientation orientation = orientation_from_direction(area->direction); if (orientation_east_to_west == orientation) swap(&width, &length); description = str_alloc_formatted("%u' passage %s", length, orientation_name(orientation)); break; } case area_type_room: description = str_alloc_formatted("%u' x %u' room", width, length); break; case area_type_stairs_down: description = alloc_stairs_description(level + 1, area_type_stairs_down); break; case area_type_stairs_up: description = alloc_stairs_description(level - 1, area_type_stairs_up); break; default: description = str_alloc_formatted("%u' x %u' area", width, length); break; } if (!area->features) return description; if (area->features & area_features_chimney_up) { str_realloc_append_formatted(&description, ", chimney up to "); realloc_append_level_description(&description, level - 1); } if (area->features & area_features_chimney_down) { str_realloc_append_formatted(&description, ", chimney down to "); realloc_append_level_description(&description, level + 1); } if (area->features & area_features_chute_entrance) { str_realloc_append_formatted(&description, ", chute down to "); realloc_append_level_description(&description, level + 1); } if (area->features & area_features_chute_exit) { str_realloc_append_formatted(&description, ", chute down from "); realloc_append_level_description(&description, level - 1); } return description; }
struct selection * selection_alloc_or_die(char const *title) { assert(title && title[0]); struct selection *selection = calloc_or_die(1, sizeof(struct selection)); selection->index = -1; selection->items = calloc_or_die(1, sizeof(ITEM *)); selection->title = strdup_or_die(title); return selection; }
/* ID_ITEM FUNCTIONS*/ id_item_t * id_item_new(char *name) { id_item_t * item; item = xmalloc(sizeof(*item)); memset(item, 0, sizeof(*item)); item->name = strdup_or_die(name); item->type = ID_UNKNOWN; item->destructor = id_item_default_release; return item; }
struct options * options_alloc(int argc, char *argv[]) { struct options *options = calloc_or_die(1, sizeof(struct options)); options->command_name = strdup_or_die(basename(argv[0])); options->rnd = rnd_alloc(); int action_index = get_options(options, argc, argv); if (options->error) return options; get_action(options, argc, argv, action_index); return options; }
static struct hash_ctx * hash_ctx_new(char *name, int type, void *ctx) { struct hash_ctx *item; item = xmalloc(sizeof(*item)); memset(item, 0, sizeof(*item)); item->name = strdup_or_die(name); item->type = type; item->ctx = ctx; return item; }
static struct manifest *alloc_manifest(int version, char *component) { struct manifest *manifest; manifest = calloc(1, sizeof(struct manifest)); if (manifest == NULL) { abort(); } manifest->version = version; manifest->component = strdup_or_die(component); return manifest; }
/********************************************************************* * * Function : create_pattern_spec * * Description : Creates a "pattern_spec" structure from a string. * When finished, free with free_pattern_spec(). * * Parameters : * 1 : pattern = Target pattern_spec to be filled in. * Will be zeroed before use. * 2 : buf = Source pattern, null terminated. NOTE: The * contents of this buffer are destroyed by this * function. If this function succeeds, the * buffer is copied to pattern->spec. If this * function fails, the contents of the buffer * are lost forever. * * Returns : JB_ERR_OK - Success * JB_ERR_PARSE - Cannot parse regex (Detailed message * written to system log) * *********************************************************************/ jb_err create_pattern_spec(struct pattern_spec *pattern, char *buf) { static const struct { /** The tag pattern prefix to match */ const char *prefix; /** The length of the prefix to match */ const size_t prefix_length; /** The pattern flag */ const unsigned flag; } tag_pattern[] = { { "TAG:", 4, PATTERN_SPEC_TAG_PATTERN}, { "NO-REQUEST-TAG:", 15, PATTERN_SPEC_NO_REQUEST_TAG_PATTERN}, { "NO-RESPONSE-TAG:", 16, PATTERN_SPEC_NO_RESPONSE_TAG_PATTERN} }; int i; assert(pattern); assert(buf); memset(pattern, '\0', sizeof(*pattern)); /* Remember the original specification for the CGI pages. */ pattern->spec = strdup_or_die(buf); /* Check if it's a tag pattern */ for (i = 0; i < SZ(tag_pattern); i++) { if (0 == strncmpic(pattern->spec, tag_pattern[i].prefix, tag_pattern[i].prefix_length)) { /* The regex starts after the prefix */ const char *tag_regex = buf + tag_pattern[i].prefix_length; pattern->flags |= tag_pattern[i].flag; return compile_pattern(tag_regex, NO_ANCHORING, pattern, &pattern->pattern.tag_regex); } } /* If it isn't a tag pattern it must be an URL pattern. */ pattern->flags |= PATTERN_SPEC_URL_PATTERN; return compile_url_pattern(pattern, buf); }
// expects filename w/o path_prefix prepended bool is_under_mounted_directory(const char *filename) { bool ret = false; int err; char *token; char *mountpoint; char *dir; char *fname; char *tmp; if (mounted_dirs == NULL) { return false; } dir = strdup_or_die(mounted_dirs); token = strtok(dir + 1, ":"); while (token != NULL) { string_or_die(&mountpoint, "%s/", token); tmp = mk_full_filename(path_prefix, filename); string_or_die(&fname, ":%s:", tmp); free_string(&tmp); err = strncmp(fname, mountpoint, strlen(mountpoint)); free_string(&fname); if (err == 0) { free_string(&mountpoint); ret = true; break; } token = strtok(NULL, ":"); free_string(&mountpoint); } free_string(&dir); return ret; }
/********************************************************************* * * Function : init_domain_components * * Description : Splits the domain name so we can compare it * against wildcards. It used to be part of * parse_http_url, but was separated because the * same code is required in chat in case of * intercepted requests. * * Parameters : * 1 : http = pointer to the http structure to hold elements. * * Returns : JB_ERR_OK on success * JB_ERR_PARSE on malformed command/URL * or >100 domains deep. * *********************************************************************/ jb_err init_domain_components(struct http_request *http) { char *vec[BUFFER_SIZE]; size_t size; char *p; http->dbuffer = strdup_or_die(http->host); /* map to lower case */ for (p = http->dbuffer; *p ; p++) { *p = (char)privoxy_tolower(*p); } /* split the domain name into components */ http->dcount = ssplit(http->dbuffer, ".", vec, SZ(vec)); if (http->dcount <= 0) { /* * Error: More than SZ(vec) components in domain * or: no components in domain */ log_error(LOG_LEVEL_ERROR, "More than SZ(vec) components in domain or none at all."); return JB_ERR_PARSE; } /* save a copy of the pointers in dvec */ size = (size_t)http->dcount * sizeof(*http->dvec); http->dvec = malloc_or_die(size); memcpy(http->dvec, vec, size); return JB_ERR_OK; }
/** * Make a destination name. If the destination option is not provided, * the destination is the base name of the input file. If the destination * is a directory, the base name of the source is always appended. * If the destination is a file, then make certain we only copy one file * to this destination. * * @param[in] sname source file name. * @returns the name of the corresponding output file. */ static inline char const * make_dest_name(char const * sname) { static char const no_dir[] = "no directory component in source name '%s'\n" "and no destination directory was specified.\n"; if (! HAVE_OPT(DESTINATION)) { char const * p = strrchr(sname, '/'); if (p == NULL) usage_message(no_dir, sname); unlink(++p); return strdup_or_die(p); } { static bool been_here = false; char const * dname = OPT_ARG(DESTINATION); struct stat sb; if (stat(dname, &sb) == 0) { if (S_ISDIR(sb.st_mode)) { char const * bn = strrchr(sname, '/'); char * p; bn = bn ? (bn + 1) : sname; p = malloc(strlen(dname) + strlen(bn) + 2); if (p == NULL) fserr(PCOPY_EXIT_NO_MEM, "allocating", "destination name"); sprintf(p, "%s/%s", dname, bn); unlink(p); return p; } if (S_ISREG(sb.st_mode)) { if (been_here) die(PCOPY_EXIT_BAD_CONFIG, "destination for multiple sources is one file"); been_here = true; unlink(dname); return strdup_or_die(dname); } errno = EINVAL; fserr(PCOPY_EXIT_BAD_CONFIG, "invalid destination fs type (not dir or file)", dname); } /* * We could not stat "dname". It must be a file name. Make sure any * directory part exists. If we call this routine again, we should * successfully stat our destination file and trip over the * "been_here" flag. */ dname = strdup_or_die(dname); been_here = true; { static char const bad_stat[] = "stat-ing dir portion"; char * p = strrchr(dname, '/'); if (p == NULL) { /* * No directory part. Destination file is for current dir. */ return dname; } *p = NUL; if (stat(dname, &sb) != 0) fserr(PCOPY_EXIT_BAD_CONFIG, bad_stat, OPT_ARG(DESTINATION)); if (! S_ISDIR(sb.st_mode)) { errno = ENOTDIR; fserr(PCOPY_EXIT_BAD_CONFIG, bad_stat, OPT_ARG(DESTINATION)); } *p = '/'; } return dname; } }
/********************************************************************* * * Function : compile_url_pattern * * Description : Compiles the three parts of an URL pattern. * * Parameters : * 1 : url = Target pattern_spec to be filled in. * 2 : buf = The url pattern to compile. Will be messed up. * * Returns : JB_ERR_OK - Success * JB_ERR_MEMORY - Out of memory * JB_ERR_PARSE - Cannot parse regex * *********************************************************************/ static jb_err compile_url_pattern(struct pattern_spec *url, char *buf) { char *p; p = strchr(buf, '/'); if (NULL != p) { /* * Only compile the regex if it consists of more than * a single slash, otherwise it wouldn't affect the result. */ if (p[1] != '\0') { /* * XXX: does it make sense to compile the slash at the beginning? */ jb_err err = compile_pattern(p, LEFT_ANCHORED, url, &url->pattern.url_spec.preg); if (JB_ERR_OK != err) { return err; } } *p = '\0'; } /* * IPv6 numeric hostnames can contain colons, thus we need * to delimit the hostname before the real port separator. * As brackets are already used in the hostname pattern, * we use angle brackets ('<', '>') instead. */ if ((buf[0] == '<') && (NULL != (p = strchr(buf + 1, '>')))) { *p++ = '\0'; buf++; if (*p == '\0') { /* IPv6 address without port number */ p = NULL; } else if (*p != ':') { /* Garbage after address delimiter */ return JB_ERR_PARSE; } } else { p = strchr(buf, ':'); } if (NULL != p) { *p++ = '\0'; url->pattern.url_spec.port_list = strdup_or_die(p); } else { url->pattern.url_spec.port_list = NULL; } if (buf[0] != '\0') { return compile_host_pattern(url, buf); } return JB_ERR_OK; }
/********************************************************************* * * Function : parse_http_request * * Description : Parse out the host and port from the URL. Find the * hostname & path, port (if ':'), and/or password (if '@') * * Parameters : * 1 : req = HTTP request line to break down * 2 : http = pointer to the http structure to hold elements * * Returns : JB_ERR_OK on success * JB_ERR_CGI_PARAMS on malformed command/URL * or >100 domains deep. * *********************************************************************/ jb_err parse_http_request(const char *req, struct http_request *http) { char *buf; char *v[3]; int n; jb_err err; memset(http, '\0', sizeof(*http)); buf = strdup_or_die(req); n = ssplit(buf, " \r\n", v, SZ(v)); if (n != 3) { freez(buf); return JB_ERR_PARSE; } /* * Fail in case of unknown methods * which we might not handle correctly. * * XXX: There should be a config option * to forward requests with unknown methods * anyway. Most of them don't need special * steps. */ if (unknown_method(v[0])) { log_error(LOG_LEVEL_ERROR, "Unknown HTTP method detected: %s", v[0]); freez(buf); return JB_ERR_PARSE; } if (JB_ERR_OK != normalize_http_version(v[2])) { freez(buf); return JB_ERR_PARSE; } http->ssl = !strcmpic(v[0], "CONNECT"); err = parse_http_url(v[1], http, !http->ssl); if (err) { freez(buf); return err; } /* * Copy the details into the structure */ http->cmd = strdup_or_die(req); http->gpc = strdup_or_die(v[0]); http->ver = strdup_or_die(v[2]); http->ocmd = strdup_or_die(http->cmd); freez(buf); return JB_ERR_OK; }
/********************************************************************* * * Function : parse_http_url * * Description : Parse out the host and port from the URL. Find the * hostname & path, port (if ':'), and/or password (if '@') * * Parameters : * 1 : url = URL (or is it URI?) to break down * 2 : http = pointer to the http structure to hold elements. * Must be initialized with valid values (like NULLs). * 3 : require_protocol = Whether or not URLs without * protocol are acceptable. * * Returns : JB_ERR_OK on success * JB_ERR_PARSE on malformed command/URL * or >100 domains deep. * *********************************************************************/ jb_err parse_http_url(const char *url, struct http_request *http, int require_protocol) { int host_available = 1; /* A proxy can dream. */ /* * Save our initial URL */ http->url = strdup_or_die(url); /* * Check for * URI. If found, we're done. */ if (*http->url == '*') { http->path = strdup_or_die("*"); http->hostport = strdup_or_die(""); if (http->url[1] != '\0') { return JB_ERR_PARSE; } return JB_ERR_OK; } /* * Split URL into protocol,hostport,path. */ { char *buf; char *url_noproto; char *url_path; buf = strdup_or_die(url); /* Find the start of the URL in our scratch space */ url_noproto = buf; if (strncmpic(url_noproto, "http://", 7) == 0) { url_noproto += 7; } else if (strncmpic(url_noproto, "https://", 8) == 0) { /* * Should only happen when called from cgi_show_url_info(). */ url_noproto += 8; http->ssl = 1; } else if (*url_noproto == '/') { /* * Short request line without protocol and host. * Most likely because the client's request * was intercepted and redirected into Privoxy. */ http->host = NULL; host_available = 0; } else if (require_protocol) { freez(buf); return JB_ERR_PARSE; } url_path = strchr(url_noproto, '/'); if (url_path != NULL) { /* * Got a path. * * NOTE: The following line ignores the path for HTTPS URLS. * This means that you get consistent behaviour if you type a * https URL in and it's parsed by the function. (When the * URL is actually retrieved, SSL hides the path part). */ http->path = strdup_or_die(http->ssl ? "/" : url_path); *url_path = '\0'; http->hostport = strdup_or_die(url_noproto); } else { /* * Repair broken HTTP requests that don't contain a path, * or CONNECT requests */ http->path = strdup_or_die("/"); http->hostport = strdup_or_die(url_noproto); } freez(buf); } if (!host_available) { /* Without host, there is nothing left to do here */ return JB_ERR_OK; } /* * Split hostport into user/password (ignored), host, port. */ { char *buf; char *host; char *port; buf = strdup_or_die(http->hostport); /* check if url contains username and/or password */ host = strchr(buf, '@'); if (host != NULL) { /* Contains username/password, skip it and the @ sign. */ host++; } else { /* No username or password. */ host = buf; } /* Move after hostname before port number */ if (*host == '[') { /* Numeric IPv6 address delimited by brackets */ host++; port = strchr(host, ']'); if (port == NULL) { /* Missing closing bracket */ freez(buf); return JB_ERR_PARSE; } *port++ = '\0'; if (*port == '\0') { port = NULL; } else if (*port != ':') { /* Garbage after closing bracket */ freez(buf); return JB_ERR_PARSE; } } else { /* Plain non-escaped hostname */ port = strchr(host, ':'); } /* check if url contains port */ if (port != NULL) { /* Contains port */ char *endptr; long parsed_port; /* Terminate hostname and point to start of port string */ *port++ = '\0'; parsed_port = strtol(port, &endptr, 10); if ((parsed_port <= 0) || (parsed_port > 65535) || (*endptr != '\0')) { log_error(LOG_LEVEL_ERROR, "Invalid port in URL: %s.", url); freez(buf); return JB_ERR_PARSE; } http->port = (int)parsed_port; } else { /* No port specified. */ http->port = (http->ssl ? 443 : 80); } http->host = strdup_or_die(host); freez(buf); } #ifdef FEATURE_EXTENDED_HOST_PATTERNS return JB_ERR_OK; #else /* Split domain name so we can compare it against wildcards */ return init_domain_components(http); #endif /* def FEATURE_EXTENDED_HOST_PATTERNS */ }
/********************************************************************* * * Function : match_portlist * * Description : Check if a given number is covered by a comma * separated list of numbers and ranges (a,b-c,d,..) * * Parameters : * 1 : portlist = String with list * 2 : port = port to check * * Returns : 0 => no match * 1 => match * *********************************************************************/ int match_portlist(const char *portlist, int port) { char *min, *max, *next, *portlist_copy; min = portlist_copy = strdup_or_die(portlist); /* * Zero-terminate first item and remember offset for next */ if (NULL != (next = strchr(portlist_copy, (int) ','))) { *next++ = '\0'; } /* * Loop through all items, checking for match */ while (NULL != min) { if (NULL == (max = strchr(min, (int) '-'))) { /* * No dash, check for equality */ if (port == atoi(min)) { freez(portlist_copy); return(1); } } else { /* * This is a range, so check if between min and max, * or, if max was omitted, between min and 65K */ *max++ = '\0'; if (port >= atoi(min) && port <= (atoi(max) ? atoi(max) : 65535)) { freez(portlist_copy); return(1); } } /* * Jump to next item */ min = next; /* * Zero-terminate next item and remember offset for n+1 */ if ((NULL != next) && (NULL != (next = strchr(next, (int) ',')))) { *next++ = '\0'; } } freez(portlist_copy); return 0; }
/* This function is meant to be called while staging file to fix any missing/incorrect paths. * While staging a file, if its parent directory is missing, this would try to create the path * by breaking it into sub-paths and fixing them top down. * Here, target_MoM is the consolidated manifest for the version you are trying to update/verify. */ int verify_fix_path(char *targetpath, struct manifest *target_MoM) { struct list *path_list = NULL; /* path_list contains the subparts in the path */ char *path; char *tmp = NULL, *target = NULL; char *url = NULL; struct stat sb; int ret = 0; struct file *file; char *tar_dotfile = NULL; struct list *list1 = NULL; /* This shouldn't happen */ if (strcmp(targetpath, "/") == 0) { return ret; } /* Removing trailing '/' from the path */ path = strdup_or_die(targetpath); if (path[strlen(path) - 1] == '/') { path[strlen(path) - 1] = '\0'; } /* Breaking down the path into parts. * eg. Path /usr/bin/foo will be broken into /usr,/usr/bin and /usr/bin/foo */ while (strcmp(path, "/") != 0) { path_list = list_prepend_data(path_list, strdup_or_die(path)); tmp = strdup_or_die(dirname(path)); free_string(&path); path = tmp; } free_string(&path); list1 = list_head(path_list); while (list1) { path = list1->data; list1 = list1->next; free_string(&target); free_string(&tar_dotfile); free_string(&url); target = mk_full_filename(path_prefix, path); /* Search for the file in the manifest, to get the hash for the file */ file = search_file_in_manifest(target_MoM, path); if (file == NULL) { fprintf(stderr, "Error: Path %s not found in any of the subscribed manifests" "in verify_fix_path for path_prefix %s\n", path, path_prefix); ret = -1; goto end; } if (file->is_deleted) { fprintf(stderr, "Error: Path %s found deleted in verify_fix_path\n", path); ret = -1; goto end; } ret = stat(target, &sb); if (ret == 0) { if (verify_file(file, target)) { continue; } fprintf(stderr, "Hash did not match for path : %s ... fixing\n", path); } else if (ret == -1 && errno == ENOENT) { fprintf(stderr, "Path %s is missing on the file system ... fixing\n", path); } else { goto end; } /* In some circumstances (Docker using layers between updates/bundle adds, * corrupt staging content) we could have content which fails to stage. * In order to avoid this causing failure in verify_fix_path, remove the * staging content before proceeding. This also cleans up in case any prior * download failed in a partial state. */ unlink_all_staged_content(file); string_or_die(&tar_dotfile, "%s/download/.%s.tar", state_dir, file->hash); string_or_die(&url, "%s/%i/files/%s.tar", content_url, file->last_change, file->hash); ret = swupd_curl_get_file(url, tar_dotfile); if (ret != 0) { fprintf(stderr, "Error: Failed to download file %s in verify_fix_path\n", file->filename); unlink(tar_dotfile); goto end; } if (untar_full_download(file) != 0) { fprintf(stderr, "Error: Failed to untar file %s\n", file->filename); ret = -1; goto end; } ret = do_staging(file, target_MoM); if (ret != 0) { fprintf(stderr, "Error: Path %s failed to stage in verify_fix_path\n", path); goto end; } } end: free_string(&target); free_string(&tar_dotfile); free_string(&url); list_free_list_and_data(path_list, free_path_data); return ret; }
static struct manifest *manifest_from_file(int version, char *component, bool header_only, bool latest, bool is_mix) { FILE *infile; char line[MANIFEST_LINE_MAXLEN], *c, *c2; int count = 0; int deleted = 0; struct manifest *manifest; struct list *includes = NULL; char *filename; char *basedir; uint64_t filecount = 0; uint64_t contentsize = 0; int manifest_hdr_version; int manifest_enc_version; if (!is_mix) { basedir = state_dir; } else { basedir = MIX_STATE_DIR; } string_or_die(&filename, "%s/%i/Manifest.%s", basedir, version, component); infile = fopen(filename, "rbm"); if (infile == NULL) { free_string(&filename); return NULL; } free_string(&filename); /* line 1: MANIFEST\t<version> */ line[0] = 0; if (fgets(line, MANIFEST_LINE_MAXLEN - 1, infile) == NULL) { goto err_close; } if (strncmp(line, "MANIFEST\t", 9) != 0) { goto err_close; } c = &line[9]; manifest_enc_version = strtoull(c, NULL, 10); if (manifest_enc_version == 0) { goto err_close; } line[0] = 0; while (strcmp(line, "\n") != 0) { /* read the header */ line[0] = 0; if (fgets(line, MANIFEST_LINE_MAXLEN - 1, infile) == NULL) { break; } c = strchr(line, '\n'); if (c) { *c = 0; } else { goto err_close; } if (strlen(line) == 0) { break; } c = strchr(line, '\t'); if (c) { c++; } else { goto err_close; } if (strncmp(line, "version:", 8) == 0) { manifest_hdr_version = strtoull(c, NULL, 10); if (manifest_hdr_version != version) { goto err_close; } } if (strncmp(line, "filecount:", 10) == 0) { filecount = strtoull(c, NULL, 10); if (filecount > 4000000) { /* Note on the number 4,000,000. We want this * to be big enough to allow Manifest.Full to * pass (currently about 450,000 March 18) but * small enough that when multiplied by * sizeof(struct file) it fits into * size_t. For a system with size_t being a 32 * bit value, this constrains it to be less * than about 6,000,000, but close to infinity * for systems with 64 bit size_t. */ fprintf(stderr, "Error: preposterous (%" PRIu64 ") number of files in %s Manifest, more than 4 million skipping\n", filecount, component); goto err_close; } } if (strncmp(line, "contentsize:", 12) == 0) { contentsize = strtoull(c, NULL, 10); if (contentsize > 2000000000000UL) { fprintf(stderr, "Error: preposterous (%" PRIu64 ") size of files in %s Manifest, more than 2TB skipping\n", contentsize, component); goto err_close; } } if (latest && strncmp(component, "MoM", 3) == 0) { if (strncmp(line, "actions:", 8) == 0) { post_update_actions = list_prepend_data(post_update_actions, strdup_or_die(c)); if (!post_update_actions->data) { fprintf(stderr, "WARNING: Unable to read post update action from Manifest.MoM. \ Another update or verify may be required.\n"); } } }
/* Consider adding a remove_leftovers() that runs in verify/fix in order to * allow this function to mkdtemp create folders for parallel build */ enum swupd_code do_staging(struct file *file, struct manifest *MoM) { char *statfile = NULL, *tmp = NULL, *tmp2 = NULL; char *dir, *base, *rel_dir; char *tarcommand = NULL; char *original = NULL; char *target = NULL; char *targetpath = NULL; char *rename_target = NULL; char *rename_tmpdir = NULL; char real_path[4096] = { 0 }; int ret; struct stat s; tmp = strdup_or_die(file->filename); tmp2 = strdup_or_die(file->filename); dir = dirname(tmp); base = basename(tmp2); rel_dir = dir; if (*dir == '/') { rel_dir = dir + 1; } string_or_die(&original, "%s/staged/%s", state_dir, file->hash); string_or_die(&targetpath, "%s%s", path_prefix, rel_dir); ret = stat(targetpath, &s); if ((ret == -1) && (errno == ENOENT)) { if (MoM) { warn("Update target directory does not exist: %s. Trying to fix it\n", targetpath); verify_fix_path(dir, MoM); } else { warn("Update target directory does not exist: %s. Auto-fix disabled\n", targetpath); } } else if (!S_ISDIR(s.st_mode)) { error("Update target exists but is NOT a directory: %s\n", targetpath); } if (!realpath(targetpath, real_path)) { ret = -1; goto out; } else if (strcmp(path_prefix, targetpath) != 0 && strcmp(targetpath, real_path) != 0) { /* * targetpath and real_path should always be equal but * in the case of the targetpath being the path_prefix * there is a trailing '/' in path_prefix but realpath * doesn't keep the trailing '/' so check for that case * specifically. */ ret = -1; goto out; } string_or_die(&target, "%s%s/.update.%s", path_prefix, rel_dir, base); ret = swupd_rm(target); if (ret < 0 && ret != -ENOENT) { error("Failed to remove %s\n", target); } string_or_die(&statfile, "%s%s", path_prefix, file->filename); memset(&s, 0, sizeof(struct stat)); ret = lstat(statfile, &s); if (ret == 0) { if ((file->is_dir && !S_ISDIR(s.st_mode)) || (file->is_link && !S_ISLNK(s.st_mode)) || (file->is_file && !S_ISREG(s.st_mode))) { //file type changed, move old out of the way for new ret = swupd_rm(statfile); if (ret < 0) { ret = SWUPD_COULDNT_REMOVE_FILE; goto out; } } } free_string(&statfile); if (file->is_dir || S_ISDIR(s.st_mode)) { /* In the btrfs only scenario there was an implicit * "create_or_update_dir()" via un-tar-ing a directory.tar after * download and the untar happens in the staging subvolume which * then gets promoted to a "real" usable subvolume. But for * a live rootfs the directory needs copied out of staged * and into the rootfs. Tar is a way to copy with * attributes and it includes internal logic that does the * right thing to overlay a directory onto something * pre-existing: */ /* In order to avoid tar transforms with directories, rename * the directory before and after the tar command */ string_or_die(&rename_tmpdir, "%s/tmprenamedir", state_dir); ret = create_staging_renamedir(rename_tmpdir); if (ret) { ret = SWUPD_COULDNT_CREATE_DIR; goto out; } string_or_die(&rename_target, "%s/%s", rename_tmpdir, base); if (rename(original, rename_target)) { ret = SWUPD_COULDNT_RENAME_DIR; goto out; } string_or_die(&tarcommand, TAR_COMMAND " -C '%s' " TAR_PERM_ATTR_ARGS " -cf - './%s' 2> /dev/null | " TAR_COMMAND " -C '%s%s' " TAR_PERM_ATTR_ARGS " -xf - 2> /dev/null", rename_tmpdir, base, path_prefix, rel_dir); ret = system(tarcommand); if (ret == -1) { ret = SWUPD_SUBPROCESS_ERROR; } if (WIFEXITED(ret)) { ret = WEXITSTATUS(ret); } free_string(&tarcommand); if (rename(rename_target, original)) { ret = SWUPD_COULDNT_RENAME_DIR; goto out; } if (ret) { ret = SWUPD_COULDNT_RENAME_DIR; goto out; } } else { /* (!file->is_dir && !S_ISDIR(stat.st_mode)) */ /* can't naively hard link(): Non-read-only files with same hash must remain * separate copies otherwise modifications to one instance of the file * propagate to all instances of the file perhaps causing subtle data corruption from * a user's perspective. In practice the rootfs is stateless and owned by us. * Additionally cross-mount hardlinks fail and it's hard to know what an admin * might have for overlaid mounts. The use of tar is a simple way to copy, but * inefficient. So prefer hardlink and fall back if needed: */ ret = -1; if (!file->is_config && !file->is_state && !file->use_xattrs) { ret = link(original, target); } if (ret < 0) { /* either the hardlink failed, or it was undesirable (config), do a tar-tar dance */ /* In order to avoid tar transforms, rename the file * before and after the tar command */ string_or_die(&rename_target, "%s/staged/.update.%s", state_dir, base); ret = rename(original, rename_target); if (ret) { ret = SWUPD_COULDNT_RENAME_FILE; goto out; } string_or_die(&tarcommand, TAR_COMMAND " -C '%s/staged' " TAR_PERM_ATTR_ARGS " -cf - '.update.%s' 2> /dev/null | " TAR_COMMAND " -C '%s%s' " TAR_PERM_ATTR_ARGS " -xf - 2> /dev/null", state_dir, base, path_prefix, rel_dir); ret = system(tarcommand); if (ret == -1) { ret = SWUPD_SUBPROCESS_ERROR; } if (WIFEXITED(ret)) { ret = WEXITSTATUS(ret); } free_string(&tarcommand); ret = rename(rename_target, original); if (ret) { ret = SWUPD_COULDNT_RENAME_FILE; goto out; } } struct stat buf; int err; free_string(&file->staging); string_or_die(&file->staging, "%s%s/.update.%s", path_prefix, rel_dir, base); err = lstat(file->staging, &buf); if (err != 0) { free_string(&file->staging); ret = SWUPD_COULDNT_CREATE_FILE; goto out; } } out: free_string(&target); free_string(&targetpath); free_string(&original); free_string(&rename_target); free_string(&rename_tmpdir); free_string(&tmp); free_string(&tmp2); return ret; }
/* Getaddrinfo implementation */ static jb_socket rfc2553_connect_to(const char *host, int portnum, struct client_state *csp, int is_proxy) { struct addrinfo hints, *result, *rp; char service[6]; int retval; jb_socket fd; fd_set wfds; struct timeval timeout; #if !defined(_WIN32) && !defined(__BEOS__) && !defined(AMIGA) && !defined(__OS2__) int flags; #endif int connect_failed; /* * XXX: Initializeing it here is only necessary * because not all situations are properly * covered yet. */ int socket_error = 0; if (is_proxy) { log_time_stage(csp, TIME_STAGE_PROXY_DNS_START); }else { log_time_stage(csp, TIME_STAGE_DNS_START); } struct access_control_addr dst[1]; /* Don't leak memory when retrying. */ freez(csp->error_message); freez(csp->http->host_ip_addr_str); retval = snprintf(service, sizeof(service), "%d", portnum); if ((-1 == retval) || (sizeof(service) <= retval)) { log_error(LOG_LEVEL_ERROR, "Port number (%d) ASCII decimal representation doesn't fit into 6 bytes", portnum); csp->error_message = strdup("Invalid port number"); csp->http->host_ip_addr_str = strdup("unknown"); return(JB_INVALID_SOCKET); } memset((char *)&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_NUMERICSERV; /* avoid service look-up */ #ifdef AI_ADDRCONFIG hints.ai_flags |= AI_ADDRCONFIG; #endif if ((retval = getaddrinfo(host, service, &hints, &result))) { if (is_proxy) { log_time_stage(csp, TIME_STAGE_PROXY_DNS_FAIL); }else { log_time_stage(csp, TIME_STAGE_DNS_FAIL); if (proxy_list) { csp->routing = ROUTE_PROXY; csp->current_forward_stage = FORWARD_STAGE_DNS_FAILURE; return connect_to_forward(csp, proxy_list, 1); } } log_error(LOG_LEVEL_INFO, "Can not resolve %s: %s", host, gai_strerror(retval)); /* XXX: Should find a better way to propagate this error. */ errno = EINVAL; csp->error_message = strdup(gai_strerror(retval)); csp->http->host_ip_addr_str = strdup("unknown"); return(JB_INVALID_SOCKET); } csp->http->host_ip_addr_str = malloc_or_die(NI_MAXHOST); for (rp = result; rp != NULL; rp = rp->ai_next) { memcpy(&dst->addr, rp->ai_addr, rp->ai_addrlen); #ifdef FEATURE_ACL if (block_acl(dst, csp)) { #ifdef __OS2__ socket_error = errno = SOCEPERM; #else socket_error = errno = EPERM; #endif continue; } #endif /* def FEATURE_ACL */ retval = getnameinfo(rp->ai_addr, rp->ai_addrlen, csp->http->host_ip_addr_str, NI_MAXHOST, NULL, 0, NI_NUMERICHOST); if (retval) { log_error(LOG_LEVEL_ERROR, "Failed to get the host name from the socket structure: %s", gai_strerror(retval)); continue; } if (is_proxy) { log_time_stage(csp, TIME_STAGE_PROXY_DNS_END); }else { csp->http->remote_host_ip_addr_str = strdup_or_die(csp->http->host_ip_addr_str); log_time_stage(csp, TIME_STAGE_DNS_END); } if (!is_proxy) { if (dst->addr.ss_family == AF_INET) { // IPv4 if (csp->current_forward_stage == FORWARD_STAGE_NONE && !csp->forward_determined) { log_time_stage(csp, TIME_STAGE_IP_RULE_MATCH_START); struct forward_spec *fwd = forward_ip(csp, dst->addr); log_time_stage(csp, TIME_STAGE_IP_RULE_MATCH_END); csp->forward_determined = 1; if (csp->routing == ROUTE_NONE) { log_time_stage(csp, TIME_STAGE_DNS_IP_RULE_MATCH_START); fwd = forward_dns_pollution_ip(csp, dst->addr); log_time_stage(csp, TIME_STAGE_DNS_IP_RULE_MATCH_END); if (csp->routing == ROUTE_NONE) { if (global_mode && proxy_list) { log_time_stage(csp, TIME_STAGE_GLOBAL_MODE); fwd = proxy_list; csp->routing = ROUTE_PROXY; }else { log_time_stage(csp, TIME_STAGE_NON_GLOBAL_MODE); csp->routing = ROUTE_DIRECT; } } } if (csp->routing == ROUTE_NONE || csp->routing == ROUTE_DIRECT) { }else if (csp->routing == ROUTE_BLOCK) { log_error(LOG_LEVEL_CONNECT, "Block request to ip: %s", csp->http->host_ip_addr_str); return JB_INVALID_SOCKET; }else { if (fwd) { return connect_to_forward(csp, fwd, 1); }else { // No proxy provided. } } } }else { csp->is_ipv6 = 1; csp->routing = ROUTE_DIRECT; log_time_stage(csp, TIME_STAGE_IPV6); } } if (is_proxy) { log_time_stage(csp, TIME_STAGE_PROXY_START); }else { log_time_stage(csp, TIME_STAGE_REMOTE_START); } fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); #ifdef _WIN32 if (fd == JB_INVALID_SOCKET) #else if (fd < 0) #endif { continue; } #ifndef _WIN32 if (fd >= FD_SETSIZE) { log_error(LOG_LEVEL_ERROR, "Server socket number too high to use select(): %d >= %d", fd, FD_SETSIZE); close_socket(fd); freeaddrinfo(result); return JB_INVALID_SOCKET; } #endif #ifdef FEATURE_EXTERNAL_FILTERS mark_socket_for_close_on_execute(fd); #endif set_no_delay_flag(fd); int yes = 1; setsockopt (fd, SOL_SOCKET, SO_NOSIGPIPE, (char *) &yes, sizeof (yes)); #if !defined(_WIN32) && !defined(__BEOS__) && !defined(AMIGA) && !defined(__OS2__) if ((flags = fcntl(fd, F_GETFL, 0)) != -1) { flags |= O_NDELAY; fcntl(fd, F_SETFL, flags); } #endif /* !defined(_WIN32) && !defined(__BEOS__) && !defined(AMIGA) && !defined(__OS2__) */ connect_failed = 0; while (connect(fd, rp->ai_addr, rp->ai_addrlen) == JB_INVALID_SOCKET) { #ifdef __OS2__ errno = sock_errno(); #endif /* __OS2__ */ #ifdef _WIN32 if (errno == WSAEINPROGRESS) #else /* ifndef _WIN32 */ if (errno == EINPROGRESS) #endif /* ndef _WIN32 || __OS2__ */ { break; } if (errno != EINTR) { socket_error = errno; close_socket(fd); connect_failed = 1; break; } } if (connect_failed) { continue; } #if !defined(_WIN32) && !defined(__BEOS__) && !defined(AMIGA) && !defined(__OS2__) if (flags != -1) { flags &= ~O_NDELAY; fcntl(fd, F_SETFL, flags); } #endif /* !defined(_WIN32) && !defined(__BEOS__) && !defined(AMIGA) && !defined(__OS2__) */ /* wait for connection to complete */ FD_ZERO(&wfds); FD_SET(fd, &wfds); memset(&timeout, 0, sizeof(timeout)); timeout.tv_sec = 30; /* MS Windows uses int, not SOCKET, for the 1st arg of select(). Weird! */ if ((select((int)fd + 1, NULL, &wfds, NULL, &timeout) > 0) && FD_ISSET(fd, &wfds)) { socklen_t optlen = sizeof(socket_error); if (!getsockopt(fd, SOL_SOCKET, SO_ERROR, &socket_error, &optlen)) { if (!socket_error) { /* Connection established, no need to try other addresses. */ break; } if (rp->ai_next != NULL) { /* * There's another address we can try, so log that this * one didn't work out. If the last one fails, too, * it will get logged outside the loop body so we don't * have to mention it here. */ log_error(LOG_LEVEL_CONNECT, "Could not connect to [%s]:%s: %s.", csp->http->host_ip_addr_str, service, strerror(socket_error)); } } else { socket_error = errno; log_error(LOG_LEVEL_ERROR, "Could not get the state of " "the connection to [%s]:%s: %s; dropping connection.", csp->http->host_ip_addr_str, service, strerror(errno)); } } /* Connection failed, try next address */ close_socket(fd); } freeaddrinfo(result); if (!rp) { log_error(LOG_LEVEL_CONNECT, "Could not connect to [%s]:%s: %s.", host, service, strerror(socket_error)); csp->error_message = strdup(strerror(socket_error)); return(JB_INVALID_SOCKET); } log_error(LOG_LEVEL_CONNECT, "Connected to %s[%s]:%s.", host, csp->http->host_ip_addr_str, service); if (is_proxy) { log_time_stage(csp, TIME_STAGE_PROXY_CONNECTED); }else { log_time_stage(csp, TIME_STAGE_REMOTE_CONNECTED); } return(fd); }