/* * common.c * Misc functions used in idevicerestore * * Copyright (c) 2012-2019 Nikias Bassen. All Rights Reserved. * Copyright (c) 2012 Martin Szulecki. All Rights Reserved. * Copyright (c) 2010 Joshua Hill. All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #ifdef WIN32 #include #include #ifndef _O_EXCL #define _O_EXCL 0x0400 #endif #ifndef O_EXCL #define O_EXCL _O_EXCL #endif #else #include #include #include #endif #include "common.h" #include "endianness.h" #define MAX_PRINT_LEN 64*1024 struct idevicerestore_mode_t idevicerestore_modes[] = { { 0, "Unknown" }, { 1, "WTF" }, { 2, "DFU" }, { 3, "Recovery" }, { 4, "Restore" }, { 5, "Normal" }, { 6, "Port DFU" }, }; int idevicerestore_debug = 0; #define idevicerestore_err_buff_size 256 static char idevicerestore_err_buff[idevicerestore_err_buff_size] = {0, }; static FILE* info_stream = NULL; static FILE* error_stream = NULL; static FILE* debug_stream = NULL; static int info_disabled = 0; static int error_disabled = 0; static int debug_disabled = 0; void info(const char* format, ...) { if (info_disabled) return; va_list vargs; va_start(vargs, format); vfprintf((info_stream) ? info_stream : stdout, format, vargs); va_end(vargs); } void error(const char* format, ...) { va_list vargs, vargs2; va_start(vargs, format); va_copy(vargs2, vargs); vsnprintf(idevicerestore_err_buff, idevicerestore_err_buff_size, format, vargs); va_end(vargs); if (!error_disabled) { vfprintf((error_stream) ? error_stream : stderr, format, vargs2); } va_end(vargs2); } void debug(const char* format, ...) { if (debug_disabled) return; if (!idevicerestore_debug) { return; } va_list vargs; va_start(vargs, format); vfprintf((debug_stream) ? debug_stream : stderr, format, vargs); va_end(vargs); } void idevicerestore_set_info_stream(FILE* strm) { if (strm) { info_disabled = 0; info_stream = strm; } else { info_disabled = 1; } } void idevicerestore_set_error_stream(FILE* strm) { if (strm) { error_disabled = 0; error_stream = strm; } else { error_disabled = 1; } } void idevicerestore_set_debug_stream(FILE* strm) { if (strm) { debug_disabled = 0; debug_stream = strm; } else { debug_disabled = 1; } } const char* idevicerestore_get_error(void) { if (idevicerestore_err_buff[0] == 0) { return NULL; } else { char* p = NULL; while ((strlen(idevicerestore_err_buff) > 0) && (p = strrchr(idevicerestore_err_buff, '\n'))) { p[0] = '\0'; } return (const char*)idevicerestore_err_buff; } } int write_file(const char* filename, const void* data, size_t size) { size_t bytes = 0; FILE* file = NULL; debug("Writing data to %s\n", filename); file = fopen(filename, "wb"); if (file == NULL) { error("write_file: Unable to open file %s\n", filename); return -1; } bytes = fwrite(data, 1, size, file); fclose(file); if (bytes != size) { error("ERROR: Unable to write entire file: %s: %d of %d\n", filename, (int)bytes, (int)size); return -1; } return size; } int read_file(const char* filename, void** data, size_t* size) { size_t bytes = 0; size_t length = 0; FILE* file = NULL; char* buffer = NULL; struct stat fst; debug("Reading data from %s\n", filename); *size = 0; *data = NULL; file = fopen(filename, "rb"); if (file == NULL) { error("read_file: cannot open %s: %s\n", filename, strerror(errno)); return -1; } if (fstat(fileno(file), &fst) < 0) { error("read_file: fstat: %s\n", strerror(errno)); return -1; } length = fst.st_size; buffer = (char*) malloc(length); if (buffer == NULL) { error("ERROR: Out of memory\n"); fclose(file); return -1; } bytes = fread(buffer, 1, length, file); fclose(file); if (bytes != length) { error("ERROR: Unable to read entire file\n"); free(buffer); return -1; } *size = length; *data = buffer; return 0; } void debug_plist(plist_t plist) { uint32_t size = 0; char* data = NULL; plist_to_xml(plist, &data, &size); if (size <= MAX_PRINT_LEN) info("%s:printing %i bytes plist:\n%s", __FILE__, size, data); else info("%s:supressed printing %i bytes plist...\n", __FILE__, size); free(data); } void print_progress_bar(double progress) { #ifndef WIN32 if (info_disabled) return; int i = 0; if(progress < 0) return; if(progress > 100) progress = 100; info("\r["); for(i = 0; i < 50; i++) { if(i < progress / 2) info("="); else info(" "); } info("] %5.1f%%", progress); if(progress >= 100) info("\n"); fflush((info_stream) ? info_stream : stdout); #endif } #define GET_RAND(min, max) ((rand() % (max - min)) + min) char *generate_guid(void) { char *guid = (char *) malloc(sizeof(char) * 37); const char *chars = "ABCDEF0123456789"; srand(time(NULL)); int i = 0; for (i = 0; i < 36; i++) { if (i == 8 || i == 13 || i == 18 || i == 23) { guid[i] = '-'; continue; } else { guid[i] = chars[GET_RAND(0, 16)]; } } guid[36] = '\0'; return guid; } int mkdir_with_parents(const char *dir, int mode) { if (!dir) return -1; if (__mkdir(dir, mode) == 0) { return 0; } else { if (errno == EEXIST) { return 0; } else if (errno == ENOENT) { // ignore } else { return -1; } } int res; char *parent = strdup(dir); char *parentdir = dirname(parent); if (parentdir && (strcmp(parentdir, ".") != 0) && (strcmp(parentdir, dir) != 0)) { res = mkdir_with_parents(parentdir, mode); } else { res = -1; } free(parent); if (res == 0) { mkdir_with_parents(dir, mode); } return res; } #ifndef HAVE_MKSTEMP /* Based on libc's __gen_tempname() from sysdeps/posix/tempname.c Copyright (C) 1991-2018 Free Software Foundation, Inc. With changes from https://stackoverflow.com/a/6036308 and some additional changes. */ int mkstemp(char *tmpl) { static const char letters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; int len; char *XXXXXX; static unsigned long long value; unsigned long long random_time_bits; unsigned int count; int fd = -1; int save_errno = errno; /* A lower bound on the number of temporary files to attempt to generate. The maximum total number of temporary file names that can exist for a given template is 62**6. It should never be necessary to try all these combinations. Instead if a reasonable number of names is tried (we define reasonable as 62**3) fail to give the system administrator the chance to remove the problems. */ #define ATTEMPTS_MIN (62 * 62 * 62) /* The number of times to attempt to generate a temporary file. To conform to POSIX, this must be no smaller than TMP_MAX. */ #if ATTEMPTS_MIN < TMP_MAX unsigned int attempts = TMP_MAX; #else unsigned int attempts = ATTEMPTS_MIN; #endif len = strlen (tmpl); if (len < 6 || strcmp (&tmpl[len - 6], "XXXXXX")) { errno = EINVAL; return -1; } /* This is where the Xs start. */ XXXXXX = &tmpl[len - 6]; /* Get some more or less random data. */ #ifdef WIN32 { SYSTEMTIME stNow; FILETIME ftNow; // get system time GetSystemTime(&stNow); if (!SystemTimeToFileTime(&stNow, &ftNow)) { errno = -1; return -1; } random_time_bits = (((unsigned long long)ftNow.dwHighDateTime << 32) | (unsigned long long)ftNow.dwLowDateTime); } value += random_time_bits ^ ((unsigned long long)GetCurrentProcessId() << 32 | (unsigned long long)GetCurrentThreadId()); #else { struct timeval tvNow = {0, 0}; gettimeofday(&tvNow, NULL); random_time_bits = (((unsigned long long)tvNow.tv_sec << 32) | (unsigned long long)tvNow.tv_usec); } value += random_time_bits ^ ((unsigned long long)getpid() << 32 | (unsigned long long)(uintptr_t)pthread_self()); #endif for (count = 0; count < attempts; value += 7777, ++count) { unsigned long long v = value; /* Fill in the random bits. */ XXXXXX[0] = letters[v % 62]; v /= 62; XXXXXX[1] = letters[v % 62]; v /= 62; XXXXXX[2] = letters[v % 62]; v /= 62; XXXXXX[3] = letters[v % 62]; v /= 62; XXXXXX[4] = letters[v % 62]; v /= 62; XXXXXX[5] = letters[v % 62]; #ifdef WIN32 fd = open (tmpl, O_RDWR | O_CREAT | O_EXCL, _S_IREAD | _S_IWRITE); #else fd = open (tmpl, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); #endif if (fd >= 0) { errno = save_errno; return fd; } else if (errno != EEXIST) return -1; } /* We got out of the loop because we ran out of combinations to try. */ errno = EEXIST; return -1; } #endif char *get_temp_filename(const char *prefix) { char *result = NULL; char *tmpdir; size_t lt; size_t lp; const char *TMPVARS[] = { "TMPDIR", "TMP", "TEMP", "TEMPDIR", NULL }; int i = 0; int fd; /* check the prefix parameter */ if (!prefix) { prefix = "tmp_"; } #ifdef WIN32 if (strchr(prefix, '/') || strchr(prefix, '\\')) return NULL; #else if (strchr(prefix, '/')) return NULL; #endif while (TMPVARS[i] && ((tmpdir = getenv(TMPVARS[i])) == NULL)) i++; if (!tmpdir || access(tmpdir, W_OK|X_OK) != 0) { #ifdef WIN32 tmpdir = "C:\\WINDOWS\\TEMP"; #else tmpdir = P_tmpdir; #endif } if (!tmpdir || access(tmpdir, W_OK|X_OK) != 0) { return NULL; } lt = strlen(tmpdir); if (lt < 1) { return NULL; } lp = strlen(prefix); result = malloc(lt + lp + 8); memcpy(result, tmpdir, lt); #ifdef WIN32 if (tmpdir[lt-1] != '/' && tmpdir[lt-1] != '\\') result[lt++] = '\\'; #else if (tmpdir[lt-1] != '/') result[lt++] = '/'; #endif strncpy(result + lt, prefix, lp); strcpy(result + lt + lp, "XXXXXX"); fd = mkstemp(result); if (fd < 0) { free(result); result = NULL; } close(fd); return result; } void idevicerestore_progress(struct idevicerestore_client_t* client, int step, double progress) { if(client && client->progress_cb) { client->progress_cb(step, progress, client->progress_cb_data); } else { // we don't want to be too verbose in regular idevicerestore. if ((step == RESTORE_STEP_UPLOAD_FS) || (step == RESTORE_STEP_VERIFY_FS) || (step == RESTORE_STEP_FLASH_FW) || (step == RESTORE_STEP_UPLOAD_IMG)) { print_progress_bar(100.0 * progress); } } } #ifndef HAVE_STRSEP char* strsep(char** strp, const char* delim) { char *p, *s; if (strp == NULL || *strp == NULL || **strp == '\0') return NULL; s = *strp; p = s + strcspn(s, delim); if (*p != '\0') *p++ = '\0'; *strp = p; return s; } #endif #ifndef HAVE_REALPATH char* realpath(const char *filename, char *resolved_name) { #ifdef WIN32 if (access(filename, F_OK) != 0) { return NULL; } if (GetFullPathName(filename, MAX_PATH, resolved_name, NULL) == 0) { return NULL; } return resolved_name; #else #error please provide a realpath implementation for this platform return NULL; #endif } #endif #ifdef WIN32 #define BS_CC '\b' #define CTRL_C_CC 0x03 #define ESC_CC 0x1B #define my_getch _getch #else #define BS_CC 0x7f static int my_getch(void) { struct termios oldt, newt; int ch; tcgetattr(STDIN_FILENO, &oldt); newt = oldt; newt.c_lflag &= ~(ICANON | ECHO); tcsetattr(STDIN_FILENO, TCSANOW, &newt); ch = getchar(); tcsetattr(STDIN_FILENO, TCSANOW, &oldt); return ch; } #endif void get_user_input(char *buf, int maxlen, int secure) { int len = 0; int c; while ((c = my_getch()) > 0) { if ((c == '\r') || (c == '\n')) { break; } else if (isprint(c)) { if (len < maxlen-1) buf[len++] = c; fputc((secure) ? '*' : c, stdout); } else if (c == BS_CC) { if (len > 0) { fputs("\b \b", stdout); len--; } } #ifdef WIN32 else if (c == CTRL_C_CC || c == ESC_CC) { c = -1; break; } #endif } if (c < 0) { len = 0; } fputs("\n", stdout); buf[len] = 0; } uint64_t _plist_dict_get_uint(plist_t dict, const char *key) { uint64_t uintval = 0; char *strval = NULL; uint64_t strsz = 0; plist_t node = plist_dict_get_item(dict, key); if (!node) { return uintval; } switch (plist_get_node_type(node)) { case PLIST_UINT: plist_get_uint_val(node, &uintval); break; case PLIST_STRING: plist_get_string_val(node, &strval); if (strval) { uintval = strtoull(strval, NULL, 0); free(strval); } break; case PLIST_DATA: plist_get_data_val(node, &strval, &strsz); if (strval) { if (strsz == 8) { uintval = le64toh(*(uint64_t*)strval); } else if (strsz == 4) { uintval = le32toh(*(uint32_t*)strval); } else if (strsz == 2) { uintval = le16toh(*(uint16_t*)strval); } else if (strsz == 1) { uintval = strval[0]; } else { error("%s: ERROR: invalid size %" PRIu64 " for data to integer conversion\n", __func__, strsz); } free(strval); } break; default: break; } return uintval; } uint8_t _plist_dict_get_bool(plist_t dict, const char *key) { uint8_t bval = 0; uint64_t uintval = 0; char *strval = NULL; uint64_t strsz = 0; plist_t node = plist_dict_get_item(dict, key); if (!node) { return 0; } switch (plist_get_node_type(node)) { case PLIST_BOOLEAN: plist_get_bool_val(node, &bval); break; case PLIST_UINT: plist_get_uint_val(node, &uintval); bval = (uint8_t)uintval; break; case PLIST_STRING: plist_get_string_val(node, &strval); if (strval) { if (strcmp(strval, "true")) { bval = 1; } else if (strcmp(strval, "false")) { bval = 0; } free(strval); } break; case PLIST_DATA: plist_get_data_val(node, &strval, &strsz); if (strval) { if (strsz == 1) { bval = strval[0]; } else { error("%s: ERROR: invalid size %" PRIu64 " for data to boolean conversion\n", __func__, strsz); } free(strval); } break; default: break; } return bval; } int _plist_dict_copy_uint(plist_t target_dict, plist_t source_dict, const char *key, const char *alt_source_key) { if (plist_dict_get_item(source_dict, (alt_source_key) ? alt_source_key : key) == NULL) { return -1; } uint64_t u64val = _plist_dict_get_uint(source_dict, (alt_source_key) ? alt_source_key : key); plist_dict_set_item(target_dict, key, plist_new_uint(u64val)); return 0; } int _plist_dict_copy_bool(plist_t target_dict, plist_t source_dict, const char *key, const char *alt_source_key) { if (plist_dict_get_item(source_dict, (alt_source_key) ? alt_source_key : key) == NULL) { return -1; } uint64_t bval = _plist_dict_get_bool(source_dict, (alt_source_key) ? alt_source_key : key); plist_dict_set_item(target_dict, key, plist_new_bool(bval)); return 0; } int _plist_dict_copy_data(plist_t target_dict, plist_t source_dict, const char *key, const char *alt_source_key) { plist_t node = plist_dict_get_item(source_dict, (alt_source_key) ? alt_source_key : key); if (!PLIST_IS_DATA(node)) { return -1; } plist_dict_set_item(target_dict, key, plist_copy(node)); return 0; } int _plist_dict_copy_string(plist_t target_dict, plist_t source_dict, const char *key, const char *alt_source_key) { plist_t node = plist_dict_get_item(source_dict, (alt_source_key) ? alt_source_key : key); if (!PLIST_IS_STRING(node)) { return -1; } plist_dict_set_item(target_dict, key, plist_copy(node)); return 0; } int _plist_dict_copy_item(plist_t target_dict, plist_t source_dict, const char *key, const char *alt_source_key) { plist_t node = plist_dict_get_item(source_dict, (alt_source_key) ? alt_source_key : key); if (!node) { return -1; } plist_dict_set_item(target_dict, key, plist_copy(node)); return 0; } const char* path_get_basename(const char* path) { #ifdef WIN32 const char *p = path + strlen(path); while (p > path) { if ((*p == '/') || (*p == '\\')) { return p+1; } p--; } return p; #else const char *p = strrchr(path, '/'); return p ? p + 1 : path; #endif }