Skip to content

Commit 10328cc

Browse files
committed
modules, tests: Improve environment compatibility and portability.
* Check that snake oil cert exists before starting test, and if it doesn't, abort early. (Debian users can run 'apt intall ssl-cert' if this cert pair is missing.) * net_telnet: Fix bounds check for TELOPT_TSPEED prior to memmove. * Get rid of warning on STDERR when running MySQL-dependent tests. * mod_auth_mysql: Use char buffer instead of single char for gender, so that we can pass NULL directly. * mod_mysql: Fix indeterminate variadic function behavior.
1 parent 683de53 commit 10328cc

File tree

12 files changed

+57
-39
lines changed

12 files changed

+57
-39
lines changed

bbs/crypt.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,6 @@ int bbs_password_verify(const char *password, const char *salt, const char *hash
284284
return res;
285285
}
286286

287-
#define BCRYPT_FULL_HASH_LEN 60
288287
int bbs_password_verify_bcrypt(const char *password, const char *combined)
289288
{
290289
int res;

include/crypt.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
*
1414
*/
1515

16+
#define BCRYPT_FULL_HASH_LEN 60
17+
1618
/*!
1719
* \brief Generate a cryptographically secure random string containing only alphanumeric characters
1820
* \param[out] buf

include/mod_mysql.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,6 @@ int sql_prepare(MYSQL_STMT *stmt, const char *fmt, const char *query);
3838
/*! \brief Automatically adjust the format string based on whether any arguments are NULL */
3939
int sql_fmt_autonull(char *fmt, ...);
4040

41-
int sql_bind_param_single(va_list ap, int i, const char *cur, MYSQL_BIND bind[], unsigned long int lengths[], int bind_ints[], long long bind_longs[], char *bind_strings[], MYSQL_TIME bind_dates[], my_bool bind_null[]);
42-
4341
#define sql_prep_bind_exec(stmt, query, fmt, ...) __sql_prep_bind_exec(stmt, query, __FILE__, __LINE__, __func__, fmt, ## __VA_ARGS__)
4442

4543
int __sql_prep_bind_exec(MYSQL_STMT *stmt, const char *query, const char *file, int line, const char *func, const char *fmt, ...);

io/io_tls.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -690,7 +690,7 @@ static int ssl_load_config(int reload)
690690

691691
if (!cfg) {
692692
if (!reload) {
693-
bbs_warning("SSL/TLS will be unavailable since tls.conf is missing\n");
693+
bbs_warning("SSL/TLS server will be unavailable since tls.conf is missing\n");
694694
}
695695
return -1; /* Impossible to do TLS server stuff if we don't know what the server key/cert are */
696696
}

modules/mod_auth_mysql.c

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -291,14 +291,13 @@ static int invalid_birthday(struct tm *tm)
291291
}
292292

293293
static int make_user(const char *username, const char *password, const char *fullname, const char *email, const char *phone,
294-
const char *address, const char *city, const char *state, const char *zip, const char *dob, char gender)
294+
const char *address, const char *city, const char *state, const char *zip, const char *dob, const char *gender)
295295
{
296-
char pw_hash[61];
296+
char pw_hash[BCRYPT_FULL_HASH_LEN + 1];
297297
MYSQL *mysql = NULL;
298298
MYSQL_STMT *stmt;
299299
int res = -1;
300300
char sql[184];
301-
char genderbuf[2] = { gender, '\0' }; /* We can't pass a char directly into sql_prep_bind_exec, we must pass a char* */
302301
struct tm birthday;
303302
char types[16] = "sssssssssts";
304303

@@ -323,8 +322,8 @@ static int make_user(const char *username, const char *password, const char *ful
323322
}
324323

325324
/* Bind parameters and execute */
326-
sql_fmt_autonull(types, username, pw_hash, fullname, email, phone, address, city, state, zip, dob ? &birthday : NULL, genderbuf);
327-
if (sql_prep_bind_exec(stmt, sql, types, username, pw_hash, fullname, email, phone, address, city, state, zip, dob ? &birthday : NULL, genderbuf)) {
325+
sql_fmt_autonull(types, username, pw_hash, fullname, email, phone, address, city, state, zip, dob ? &birthday : NULL, gender);
326+
if (sql_prep_bind_exec(stmt, sql, types, username, pw_hash, fullname, email, phone, address, city, state, zip, dob ? &birthday : NULL, gender)) {
328327
goto cleanup;
329328
}
330329
res = 0;
@@ -341,9 +340,8 @@ static int user_register(struct bbs_node *node)
341340
{
342341
/* bcrypt caps password lengths at 72, so that's where that came from */
343342
char fullname[64], username[64], password[72], password2[72];
344-
char email[64], phone[16] = "", address[64] = "", city[64], state[32], zip[10] = "", dob[11] = "";
343+
char email[64], phone[16] = "", address[64] = "", city[64], state[32], zip[10] = "", dob[11] = "", gender[2] = "";
345344
char how_heard[256] = "";
346-
int gender = 0;
347345
int res;
348346
#define MAX_REG_ATTEMPTS 6
349347
int tries = MAX_REG_ATTEMPTS;
@@ -458,12 +456,13 @@ static int user_register(struct bbs_node *node)
458456
bbs_node_unbuffer(node); /* We need to be unbuffered for tread */
459457
if (register_gender) {
460458
for (; tries > 0; tries--) { /* Retries here count less than retries of the main loop */
459+
int c;
461460
NEG_RETURN(bbs_node_writef(node, "%-*s", REG_QLEN, REG_FMT "\rGender (MFX): ")); /* Erase existing line in case we're retrying */
462-
gender = bbs_node_tread(node, MIN_MS(1));
463-
NONPOS_RETURN(gender);
464-
gender = (char) tolower(gender);
465-
if (gender == 'm' || gender == 'f' || gender == 'x') {
466-
NEG_RETURN(bbs_node_writef(node, "%c\n", gender)); /* Print response + newline */
461+
c = bbs_node_tread(node, MIN_MS(1));
462+
NONPOS_RETURN(c);
463+
gender[0] = (char) tolower(c);
464+
if (gender[0] == 'm' || gender[0] == 'f' || gender[0] == 'x') {
465+
NEG_RETURN(bbs_node_writef(node, "%s\n", gender)); /* Print response + newline */
467466
break; /* Got a valid response */
468467
}
469468
/* Invalid, try again */
@@ -497,8 +496,9 @@ static int user_register(struct bbs_node *node)
497496
bbs_auth("New registration attempt for user %s from IP %s\n", username, node->ip);
498497

499498
/* How heard is logged but not passed to make_user */
500-
bbs_debug(1, "New registration attempt: name = %s, username = %s, email = %s, phone = %s, address = %s, city = %s, state = %s, zip = %s, dob = %s, gender = %c, how heard = %s\n",
501-
fullname, username, email, S_IF(phone), S_IF(address), city, state, S_IF(zip), S_IF(dob), gender ? gender : ' ', S_IF(how_heard));
499+
bbs_debug(1, "New registration attempt: "
500+
"name = '%s', username = '%s', email = '%s', phone = '%s', address = '%s', city = '%s', state = '%s', zip = '%s', dob = '%s', gender = '%s', how heard = '%s'\n",
501+
fullname, username, email, S_IF(phone), S_IF(address), city, state, S_IF(zip), S_IF(dob), S_IF(gender), S_IF(how_heard));
502502

503503
#define NULL_IFEMPTY(s) (!*s ? NULL : s)
504504

@@ -538,7 +538,7 @@ static int user_register(struct bbs_node *node)
538538
}
539539

540540
/* Actually create the user */
541-
res = make_user(username, password, fullname, email, NULL_IFEMPTY(phone), NULL_IFEMPTY(address), city, state, NULL_IFEMPTY(zip), NULL_IFEMPTY(dob), (char) gender);
541+
res = make_user(username, password, fullname, email, NULL_IFEMPTY(phone), NULL_IFEMPTY(address), city, state, NULL_IFEMPTY(zip), NULL_IFEMPTY(dob), NULL_IFEMPTY(gender));
542542

543543
if (res) {
544544
NEG_RETURN(bbs_node_writef(node, "%s%s%s\n", COLOR(COLOR_FAILURE), "Your registration was rejected.", COLOR_RESET));
@@ -547,6 +547,7 @@ static int user_register(struct bbs_node *node)
547547
}
548548
/* If user registration actually succeeded, then this function call will succeed. If not, it won't. */
549549
res = bbs_authenticate(node, username, password);
550+
bbs_memzero(password, sizeof(password)); /* No longer need the password */
550551
if (res) {
551552
/* Something went wrong */
552553
NEG_RETURN(bbs_node_writef(node, "%s%s%s\n", COLOR(COLOR_FAILURE), "An error occured in processing your registration.\n", COLOR_RESET));

modules/mod_mysql.c

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ int sql_fmt_autonull(char *fmt, ...)
208208
return 0;
209209
}
210210

211-
int sql_bind_param_single(va_list ap, int i, const char *cur, MYSQL_BIND bind[], unsigned long int lengths[], int bind_ints[], long long bind_longs[], char *bind_strings[], MYSQL_TIME bind_dates[], my_bool bind_null[])
211+
static int sql_bind_param_single(va_list *ap_ptr, int i, const char *cur, MYSQL_BIND bind[], unsigned long int lengths[], int bind_ints[], long long bind_longs[], char *bind_strings[], MYSQL_TIME bind_dates[], my_bool bind_null[])
212212
{
213213
struct tm *tm;
214214
char format_char = (char) tolower(*cur);
@@ -219,39 +219,35 @@ int sql_bind_param_single(va_list ap, int i, const char *cur, MYSQL_BIND bind[],
219219
bbs_assert(0); /* We'd crash anyways below. */
220220
}
221221

222-
#if 0
223-
bbs_debug(10, "Executing fmt char: %c\n", format_char);
224-
#endif
225-
226222
/* Ref: https://dev.mysql.com/doc/c-api/5.7/en/c-api-prepared-statement-data-structures.html */
227223
/* See for C <-> MySQL/MariaDB types: https://dev.mysql.com/doc/c-api/5.7/en/c-api-prepared-statement-type-codes.html */
228224
switch (format_char) {
229225
case 'i': /* Integer */
230-
bind_ints[i] = va_arg(ap, int);
226+
bind_ints[i] = va_arg(*ap_ptr, int);
231227
/* This is a number type, so there is no need to specify buffer_length */
232228
bind[i].buffer_type = MYSQL_TYPE_LONG; /* Yes, this is correct */
233229
bind[i].buffer = (char *) &bind_ints[i];
234230
bind[i].is_null = &bind_null[i];
235231
bind[i].length = 0;
236232
break;
237233
case 'l': /* Long int */
238-
bind_longs[i] = va_arg(ap, long long);
234+
bind_longs[i] = va_arg(*ap_ptr, long long);
239235
/* This is a number type, so there is no need to specify buffer_length */
240236
bind[i].buffer_type = MYSQL_TYPE_LONGLONG;
241237
bind[i].buffer = (char *) &bind_longs[i];
242238
bind[i].is_null = &bind_null[i];
243239
bind[i].length = 0;
244240
break;
245241
case 'd': /* Double */
246-
bind_ints[i] = va_arg(ap, int);
242+
bind_ints[i] = va_arg(*ap_ptr, int);
247243
/* This is a number type, so there is no need to specify buffer_length */
248244
bind[i].buffer_type = MYSQL_TYPE_LONG;
249245
bind[i].buffer = (char *) &bind_ints[i];
250246
bind[i].is_null = &bind_null[i];
251247
bind[i].length = 0;
252248
break;
253249
case 's': /* String */
254-
bind_strings[i] = va_arg(ap, char *);
250+
bind_strings[i] = va_arg(*ap_ptr, char *);
255251
lengths[i] = strlen(S_IF(bind_strings[i]));
256252
if (!bind_strings[i] && !bind_null[i]) {
257253
bbs_warning("String at index %d is NULL, but not specified? (Format char: %c)\n", i, *cur);
@@ -263,7 +259,7 @@ int sql_bind_param_single(va_list ap, int i, const char *cur, MYSQL_BIND bind[],
263259
bind[i].length = &lengths[i]; /* For strings, we actually do need the length. We'll be able to find it in the array. */
264260
break;
265261
case 't': /* Date */
266-
tm = va_arg(ap, struct tm *);
262+
tm = va_arg(*ap_ptr, struct tm *);
267263
if (!bind_null[i]) {
268264
bind_dates[i].year = (unsigned int) TM_YEAR(tm->tm_year);
269265
bind_dates[i].month = (unsigned int) TM_MONTH(tm->tm_mon);
@@ -313,7 +309,10 @@ int __sql_prep_bind_exec(MYSQL_STMT *stmt, const char *query, const char *file,
313309

314310
va_start(ap, fmt);
315311
for (i = 0; i < num_args; i++, cur++) { /* Bind the parameters themselves for this round */
316-
if (sql_bind_param_single(ap, (int) i, cur, bind, lengths, bind_ints, bind_longs, bind_strings, bind_dates, bind_null)) {
312+
/* On some platforms, we can technically pass ap by value,
313+
* but its value upon returning is technically indeterminate (we're supposed to call va_end after that).
314+
* Since we want to iterate through, we need to explicitly pass the pointer instead. */
315+
if (sql_bind_param_single(&ap, (int) i, cur, bind, lengths, bind_ints, bind_longs, bind_strings, bind_dates, bind_null)) {
317316
va_end(ap);
318317
return -1;
319318
}

nets/net_telnet.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -770,7 +770,7 @@ static int __telnet_process_command(struct bbs_node *node, struct telnet_setting
770770
}
771771
break;
772772
case TELOPT_TSPEED:
773-
if (buf[3] == TELQUAL_IS && res >= 3) {
773+
if (buf[3] == TELQUAL_IS && res >= 6) {
774774
bbs_debug(3, "Terminal speed is %.*s\n", (int) res - 6, buf + 4); /* First 4 bytes are command, and last two are IAC SE */
775775
if (res - 6 < (int) len - 1) {
776776
memmove(buf, buf + 4, (size_t) res - 6);

tests/test.c

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,7 @@ static int mysql_spawn(void)
591591
"--datadir=" MYSQL_DATA_DIR,
592592
"--pid-file=" MYSQL_PID_FILE,
593593
"--general_log_file=" MYSQL_LOG_FILE,
594+
"--expire-logs-days=0", /* Prevents warning about needing --log-bin to make --expire-log-days or --bin-log-expire-log-seconds work */
594595
"--log-error=" MYSQL_ERROR_LOG,
595596
"--socket=" MYSQL_SOCKET,
596597
"--port=" XSTR(MYSQL_PORT),
@@ -606,8 +607,14 @@ static int mysql_spawn(void)
606607
TEST_MKDIR(MYSQL_DATA_DIR);
607608

608609
/* The mysql user exist from installing MySQL as a service */
610+
errno = 0;
609611
pwd = getpwnam("mysql"); /* Not thread-safe, but we don't need that */
610612
if (!pwd) {
613+
if (errno == 0) {
614+
/* errno can be 0 for certain errors, this happens when the username was not found */
615+
bbs_error("User '%s' does not exist, can't run MySQL-dependent test\n", "mysql");
616+
return -1;
617+
}
611618
bbs_error("getpwnam failed: %s\n", strerror(errno));
612619
return -1;
613620
}
@@ -622,7 +629,7 @@ static int mysql_spawn(void)
622629
}
623630

624631
/* First, initialize the temporary DB: */
625-
if (system("mysql_install_db --skip-test-db --user=mysql --ldata=" MYSQL_DATA_DIR " > /dev/null")) { /* Yuck... but why reinvent the wheel? */
632+
if (system("mysql_install_db --expire-logs-days=0 --skip-test-db --user=mysql --ldata=" MYSQL_DATA_DIR " > /dev/null")) { /* Yuck... but why reinvent the wheel? */
626633
bbs_error("Failed to initialize database\n");
627634
return -1;
628635
}
@@ -1058,7 +1065,7 @@ static int run_test(const char *filename, int multiple)
10581065
res = -1;
10591066
} else {
10601067
struct timeval start, end;
1061-
int64_t sec_dif, usec_dif, tot_dif;
1068+
int64_t sec_dif, usec_dif, tot_dif = 0;
10621069
int core_before = 0;
10631070
pid_t childpid = -1;
10641071
if (eaccess(TEST_ROOT_DIR, R_OK) && mkdir(TEST_ROOT_DIR, 0700)) {
@@ -1131,7 +1138,7 @@ static int run_test(const char *filename, int multiple)
11311138
if (multiple && !test_autorun) {
11321139
bbs_debug(2, "Skipping test %s\n", testmod->name);
11331140
total_fail--;
1134-
goto cleanup;
1141+
goto done;
11351142
}
11361143
if (option_use_mysql && !mysql_child && mysql_spawn()) {
11371144
res = -1;
@@ -1214,16 +1221,17 @@ static int run_test(const char *filename, int multiple)
12141221
bbs_debug(1, "%d soft assertion%s failed\n", soft_assertions_failed, ESS(soft_assertions_failed));
12151222
res = -1;
12161223
}
1224+
cleanup:
12171225
if (res) {
1218-
fprintf(stderr, "== Test %sFAILED%s: %5lums %-20s %s\n", COLOR(COLOR_FAILURE), COLOR_RESET, tot_dif, testmod->name, testmod->description);
1226+
fprintf(stderr, "== Test %sFAILED%s: %5lums %-25s %s\n", COLOR(COLOR_FAILURE), COLOR_RESET, tot_dif, testmod->name, testmod->description);
12191227
} else {
1220-
fprintf(stderr, "== Test %sPASSED%s: %5lums %-20s %s\n", COLOR(COLOR_SUCCESS), COLOR_RESET, tot_dif, testmod->name, testmod->description);
1228+
fprintf(stderr, "== Test %sPASSED%s: %5lums %-25s %s\n", COLOR(COLOR_SUCCESS), COLOR_RESET, tot_dif, testmod->name, testmod->description);
12211229
total_pass++;
12221230
total_fail--; /* We didn't actually fail so undo that bit */
12231231
}
12241232
}
12251233

1226-
cleanup:
1234+
done:
12271235
dlclose(lib);
12281236
if (testmod) {
12291237
bbs_warning("Test module still registered?\n");

tests/test.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ struct test_module *TEST_MODULE_SELF_SYM(void);
7575
#define TEST_ADD_CONFIG_INTO_DIR(filename, dir) TEST_COPY_CONFIG(TEST_CONFIGS_SRC_DIR "/" filename, dir)
7676
#define TEST_ADD_CONFIG(filename) TEST_ADD_CONFIG_NAME(filename, filename)
7777
#define TEST_ADD_SUBCONFIG(subdir, filename) TEST_ADD_CONFIG_NAME(subdir "/" filename, filename)
78+
#define TEST_REQUIRE_FILE(file) if (eaccess(file, R_OK)) { bbs_error("Aborting test, can't access required file '%s': %s\n", file, strerror(errno)); return -1; }
7879

7980
#define TEST_HOSTNAME "bbs.example.com"
8081

tests/test_ftps.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,12 @@ static int pre(void)
3131
test_preload_module("io_tls.so");
3232
test_load_module("net_ftp.so");
3333

34-
TEST_ADD_CONFIG("transfers.conf");
34+
/* Not all platforms have it and we don't create it.
35+
* On Debian, can run 'apt-get install ssl-cert' if this cert pair is missing. */
36+
TEST_REQUIRE_FILE("/etc/ssl/private/ssl-cert-snakeoil.key");
37+
3538
TEST_ADD_CONFIG("tls.conf");
39+
TEST_ADD_CONFIG("transfers.conf");
3640
TEST_ADD_SUBCONFIG("tls", "net_ftp.conf");
3741

3842
TEST_RESET_MKDIR(TEST_TRANSFER_DIR);

0 commit comments

Comments
 (0)