Skip to content

Commit

Permalink
feat: Use keyring
Browse files Browse the repository at this point in the history
  • Loading branch information
ajami1331 committed Jan 4, 2025
1 parent 72567fe commit 839092e
Show file tree
Hide file tree
Showing 7 changed files with 302 additions and 17 deletions.
12 changes: 9 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,14 @@ if(NOT curl_FOUND)
endif()
endif()

include_directories(${CMAKE_SOURCE_DIR}/lib)
file(GLOB_RECURSE SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} src/* lib/*)
include_directories(${CMAKE_SOURCE_DIR}/lib/include)
file(GLOB_RECURSE SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} src/* lib/**/*.h lib/*.c)
add_executable(${PROJECT_NAME} ${SOURCES})

target_link_libraries(${PROJECT_NAME} CURL::libcurl_static)
target_link_libraries(${PROJECT_NAME} CURL::libcurl_static)

if (LINUX)
find_package(PkgConfig REQUIRED)
pkg_check_modules(deps REQUIRED IMPORTED_TARGET glib-2.0 libsecret-1)
target_link_libraries(${PROJECT_NAME} PkgConfig::deps)
endif()
File renamed without changes.
207 changes: 207 additions & 0 deletions lib/include/sr_keychain.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@

/**
* Copyright (c) 2021 Simon Rodriguez
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "sr_keychain.h"

#ifndef SR_KEYCHAIN_MALLOC
# define SR_KEYCHAIN_MALLOC(S) (malloc(S))
#endif

#ifndef SR_KEYCHAIN_FREE
# define SR_KEYCHAIN_FREE(S) (free(S))
#endif

#ifndef SR_KEYCHAIN_MEMCPY
# define SR_KEYCHAIN_MEMCPY(D, S, T) (memcpy(D, S, T))
#endif

#ifndef SR_KEYCHAIN_LINUX_SCHEME_NAME
# define SR_KEYCHAIN_LINUX_SCHEME_NAME "com.sr.sr_keychain"
#endif

#ifdef _WIN32
# include <windows.h>
#else
# include <termios.h>
#endif

#ifdef __APPLE__
# include <Security/Security.h>
#endif

#ifdef _WIN32
# include <wincred.h>
# include <stdio.h>

wchar_t* _sr_keychain_get_complete_url(const char* domain, const char* user) {
const size_t length = strlen(user) + 1 + strlen(domain);
char* domainAndUser = (char*)SR_KEYCHAIN_MALLOC(sizeof(char) * (length + 1));
if(domainAndUser == NULL) {
return NULL;
}
snprintf(domainAndUser, length, "%s@%s", user, domain);

const int sizeWide = MultiByteToWideChar(CP_UTF8, 0, domainAndUser, -1, NULL, 0);
wchar_t* targetName = (wchar_t*)SR_KEYCHAIN_MALLOC(sizeWide * sizeof(wchar_t));
MultiByteToWideChar(CP_UTF8, 0, domainAndUser, -1, targetName, sizeWide);
SR_KEYCHAIN_FREE(domainAndUser);
return targetName;
}
#endif

#ifdef __linux__
# include <libsecret/secret.h>

// Declare secret scheme.
const SecretSchema* _sr_keychain_get_schema() {
static SecretSchema schema;
schema.name = SR_KEYCHAIN_LINUX_SCHEME_NAME;
schema.flags = SECRET_SCHEMA_NONE;
schema.attributes[0].name = "domain";
schema.attributes[0].type = SECRET_SCHEMA_ATTRIBUTE_STRING;
schema.attributes[1].name = "user";
schema.attributes[1].type = SECRET_SCHEMA_ATTRIBUTE_STRING;
schema.attributes[2].name = "NULL";
schema.attributes[2].type = (SecretSchemaAttributeType)0;
return &schema;
}
#endif

int sr_keychain_get_password(const char* domain, const char* user, char** password) {
#if defined(__APPLE__)
UInt32 passLength;
char* passBuffer;
OSStatus stat = SecKeychainFindInternetPassword(NULL, strlen(domain), domain, 0, NULL, strlen(user), user, 0, NULL, 0, kSecProtocolTypeAny, kSecAuthenticationTypeDefault, &passLength, (void**)&passBuffer, NULL);
if(stat == 0) {
*password = (char*)SR_KEYCHAIN_MALLOC(sizeof(char) * (passLength + 1));
SR_KEYCHAIN_MEMCPY(*password, passBuffer, sizeof(char) * passLength);
(*password)[passLength] = '\0';
return 0;
}

#elif defined(_WIN32)
wchar_t* targetName = _sr_keychain_get_complete_url(domain, user);
if(targetName == NULL) {
return 1;
}
PCREDENTIALW credential;
BOOL stat = CredRead(targetName, CRED_TYPE_GENERIC, 0, &credential);
SR_KEYCHAIN_FREE(targetName);

if(stat) {
const int passLength = credential->CredentialBlobSize;
*password = (char*)SR_KEYCHAIN_MALLOC(sizeof(char) * (passLength + 1));
SR_KEYCHAIN_MEMCPY(*password, (const char*)credential->CredentialBlob, sizeof(char) * passLength);
(*password)[passLength] = '\0';
CredFree(credential);
return 0;
}

#elif defined(__linux__)
GError* stat = NULL;
gchar* passBuffer = secret_password_lookup_sync(_sr_keychain_get_schema(), NULL, &stat,
"domain", domain, "user", user, NULL);
if(stat != NULL) {
g_error_free(stat);
return 1;
}
if(passBuffer == NULL) {
return 1;
}
const int passLength = strlen(passBuffer);
*password = (char*)SR_KEYCHAIN_MALLOC(sizeof(char) * (passLength + 1));
SR_KEYCHAIN_MEMCPY(*password, passBuffer, sizeof(char) * passLength);
(*password)[passLength] = '\0';
secret_password_free(passBuffer);
return 0;

#else
# pragma message("Unsupported platform for sr_keychain.")
#endif
return 1;
}

int sr_keychain_set_password(const char* domain, char* user, const char* password) {
#if defined(__APPLE__)
SecKeychainItemRef item;
OSStatus stat = SecKeychainFindInternetPassword(NULL, strlen(domain), domain, 0, NULL, strlen(user), user, 0, NULL, 0, kSecProtocolTypeAny, kSecAuthenticationTypeDefault, 0, NULL, &item);
if(stat == 0) {
// If the item already exists, modify it.
stat = SecKeychainItemModifyAttributesAndData(item, NULL, strlen(password), password);
} else {
// Else create it.
stat = SecKeychainAddInternetPassword(NULL, strlen(domain), domain, 0, NULL, strlen(user), user, 0, NULL, 0, kSecProtocolTypeAny, kSecAuthenticationTypeDefault, strlen(password), password, NULL);
}
return stat == 0 ? 0 : 1;

#elif defined(_WIN32)
wchar_t* targetName = _sr_keychain_get_complete_url(domain, user);
if(targetName == NULL) {
return 1;
}
CREDENTIALW credsToAdd = {0};
credsToAdd.Flags = 0;
credsToAdd.Type = CRED_TYPE_GENERIC;
credsToAdd.TargetName = (LPWSTR)targetName;
credsToAdd.CredentialBlob = (LPBYTE)password;
credsToAdd.CredentialBlobSize = (int)strlen(password);
credsToAdd.Persist = CRED_PERSIST_LOCAL_MACHINE;
// This will overwrite the credential if it already exists.
BOOL stat = CredWrite(&credsToAdd, 0);
SR_KEYCHAIN_FREE(targetName);
return stat ? 0 : 1;

#elif defined(__linux__)
GError* stat = NULL;
secret_password_store_sync(_sr_keychain_get_schema(), SECRET_COLLECTION_DEFAULT, "", password, NULL, &stat, "domain", domain, "user", user, NULL);
if(stat != NULL) {
g_error_free(stat);
return 1;
}
return 0;

#else
# pragma message("Unsupported platform for sr_keychain.")
#endif
return 1;
}

void sr_keychain_set_stdin_printback(int enable) {
#ifdef _WIN32
HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
DWORD mode;
GetConsoleMode(hstdin, &mode);
if(enable != 0) {
mode |= ENABLE_ECHO_INPUT;
} else {
mode &= ~ENABLE_ECHO_INPUT;
}
SetConsoleMode(hstdin, mode);

#else
struct termios tty;
tcgetattr(STDIN_FILENO, &tty);
if(enable != 0) {
tty.c_lflag |= ECHO;
} else {
tty.c_lflag &= ~ECHO;
}
tcsetattr(STDIN_FILENO, TCSANOW, &tty);
#endif
}
37 changes: 37 additions & 0 deletions lib/include/sr_keychain.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@

/**
* Copyright (c) 2021 Simon Rodriguez
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef SR_KEYCHAIN_H
#define SR_KEYCHAIN_H

#ifdef __cplusplus
extern "C" {
#endif

int sr_keychain_get_password(const char* domain, const char* user, char** password);

int sr_keychain_set_password(const char* domain, char* user, const char* password);

void sr_keychain_set_stdin_printback(int enable);

#ifdef __cplusplus
}
#endif

#endif
File renamed without changes.
File renamed without changes.
63 changes: 49 additions & 14 deletions src/main.c
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
#include <stdio.h>
#include <stdlib.h>
#include <curl/curl.h>
#include "ketopt.h"
#include <ketopt.h>
#include <sr_keychain.h>
#ifndef NDEBUG
#define STB_LEAKCHECK_IMPLEMENTATION
#define STB_LEAKCHECK_SHOWALL
#include "stb_leakcheck.h"
#include <stb_leakcheck.h>
#define TARGET_URL "http://judge_ui:8888"
#else
#define TARGET_URL "https://judge.eluminatis-of-lu.com"
Expand Down Expand Up @@ -34,7 +35,7 @@ struct command

struct command root_command = {
.name = "socli",
.desc = "socli is a command line tool for managing SeriousOJ." "\nURL: " TARGET_URL,
.desc = "socli is a command line tool for managing SeriousOJ.",
.help = "\nUsage: socli [command] [options]\n\nCommands:\n",
.sub = NULL,
.func = NULL};
Expand Down Expand Up @@ -74,6 +75,37 @@ int print_help_and_traverse(struct command *cur, int argc, char **argv)
return -1;
}

void print_cookies()
{
struct curl_slist *cookies;
CURLcode rev = curl_easy_getinfo(curl, CURLINFO_COOKIELIST, &cookies);
if (cookies)
{
for (struct curl_slist *cookie = cookies; cookie; cookie = cookie->next)
{
printf("%s\n", cookie->data);
}
curl_slist_free_all(cookies);
}
}

void save_cookies()
{
struct curl_slist *cookies;
curl_easy_getinfo(curl, CURLINFO_COOKIELIST, &cookies);
if (cookies)
{
for (struct curl_slist *cookie = cookies; cookie; cookie = cookie->next)
{
if (sr_keychain_set_password(TARGET_URL, "socli", cookie->data))
{
fprintf(stderr, "Failed to save cookie to keyring.\n");
}
}
curl_slist_free_all(cookies);
}
}

int login_command_func(struct command *cur, int argc, char **argv)
{
static ko_longopt_t login_longopts[] = {
Expand Down Expand Up @@ -133,13 +165,14 @@ int login_command_func(struct command *cur, int argc, char **argv)
socli_exit(EXIT_FAILURE);
}
CURLcode http_code;
curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &http_code);
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
if (!(http_code == 302 && res != CURLE_ABORTED_BY_CALLBACK))
{
fprintf(stderr, "login failed\n");
socli_exit(EXIT_FAILURE);
}
printf("login success\n");
save_cookies();
}
return 0;
}
Expand Down Expand Up @@ -197,7 +230,7 @@ int make_announcement_command_func(struct command *cur, int argc, char **argv)
socli_exit(EXIT_FAILURE);
}
CURLcode http_code;
curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &http_code);
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
if (!(http_code == 200 && res != CURLE_ABORTED_BY_CALLBACK))
{
fprintf(stderr, "make announcement failed\n");
Expand All @@ -213,7 +246,7 @@ size_t noop_write_callback(char *ptr, size_t size, size_t nmemb, void *userdata)
return size * nmemb;
}

const char* get_home_directory()
const char *get_home_directory()
{
#ifdef _WIN32
static char home_dir[MAX_PATH];
Expand Down Expand Up @@ -257,7 +290,8 @@ void cleanup_memory()
}

int main(int argc, char **argv)
{
{
printf("URL: %s\n", TARGET_URL);
atexit(cleanup_memory);

curl = curl_easy_init();
Expand All @@ -266,14 +300,15 @@ int main(int argc, char **argv)
fprintf(stderr, "Failed to initialize curl\n");
return 1;
}

curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "");

char COOKIEJAR[1024];

snprintf(COOKIEJAR, 1023, "%s/.socli_cookie", get_home_directory());

curl_easy_setopt(curl, CURLOPT_COOKIEFILE, COOKIEJAR);
curl_easy_perform(curl);
curl_easy_setopt(curl, CURLOPT_COOKIEJAR, COOKIEJAR);
char *cookie = NULL;
if (!sr_keychain_get_password(TARGET_URL, "socli", &cookie))
{
curl_easy_setopt(curl, CURLOPT_COOKIELIST, cookie);
free(cookie);
}

#ifdef NDEBUG
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, noop_write_callback);
Expand Down

0 comments on commit 839092e

Please sign in to comment.