Mercurial
comparison connectors/main.c @ 279:b3b547563ec7
Add Google connector service and agent wiki
Implement the C/Seobeo Google Drive and Gmail connector with encrypted OAuth storage, Zenbu authentication, browser testing, AI tool discovery, chunked HTTP decoding, and Bazel coverage. Consolidate repository guidance into progressive wiki documentation and enforce arena-first allocation for new first-party C code.
Co-authored-by: Copilot <[email protected]>
Copilot-Session: 84c338fd-0939-4bb3-b7f3-1062eb213e5d
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Mon, 17 Aug 2026 22:22:36 -0700 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 278:8d560f50ed4c | 279:b3b547563ec7 |
|---|---|
| 1 #include "connectors/connector.h" | |
| 2 #include "connectors/auth_http_adapter.h" | |
| 3 | |
| 4 #include <openssl/crypto.h> | |
| 5 | |
| 6 #include <stdio.h> | |
| 7 #include <stdlib.h> | |
| 8 #include <string.h> | |
| 9 | |
| 10 typedef struct { | |
| 11 char database[1024]; | |
| 12 char auth_database[1024]; | |
| 13 char auth_cookie_secret_hex[AUTH_CRYPTO_COOKIE_SECRET_MAX_BYTES * 2 + 1]; | |
| 14 int64 auth_session_idle_ttl; | |
| 15 char bind[128]; | |
| 16 char port[16]; | |
| 17 char static_dir[1024]; | |
| 18 char client_id[1024]; | |
| 19 char client_secret[2048]; | |
| 20 char redirect_uri[2048]; | |
| 21 char master_key[256]; | |
| 22 uint32 master_key_version; | |
| 23 } Service_Config; | |
| 24 | |
| 25 static boolean set_value(Service_Config *config, const char *key, const char *value) | |
| 26 { | |
| 27 #define SET(name, field) if (!strcmp(key, name)) { \ | |
| 28 if (strlen(value) >= sizeof(config->field)) return FALSE; \ | |
| 29 strcpy(config->field, value); return TRUE; \ | |
| 30 } | |
| 31 SET("DATABASE", database) | |
| 32 SET("DB_PATH", database) | |
| 33 SET("database", database) | |
| 34 SET("AUTH_DATABASE", auth_database) | |
| 35 SET("AUTH_DB_PATH", auth_database) | |
| 36 SET("auth_database", auth_database) | |
| 37 SET("AUTH_COOKIE_SECRET", auth_cookie_secret_hex) | |
| 38 SET("AUTH_COOKIE_SECRET_HEX", auth_cookie_secret_hex) | |
| 39 SET("auth_cookie_secret_hex", auth_cookie_secret_hex) | |
| 40 SET("BIND", bind) | |
| 41 SET("SERVER_HOST", bind) | |
| 42 SET("bind", bind) | |
| 43 SET("PORT", port) | |
| 44 SET("SERVER_PORT", port) | |
| 45 SET("port", port) | |
| 46 SET("STATIC_DIR", static_dir) | |
| 47 SET("static_dir", static_dir) | |
| 48 SET("GOOGLE_CLIENT_ID", client_id) | |
| 49 SET("google_client_id", client_id) | |
| 50 SET("GOOGLE_CLIENT_SECRET", client_secret) | |
| 51 SET("google_client_secret", client_secret) | |
| 52 SET("GOOGLE_REDIRECT_URI", redirect_uri) | |
| 53 SET("google_redirect_uri", redirect_uri) | |
| 54 SET("MASTER_KEY_BASE64URL", master_key) | |
| 55 SET("master_key_base64url", master_key) | |
| 56 if (!strcmp(key, "MASTER_KEY_VERSION") || | |
| 57 !strcmp(key, "master_key_version")) { | |
| 58 config->master_key_version = (uint32)strtoul(value, NULL, 10); | |
| 59 return config->master_key_version > 0; | |
| 60 } | |
| 61 if (!strcmp(key, "AUTH_SESSION_IDLE_TTL") || | |
| 62 !strcmp(key, "auth_session_idle_ttl")) { | |
| 63 char *end = NULL; | |
| 64 long long parsed = strtoll(value, &end, 10); | |
| 65 if (!end || *end || parsed <= 0) | |
| 66 return FALSE; | |
| 67 config->auth_session_idle_ttl = (int64)parsed; | |
| 68 return TRUE; | |
| 69 } | |
| 70 #undef SET | |
| 71 return TRUE; | |
| 72 } | |
| 73 | |
| 74 static boolean load_config(const char *path, Service_Config *config) | |
| 75 { | |
| 76 FILE *file = fopen(path, "r"); | |
| 77 char workspace_path[2048] = {0}; | |
| 78 if (!file) { | |
| 79 const char *workspace = getenv("BUILD_WORKSPACE_DIRECTORY"); | |
| 80 if (workspace && workspace[0]) { | |
| 81 int written = snprintf( | |
| 82 workspace_path, sizeof(workspace_path), "%s/%s", workspace, path); | |
| 83 if (written > 0 && (size_t)written < sizeof(workspace_path)) | |
| 84 file = fopen(workspace_path, "r"); | |
| 85 } | |
| 86 } | |
| 87 if (!file) | |
| 88 return FALSE; | |
| 89 memset(config, 0, sizeof(*config)); | |
| 90 strcpy(config->bind, "127.0.0.1"); | |
| 91 strcpy(config->port, "6981"); | |
| 92 strcpy(config->static_dir, "connectors"); | |
| 93 config->auth_session_idle_ttl = 604800; | |
| 94 char line[4096]; | |
| 95 boolean ok = TRUE; | |
| 96 while (ok && fgets(line, sizeof(line), file)) { | |
| 97 char *newline = strpbrk(line, "\r\n"); | |
| 98 if (newline) | |
| 99 *newline = '\0'; | |
| 100 if (!line[0] || line[0] == '#') | |
| 101 continue; | |
| 102 char *equals = strchr(line, '='); | |
| 103 if (!equals) { | |
| 104 ok = FALSE; | |
| 105 break; | |
| 106 } | |
| 107 *equals = '\0'; | |
| 108 ok = set_value(config, line, equals + 1); | |
| 109 } | |
| 110 fclose(file); | |
| 111 return ok && config->database[0] && config->auth_database[0] && | |
| 112 config->auth_cookie_secret_hex[0] && config->client_id[0] && | |
| 113 config->client_secret[0] && config->redirect_uri[0] && | |
| 114 config->master_key[0] && config->master_key_version; | |
| 115 } | |
| 116 | |
| 117 int main(int argc, char **argv) | |
| 118 { | |
| 119 const char *config_path = argc > 1 ? argv[1] : getenv("CONNECTOR_CONFIG_PATH"); | |
| 120 if (!config_path || !config_path[0]) | |
| 121 config_path = "connectors/.config"; | |
| 122 Service_Config service; | |
| 123 if (!load_config(config_path, &service)) { | |
| 124 fprintf(stderr, "Invalid connector config: %s\n", config_path); | |
| 125 return 1; | |
| 126 } | |
| 127 Connector_Master_Key key = {.version = service.master_key_version}; | |
| 128 size_t key_length = 0; | |
| 129 if (!Connector_Base64Url_Decode( | |
| 130 service.master_key, key.key, sizeof(key.key), &key_length) || | |
| 131 key_length != sizeof(key.key)) { | |
| 132 fprintf(stderr, "MASTER_KEY_BASE64URL must decode to 32 bytes\n"); | |
| 133 return 1; | |
| 134 } | |
| 135 Connector_Store store; | |
| 136 if (!Connector_Store_Open(&store, service.database, &key)) { | |
| 137 fprintf(stderr, "Unable to open connector database\n"); | |
| 138 return 1; | |
| 139 } | |
| 140 Connector_Auth_HTTP_Context auth_context; | |
| 141 if (!Connector_Auth_HTTP_Init( | |
| 142 &auth_context, service.auth_database, | |
| 143 service.auth_cookie_secret_hex, service.auth_session_idle_ttl)) { | |
| 144 OPENSSL_cleanse( | |
| 145 service.auth_cookie_secret_hex, | |
| 146 sizeof(service.auth_cookie_secret_hex)); | |
| 147 fprintf(stderr, "Unable to initialize shared HTTP authentication\n"); | |
| 148 Connector_Store_Close(&store); | |
| 149 return 1; | |
| 150 } | |
| 151 OPENSSL_cleanse( | |
| 152 service.auth_cookie_secret_hex, sizeof(service.auth_cookie_secret_hex)); | |
| 153 Connector_Google_Config google = { | |
| 154 .client_id = service.client_id, | |
| 155 .client_secret = service.client_secret, | |
| 156 .redirect_uri = service.redirect_uri, | |
| 157 .oauth_authorize_url = "https://accounts.google.com/o/oauth2/v2/auth", | |
| 158 .oauth_token_url = "https://oauth2.googleapis.com/token", | |
| 159 .oauth_revoke_url = "https://oauth2.googleapis.com/revoke", | |
| 160 .identity_url = "https://openidconnect.googleapis.com/v1/userinfo", | |
| 161 .drive_api_url = "https://www.googleapis.com", | |
| 162 .drive_upload_url = "https://www.googleapis.com", | |
| 163 .gmail_api_url = "https://gmail.googleapis.com" | |
| 164 }; | |
| 165 Connector_Auth_Adapter auth = | |
| 166 Connector_Auth_HTTP_Create_Adapter(&auth_context); | |
| 167 Connector_Service_Configure(&store, &google, auth); | |
| 168 Seobeo_Router_Init(); | |
| 169 Connector_Service_Register_Routes(); | |
| 170 int result = Seobeo_Web_Server_Start_On( | |
| 171 service.bind, service.static_dir, service.port, SEOBEO_MODE_EDGE, 4); | |
| 172 Seobeo_Router_Destroy(); | |
| 173 Connector_Auth_HTTP_Destroy(&auth_context); | |
| 174 Connector_Store_Close(&store); | |
| 175 return result; | |
| 176 } |