Skip to content

Commit

Permalink
core: Improve authentication compatibility.
Browse files Browse the repository at this point in the history
* base64.c: Don't reject base64 encodings that aren't padded.
* auth.c: Change possible auth token char for compatibility.
* auth.c: Allow temporary tokens to not expire automatically.
* auth.c: Fix case-sensitive username comparison for temp tokens.
* pty.c: Fix misleading log message for CTRL keys.
  • Loading branch information
InterLinked1 committed Mar 9, 2024
1 parent 8387dc3 commit 48f371b
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 20 deletions.
73 changes: 60 additions & 13 deletions bbs/auth.c
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,8 @@ static RWLIST_HEAD_STATIC(cached_logins, cached_login);
struct pw_auth_token {
char *username;
time_t added;
char token[48];
time_t expires;
char token[TEMP_PASSWORD_TOKEN_BUFLEN];
RWLIST_ENTRY(pw_auth_token) entry;
};

Expand All @@ -305,7 +306,9 @@ static void cached_login_destroy(struct cached_login *l)

static void auth_token_destory(struct pw_auth_token *t)
{
free_if(t->username);
bbs_debug(3, "Purging temporary login token for %s\n", t->username);
bbs_memzero(t->token, sizeof(t->token)); /* This token isn't really sensitive, since it's no longer valid, but scrub it anyways */
free(t->username);
free(t);
}

Expand All @@ -315,10 +318,13 @@ void login_cache_cleanup(void)
RWLIST_WRLOCK_REMOVE_ALL(&auth_tokens, entry, auth_token_destory);
}

#define POSSIBLE_AUTH_TOKEN_CHAR '\\'
#define MAX_TOKEN_AGE 15
/* Changed from \\, since libetpan will double the leading \,
* butchering the password, making such tokens impossible
* to use with applications using that library. */
#define POSSIBLE_AUTH_TOKEN_CHAR '}'
#define DEFAULT_MAX_TOKEN_AGE 15

int bbs_user_temp_authorization_token(struct bbs_user *user, char *buf, size_t len)
static int create_temp_authorization_token(struct bbs_user *user, char *buf, size_t len, time_t expires)
{
struct pw_auth_token *t;

Expand Down Expand Up @@ -364,43 +370,84 @@ int bbs_user_temp_authorization_token(struct bbs_user *user, char *buf, size_t l
return -1;
}
t->token[0] = POSSIBLE_AUTH_TOKEN_CHAR; /* Use an uncommon character to indicate possible token */
if (bbs_rand_alnum(t->token + 1, sizeof(t->token) - 1)) {
if (bbs_rand_alnum(t->token + 1, sizeof(t->token) - 2)) {
RWLIST_UNLOCK(&auth_tokens);
free(t);
return -1;
}
t->token[TEMP_PASSWORD_TOKEN_BUFLEN - 1] = '\0';
t->username = strdup(bbs_username(user));
if (ALLOC_FAILURE(t->username)) {
RWLIST_UNLOCK(&auth_tokens);
free(t);
return -1;
}
t->added = time(NULL);
t->expires = expires;
RWLIST_INSERT_TAIL(&auth_tokens, t, entry);
RWLIST_UNLOCK(&auth_tokens);
safe_strncpy(buf, t->token, len);
bbs_assert(!strcmp(buf, t->token)); /* Provided buffer must be large enough */
bbs_verb(5, "Created %s login token for %s\n", expires ? "temporary" : "semi-permanent", bbs_username(user));
return 0;
}

int bbs_user_temp_authorization_token(struct bbs_user *user, char *buf, size_t len)
{
return create_temp_authorization_token(user, buf, len, time(NULL) + DEFAULT_MAX_TOKEN_AGE);
}

int bbs_user_semiperm_authorization_token(struct bbs_user *user, char *buf, size_t len)
{
return create_temp_authorization_token(user, buf, len, 0);
}

int bbs_user_semiperm_authorization_token_purge(const char *buf)
{
int res = -1;
struct pw_auth_token *t;

RWLIST_WRLOCK(&auth_tokens);
RWLIST_TRAVERSE_SAFE_BEGIN(&auth_tokens, t, entry) {
if (!strcmp(t->token, buf)) {
RWLIST_REMOVE_CURRENT(entry);
auth_token_destory(t);
res = 0;
break;
}
}
RWLIST_TRAVERSE_SAFE_END;
RWLIST_UNLOCK(&auth_tokens);
return res;
}

/*! \retval 1 if valid match, 0 if not */
static int valid_temp_token(const char *username, const char *password)
{
struct pw_auth_token *t;
time_t now, cutoff;
time_t now;
int total = 0;
int match = 0;

now = time(NULL);
cutoff = now - MAX_TOKEN_AGE;

/* Purge any stale tokens. */
RWLIST_WRLOCK(&auth_tokens);
RWLIST_TRAVERSE_SAFE_BEGIN(&auth_tokens, t, entry) {
if (t->added < cutoff) {
if (t->expires && t->expires < now) { /* If it has an expiration time and it's already past, purge it */
RWLIST_REMOVE_CURRENT(entry);
auth_token_destory(t);
} else {
if (!strcmp(username, t->username) && !strcmp(password, t->token)) {
if (!strcasecmp(username, t->username) && !strcmp(password, t->token)) {
match = 1;
}
RWLIST_REMOVE_CURRENT(entry);
auth_token_destory(t); /* What good is a one-time token if we reuse it? */
if (t->expires) {
RWLIST_REMOVE_CURRENT(entry);
auth_token_destory(t); /* What good is a one-time token if we reuse it? */
}
/* Don't break, we still want to purge any tokens that may be stale. */
}
total++;
}
RWLIST_TRAVERSE_SAFE_END;
RWLIST_UNLOCK(&auth_tokens);
Expand Down Expand Up @@ -451,7 +498,7 @@ static int login_is_cached(struct bbs_node *node, const char *username, const ch
continue;
}
if (strcmp(l->ip, node->ip)) { /* Cached logins only good from same IP */
bbs_debug(3, "Cached login denied (different IP address)\n");
bbs_debug(3, "Cached login denied (different IP addresses: %s != %s)\n", l->ip, node->ip);
continue;
}
if (strcmp(l->hash, hash)) {
Expand Down
3 changes: 1 addition & 2 deletions bbs/base64.c
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,7 @@ unsigned char *base64_decode(const unsigned char *restrict data, int input_lengt
unsigned char *decoded_data;

if (input_length % 4 != 0) {
bbs_warning("Input length %d is invalid\n", input_length);
return NULL;
bbs_debug(4, "Input has length %d (not 4-byte padded)\n", input_length);
}

output_length = input_length / 4 * 3;
Expand Down
7 changes: 5 additions & 2 deletions bbs/pty.c
Original file line number Diff line number Diff line change
Expand Up @@ -692,8 +692,11 @@ void *pty_master(void *varg)
bbs_debug(3, "Received ^%c, cancelling pending output\n", 'A' - 1 + *buf);
break;
default:
/* XXX In the future, could be used by the BBS to do certain things too */
bbs_debug(3, "Ignoring ^%c and not forwarding it\n", 'A' - 1 + *buf);
/* Pass these through to the BBS or currently executing program */
bbs_debug(3, "Received ^%c, forwarding it\n", 'A' - 1 + *buf);
/* These actually *do* get forwarded to an executing program,
* not entirely sure from the logic here how that is, but that is
* actually what we want, so it works out... */
}
continue;
}
Expand Down
24 changes: 21 additions & 3 deletions include/auth.h
Original file line number Diff line number Diff line change
Expand Up @@ -139,15 +139,33 @@ struct bbs_user **bbs_user_list(void);
/*! \brief Clean up any cached logins */
void login_cache_cleanup(void);

#define TEMP_PASSWORD_TOKEN_BUFLEN 48

/*!
* \brief Generate a temporary token that can be used to authenticate a user in lieu of a password
* \brief Generate a one-time temporary token that can be used to authenticate a user in lieu of a password
* \param user
* \param[out] buf
* \param len Must be at least 48.
* \param[out] buf. Temporary one-time token, which will be valid for up to 15 seconds.
* \param len Must be at least TEMP_PASSWORD_TOKEN_BUFLEN.
* \retval 0 on success, -1 on failure
*/
int bbs_user_temp_authorization_token(struct bbs_user *user, char *buf, size_t len);

/*!
* \brief Generate a temporary token that can be used to authenticate a user in lieu of a password
* \param user
* \param[out] buf. Temporary token, which will be valid as long as the BBS is running, until bbs_user_semiperm_authorization_token_purge is called.
* \param len Must be at least TEMP_PASSWORD_TOKEN_BUFLEN.
* \retval 0 on success, -1 on failure
*/
int bbs_user_semiperm_authorization_token(struct bbs_user *user, char *buf, size_t len);

/*!
* \brief Purge a temporary token previously created using bbs_user_semiperm_authorization_token
* \param buf Temporary token
* \retval 0 on success, -1 on failure
*/
int bbs_user_semiperm_authorization_token_purge(const char *buf);

/*!
* \brief Attempt to authenticate a user
* \param user
Expand Down

0 comments on commit 48f371b

Please sign in to comment.