+
+static void user_data_init(struct user_data_st *user_data, SSL *con, char *buf,
+ size_t bufmax, int mode)
+{
+ user_data->con = con;
+ user_data->buf = buf;
+ user_data->bufmax = bufmax;
+ user_data->buflen = 0;
+ user_data->bufoff = 0;
+ user_data->mode = mode;
+}
+
+static int user_data_add(struct user_data_st *user_data, size_t i)
+{
+ if (user_data->buflen != 0 || i > user_data->bufmax - 1)
+ return 0;
+
+ user_data->buflen = i;
+ user_data->bufoff = 0;
+
+ return 1;
+}
+
+#define USER_COMMAND_HELP 0
+#define USER_COMMAND_QUIT 1
+#define USER_COMMAND_RECONNECT 2
+#define USER_COMMAND_RENEGOTIATE 3
+#define USER_COMMAND_KEY_UPDATE 4
+
+static int user_data_execute(struct user_data_st *user_data, int cmd, char *arg)
+{
+ switch (cmd) {
+ case USER_COMMAND_HELP:
+ /* This only ever occurs in advanced mode, so just emit advanced help */
+ BIO_printf(bio_err, "Enter text to send to the peer followed by <enter>\n");
+ BIO_printf(bio_err, "To issue a command insert {cmd} or {cmd:arg} anywhere in the text\n");
+ BIO_printf(bio_err, "Entering {{ will send { to the peer\n");
+ BIO_printf(bio_err, "The following commands are available\n");
+ BIO_printf(bio_err, " {help}: Get this help text\n");
+ BIO_printf(bio_err, " {quit}: Close the connection to the peer\n");
+ BIO_printf(bio_err, " {reconnect}: Reconnect to the peer\n");
+ if (SSL_version(user_data->con) == TLS1_3_VERSION) {
+ BIO_printf(bio_err, " {keyup:req|noreq}: Send a Key Update message\n");
+ BIO_printf(bio_err, " Arguments:\n");
+ BIO_printf(bio_err, " req = peer update requested (default)\n");
+ BIO_printf(bio_err, " noreq = peer update not requested\n");
+ } else {
+ BIO_printf(bio_err, " {reneg}: Attempt to renegotiate\n");
+ }
+ BIO_printf(bio_err, "\n");
+ return USER_DATA_PROCESS_NO_DATA;
+
+ case USER_COMMAND_QUIT:
+ BIO_printf(bio_err, "DONE\n");
+ return USER_DATA_PROCESS_SHUT;
+
+ case USER_COMMAND_RECONNECT:
+ BIO_printf(bio_err, "RECONNECTING\n");
+ do_ssl_shutdown(user_data->con);
+ SSL_set_connect_state(user_data->con);
+ BIO_closesocket(SSL_get_fd(user_data->con));
+ return USER_DATA_PROCESS_RESTART;
+
+ case USER_COMMAND_RENEGOTIATE:
+ BIO_printf(bio_err, "RENEGOTIATING\n");
+ if (!SSL_renegotiate(user_data->con))
+ break;
+ return USER_DATA_PROCESS_CONTINUE;
+
+ case USER_COMMAND_KEY_UPDATE: {
+ int updatetype;
+
+ if (OPENSSL_strcasecmp(arg, "req") == 0)
+ updatetype = SSL_KEY_UPDATE_REQUESTED;
+ else if (OPENSSL_strcasecmp(arg, "noreq") == 0)
+ updatetype = SSL_KEY_UPDATE_NOT_REQUESTED;
+ else
+ return USER_DATA_PROCESS_BAD_ARGUMENT;
+ BIO_printf(bio_err, "KEYUPDATE\n");
+ if (!SSL_key_update(user_data->con, updatetype))
+ break;
+ return USER_DATA_PROCESS_CONTINUE;
+ }
+ default:
+ break;
+ }
+
+ BIO_printf(bio_err, "ERROR\n");
+ ERR_print_errors(bio_err);
+
+ return USER_DATA_PROCESS_SHUT;
+}
+
+static int user_data_process(struct user_data_st *user_data, size_t *len,
+ size_t *off)
+{
+ char *buf_start = user_data->buf + user_data->bufoff;
+ size_t outlen = user_data->buflen;
+
+ if (user_data->buflen == 0) {
+ *len = 0;
+ *off = 0;
+ return USER_DATA_PROCESS_NO_DATA;
+ }
+
+ if (user_data->mode == USER_DATA_MODE_BASIC) {
+ switch (buf_start[0]) {
+ case 'Q':
+ user_data->buflen = user_data->bufoff = *len = *off = 0;
+ return user_data_execute(user_data, USER_COMMAND_QUIT, NULL);
+
+ case 'C':
+ user_data->buflen = user_data->bufoff = *len = *off = 0;
+ return user_data_execute(user_data, USER_COMMAND_RECONNECT, NULL);
+
+ case 'R':
+ user_data->buflen = user_data->bufoff = *len = *off = 0;
+ return user_data_execute(user_data, USER_COMMAND_RENEGOTIATE, NULL);
+
+ case 'K':
+ case 'k':
+ user_data->buflen = user_data->bufoff = *len = *off = 0;
+ return user_data_execute(user_data, USER_COMMAND_KEY_UPDATE,
+ buf_start[0] == 'K' ? "req" : "noreq");
+ default:
+ break;
+ }
+ } else if (user_data->mode == USER_DATA_MODE_ADVANCED) {
+ char *cmd_start = buf_start;
+
+ cmd_start[outlen] = '\0';
+ do {
+ cmd_start = strstr(cmd_start, "{");
+ if (cmd_start == buf_start && *(cmd_start + 1) == '{') {
+ /* The "{" is escaped, so skip it */
+ cmd_start += 2;
+ buf_start++;
+ user_data->bufoff++;
+ user_data->buflen--;
+ outlen--;
+ continue;
+ }
+ } while(0);
+
+ if (cmd_start == buf_start) {
+ /* Command detected */
+ char *cmd_end = strstr(cmd_start, "}");
+ char *arg_start;
+ int cmd = -1, ret = USER_DATA_PROCESS_NO_DATA;
+ size_t oldoff;
+
+ if (cmd_end == NULL) {
+ /* Malformed command */
+ cmd_start[outlen - 1] = '\0';
+ BIO_printf(bio_err,
+ "ERROR PROCESSING COMMAND. REST OF LINE IGNORED: %s\n",
+ cmd_start);
+ user_data->buflen = user_data->bufoff = *len = *off = 0;
+ return USER_DATA_PROCESS_NO_DATA;
+ }
+ *cmd_end = '\0';
+ arg_start = strstr(cmd_start, ":");
+ if (arg_start != NULL) {
+ *arg_start = '\0';
+ arg_start++;
+ }
+ /* Skip over the { */
+ cmd_start++;
+ /*
+ * Now we have cmd_start pointing to a NUL terminated string for
+ * the command, and arg_start either being NULL or pointing to a
+ * NUL terminated string for the argument.
+ */
+ if (OPENSSL_strcasecmp(cmd_start, "help") == 0) {
+ cmd = USER_COMMAND_HELP;
+ } else if (OPENSSL_strcasecmp(cmd_start, "quit") == 0) {
+ cmd = USER_COMMAND_QUIT;
+ } else if (OPENSSL_strcasecmp(cmd_start, "reconnect") == 0) {
+ cmd = USER_COMMAND_RECONNECT;
+ } else if (SSL_version(user_data->con) == TLS1_3_VERSION) {
+ if (OPENSSL_strcasecmp(cmd_start, "keyup") == 0) {
+ cmd = USER_COMMAND_KEY_UPDATE;
+ if (arg_start == NULL)
+ arg_start = "req";
+ }
+ } else {
+ /* (D)TLSv1.2 or below */
+ if (OPENSSL_strcasecmp(cmd_start, "reneg") == 0)
+ cmd = USER_COMMAND_RENEGOTIATE;
+ }
+ if (cmd == -1) {
+ BIO_printf(bio_err, "UNRECOGNISED COMMAND (IGNORED): %s\n",
+ cmd_start);
+ } else {
+ ret = user_data_execute(user_data, cmd, arg_start);
+ if (ret == USER_DATA_PROCESS_BAD_ARGUMENT) {
+ BIO_printf(bio_err, "BAD ARGUMENT (COMMAND IGNORED): %s\n",
+ arg_start);
+ ret = USER_DATA_PROCESS_NO_DATA;
+ }
+ }
+ oldoff = user_data->bufoff;
+ user_data->bufoff = (cmd_end - user_data->buf) + 1;
+ user_data->buflen -= user_data->bufoff - oldoff;
+ if (user_data->buf + 1 == cmd_start
+ && user_data->buflen == 1
+ && user_data->buf[user_data->bufoff] == '\n') {
+ /*
+ * This command was the only thing on the whole line. We
+ * supress the final `\n`
+ */
+ user_data->bufoff = 0;
+ user_data->buflen = 0;
+ }
+ *len = *off = 0;
+ return ret;
+ } else if (cmd_start != NULL) {
+ /*
+ * There is a command on this line, but its not at the start. Output
+ * the start of the line, and we'll process the command next time
+ * we call this function
+ */
+ outlen = cmd_start - buf_start;
+ }
+ }
+
+#ifdef CHARSET_EBCDIC
+ ebcdic2ascii(buf_start, buf_start, outlen);
+#endif
+ *len = outlen;
+ *off = user_data->bufoff;
+ user_data->buflen -= outlen;
+ if (user_data->buflen == 0)
+ user_data->bufoff = 0;
+ else
+ user_data->bufoff += outlen;
+ return USER_DATA_PROCESS_CONTINUE;
+}
+
+static int user_data_has_data(struct user_data_st *user_data)
+{
+ return user_data->buflen > 0;
+}