END_TEST START_TEST (test_trust_creates_known_hosts_file_and_directory) { char dir[PATH_MAX]; int r = check_tmpdir(dir, sizeof(dir), ".neo4j_XXXXXX"); ck_assert_int_eq(r, 0); const char *kh_path = "/sub/dir/kh"; size_t dirlen = strlen(dir); ck_assert_int_lt(dirlen + strlen(kh_path), PATH_MAX); char path[PATH_MAX]; memcpy(path, dir, dirlen); strncpy(path + dirlen, kh_path, PATH_MAX - dirlen); ck_assert_int_eq(neo4j_config_set_known_hosts_file(config, path), 0); struct callback_data data; neo4j_config_set_unverified_host_callback(config, trust_host, &data); r = neo4j_check_known_hosts("host.local", 6546, "aa7b6261e21d7b2950e044453543bce3840429e2", config, 0); ck_assert_str_eq(data.host, "host.local:6546"); ck_assert_str_eq(data.fingerprint, "aa7b6261e21d7b2950e044453543bce3840429e2"); ck_assert_int_eq(data.reason, NEO4J_HOST_VERIFICATION_UNRECOGNIZED); ck_assert_int_eq(r, 0); neo4j_config_set_unverified_host_callback(config, NULL, NULL); r = neo4j_check_known_hosts("host.local", 6546, "aa7b6261e21d7b2950e044453543bce3840429e2", config, 0); ck_assert_int_eq(r, 0); }
END_TEST START_TEST (test_mismatch_host_invokes_callback_and_trusts) { struct callback_data data; neo4j_config_set_unverified_host_callback(config, trust_host, &data); int r = neo4j_check_known_hosts("host.local", 6546, "ffffff61e21d7b2950e044453543bce3840429e2", config, 0); ck_assert_str_eq(data.host, "host.local:6546"); ck_assert_str_eq(data.fingerprint, "ffffff61e21d7b2950e044453543bce3840429e2"); ck_assert_int_eq(data.reason, NEO4J_HOST_VERIFICATION_MISMATCH); ck_assert_int_eq(r, 0); neo4j_config_set_unverified_host_callback(config, NULL, NULL); r = neo4j_check_known_hosts("host.local", 6546, "ffffff61e21d7b2950e044453543bce3840429e2", config, 0); ck_assert_int_eq(r, 0); r = neo4j_check_known_hosts("host.local", 6546, "aa7b6261e21d7b2950e044453543bce3840429e2", config, 0); ck_assert_int_eq(r, 1); }
END_TEST START_TEST (test_unfound_host_invokes_callback_and_accepts_once) { struct callback_data data; neo4j_config_set_unverified_host_callback(config, accept_host, &data); int r = neo4j_check_known_hosts("unknown.local", 6546, "aa7b6261e21d7b2950e044453543bce3840429e2", config, 0); ck_assert_str_eq(data.host, "unknown.local:6546"); ck_assert_str_eq(data.fingerprint, "aa7b6261e21d7b2950e044453543bce3840429e2"); ck_assert_int_eq(data.reason, NEO4J_HOST_VERIFICATION_UNRECOGNIZED); ck_assert_int_eq(r, 0); neo4j_config_set_unverified_host_callback(config, NULL, NULL); r = neo4j_check_known_hosts("unknown.local", 6546, "aa7b6261e21d7b2950e044453543bce3840429e2", config, 0); ck_assert_int_eq(r, 1); r = neo4j_check_known_hosts("host.local", 6546, "aa7b6261e21d7b2950e044453543bce3840429e2", config, 0); ck_assert_int_eq(r, 0); }
int main(int argc, char *argv[]) { FILE *tty = fopen(_PATH_TTY, "r+"); if (tty == NULL && errno != ENOENT) { perror("can't open " _PATH_TTY); exit(EXIT_FAILURE); } char prog_name[PATH_MAX]; if (neo4j_basename(argv[0], prog_name, sizeof(prog_name)) < 0) { perror("unexpected error"); exit(EXIT_FAILURE); } uint8_t log_level = NEO4J_LOG_WARN; struct neo4j_logger_provider *provider = NULL; struct io_handler io_handlers[NEO4J_MAX_IO_ARGS]; unsigned int nio_handlers = 0; neo4j_client_init(); int result = EXIT_FAILURE; if (shell_state_init(&state, prog_name, stdin, stdout, stderr, tty)) { neo4j_perror(stderr, errno, "unexpected error"); goto cleanup; } state.interactive = isatty(STDIN_FILENO); char histfile[PATH_MAX]; if (neo4j_dot_dir(histfile, sizeof(histfile), NEO4J_HISTORY_FILE) < 0) { neo4j_perror(state.err, (errno == ERANGE)? ENAMETOOLONG : errno, "unexpected error"); goto cleanup; } state.histfile = histfile; if (isatty(fileno(stderr))) { state.error_colorize = ansi_error_colorization; } int c; while ((c = getopt_long(argc, argv, shortopts, longopts, NULL)) >= 0) { switch (c) { case 'h': usage(state.out, prog_name); result = EXIT_SUCCESS; goto cleanup; case 'v': ++log_level; break; case HISTFILE_OPT: state.histfile = (optarg[0] != '\0')? optarg : NULL; break; case CA_FILE_OPT: if (neo4j_config_set_TLS_ca_file(state.config, optarg)) { neo4j_perror(state.err, errno, "unexpected error"); goto cleanup; } break; case CA_DIRECTORY_OPT: if (neo4j_config_set_TLS_ca_dir(state.config, optarg)) { neo4j_perror(state.err, errno, "unexpected error"); goto cleanup; } break; case COLORIZE_OPT: state.error_colorize = ansi_error_colorization; break; case NO_COLORIZE_OPT: state.error_colorize = no_error_colorization; break; case INSECURE_OPT: state.connect_flags |= NEO4J_INSECURE; break; case NON_INTERACTIVE_OPT: state.interactive = false; if (tty != NULL) { fclose(tty); tty = NULL; } break; case 'u': if (neo4j_config_set_username(state.config, optarg)) { neo4j_perror(state.err, errno, "unexpected error"); goto cleanup; } break; case 'p': if (neo4j_config_set_password(state.config, optarg)) { neo4j_perror(state.err, errno, "unexpected error"); goto cleanup; } break; case 'P': if (tty == NULL) { fprintf(state.err, "Cannot prompt for a password without a tty\n"); goto cleanup; } state.password_prompt = true; break; case KNOWN_HOSTS_OPT: if (neo4j_config_set_known_hosts_file(state.config, optarg)) { neo4j_perror(state.err, errno, "unexpected error"); goto cleanup; } break; case NO_KNOWN_HOSTS_OPT: if (neo4j_config_set_trust_known_hosts(state.config, false)) { neo4j_perror(state.err, errno, "unexpected error"); goto cleanup; } break; case NOHIST_OPT: state.histfile = NULL; break; case PIPELINE_MAX_OPT: { int arg = atoi(optarg); if (arg < 1) { fprintf(state.err, "Invalid pipeline-max '%s'\n", optarg); goto cleanup; } state.pipeline_max = arg; neo4j_config_set_max_pipelined_requests(state.config, arg * 2); } break; case 'i': state.interactive = false; if (add_io_handler(io_handlers, &nio_handlers, optarg, false, source)) { goto cleanup; } break; case SOURCE_MAX_DEPTH_OPT: { int arg = atoi(optarg); if (arg < 1) { fprintf(state.err, "Invalid source-max-depth '%s'\n", optarg); goto cleanup; } state.source_max_depth = arg; } break; case 'e': state.interactive = false; if (add_io_handler(io_handlers, &nio_handlers, optarg, false, eval)) { goto cleanup; } break; case 'o': if (add_io_handler(io_handlers, &nio_handlers, optarg, true, redirect_output)) { goto cleanup; } break; case VERSION_OPT: fprintf(state.out, "neo4j-client: %s\n", PACKAGE_VERSION); fprintf(state.out, "libneo4j-client: %s\n", libneo4j_client_version()); fprintf(state.out, "libcypher-parser: %s\n", libcypher_parser_version()); result = EXIT_SUCCESS; goto cleanup; default: usage(state.err, prog_name); goto cleanup; } } if (nio_handlers > 0 && io_handlers[nio_handlers-1].is_output) { fprintf(stderr, "--output/-o must be followed by --source/-i or --eval/-e\n"); goto cleanup; } argc -= optind; argv += optind; if (argc > 2) { usage(state.err, prog_name); goto cleanup; } uint8_t logger_flags = 0; if (log_level < NEO4J_LOG_DEBUG) { logger_flags = NEO4J_STD_LOGGER_NO_PREFIX; } provider = neo4j_std_logger_provider(state.err, log_level, logger_flags); if (provider == NULL) { neo4j_perror(state.err, errno, "unexpected error"); goto cleanup; } neo4j_config_set_logger_provider(state.config, provider); if (state.interactive) { state.password_prompt = true; } if (tty != NULL) { neo4j_config_set_unverified_host_callback(state.config, host_verification, &state); if (state.password_prompt) { neo4j_config_set_authentication_reattempt_callback(state.config, auth_reattempt, &state); } } if (argc >= 1 && db_connect(&state, cypher_input_position_zero, argv[0], (argc > 1)? argv[1] : NULL)) { goto cleanup; } // remove any password from the config if (neo4j_config_set_password(state.config, NULL)) { // can't fail } if (signal(SIGINT, interrupt_handler) == SIG_ERR) { perror("unexpected error"); goto cleanup; } if (state.interactive) { state.render = render_results_table; state.render_flags = NEO4J_RENDER_SHOW_NULLS; state.infile = "<interactive>"; state.source_depth = 1; if (interact(&state)) { goto cleanup; } } else if (nio_handlers > 0) { state.render = render_results_csv; for (unsigned int i = 0; i < nio_handlers; ++i) { if (io_handlers[i].handle(&state, io_handlers[i].arg)) { goto cleanup; } } } else { state.render = render_results_csv; state.infile = "<stdin>"; state.source_depth = 1; if (batch(&state, state.in)) { goto cleanup; } } result = EXIT_SUCCESS; cleanup: shell_state_destroy(&state); if (provider != NULL) { neo4j_std_logger_provider_free(provider); } for (unsigned int i = 0; i < nio_handlers; ++i) { free(io_handlers[i].arg); } if (tty != NULL) { fclose(tty); } neo4j_client_cleanup(); return result; }