Support device encryption with passphrase
This commit is contained in:
@@ -188,7 +188,7 @@ static void call_anim_step(call_anim *anim, float interpolated)
|
||||
anim->callback(anim->data, interpolated);
|
||||
}
|
||||
|
||||
static void anim_update(uint32_t diff, void *data)
|
||||
static int anim_update(uint32_t diff, void *data)
|
||||
{
|
||||
struct anim_list *list = data;
|
||||
struct anim_list_it *it;
|
||||
@@ -275,6 +275,8 @@ static void anim_update(uint32_t diff, void *data)
|
||||
|
||||
list->in_update_loop = 0;
|
||||
pthread_mutex_unlock(&list->mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint32_t anim_generate_id(void)
|
||||
|
||||
@@ -143,14 +143,14 @@ int list_rm_noreorder(ptrToList list_p, void *item, callback destroy_callback_p)
|
||||
return list_rm_opt(list_p, item, destroy_callback_p, 0);
|
||||
}
|
||||
|
||||
int list_rm_at(ptrToList list_p, int idx, callback destroy_callback_p)
|
||||
listItself list_rm_at(ptrToList list_p, int idx, callback destroy_callback_p)
|
||||
{
|
||||
void ***list = (void***)list_p;
|
||||
callbackPtr destroy_callback = (callbackPtr)destroy_callback_p;
|
||||
|
||||
int size = list_size(*list);
|
||||
if(idx < 0 || idx >= size-1)
|
||||
return -1;
|
||||
return NULL;
|
||||
|
||||
void *item = (*list)[idx];
|
||||
if(destroy_callback)
|
||||
@@ -161,15 +161,15 @@ int list_rm_at(ptrToList list_p, int idx, callback destroy_callback_p)
|
||||
{
|
||||
free(*list);
|
||||
*list = NULL;
|
||||
return 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int i = idx;
|
||||
for(; i < size; ++i)
|
||||
(*list)[i] = (*list)[i+1];
|
||||
|
||||
*list= realloc(*list, size*sizeof(item));
|
||||
return 0;
|
||||
*list = realloc(*list, size*sizeof(item));
|
||||
return *list + idx;
|
||||
}
|
||||
|
||||
void list_clear(ptrToList list_p, callback destroy_callback_p)
|
||||
@@ -289,7 +289,7 @@ int map_find(map *m, const char *key)
|
||||
for(i = 0; m->keys && m->keys[i]; ++i)
|
||||
if(strcmp(m->keys[i], key) == 0)
|
||||
return i;
|
||||
return -1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
void *map_get_val(map *m, const char *key)
|
||||
@@ -367,7 +367,7 @@ int imap_find(imap *m, int key)
|
||||
for(i = 0; i < m->size; ++i)
|
||||
if(key == m->keys[i])
|
||||
return i;
|
||||
return -1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
void *imap_get_val(imap *m, int key)
|
||||
|
||||
@@ -30,7 +30,7 @@ int list_add_from_list(ptrToList list_p, listItself src_p);
|
||||
int list_rm(ptrToList list_p, void *item, callback destroy_callback_p);
|
||||
int list_rm_noreorder(ptrToList list_p, void *item, callback destroy_callback_p);
|
||||
int list_rm_opt(ptrToList list_p, void *item, callback destroy_callback_p, int reorder);
|
||||
int list_rm_at(ptrToList list_p, int idx, callback destroy_callback_p);
|
||||
listItself list_rm_at(ptrToList list_p, int idx, callback destroy_callback_p); // returns pointer to the next item in list or NULL
|
||||
int list_size(listItself list);
|
||||
int list_item_count(listItself list);
|
||||
int list_copy(ptrToList dest_p, listItself src);
|
||||
|
||||
@@ -115,6 +115,12 @@ static int convert_ft_bitmap(FT_BitmapGlyph bit, px_type color, px_type *res_dat
|
||||
|
||||
buff = (uint8_t*)bit->bitmap.buffer;
|
||||
res_itr = (px_type*)(((uint32_t*)res_data) + (line->offY + line->base - bit->top)*stride + (line->offX + pos->x + bit->left));
|
||||
|
||||
// FIXME: if bit->left is negative and everything else is 0 (e.g. letter 'j' in Roboto-Regular),
|
||||
// the result might end up being before the buffer - I'm not sure how to properly handle this.
|
||||
if(res_itr < res_data)
|
||||
res_itr = res_data;
|
||||
|
||||
for(y = 0; y < bit->bitmap.rows; ++y)
|
||||
{
|
||||
for(x = 0; x < bit->bitmap.width; ++x)
|
||||
|
||||
@@ -687,7 +687,7 @@ static void keyaction_call_cur_act(struct keyaction_ctx *c, int action)
|
||||
ERROR("keyaction_call_cur_act: current action not found in actions!\n");
|
||||
}
|
||||
|
||||
static void keyaction_repeat_worker(uint32_t diff, void *data)
|
||||
static int keyaction_repeat_worker(uint32_t diff, void *data)
|
||||
{
|
||||
struct keyaction_ctx *c = data;
|
||||
|
||||
@@ -703,6 +703,8 @@ static void keyaction_repeat_worker(uint32_t diff, void *data)
|
||||
c->repeat_timer -= diff;
|
||||
}
|
||||
pthread_mutex_unlock(&c->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void keyaction_clear_active(void)
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "keyboard.h"
|
||||
#include "util.h"
|
||||
#include "log.h"
|
||||
#include "workers.h"
|
||||
|
||||
#define KS(x) ((x-1) << 16)
|
||||
#define GET_KS(x) ((x & 0xFF0000)>> 16)
|
||||
@@ -28,13 +29,17 @@
|
||||
#define KF(x) ((x) << 8)
|
||||
#define GET_KF(x) ((x & 0xFF00) >> 8)
|
||||
#define KFLAG_HALF KF(0x01)
|
||||
#define KFLAG_PLUS_HALF KF(0x02)
|
||||
|
||||
static const char *specialKeys[] = {
|
||||
NULL, // OSK_EMPTY
|
||||
"OK", // OSK_ENTER
|
||||
"<", // OSK_BACKSPACE
|
||||
"^", // OSK_SHIFT
|
||||
"X", // OSK_CLEAR
|
||||
"abc", // OSK_CHARSET1
|
||||
"ABC", // OSK_CHARSET2
|
||||
"?123",// OSK_CHARSET3
|
||||
"=\\<",// OSK_CHARSET4
|
||||
};
|
||||
|
||||
// One keycode
|
||||
@@ -51,14 +56,45 @@ static const uint32_t pinKeycodeMap[] = {
|
||||
// rows, cols
|
||||
static const uint32_t pinKeycodeMapDimensions[] = { 4, 5 };
|
||||
|
||||
static const uint32_t normalKeycodeMap[] = {
|
||||
static const uint32_t normalKeycodeMapCharset1[] = {
|
||||
'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p',
|
||||
OSK_EMPTY| KFLAG_HALF, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l',
|
||||
OSK_SHIFT, 'z', 'x', 'c', 'v', 'b', 'n', 'm', OSK_BACKSPACE | KS(2),
|
||||
OSK_BACKSPACE, ' ' | KS(6), '.', OSK_ENTER | KS(2),
|
||||
OSK_CHARSET2 | KFLAG_PLUS_HALF, 'z', 'x', 'c', 'v', 'b', 'n', 'm', OSK_BACKSPACE | KFLAG_PLUS_HALF, OSK_EMPTY,
|
||||
OSK_CHARSET3 | KS(2), ' ' | KS(5), '.', OSK_ENTER | KS(2),
|
||||
0
|
||||
};
|
||||
|
||||
static const uint32_t normalKeycodeMapCharset2[] = {
|
||||
'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P',
|
||||
OSK_EMPTY| KFLAG_HALF, 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L',
|
||||
OSK_CHARSET1 | KFLAG_PLUS_HALF, 'Z', 'X', 'C', 'V', 'B', 'N', 'M', OSK_BACKSPACE | KFLAG_PLUS_HALF, OSK_EMPTY,
|
||||
OSK_CHARSET3 | KS(2), ' ' | KS(5), '.', OSK_ENTER | KS(2),
|
||||
0
|
||||
};
|
||||
|
||||
static const uint32_t normalKeycodeMapCharset3[] = {
|
||||
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
|
||||
OSK_EMPTY | KFLAG_HALF, '@', '#', '$', '%', '&', '-', '+', '(', ')',
|
||||
OSK_CHARSET4 | KFLAG_PLUS_HALF, '*', '"', '\'', ':', ';', '!', '?', OSK_BACKSPACE | KFLAG_PLUS_HALF, OSK_EMPTY,
|
||||
OSK_CHARSET1 | KS(2), ',', '_', ' ' | KS(3), '/', OSK_ENTER | KS(2),
|
||||
0
|
||||
};
|
||||
|
||||
static const uint32_t normalKeycodeMapCharset4[] = {
|
||||
'~', '`', '|', '<', '>', '-', '+', '!', '?', ';',
|
||||
OSK_EMPTY | KFLAG_HALF, '^', '\\', '$', '%', '&', '-', '+', '{', '}',
|
||||
OSK_CHARSET3 | KFLAG_PLUS_HALF, '*', '"', '\'', ':', ';', '[', ']', OSK_BACKSPACE | KFLAG_PLUS_HALF, OSK_EMPTY,
|
||||
OSK_CHARSET1 | KS(2), ',', ' ' | KS(4), '/', OSK_ENTER | KS(2),
|
||||
0
|
||||
};
|
||||
|
||||
static const uint32_t *normalKeycodeMapCharsetMapping[] = {
|
||||
normalKeycodeMapCharset4, // OSK_CHARSET4
|
||||
normalKeycodeMapCharset3, // OSK_CHARSET3
|
||||
normalKeycodeMapCharset2, // OSK_CHARSET2
|
||||
normalKeycodeMapCharset1, // OSK_CHARSET1
|
||||
};
|
||||
|
||||
// rows, cols
|
||||
static const uint32_t normalKeycodeMapDimensions[] = { 4, 10 };
|
||||
|
||||
@@ -69,15 +105,38 @@ struct keyboard_btn_data {
|
||||
int btn_idx;
|
||||
};
|
||||
|
||||
static int keyboard_init_map(struct keyboard *k, const uint32_t *map, const uint32_t *dimen);
|
||||
|
||||
|
||||
static int keyboard_charset_switch_worker(uint32_t diff, void *data)
|
||||
{
|
||||
void **keyboard_bnt_data_old = NULL;
|
||||
struct keyboard_btn_data *d = data;
|
||||
uint8_t keycode = (d->k->keycode_map[d->btn_idx] & 0xFF);
|
||||
|
||||
fb_batch_start();
|
||||
list_clear(&d->k->btns, &button_destroy);
|
||||
list_swap(&d->k->keyboard_bnt_data, &keyboard_bnt_data_old);
|
||||
keyboard_init_map(d->k, normalKeycodeMapCharsetMapping[keycode - OSK_CHARSET4], normalKeycodeMapDimensions);
|
||||
fb_batch_end();
|
||||
fb_request_draw();
|
||||
|
||||
list_clear(&keyboard_bnt_data_old, free);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void keyboard_btn_clicked(void *data)
|
||||
{
|
||||
struct keyboard_btn_data *d = data;
|
||||
uint8_t keycode = (d->k->keycode_map[d->btn_idx] & 0xFF);
|
||||
if(d->k->key_pressed)
|
||||
|
||||
if(keycode >= OSK_CHARSET4 && keycode <= OSK_CHARSET1)
|
||||
workers_add(keyboard_charset_switch_worker, data);
|
||||
else if(d->k->key_pressed)
|
||||
d->k->key_pressed(d->k->key_pressed_data, keycode);
|
||||
}
|
||||
|
||||
static int keyboard_init_map(struct keyboard *k, const uint32_t *map, const uint32_t *dimen)
|
||||
int keyboard_init_map(struct keyboard *k, const uint32_t *map, const uint32_t *dimen)
|
||||
{
|
||||
button *btn;
|
||||
int i, idx = 0;
|
||||
@@ -98,6 +157,8 @@ static int keyboard_init_map(struct keyboard *k, const uint32_t *map, const uint
|
||||
|
||||
if(map[i] & KFLAG_HALF)
|
||||
w /= 2;
|
||||
else if(map[i] & KFLAG_PLUS_HALF)
|
||||
w = w*1.5 + PADDING*0.5;
|
||||
|
||||
if(code != OSK_EMPTY)
|
||||
{
|
||||
@@ -116,6 +177,7 @@ static int keyboard_init_map(struct keyboard *k, const uint32_t *map, const uint
|
||||
buf[0] = (map[i] & 0xFF);
|
||||
button_init_ui(btn, ((int8_t)buf[0]) >= 0 ? buf : specialKeys[0xFF - (map[i] & 0xFF)], SIZE_NORMAL);
|
||||
list_add(&k->btns, btn);
|
||||
list_add(&k->keyboard_bnt_data, d);
|
||||
}
|
||||
|
||||
col += GET_KS(map[i])+1;
|
||||
@@ -148,7 +210,7 @@ struct keyboard *keyboard_create(int type, int x, int y, int w, int h)
|
||||
break;
|
||||
case KEYBOARD_NORMAL:
|
||||
default:
|
||||
keyboard_init_map(k, normalKeycodeMap, normalKeycodeMapDimensions);
|
||||
keyboard_init_map(k, normalKeycodeMapCharset1, normalKeycodeMapDimensions);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -158,6 +220,7 @@ struct keyboard *keyboard_create(int type, int x, int y, int w, int h)
|
||||
void keyboard_destroy(struct keyboard *k)
|
||||
{
|
||||
list_clear(&k->btns, &button_destroy);
|
||||
list_clear(&k->keyboard_bnt_data, free);
|
||||
free(k);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,14 +26,18 @@
|
||||
#define OSK_EMPTY 0xFF
|
||||
#define OSK_ENTER 0xFE
|
||||
#define OSK_BACKSPACE 0xFD
|
||||
#define OSK_SHIFT 0xFC
|
||||
#define OSK_CLEAR 0xFB
|
||||
#define OSK_CLEAR 0xFC
|
||||
#define OSK_CHARSET1 0xFB
|
||||
#define OSK_CHARSET2 0xFA
|
||||
#define OSK_CHARSET3 0xF9
|
||||
#define OSK_CHARSET4 0xF8
|
||||
|
||||
typedef void (*keyboard_on_pressed_callback)(void *data, uint8_t keycode);
|
||||
struct keyboard
|
||||
{
|
||||
FB_ITEM_POS
|
||||
button **btns;
|
||||
void **keyboard_bnt_data;
|
||||
const uint32_t *keycode_map;
|
||||
keyboard_on_pressed_callback key_pressed;
|
||||
void *key_pressed_data;
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
#define OVERSCROLL_MARK_H (4*DPI_MUL)
|
||||
#define OVERSCROLL_RETURN_SPD (10*DPI_MUL)
|
||||
|
||||
static void listview_bounceback(uint32_t diff, void *data)
|
||||
static int listview_bounceback(uint32_t diff, void *data)
|
||||
{
|
||||
listview *v = (listview*)data;
|
||||
const int max = v->fullH - v->h;
|
||||
@@ -59,11 +59,13 @@ static void listview_bounceback(uint32_t diff, void *data)
|
||||
v->overscroll_marks[0]->w = 0;
|
||||
if(v->overscroll_marks[1]->w != 0)
|
||||
v->overscroll_marks[1]->w = 0;
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(v->touch.id == -1)
|
||||
listview_scroll_by(v, step);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void listview_init_ui(listview *view)
|
||||
|
||||
@@ -425,7 +425,6 @@ char *run_get_stdout_with_exit_with_env(char **cmd, int *exit_code, char *const
|
||||
close(fd[0]);
|
||||
|
||||
waitpid(pid, exit_code, 0);
|
||||
*exit_code = WEXITSTATUS(*exit_code);
|
||||
|
||||
if(written == 0)
|
||||
{
|
||||
|
||||
@@ -61,10 +61,12 @@ static void *worker_thread_work(void *data)
|
||||
clock_gettime(CLOCK_MONOTONIC, &curr);
|
||||
diff = timespec_diff(&last, &curr);
|
||||
|
||||
if(t->workers)
|
||||
for(w = t->workers; w && *w;)
|
||||
{
|
||||
for(w = t->workers; *w; ++w)
|
||||
(*w)->call(diff, (*w)->data);
|
||||
if((*w)->call(diff, (*w)->data))
|
||||
w = list_rm_at(&worker_thread.workers, w - t->workers, &free);
|
||||
else
|
||||
++w;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&t->mutex);
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
#include <stdint.h>
|
||||
#include <pthread.h>
|
||||
|
||||
typedef void (*worker_call)(uint32_t, void *); // ms_diff, data
|
||||
typedef int (*worker_call)(uint32_t, void *); // ms_diff, data. Returns 1 if it should be removed
|
||||
|
||||
void workers_start(void);
|
||||
void workers_stop(void);
|
||||
|
||||
@@ -48,7 +48,7 @@ int encryption_before_mount(struct fstab *fstab)
|
||||
output = run_get_stdout_with_exit_with_env(encmnt_cmd, &exit_code, encmnt_envp);
|
||||
if(exit_code != 0 || !output)
|
||||
{
|
||||
ERROR("Failed to run trampoline_encmnt: %s", output);
|
||||
ERROR("Failed to run trampoline_encmnt, exit code %d: %s", exit_code, output);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,14 @@ LOCAL_SHARED_LIBRARIES := libcryptfslollipop libcutils
|
||||
LOCAL_STATIC_LIBRARIES := libmultirom_static
|
||||
LOCAL_WHOLE_STATIC_LIBRARIES := libm libpng libz libft2_mrom_static
|
||||
|
||||
mr_twrp_path := bootable/recovery
|
||||
ifneq ($(wildcard bootable/recovery/crypto/lollipop/cryptfs.h),)
|
||||
mr_twrp_path := bootable/recovery
|
||||
else ifneq($(wildcard bootable/recovery-twrp/crypto/lollipop/cryptfs.h),)
|
||||
mr_twrp_path := bootable/recovery-twrp
|
||||
else
|
||||
$(error Failed to find path to TWRP, which is required to build MultiROM with encryption support)
|
||||
endif
|
||||
|
||||
LOCAL_C_INCLUDES += $(multirom_local_path) $(mr_twrp_path) $(mr_twrp_path)/crypto/scrypt/lib/crypto external/openssl/include
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
|
||||
@@ -153,6 +153,7 @@ static int handle_decrypt(int stdout_fd, const char *password)
|
||||
if(de->d_type == DT_BLK && strncmp(de->d_name, "dm-", 3) == 0)
|
||||
{
|
||||
snprintf(buff, sizeof(buff), "/dev/block/%s\n", de->d_name);
|
||||
INFO("Found block device %s", buff);
|
||||
write(stdout_fd, buff, strlen(buff));
|
||||
fsync(stdout_fd);
|
||||
res = 0;
|
||||
|
||||
@@ -39,6 +39,9 @@ struct pwui_type_pass_data {
|
||||
fb_text *passwd_text;
|
||||
fb_rect *cursor_rect;
|
||||
struct keyboard *keyboard;
|
||||
char *pass_buf;
|
||||
char *pass_buf_stars;
|
||||
size_t pass_buf_cap;
|
||||
};
|
||||
|
||||
static pthread_mutex_t exit_code_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
@@ -112,12 +115,26 @@ static void type_pass_key_pressed(void *data, uint8_t code)
|
||||
|
||||
if(code < 128)
|
||||
{
|
||||
char *old = fb_text_get_content(d->passwd_text);
|
||||
char *new_text = malloc(strlen(old)+2);
|
||||
sprintf(new_text, "%s%c", old, (char)code);
|
||||
fb_text_set_content(d->passwd_text, new_text);
|
||||
size_t pass_len = strlen(d->pass_buf);
|
||||
while(d->pass_buf_cap < pass_len + 2)
|
||||
{
|
||||
d->pass_buf_cap *= 2;
|
||||
d->pass_buf = realloc(d->pass_buf, d->pass_buf_cap);
|
||||
d->pass_buf_stars = realloc(d->pass_buf_stars, d->pass_buf_cap);
|
||||
}
|
||||
|
||||
if(pass_len > 0)
|
||||
d->pass_buf_stars[pass_len-1] = '*';
|
||||
d->pass_buf_stars[pass_len] = (char)code;
|
||||
d->pass_buf_stars[pass_len+1] = 0;
|
||||
|
||||
d->pass_buf[pass_len++] = (char)code;
|
||||
d->pass_buf[pass_len] = 0;
|
||||
|
||||
fb_text_set_content(d->passwd_text, d->pass_buf_stars);
|
||||
center_text(d->passwd_text, 0, 0, fb_width, fb_height);
|
||||
free(new_text);
|
||||
fb_request_draw();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -125,23 +142,26 @@ static void type_pass_key_pressed(void *data, uint8_t code)
|
||||
{
|
||||
case OSK_BACKSPACE:
|
||||
{
|
||||
char *old = fb_text_get_content(d->passwd_text);
|
||||
int len = strlen(old);
|
||||
if(len <= 0)
|
||||
size_t pass_len = strlen(d->pass_buf);
|
||||
if(pass_len == 0)
|
||||
break;
|
||||
|
||||
char *new_text = strdup(old);
|
||||
new_text[len-1] = 0;
|
||||
fb_text_set_content(d->passwd_text, new_text);
|
||||
d->pass_buf_stars[--pass_len] = 0;
|
||||
d->pass_buf[pass_len] = 0;
|
||||
|
||||
fb_text_set_content(d->passwd_text, d->pass_buf_stars);
|
||||
center_text(d->passwd_text, 0, 0, fb_width, fb_height);
|
||||
free(new_text);
|
||||
fb_request_draw();
|
||||
break;
|
||||
}
|
||||
case OSK_CLEAR:
|
||||
d->pass_buf[0] = 0;
|
||||
d->pass_buf_stars[0] = 0;
|
||||
fb_text_set_content(d->passwd_text, "");
|
||||
fb_request_draw();
|
||||
break;
|
||||
case OSK_ENTER:
|
||||
try_password(fb_text_get_content(d->passwd_text));
|
||||
try_password(d->pass_buf);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -156,13 +176,20 @@ static void type_pass_init(int pwtype)
|
||||
d->passwd_text = fb_add_text(0, 0, C_TEXT, SIZE_BIG, "");
|
||||
center_text(d->passwd_text, 0, 0, fb_width, fb_height);
|
||||
|
||||
d->pass_buf_cap = 12;
|
||||
d->pass_buf = mzalloc(d->pass_buf_cap);
|
||||
d->pass_buf_stars = mzalloc(d->pass_buf_cap);
|
||||
|
||||
pwui_type_data = d;
|
||||
}
|
||||
|
||||
static void type_pass_destroy(int pwtype)
|
||||
{
|
||||
struct pwui_type_pass_data *d = pwui_type_data;
|
||||
|
||||
keyboard_destroy(d->keyboard);
|
||||
free(d->pass_buf);
|
||||
free(d->pass_buf_stars);
|
||||
free(d);
|
||||
pwui_type_data = NULL;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user