/** * @return >0: the version that was negotiated * 0: no agreement on version was reached * -1: error */ int ServerNegotiateProtocol(const ConnectionInfo *conn_info) { int ret; char input[CF_SMALLBUF] = ""; /* Send "CFE_v%d cf-serverd version". */ char version_string[CF_MAXVARSIZE]; int len = snprintf(version_string, sizeof(version_string), "CFE_v%d cf-serverd %s\n", SERVER_PROTOCOL_VERSION, VERSION); ret = TLSSend(conn_info->ssl, version_string, len); if (ret != len) { Log(LOG_LEVEL_ERR, "Connection was hung up!"); return -1; } /* Receive CFE_v%d ... */ ret = TLSRecvLines(conn_info->ssl, input, sizeof(input)); if (ret <= 0) { Log(LOG_LEVEL_ERR, "Client closed connection early! He probably does not trust our key..."); return -1; } int version_received = -1; ret = sscanf(input, "CFE_v%d", &version_received); if (ret != 1) { Log(LOG_LEVEL_ERR, "Protocol version negotiation failed! Received: %s", input); return -1; } /* For now we support only one version, so just check they match... */ if (version_received == SERVER_PROTOCOL_VERSION) { char s[] = "OK\n"; TLSSend(conn_info->ssl, s, sizeof(s)-1); return version_received; } else { char s[] = "BAD unsupported protocol version\n"; TLSSend(conn_info->ssl, s, sizeof(s)-1); Log(LOG_LEVEL_ERR, "Client advertises unsupported protocol version: %d", version_received); return 0; } }
/** * @brief Return the client's username into the #username variable * @TODO More protocol identity: IDENTIFY USERNAME=xxx HOSTNAME=xxx CUSTOMNAME=xxx * @return 1 if IDENTITY command was parsed correctly. Identity fields * (only #username for now) have the respective string values, or they are * empty if field was not on IDENTITY line. * @return -1 in case of error. */ int ServerIdentifyClient(const ConnectionInfo *conn_info, char *username, size_t username_size) { char line[1024], word1[1024], word2[1024]; int line_pos = 0, chars_read = 0; int ret; /* Reset all identity variables, we'll set them later according to fields * on IDENTITY line. For now only "username" setting exists... */ username[0] = '\0'; ret = TLSRecvLines(conn_info->ssl, line, sizeof(line)); if (ret <= 0) { return -1; } /* Assert sscanf() is safe to use. */ assert(sizeof(word1)>=sizeof(line) && sizeof(word2)>=sizeof(line)); ret = sscanf(line, "IDENTITY %[^=]=%s %n", word1, word2, &chars_read); while (ret >= 2) { /* Found USERNAME identity setting */ if (strcmp(word1, "USERNAME") == 0) { if (strlen(word2) < username_size) { strcpy(username, word2); } else { Log(LOG_LEVEL_ERR, "IDENTITY parameter too long: %s=%s", word1, word2); return -1; } Log(LOG_LEVEL_VERBOSE, "Setting IDENTITY: %s=%s", word1, word2); } else { Log(LOG_LEVEL_VERBOSE, "Received unknown IDENTITY parameter: %s=%s", word1, word2); } line_pos += chars_read; ret = sscanf(&line[line_pos], " %[^=]=%s %n", word1, word2, &chars_read); } return 1; }
/** * 1. Receive "CFE_v%d" server hello * 2. Send two lines: one "CFE_v%d" with the protocol version we wish to have, * and another with id, e.g. "IDENTITY USERNAME=blah". * 3. Receive "OK WELCOME" * * @return > 0: success. #conn_info->type has been updated with the negotiated * protocol version. * 0: server denial * -1: error */ int TLSClientIdentificationDialog(ConnectionInfo *conn_info, const char *username) { char line[1024] = ""; int ret; /* Receive CFE_v%d ... That's the first thing the server sends. */ ret = TLSRecvLines(conn_info->ssl, line, sizeof(line)); ProtocolVersion wanted_version; if (conn_info->protocol == CF_PROTOCOL_UNDEFINED) { /* TODO parse CFE_v%d received and use that version if it's lower. */ wanted_version = CF_PROTOCOL_LATEST; } else { wanted_version = conn_info->protocol; } /* Send "CFE_v%d cf-agent version". */ char version_string[128]; int len = snprintf(version_string, sizeof(version_string), "CFE_v%d %s %s\n", wanted_version, "cf-agent", VERSION); /* TODO argv[0] */ ret = TLSSend(conn_info->ssl, version_string, len); if (ret != len) { Log(LOG_LEVEL_ERR, "Connection was hung up during identification!"); return -1; } strcpy(line, "IDENTITY"); size_t line_len = strlen(line); if (username != NULL) { ret = snprintf(&line[line_len], sizeof(line) - line_len, " USERNAME=%s", username); if (ret >= sizeof(line) - line_len) { Log(LOG_LEVEL_ERR, "Sending IDENTITY truncated: %s", line); return -1; } line_len += ret; } /* Overwrite the terminating '\0', we don't need it anyway. */ line[line_len] = '\n'; line_len++; ret = TLSSend(conn_info->ssl, line, line_len); if (ret == -1) { Log(LOG_LEVEL_ERR, "Connection was hung up during identification! (2)"); return -1; } /* Server might hang up here, after we sent identification! We * must get the "OK WELCOME" message for everything to be OK. */ static const char OK[] = "OK WELCOME"; size_t OK_len = sizeof(OK) - 1; ret = TLSRecvLines(conn_info->ssl, line, sizeof(line)); if (ret == -1) { Log(LOG_LEVEL_ERR, "Connection was hung up during identification! (3)"); return -1; } if (ret < OK_len || strncmp(line, OK, OK_len) != 0) { Log(LOG_LEVEL_ERR, "Peer did not accept our identity! Responded: %s", line); return 0; } /* Before it contained the protocol version we requested from the server, * now we put in the value that was negotiated. */ conn_info->protocol = wanted_version; return 1; }
/** * 1. Send "CFE_v%d" server hello. * 2. Receive two lines: One "CFE_v%d" with the protocol version the client * wishes to have, and one "IDENTITY USERNAME=blah ..." with identification * information for the client. * * @note For Identification dialog to end successfully, one "OK WELCOME" line * must be sent right after this function, after identity is verified. * * @TODO More protocol identity. E.g. * IDENTITY USERNAME=xxx HOSTNAME=xxx CUSTOMNAME=xxx * * @retval true if protocol version was successfully negotiated and IDENTITY * command was parsed correctly. Identity fields (only #username for * now) have the respective string values, or they are empty if field * was not on IDENTITY line. #conn_info->type has been updated with * the negotiated protocol version. * @retval false in case of error. */ static bool ServerIdentificationDialog(ConnectionInfo *conn_info, char *username, size_t username_size) { int ret; char input[1024] = ""; /* The only protocol version we support inside TLS, for now. */ const int SERVER_PROTOCOL_VERSION = CF_PROTOCOL_LATEST; /* Send "CFE_v%d cf-serverd version". */ char version_string[CF_MAXVARSIZE]; int len = snprintf(version_string, sizeof(version_string), "CFE_v%d cf-serverd %s\n", SERVER_PROTOCOL_VERSION, VERSION); ret = TLSSend(conn_info->ssl, version_string, len); if (ret != len) { Log(LOG_LEVEL_NOTICE, "Connection was hung up!"); return false; } /* Receive CFE_v%d ... \n IDENTITY USERNAME=... */ int input_len = TLSRecvLines(conn_info->ssl, input, sizeof(input)); if (input_len <= 0) { Log(LOG_LEVEL_NOTICE, "Client closed connection early! He probably does not trust our key..."); return false; } int version_received = -1; ret = sscanf(input, "CFE_v%d", &version_received); if (ret != 1) { Log(LOG_LEVEL_NOTICE, "Protocol version negotiation failed! Received: %s", input); return false; } /* For now we support only one version inside TLS. */ /* TODO value should not be hardcoded but compared to enum ProtocolVersion. */ if (version_received != SERVER_PROTOCOL_VERSION) { Log(LOG_LEVEL_NOTICE, "Client advertises disallowed protocol version: %d", version_received); return false; /* TODO send "BAD ..." ? */ } /* Did we receive 2nd line or do we need to receive again? */ const char id_line[] = "\nIDENTITY "; char *line2 = memmem(input, input_len, id_line, strlen(id_line)); if (line2 == NULL) { /* Wait for 2nd line to arrive. */ input_len = TLSRecvLines(conn_info->ssl, input, sizeof(input)); if (input_len <= 0) { Log(LOG_LEVEL_NOTICE, "Client closed connection during identification dialog!"); return false; } line2 = input; } else { line2++; /* skip '\n' */ } /***** Parse all IDENTITY fields from line2 *****/ char word1[1024], word2[1024]; int line2_pos = 0, chars_read = 0; /* Reset all identity variables, we'll set them according to fields * on IDENTITY line. For now only "username" setting exists... */ username[0] = '\0'; /* Assert sscanf() is safe to use. */ assert(sizeof(word1)>=sizeof(input) && sizeof(word2)>=sizeof(input)); ret = sscanf(line2, "IDENTITY %[^=]=%s%n", word1, word2, &chars_read); while (ret >= 2) { /* Found USERNAME identity setting */ if (strcmp(word1, "USERNAME") == 0) { if ((strlen(word2) < username_size) && (IsUserNameValid(word2) == true)) { strcpy(username, word2); } else { Log(LOG_LEVEL_NOTICE, "Received invalid IDENTITY: %s=%s", word1, word2); return false; } Log(LOG_LEVEL_VERBOSE, "Setting IDENTITY: %s=%s", word1, word2); } /* ... else if (strcmp()) for other acceptable IDENTITY parameters. */ else { Log(LOG_LEVEL_VERBOSE, "Received unknown IDENTITY parameter: %s=%s", word1, word2); } line2_pos += chars_read; ret = sscanf(&line2[line2_pos], " %[^=]=%s%n", word1, word2, &chars_read); } /* Version client and server agreed on. */ conn_info->type = version_received; return true; }