telnet_server: support C-p, C-n for moving through history
[openocd.git] / src / server / telnet_server.c
1 /***************************************************************************
2 * Copyright (C) 2005 by Dominic Rath *
3 * Dominic.Rath@gmx.de *
4 * *
5 * Copyright (C) 2007-2010 √ėyvind Harboe *
6 * oyvind.harboe@zylin.com *
7 * *
8 * Copyright (C) 2008 by Spencer Oliver *
9 * spen@spen-soft.co.uk *
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 * This program is distributed in the hope that it will be useful, *
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
19 * GNU General Public License for more details. *
20 * *
21 * You should have received a copy of the GNU General Public License *
22 * along with this program; if not, write to the *
23 * Free Software Foundation, Inc., *
24 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
25 ***************************************************************************/
26
27 #ifdef HAVE_CONFIG_H
28 #include "config.h"
29 #endif
30
31 #include "telnet_server.h"
32 #include <target/target_request.h>
33 #include <helper/configuration.h>
34
35 static const char *telnet_port;
36
37 static char *negotiate =
38 "\xFF\xFB\x03" /* IAC WILL Suppress Go Ahead */
39 "\xFF\xFB\x01" /* IAC WILL Echo */
40 "\xFF\xFD\x03" /* IAC DO Suppress Go Ahead */
41 "\xFF\xFE\x01"; /* IAC DON'T Echo */
42
43 #define CTRL(c) (c - '@')
44 #define TELNET_HISTORY ".openocd_history"
45
46 /* The only way we can detect that the socket is closed is the first time
47 * we write to it, we will fail. Subsequent write operations will
48 * succeed. Shudder!
49 */
50 static int telnet_write(struct connection *connection, const void *data,
51 int len)
52 {
53 struct telnet_connection *t_con = connection->priv;
54 if (t_con->closed)
55 return ERROR_SERVER_REMOTE_CLOSED;
56
57 if (connection_write(connection, data, len) == len)
58 return ERROR_OK;
59 t_con->closed = 1;
60 return ERROR_SERVER_REMOTE_CLOSED;
61 }
62
63 static int telnet_prompt(struct connection *connection)
64 {
65 struct telnet_connection *t_con = connection->priv;
66
67 return telnet_write(connection, t_con->prompt, strlen(t_con->prompt));
68 }
69
70 static int telnet_outputline(struct connection *connection, const char *line)
71 {
72 int len;
73
74 /* process lines in buffer */
75 while (*line) {
76 char *line_end = strchr(line, '\n');
77
78 if (line_end)
79 len = line_end-line;
80 else
81 len = strlen(line);
82
83 telnet_write(connection, line, len);
84 if (line_end) {
85 telnet_write(connection, "\r\n", 2);
86 line += len + 1;
87 } else
88 line += len;
89 }
90
91 return ERROR_OK;
92 }
93
94 static int telnet_output(struct command_context *cmd_ctx, const char *line)
95 {
96 struct connection *connection = cmd_ctx->output_handler_priv;
97
98 return telnet_outputline(connection, line);
99 }
100
101 static void telnet_log_callback(void *priv, const char *file, unsigned line,
102 const char *function, const char *string)
103 {
104 struct connection *connection = priv;
105 struct telnet_connection *t_con = connection->priv;
106 int i;
107
108 /* if there is no prompt, simply output the message */
109 if (t_con->line_cursor < 0) {
110 telnet_outputline(connection, string);
111 return;
112 }
113
114 /* clear the command line */
115 for (i = strlen(t_con->prompt) + t_con->line_size; i > 0; i -= 16)
116 telnet_write(connection, "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b", i > 16 ? 16 : i);
117 for (i = strlen(t_con->prompt) + t_con->line_size; i > 0; i -= 16)
118 telnet_write(connection, " ", i > 16 ? 16 : i);
119 for (i = strlen(t_con->prompt) + t_con->line_size; i > 0; i -= 16)
120 telnet_write(connection, "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b", i > 16 ? 16 : i);
121
122 /* output the message */
123 telnet_outputline(connection, string);
124
125 /* put the command line to its previous state */
126 telnet_prompt(connection);
127 telnet_write(connection, t_con->line, t_con->line_size);
128 for (i = t_con->line_size; i > t_con->line_cursor; i--)
129 telnet_write(connection, "\b", 1);
130 }
131
132 static void telnet_load_history(struct telnet_connection *t_con)
133 {
134 FILE *histfp;
135 char buffer[TELNET_BUFFER_SIZE];
136 int i = 0;
137
138 char *history = get_home_dir(TELNET_HISTORY);
139
140 if (history == NULL) {
141 LOG_INFO("unable to get user home directory, telnet history will be disabled");
142 return;
143 }
144
145 histfp = fopen(history, "rb");
146
147 if (histfp) {
148
149 while (fgets(buffer, sizeof(buffer), histfp) != NULL) {
150
151 char *p = strchr(buffer, '\n');
152 if (p)
153 *p = '\0';
154 if (buffer[0] && i < TELNET_LINE_HISTORY_SIZE)
155 t_con->history[i++] = strdup(buffer);
156 }
157
158 t_con->next_history = i;
159 t_con->next_history %= TELNET_LINE_HISTORY_SIZE;
160 /* try to set to last entry - 1, that way we skip over any exit/shutdown cmds */
161 t_con->current_history = t_con->next_history > 0 ? i - 1 : 0;
162 fclose(histfp);
163 }
164
165 free(history);
166 }
167
168 static void telnet_save_history(struct telnet_connection *t_con)
169 {
170 FILE *histfp;
171 int i;
172 int num;
173
174 char *history = get_home_dir(TELNET_HISTORY);
175
176 if (history == NULL) {
177 LOG_INFO("unable to get user home directory, telnet history will be disabled");
178 return;
179 }
180
181 histfp = fopen(history, "wb");
182
183 if (histfp) {
184
185 num = TELNET_LINE_HISTORY_SIZE;
186 i = t_con->current_history + 1;
187 i %= TELNET_LINE_HISTORY_SIZE;
188
189 while (t_con->history[i] == NULL && num > 0) {
190 i++;
191 i %= TELNET_LINE_HISTORY_SIZE;
192 num--;
193 }
194
195 if (num > 0) {
196 for (; num > 0; num--) {
197 fprintf(histfp, "%s\n", t_con->history[i]);
198 i++;
199 i %= TELNET_LINE_HISTORY_SIZE;
200 }
201 }
202 fclose(histfp);
203 }
204
205 free(history);
206 }
207
208 static int telnet_new_connection(struct connection *connection)
209 {
210 struct telnet_connection *telnet_connection = malloc(sizeof(struct telnet_connection));
211 struct telnet_service *telnet_service = connection->service->priv;
212 int i;
213
214 connection->priv = telnet_connection;
215
216 /* initialize telnet connection information */
217 telnet_connection->closed = 0;
218 telnet_connection->line_size = 0;
219 telnet_connection->line_cursor = 0;
220 telnet_connection->option_size = 0;
221 telnet_connection->prompt = strdup("> ");
222 telnet_connection->state = TELNET_STATE_DATA;
223
224 /* output goes through telnet connection */
225 command_set_output_handler(connection->cmd_ctx, telnet_output, connection);
226
227 /* negotiate telnet options */
228 telnet_write(connection, negotiate, strlen(negotiate));
229
230 /* print connection banner */
231 if (telnet_service->banner) {
232 telnet_write(connection, telnet_service->banner, strlen(telnet_service->banner));
233 telnet_write(connection, "\r\n", 2);
234 }
235
236 /* the prompt is always placed at the line beginning */
237 telnet_write(connection, "\r", 1);
238 telnet_prompt(connection);
239
240 /* initialize history */
241 for (i = 0; i < TELNET_LINE_HISTORY_SIZE; i++)
242 telnet_connection->history[i] = NULL;
243 telnet_connection->next_history = 0;
244 telnet_connection->current_history = 0;
245 telnet_load_history(telnet_connection);
246
247 log_add_callback(telnet_log_callback, connection);
248
249 return ERROR_OK;
250 }
251
252 static void telnet_clear_line(struct connection *connection,
253 struct telnet_connection *t_con)
254 {
255 /* move to end of line */
256 if (t_con->line_cursor < t_con->line_size)
257 telnet_write(connection,
258 t_con->line + t_con->line_cursor,
259 t_con->line_size - t_con->line_cursor);
260
261 /* backspace, overwrite with space, backspace */
262 while (t_con->line_size > 0) {
263 telnet_write(connection, "\b \b", 3);
264 t_con->line_size--;
265 }
266 t_con->line_cursor = 0;
267 }
268
269 static void telnet_history_go(struct connection *connection, int idx)
270 {
271 struct telnet_connection *t_con = connection->priv;
272
273 if (t_con->history[idx]) {
274 telnet_clear_line(connection, t_con);
275 t_con->line_size = strlen(t_con->history[idx]);
276 t_con->line_cursor = t_con->line_size;
277 memcpy(t_con->line, t_con->history[idx], t_con->line_size);
278 telnet_write(connection, t_con->line, t_con->line_size);
279 t_con->current_history = idx;
280 }
281 t_con->state = TELNET_STATE_DATA;
282 }
283
284 static void telnet_history_up(struct connection *connection)
285 {
286 struct telnet_connection *t_con = connection->priv;
287
288 int last_history = (t_con->current_history > 0) ?
289 t_con->current_history - 1 :
290 TELNET_LINE_HISTORY_SIZE-1;
291 telnet_history_go(connection, last_history);
292 }
293
294 static void telnet_history_down(struct connection *connection)
295 {
296 struct telnet_connection *t_con = connection->priv;
297
298 int next_history = (t_con->current_history + 1) % TELNET_LINE_HISTORY_SIZE;
299 telnet_history_go(connection, next_history);
300 }
301
302 static int telnet_input(struct connection *connection)
303 {
304 int bytes_read;
305 unsigned char buffer[TELNET_BUFFER_SIZE];
306 unsigned char *buf_p;
307 struct telnet_connection *t_con = connection->priv;
308 struct command_context *command_context = connection->cmd_ctx;
309
310 bytes_read = connection_read(connection, buffer, TELNET_BUFFER_SIZE);
311
312 if (bytes_read == 0)
313 return ERROR_SERVER_REMOTE_CLOSED;
314 else if (bytes_read == -1) {
315 LOG_ERROR("error during read: %s", strerror(errno));
316 return ERROR_SERVER_REMOTE_CLOSED;
317 }
318
319 buf_p = buffer;
320 while (bytes_read) {
321 switch (t_con->state) {
322 case TELNET_STATE_DATA:
323 if (*buf_p == 0xff)
324 t_con->state = TELNET_STATE_IAC;
325 else {
326 if (isprint(*buf_p)) { /* printable character */
327 /* watch buffer size leaving one spare character for
328 * string null termination */
329 if (t_con->line_size == TELNET_LINE_MAX_SIZE-1) {
330 /* output audible bell if buffer is full
331 * "\a" does not work, at least on windows */
332 telnet_write(connection, "\x07", 1);
333 } else if (t_con->line_cursor == t_con->line_size) {
334 telnet_write(connection, buf_p, 1);
335 t_con->line[t_con->line_size++] = *buf_p;
336 t_con->line_cursor++;
337 } else {
338 int i;
339 memmove(t_con->line + t_con->line_cursor + 1,
340 t_con->line + t_con->line_cursor,
341 t_con->line_size - t_con->line_cursor);
342 t_con->line[t_con->line_cursor] = *buf_p;
343 t_con->line_size++;
344 telnet_write(connection,
345 t_con->line + t_con->line_cursor,
346 t_con->line_size - t_con->line_cursor);
347 t_con->line_cursor++;
348 for (i = t_con->line_cursor; i < t_con->line_size; i++)
349 telnet_write(connection, "\b", 1);
350 }
351 } else { /* non-printable */
352 if (*buf_p == 0x1b) { /* escape */
353 t_con->state = TELNET_STATE_ESCAPE;
354 t_con->last_escape = '\x00';
355 } else if ((*buf_p == 0xd) || (*buf_p == 0xa)) { /* CR/LF */
356 int retval;
357
358 /* skip over combinations with CR/LF and NUL characters */
359 if ((bytes_read > 1) && ((*(buf_p + 1) == 0xa) ||
360 (*(buf_p + 1) == 0xd))) {
361 buf_p++;
362 bytes_read--;
363 }
364 if ((bytes_read > 1) && (*(buf_p + 1) == 0)) {
365 buf_p++;
366 bytes_read--;
367 }
368 t_con->line[t_con->line_size] = 0;
369
370 telnet_write(connection, "\r\n\x00", 3);
371
372 if (strcmp(t_con->line, "history") == 0) {
373 int i;
374 for (i = 1; i < TELNET_LINE_HISTORY_SIZE; i++) {
375 /* the t_con->next_history line contains empty string
376 * (unless NULL), thus it is not printed */
377 char *history_line = t_con->history[(t_con->
378 next_history + i) %
379 TELNET_LINE_HISTORY_SIZE];
380 if (history_line) {
381 telnet_write(connection, history_line,
382 strlen(history_line));
383 telnet_write(connection, "\r\n\x00", 3);
384 }
385 }
386 t_con->line_size = 0;
387 t_con->line_cursor = 0;
388 continue;
389 }
390
391 /* save only non-blank not repeating lines in the history */
392 char *prev_line = t_con->history[(t_con->current_history > 0) ?
393 t_con->current_history - 1 : TELNET_LINE_HISTORY_SIZE-1];
394 if (*t_con->line && (prev_line == NULL ||
395 strcmp(t_con->line, prev_line))) {
396 /* if the history slot is already taken, free it */
397 if (t_con->history[t_con->next_history])
398 free(t_con->history[t_con->next_history]);
399
400 /* add line to history */
401 t_con->history[t_con->next_history] = strdup(t_con->line);
402
403 /* wrap history at TELNET_LINE_HISTORY_SIZE */
404 t_con->next_history = (t_con->next_history + 1) %
405 TELNET_LINE_HISTORY_SIZE;
406
407 /* current history line starts at the new entry */
408 t_con->current_history =
409 t_con->next_history;
410
411 if (t_con->history[t_con->current_history])
412 free(t_con->history[t_con->current_history]);
413 t_con->history[t_con->current_history] = strdup("");
414 }
415
416 t_con->line_size = 0;
417
418 /* to suppress prompt in log callback during command execution */
419 t_con->line_cursor = -1;
420
421 if (strcmp(t_con->line, "shutdown") == 0)
422 telnet_save_history(t_con);
423
424 retval = command_run_line(command_context, t_con->line);
425
426 t_con->line_cursor = 0;
427
428 if (retval == ERROR_COMMAND_CLOSE_CONNECTION)
429 return ERROR_SERVER_REMOTE_CLOSED;
430
431 /* the prompt is always * placed at the line beginning */
432 telnet_write(connection, "\r", 1);
433
434 retval = telnet_prompt(connection);
435 if (retval == ERROR_SERVER_REMOTE_CLOSED)
436 return ERROR_SERVER_REMOTE_CLOSED;
437
438 } else if ((*buf_p == 0x7f) || (*buf_p == 0x8)) { /* delete character */
439 if (t_con->line_cursor > 0) {
440 if (t_con->line_cursor != t_con->line_size) {
441 int i;
442 telnet_write(connection, "\b", 1);
443 t_con->line_cursor--;
444 t_con->line_size--;
445 memmove(t_con->line + t_con->line_cursor,
446 t_con->line + t_con->line_cursor + 1,
447 t_con->line_size -
448 t_con->line_cursor);
449
450 telnet_write(connection,
451 t_con->line + t_con->line_cursor,
452 t_con->line_size -
453 t_con->line_cursor);
454 telnet_write(connection, " \b", 2);
455 for (i = t_con->line_cursor; i < t_con->line_size; i++)
456 telnet_write(connection, "\b", 1);
457 } else {
458 t_con->line_size--;
459 t_con->line_cursor--;
460 /* back space: move the 'printer' head one char
461 * back, overwrite with space, move back again */
462 telnet_write(connection, "\b \b", 3);
463 }
464 }
465 } else if (*buf_p == 0x15) /* clear line */
466 telnet_clear_line(connection, t_con);
467 else if (*buf_p == CTRL('B')) { /* cursor left */
468 if (t_con->line_cursor > 0) {
469 telnet_write(connection, "\b", 1);
470 t_con->line_cursor--;
471 }
472 t_con->state = TELNET_STATE_DATA;
473 } else if (*buf_p == CTRL('F')) { /* cursor right */
474 if (t_con->line_cursor < t_con->line_size)
475 telnet_write(connection, t_con->line + t_con->line_cursor++, 1);
476 t_con->state = TELNET_STATE_DATA;
477 } else if (*buf_p == CTRL('P')) /* cursor up */
478 telnet_history_up(connection);
479 else if (*buf_p == CTRL('N')) /* cursor down */
480 telnet_history_down(connection);
481 else
482 LOG_DEBUG("unhandled nonprintable: %2.2x", *buf_p);
483 }
484 }
485 break;
486 case TELNET_STATE_IAC:
487 switch (*buf_p) {
488 case 0xfe:
489 t_con->state = TELNET_STATE_DONT;
490 break;
491 case 0xfd:
492 t_con->state = TELNET_STATE_DO;
493 break;
494 case 0xfc:
495 t_con->state = TELNET_STATE_WONT;
496 break;
497 case 0xfb:
498 t_con->state = TELNET_STATE_WILL;
499 break;
500 }
501 break;
502 case TELNET_STATE_SB:
503 break;
504 case TELNET_STATE_SE:
505 break;
506 case TELNET_STATE_WILL:
507 case TELNET_STATE_WONT:
508 case TELNET_STATE_DO:
509 case TELNET_STATE_DONT:
510 t_con->state = TELNET_STATE_DATA;
511 break;
512 case TELNET_STATE_ESCAPE:
513 if (t_con->last_escape == '[') {
514 if (*buf_p == 'D') { /* cursor left */
515 if (t_con->line_cursor > 0) {
516 telnet_write(connection, "\b", 1);
517 t_con->line_cursor--;
518 }
519 t_con->state = TELNET_STATE_DATA;
520 } else if (*buf_p == 'C') { /* cursor right */
521 if (t_con->line_cursor < t_con->line_size)
522 telnet_write(connection,
523 t_con->line + t_con->line_cursor++, 1);
524 t_con->state = TELNET_STATE_DATA;
525 } else if (*buf_p == 'A') { /* cursor up */
526 telnet_history_up(connection);
527 } else if (*buf_p == 'B') { /* cursor down */
528 telnet_history_down(connection);
529 } else if (*buf_p == '3')
530 t_con->last_escape = *buf_p;
531 else
532 t_con->state = TELNET_STATE_DATA;
533 } else if (t_con->last_escape == '3') {
534 /* Remove character */
535 if (*buf_p == '~') {
536 if (t_con->line_cursor < t_con->line_size) {
537 int i;
538 t_con->line_size--;
539 /* remove char from line buffer */
540 memmove(t_con->line + t_con->line_cursor,
541 t_con->line + t_con->line_cursor + 1,
542 t_con->line_size - t_con->line_cursor);
543
544 /* print remainder of buffer */
545 telnet_write(connection, t_con->line + t_con->line_cursor,
546 t_con->line_size - t_con->line_cursor);
547 /* overwrite last char with whitespace */
548 telnet_write(connection, " \b", 2);
549
550 /* move back to cursor position*/
551 for (i = t_con->line_cursor; i < t_con->line_size; i++)
552 telnet_write(connection, "\b", 1);
553 }
554
555 t_con->state = TELNET_STATE_DATA;
556 } else
557 t_con->state = TELNET_STATE_DATA;
558 } else if (t_con->last_escape == '\x00') {
559 if (*buf_p == '[')
560 t_con->last_escape = *buf_p;
561 else
562 t_con->state = TELNET_STATE_DATA;
563 } else {
564 LOG_ERROR("BUG: unexpected value in t_con->last_escape");
565 t_con->state = TELNET_STATE_DATA;
566 }
567
568 break;
569 default:
570 LOG_ERROR("unknown telnet state");
571 exit(-1);
572 }
573
574 bytes_read--;
575 buf_p++;
576 }
577
578 return ERROR_OK;
579 }
580
581 static int telnet_connection_closed(struct connection *connection)
582 {
583 struct telnet_connection *t_con = connection->priv;
584 int i;
585
586 log_remove_callback(telnet_log_callback, connection);
587
588 if (t_con->prompt) {
589 free(t_con->prompt);
590 t_con->prompt = NULL;
591 }
592
593 /* save telnet history */
594 telnet_save_history(t_con);
595
596 for (i = 0; i < TELNET_LINE_HISTORY_SIZE; i++) {
597 if (t_con->history[i]) {
598 free(t_con->history[i]);
599 t_con->history[i] = NULL;
600 }
601 }
602
603 /* if this connection registered a debug-message receiver delete it */
604 delete_debug_msg_receiver(connection->cmd_ctx, NULL);
605
606 if (connection->priv) {
607 free(connection->priv);
608 connection->priv = NULL;
609 } else
610 LOG_ERROR("BUG: connection->priv == NULL");
611
612 return ERROR_OK;
613 }
614
615 int telnet_init(char *banner)
616 {
617 if (strcmp(telnet_port, "disabled") == 0) {
618 LOG_INFO("telnet server disabled");
619 return ERROR_OK;
620 }
621
622 struct telnet_service *telnet_service = malloc(sizeof(struct telnet_service));
623
624 telnet_service->banner = banner;
625
626 return add_service("telnet",
627 telnet_port,
628 1,
629 telnet_new_connection,
630 telnet_input,
631 telnet_connection_closed,
632 telnet_service);
633 }
634
635 /* daemon configuration command telnet_port */
636 COMMAND_HANDLER(handle_telnet_port_command)
637 {
638 return CALL_COMMAND_HANDLER(server_pipe_command, &telnet_port);
639 }
640
641 COMMAND_HANDLER(handle_exit_command)
642 {
643 return ERROR_COMMAND_CLOSE_CONNECTION;
644 }
645
646 static const struct command_registration telnet_command_handlers[] = {
647 {
648 .name = "exit",
649 .handler = handle_exit_command,
650 .mode = COMMAND_EXEC,
651 .usage = "",
652 .help = "exit telnet session",
653 },
654 {
655 .name = "telnet_port",
656 .handler = handle_telnet_port_command,
657 .mode = COMMAND_ANY,
658 .help = "Specify port on which to listen "
659 "for incoming telnet connections. "
660 "Read help on 'gdb_port'.",
661 .usage = "[port_num]",
662 },
663 COMMAND_REGISTRATION_DONE
664 };
665
666 int telnet_register_commands(struct command_context *cmd_ctx)
667 {
668 telnet_port = strdup("4444");
669 return register_commands(cmd_ctx, NULL, telnet_command_handlers);
670 }

Linking to existing account procedure

If you already have an account and want to add another login method you MUST first sign in with your existing account and then change URL to read https://review.openocd.org/login/?link to get to this page again but this time it'll work for linking. Thank you.

SSH host keys fingerprints

1024 SHA256:YKx8b7u5ZWdcbp7/4AeXNaqElP49m6QrwfXaqQGJAOk gerrit-code-review@openocd.zylin.com (DSA)
384 SHA256:jHIbSQa4REvwCFG4cq5LBlBLxmxSqelQPem/EXIrxjk gerrit-code-review@openocd.org (ECDSA)
521 SHA256:UAOPYkU9Fjtcao0Ul/Rrlnj/OsQvt+pgdYSZ4jOYdgs gerrit-code-review@openocd.org (ECDSA)
256 SHA256:A13M5QlnozFOvTllybRZH6vm7iSt0XLxbA48yfc2yfY gerrit-code-review@openocd.org (ECDSA)
256 SHA256:spYMBqEYoAOtK7yZBrcwE8ZpYt6b68Cfh9yEVetvbXg gerrit-code-review@openocd.org (ED25519)
+--[ED25519 256]--+
|=..              |
|+o..   .         |
|*.o   . .        |
|+B . . .         |
|Bo. = o S        |
|Oo.+ + =         |
|oB=.* = . o      |
| =+=.+   + E     |
|. .=o   . o      |
+----[SHA256]-----+
2048 SHA256:0Onrb7/PHjpo6iVZ7xQX2riKN83FJ3KGU0TvI0TaFG4 gerrit-code-review@openocd.zylin.com (RSA)