diff options
| -rw-r--r-- | meta-oe/recipes-graphics/xorg-app/xterm/CVE-2023-40359.patch | 388 | ||||
| -rw-r--r-- | meta-oe/recipes-graphics/xorg-app/xterm_372.bb | 1 |
2 files changed, 389 insertions, 0 deletions
diff --git a/meta-oe/recipes-graphics/xorg-app/xterm/CVE-2023-40359.patch b/meta-oe/recipes-graphics/xorg-app/xterm/CVE-2023-40359.patch new file mode 100644 index 0000000000..342a8d8725 --- /dev/null +++ b/meta-oe/recipes-graphics/xorg-app/xterm/CVE-2023-40359.patch | |||
| @@ -0,0 +1,388 @@ | |||
| 1 | From 41ba5cf31da5e43477811b28009d64d3f643fd29 Mon Sep 17 00:00:00 2001 | ||
| 2 | From: "Thomas E. Dickey" <dickey@invisible-island.net> | ||
| 3 | Date: Wed, 8 Mar 2023 01:06:03 +0000 | ||
| 4 | Subject: [PATCH] snapshot of project "xterm", label xterm-379c | ||
| 5 | |||
| 6 | Upstream-Status: Backport from https://github.com/ThomasDickey/xterm-snapshots/commit/41ba5cf31da5e43477811b28009d64d3f643fd29 | ||
| 7 | CVE: CVE-2023-40359 | ||
| 8 | |||
| 9 | Signed-off-by: Rohini Sangam <rsangam@mvista.com> | ||
| 10 | |||
| 11 | --- | ||
| 12 | graphics_regis.c | 235 +++++++++++++++++++++++++++-------------------- | ||
| 13 | 1 file changed, 133 insertions(+), 102 deletions(-) | ||
| 14 | |||
| 15 | diff --git a/graphics_regis.c b/graphics_regis.c | ||
| 16 | index 479bb79..cf14437 100644 | ||
| 17 | --- a/graphics_regis.c | ||
| 18 | +++ b/graphics_regis.c | ||
| 19 | @@ -1,8 +1,8 @@ | ||
| 20 | -/* $XTermId: graphics_regis.c,v 1.129 2022/02/21 13:33:08 tom Exp $ */ | ||
| 21 | +/* $XTermId: graphics_regis.c,v 1.139 2023/03/08 01:06:03 tom Exp $ */ | ||
| 22 | |||
| 23 | /* | ||
| 24 | - * Copyright 2014-2021,2022 by Ross Combs | ||
| 25 | - * Copyright 2014-2021,2022 by Thomas E. Dickey | ||
| 26 | + * Copyright 2014-2022,2023 by Ross Combs | ||
| 27 | + * Copyright 2014-2022,2023 by Thomas E. Dickeiy | ||
| 28 | * | ||
| 29 | * All Rights Reserved | ||
| 30 | * | ||
| 31 | @@ -119,6 +119,14 @@ typedef struct RegisTextControls { | ||
| 32 | int slant; /* for italic/oblique */ | ||
| 33 | } RegisTextControls; | ||
| 34 | |||
| 35 | +#define S_QUOTE '\'' | ||
| 36 | +#define D_QUOTE '"' | ||
| 37 | + | ||
| 38 | +#define isQuote(ch) ((ch) == S_QUOTE || (ch) == D_QUOTE) | ||
| 39 | +#define PickQuote(ch) ((ch) == S_QUOTE ? D_QUOTE : S_QUOTE) | ||
| 40 | + | ||
| 41 | +#define isName(c) ((c) == '_' || isalnum(CharOf(c))) | ||
| 42 | + | ||
| 43 | #define FixedCopy(dst, src, len) strncpy(dst, src, len - 1)[len - 1] = '\0' | ||
| 44 | #define CopyFontname(dst, src) FixedCopy(dst, src, (size_t) REGIS_FONTNAME_LEN) | ||
| 45 | |||
| 46 | @@ -538,8 +546,8 @@ draw_or_save_patterned_pixel(RegisGraphicsContext *context, int x, int y) | ||
| 47 | static int | ||
| 48 | sort_points(void const *l, void const *r) | ||
| 49 | { | ||
| 50 | - RegisPoint const *const lp = l; | ||
| 51 | - RegisPoint const *const rp = r; | ||
| 52 | + RegisPoint const *const lp = (RegisPoint const *) l; | ||
| 53 | + RegisPoint const *const rp = (RegisPoint const *) r; | ||
| 54 | |||
| 55 | if (lp->y < rp->y) | ||
| 56 | return -1; | ||
| 57 | @@ -3151,6 +3159,37 @@ extract_regis_command(RegisDataFragment *input, char *command) | ||
| 58 | return 1; | ||
| 59 | } | ||
| 60 | |||
| 61 | +/* | ||
| 62 | + * * Check a ReGIS alphabet name before reporting it, to pick an appropriate | ||
| 63 | + * * delimiter. If the string is empty, or contains nonreportable characters, | ||
| 64 | + * * just return NUL. | ||
| 65 | + * */ | ||
| 66 | +static int | ||
| 67 | +pick_quote(const char *value) | ||
| 68 | +{ | ||
| 69 | + Bool s_quote = False; | ||
| 70 | + Bool d_quote = False; | ||
| 71 | + | ||
| 72 | + if (*value != '\0') { | ||
| 73 | + while (*value != '\0') { | ||
| 74 | + int ch = CharOf(*value++); | ||
| 75 | + if (ch == D_QUOTE) | ||
| 76 | + d_quote = True; | ||
| 77 | + else if (ch == S_QUOTE) | ||
| 78 | + s_quote = True; | ||
| 79 | + else if (!isName(ch)) | ||
| 80 | + s_quote = d_quote = True; | ||
| 81 | + } | ||
| 82 | + } else { | ||
| 83 | + s_quote = d_quote = True; | ||
| 84 | + } | ||
| 85 | + return ((s_quote && d_quote) | ||
| 86 | + ? 0 | ||
| 87 | + : (s_quote | ||
| 88 | + ? D_QUOTE | ||
| 89 | + : S_QUOTE)); | ||
| 90 | +} | ||
| 91 | + | ||
| 92 | static int | ||
| 93 | extract_regis_string(RegisDataFragment *input, char *out, unsigned maxlen) | ||
| 94 | { | ||
| 95 | @@ -3166,7 +3205,7 @@ extract_regis_string(RegisDataFragment *input, char *out, unsigned maxlen) | ||
| 96 | return 0; | ||
| 97 | |||
| 98 | ch = peek_fragment(input); | ||
| 99 | - if (ch != '\'' && ch != '"') | ||
| 100 | + if (!isQuote(ch)) | ||
| 101 | return 0; | ||
| 102 | open_quote_ch = ch; | ||
| 103 | outlen = 0U; | ||
| 104 | @@ -3246,7 +3285,7 @@ extract_regis_parenthesized_data(RegisDataFragment *input, | ||
| 105 | for (; input->pos < input->len; input->pos++, output->len++) { | ||
| 106 | char prev_ch = ch; | ||
| 107 | ch = input->start[input->pos]; | ||
| 108 | - if (ch == '\'' || ch == '"') { | ||
| 109 | + if (isQuote(ch)) { | ||
| 110 | if (open_quote_ch == '\0') { | ||
| 111 | open_quote_ch = ch; | ||
| 112 | } else { | ||
| 113 | @@ -3314,7 +3353,7 @@ extract_regis_option(RegisDataFragment *input, | ||
| 114 | if (ch == ';' || ch == ',' || | ||
| 115 | ch == '(' || ch == ')' || | ||
| 116 | ch == '[' || ch == ']' || | ||
| 117 | - ch == '"' || ch == '\'' || | ||
| 118 | + isQuote(ch) || | ||
| 119 | isdigit(CharOf(ch))) { | ||
| 120 | return 0; | ||
| 121 | } | ||
| 122 | @@ -3330,7 +3369,7 @@ extract_regis_option(RegisDataFragment *input, | ||
| 123 | TRACE(("looking at char '%c' in option '%c'\n", ch, *option)); | ||
| 124 | /* FIXME: any special rules for commas? */ | ||
| 125 | /* FIXME: handle escaped quotes */ | ||
| 126 | - if (ch == '\'' || ch == '"') { | ||
| 127 | + if (isQuote(ch)) { | ||
| 128 | if (open_quote_ch == ch) { | ||
| 129 | open_quote_ch = '\0'; | ||
| 130 | } else { | ||
| 131 | @@ -5008,6 +5047,7 @@ parse_regis_command(RegisParseState *state) | ||
| 132 | static int | ||
| 133 | parse_regis_option(RegisParseState *state, RegisGraphicsContext *context) | ||
| 134 | { | ||
| 135 | + XtermWidget xw = context->display_graphic->xw; | ||
| 136 | RegisDataFragment optionarg; | ||
| 137 | |||
| 138 | if (!extract_regis_option(&state->input, &state->option, &optionarg)) | ||
| 139 | @@ -5586,13 +5626,18 @@ parse_regis_option(RegisParseState *state, RegisGraphicsContext *context) | ||
| 140 | state->option, fragment_to_tempstr(&optionarg))); | ||
| 141 | break; | ||
| 142 | } { | ||
| 143 | - char reply[64]; | ||
| 144 | + unsigned err_code = 0U; | ||
| 145 | + unsigned err_char = 0U; | ||
| 146 | |||
| 147 | TRACE(("got report last error condition\n")); | ||
| 148 | /* FIXME: implement after adding error tracking */ | ||
| 149 | - sprintf(reply, "\"%u,%u\"\r", 0U, 0U); | ||
| 150 | - unparseputs(context->display_graphic->xw, reply); | ||
| 151 | - unparse_end(context->display_graphic->xw); | ||
| 152 | + unparseputc(xw, D_QUOTE); | ||
| 153 | + unparseputn(xw, err_code); | ||
| 154 | + unparseputc(xw, ','); | ||
| 155 | + unparseputn(xw, err_char); | ||
| 156 | + unparseputc(xw, D_QUOTE); | ||
| 157 | + unparseputc(xw, '\r'); | ||
| 158 | + unparse_end(xw); | ||
| 159 | } | ||
| 160 | break; | ||
| 161 | case 'I': | ||
| 162 | @@ -5639,8 +5684,8 @@ parse_regis_option(RegisParseState *state, RegisGraphicsContext *context) | ||
| 163 | /* FIXME: implement arrow key movement */ | ||
| 164 | /* FIXME: implement button/key collection */ | ||
| 165 | |||
| 166 | - unparseputs(context->display_graphic->xw, "\r"); | ||
| 167 | - unparse_end(context->display_graphic->xw); | ||
| 168 | + unparseputc(xw, '\r'); | ||
| 169 | + unparse_end(xw); | ||
| 170 | |||
| 171 | skip_regis_whitespace(&optionarg); | ||
| 172 | if (!fragment_consumed(&optionarg)) { | ||
| 173 | @@ -5657,25 +5702,22 @@ parse_regis_option(RegisParseState *state, RegisGraphicsContext *context) | ||
| 174 | if (!fragment_consumed(&optionarg)) { | ||
| 175 | TRACE(("DATA_ERROR: unexpected arguments to ReGIS report command option '%c' arg \"%s\"\n", | ||
| 176 | state->option, fragment_to_tempstr(&optionarg))); | ||
| 177 | - break; | ||
| 178 | - } { | ||
| 179 | - char buffer[32]; | ||
| 180 | - | ||
| 181 | - if (state->load_index == MAX_REGIS_ALPHABETS) { | ||
| 182 | - /* If this happens something went wrong elsewhere. */ | ||
| 183 | - TRACE(("DATA_ERROR: unable to report current load alphabet\n")); | ||
| 184 | - unparseputs(context->display_graphic->xw, "A0\"\"\r"); | ||
| 185 | - unparse_end(context->display_graphic->xw); | ||
| 186 | - break; | ||
| 187 | + } else if (state->load_index == MAX_REGIS_ALPHABETS) { | ||
| 188 | + /* If this happens something went wrong elsewhere. */ | ||
| 189 | + TRACE(("DATA_ERROR: unable to report current load alphabet\n")); | ||
| 190 | + unparseputs(xw, "A0\"\"\r"); | ||
| 191 | + unparse_end(xw); | ||
| 192 | + } else { | ||
| 193 | + int delim = pick_quote(state->load_name); | ||
| 194 | + if (delim != '\0') { | ||
| 195 | + unparseputs(xw, "A"); | ||
| 196 | + unparseputn(xw, state->load_alphabet); | ||
| 197 | + unparseputc(xw, delim); | ||
| 198 | + unparseputs(xw, state->load_name); | ||
| 199 | + unparseputc(xw, delim); | ||
| 200 | } | ||
| 201 | - | ||
| 202 | - unparseputs(context->display_graphic->xw, "A"); | ||
| 203 | - sprintf(buffer, "%u", state->load_alphabet); | ||
| 204 | - unparseputs(context->display_graphic->xw, buffer); | ||
| 205 | - unparseputs(context->display_graphic->xw, "\""); | ||
| 206 | - unparseputs(context->display_graphic->xw, state->load_name); | ||
| 207 | - unparseputs(context->display_graphic->xw, "\"\r"); | ||
| 208 | - unparse_end(context->display_graphic->xw); | ||
| 209 | + unparseputc(xw, '\r'); | ||
| 210 | + unparse_end(xw); | ||
| 211 | } | ||
| 212 | break; | ||
| 213 | case 'M': | ||
| 214 | @@ -5717,13 +5759,18 @@ parse_regis_option(RegisParseState *state, RegisGraphicsContext *context) | ||
| 215 | } | ||
| 216 | |||
| 217 | if (name == '=') { | ||
| 218 | - char reply[64]; | ||
| 219 | + unsigned max_available = 1000U; | ||
| 220 | + unsigned cur_available = max_available; | ||
| 221 | |||
| 222 | TRACE(("got report macrograph storage request\n")); | ||
| 223 | /* FIXME: Implement when macrographs are supported. */ | ||
| 224 | - sprintf(reply, "\"%u,%u\"\r", 1000U, 1000U); | ||
| 225 | - unparseputs(context->display_graphic->xw, reply); | ||
| 226 | - unparse_end(context->display_graphic->xw); | ||
| 227 | + unparseputc(xw, D_QUOTE); | ||
| 228 | + unparseputn(xw, cur_available); | ||
| 229 | + unparseputc(xw, ','); | ||
| 230 | + unparseputn(xw, max_available); | ||
| 231 | + unparseputc(xw, D_QUOTE); | ||
| 232 | + unparseputc(xw, '\r'); | ||
| 233 | + unparse_end(xw); | ||
| 234 | } else if (name < 'A' || name > 'Z') { | ||
| 235 | TRACE(("DATA_ERROR: invalid macrograph name: \"%c\"\n", name)); | ||
| 236 | /* FIXME: what should happen? */ | ||
| 237 | @@ -5732,12 +5779,13 @@ parse_regis_option(RegisParseState *state, RegisGraphicsContext *context) | ||
| 238 | char temp[8]; | ||
| 239 | |||
| 240 | TRACE(("got report macrograph request for name '%c'\n", name)); | ||
| 241 | - sprintf(temp, "@=%c", name); | ||
| 242 | - unparseputs(context->display_graphic->xw, temp); | ||
| 243 | + unparseputs(xw, "@="); | ||
| 244 | + unparseputc(xw, name); | ||
| 245 | /* FIXME: Allow this to be disabled for security reasons. */ | ||
| 246 | /* FIXME: implement when macrographs are supported. */ | ||
| 247 | - unparseputs(context->display_graphic->xw, "@;\r"); | ||
| 248 | - unparse_end(context->display_graphic->xw); | ||
| 249 | + unparseputs(xw, "@;"); | ||
| 250 | + unparseputc(xw, '\r'); | ||
| 251 | + unparse_end(xw); | ||
| 252 | } | ||
| 253 | } | ||
| 254 | break; | ||
| 255 | @@ -5785,78 +5833,61 @@ parse_regis_option(RegisParseState *state, RegisGraphicsContext *context) | ||
| 256 | TRACE(("got report cursor position (output=%d)\n", output)); | ||
| 257 | |||
| 258 | /* FIXME: look into supporting ANSI locator reports (DECLRP) */ | ||
| 259 | + unparseputc(xw, L_BLOK); | ||
| 260 | if (output == 1) { | ||
| 261 | - char reply[64]; | ||
| 262 | + /* FIXME: verify in absolute, not user, coordinates */ | ||
| 263 | + unparseputn(xw, (unsigned) context->graphics_output_cursor_x); | ||
| 264 | + unparseputc(xw, ','); | ||
| 265 | + unparseputn(xw, (unsigned) context->graphics_output_cursor_y); | ||
| 266 | + } else if (context->multi_input_mode) { | ||
| 267 | + /* FIXME: track input coordinates */ | ||
| 268 | + unsigned x = 0, y = 0; /* placeholders */ | ||
| 269 | + | ||
| 270 | + /* send CSI240~[x,y]\r with current input cursor location */ | ||
| 271 | + | ||
| 272 | + /* FIXME: verify no leading char or button sequence */ | ||
| 273 | + /* FIXME: should we ever send an eight-bit CSI? */ | ||
| 274 | |||
| 275 | /* FIXME: verify in absolute, not user, coordinates */ | ||
| 276 | - sprintf(reply, "[%d,%d]\r", | ||
| 277 | - context->graphics_output_cursor_x, | ||
| 278 | - context->graphics_output_cursor_y); | ||
| 279 | - unparseputs(context->display_graphic->xw, reply); | ||
| 280 | - unparse_end(context->display_graphic->xw); | ||
| 281 | + TRACE(("sending multi-mode input report at %u,%u\n", x, y)); | ||
| 282 | + unparseputn(xw, x); | ||
| 283 | + unparseputc(xw, ','); | ||
| 284 | + unparseputn(xw, y); | ||
| 285 | } else { | ||
| 286 | - char reply[64]; | ||
| 287 | - int x, y; | ||
| 288 | - | ||
| 289 | - if (context->multi_input_mode) { | ||
| 290 | - /* FIXME: track input coordinates */ | ||
| 291 | - x = y = 0; /* placeholders */ | ||
| 292 | - | ||
| 293 | - /* send CSI240~[x,y]\r with current input cursor location */ | ||
| 294 | - | ||
| 295 | - /* FIXME: verify no leading char or button sequence */ | ||
| 296 | - /* FIXME: should we ever send an eight-bit CSI? */ | ||
| 297 | - /* FIXME: verify in absolute, not user, coordinates */ | ||
| 298 | - TRACE(("sending multi-mode input report at %d,%d\n", | ||
| 299 | - x, y)); | ||
| 300 | - sprintf(reply, "[%d,%d]\r", x, y); | ||
| 301 | - unparseputs(context->display_graphic->xw, reply); | ||
| 302 | - unparse_end(context->display_graphic->xw); | ||
| 303 | - break; | ||
| 304 | - } else { | ||
| 305 | - char ch; | ||
| 306 | - | ||
| 307 | - /* FIXME: wait for first non-arrow keypress or mouse click, and don't update graphics while waiting */ | ||
| 308 | - ch = ' '; /* placeholder */ | ||
| 309 | - x = y = 0; /* placeholders */ | ||
| 310 | - | ||
| 311 | - /* send <key or button>[x,y]\r to report input cursor location */ | ||
| 312 | - | ||
| 313 | - /* null button: CSI240~ */ | ||
| 314 | - /* left button: CSI241~ */ | ||
| 315 | - /* middle button: CSI243~ */ | ||
| 316 | - /* right button: CSI245~ */ | ||
| 317 | - /* extra button: CSI247~ */ | ||
| 318 | - /* FIXME: support DECLBD to change button assignments */ | ||
| 319 | - /* FIXME: verify no leading char or button sequence */ | ||
| 320 | - TRACE(("sending one-shot input report with %c at %d,%d\n", | ||
| 321 | - ch, x, y)); | ||
| 322 | -#if 0 /* FIXME - dead code */ | ||
| 323 | - if (ch == '\r') { | ||
| 324 | - /* Return only reports the location. */ | ||
| 325 | - sprintf(reply, "[%d,%d]\r", x, y); | ||
| 326 | - } else if (ch == '\177') { | ||
| 327 | - /* DEL exits locator mode reporting nothing. */ | ||
| 328 | - sprintf(reply, "\r"); | ||
| 329 | - } else | ||
| 330 | -#endif | ||
| 331 | - { | ||
| 332 | - sprintf(reply, "%c[%d,%d]\r", ch, x, y); | ||
| 333 | - } | ||
| 334 | - unparseputs(context->display_graphic->xw, reply); | ||
| 335 | - unparse_end(context->display_graphic->xw); | ||
| 336 | - /* FIXME: exit one-shot mode and disable input cursor */ | ||
| 337 | - break; | ||
| 338 | + | ||
| 339 | + char ch = ' '; /* placeholder */ | ||
| 340 | + unsigned x = 0, y = 0; /* placeholders */ | ||
| 341 | + | ||
| 342 | + /* FIXME: wait for first non-arrow keypress or mouse click, and don't update graphics while waiting */ | ||
| 343 | + /* send <key or button>[x,y]\r to report input cursor location */ | ||
| 344 | + | ||
| 345 | + /* null button: CSI240~ */ | ||
| 346 | + /* left button: CSI241~ */ | ||
| 347 | + /* middle button: CSI243~ */ | ||
| 348 | + /* right button: CSI245~ */ | ||
| 349 | + /* extra button: CSI247~ */ | ||
| 350 | + /* FIXME: support DECLBD to change button assignments */ | ||
| 351 | + /* FIXME: verify no leading char or button sequence */ | ||
| 352 | + TRACE(("sending one-shot input report with %c at %u,%u\n", | ||
| 353 | + ch, x, y)); | ||
| 354 | + if (ch != '\177') { | ||
| 355 | + unparseputn(xw, x); | ||
| 356 | + unparseputc(xw, ','); | ||
| 357 | + unparseputn(xw, y); | ||
| 358 | } | ||
| 359 | + /* FIXME: exit one-shot mode and disable input cursor */ | ||
| 360 | } | ||
| 361 | + unparseputc(xw, R_BLOK); | ||
| 362 | + unparseputc(xw, '\r'); | ||
| 363 | + unparse_end(xw); | ||
| 364 | } | ||
| 365 | break; | ||
| 366 | default: | ||
| 367 | TRACE(("DATA_ERROR: sending empty report for unknown ReGIS report command option '%c' arg \"%s\"\n", | ||
| 368 | state->option, fragment_to_tempstr(&optionarg))); | ||
| 369 | /* Unknown report request types must receive empty reports. */ | ||
| 370 | - unparseputs(context->display_graphic->xw, "\r"); | ||
| 371 | - unparse_end(context->display_graphic->xw); | ||
| 372 | + unparseputs(xw, "\r"); | ||
| 373 | + unparse_end(xw); | ||
| 374 | break; | ||
| 375 | } | ||
| 376 | break; | ||
| 377 | @@ -6154,7 +6185,7 @@ parse_regis_option(RegisParseState *state, RegisGraphicsContext *context) | ||
| 378 | |||
| 379 | TRACE(("using display page number: %d\n", page)); | ||
| 380 | context->display_page = (unsigned) page; | ||
| 381 | - map_regis_graphics_pages(context->display_graphic->xw, context); | ||
| 382 | + map_regis_graphics_pages(xw, context); | ||
| 383 | } | ||
| 384 | break; | ||
| 385 | case 'T': | ||
| 386 | -- | ||
| 387 | 2.35.7 | ||
| 388 | |||
diff --git a/meta-oe/recipes-graphics/xorg-app/xterm_372.bb b/meta-oe/recipes-graphics/xorg-app/xterm_372.bb index 223bc0a498..84308b1848 100644 --- a/meta-oe/recipes-graphics/xorg-app/xterm_372.bb +++ b/meta-oe/recipes-graphics/xorg-app/xterm_372.bb | |||
| @@ -7,6 +7,7 @@ LIC_FILES_CHKSUM = "file://xterm.h;beginline=3;endline=31;md5=5ec6748ed90e588caa | |||
| 7 | SRC_URI = "http://invisible-mirror.net/archives/${BPN}/${BP}.tgz \ | 7 | SRC_URI = "http://invisible-mirror.net/archives/${BPN}/${BP}.tgz \ |
| 8 | file://0001-Add-configure-time-check-for-setsid.patch \ | 8 | file://0001-Add-configure-time-check-for-setsid.patch \ |
| 9 | file://CVE-2022-45063.patch \ | 9 | file://CVE-2022-45063.patch \ |
| 10 | file://CVE-2023-40359.patch \ | ||
| 10 | " | 11 | " |
| 11 | 12 | ||
| 12 | SRC_URI[sha256sum] = "c6d08127cb2409c3a04bcae559b7025196ed770bb7bf26630abcb45d95f60ab1" | 13 | SRC_URI[sha256sum] = "c6d08127cb2409c3a04bcae559b7025196ed770bb7bf26630abcb45d95f60ab1" |
