Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6d16baf052 | |||
| 8b05e85683 | |||
| 6373468050 | |||
| d116e6a652 | |||
| cb3c837f0b |
+19
-3
@@ -1,13 +1,29 @@
|
|||||||
# Easy IRL Stream
|
# Easy IRL Stream
|
||||||
|
|
||||||
|
## v1.1.1 — SSL Fix
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
- **Fixed SSL connect errors on IPv6 networks** — Forced IPv4 resolution (`CURL_IPRESOLVE_V4`) to avoid broken IPv6 TLS handshakes that caused `SEC_E_INVALID_TOKEN` errors with Schannel.
|
||||||
|
- **Additional SSL hardening** — TLS 1.2 is enforced as both minimum and maximum version, Schannel revocation checks are disabled (`CURLSSLOPT_NO_REVOKE`), and HTTP/1.1 is forced to prevent ALPN-related handshake failures with MITM proxies.
|
||||||
|
|
||||||
|
### Improvements
|
||||||
|
- **Verbose curl logging** — All HTTPS requests now log the full TLS handshake process (DNS resolution, IP used, SSL backend, cipher, errors) to the OBS log for easier diagnostics.
|
||||||
|
- **curl version info on startup** — The OBS log now shows the curl version and SSL backend when the plugin loads.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## v1.1.0 — Update Check, Watermark & SSL Fix
|
## v1.1.0 — Update Check, Watermark & SSL Fix
|
||||||
|
|
||||||
### New Features
|
### New Features
|
||||||
- **Mandatory update check** — The plugin now checks for updates on startup. If a newer version is available, a dialog is shown, the download page opens, and the plugin stays disabled until updated.
|
- **Mandatory update check** — The plugin checks for updates on startup. If a newer version is available, a dialog is shown, the download page opens, and the plugin stays disabled until updated.
|
||||||
- **Watermark for free users** — Non-Patreon users now see a small "Easy IRL Stream - stools.cc" watermark in the bottom-right corner of the video. Patreon supporters get it removed automatically.
|
- **Watermark for free users** — Non-Patreon users now see a small "Easy IRL Stream - stools.cc" watermark in the bottom-right corner of the video. Patreon supporters get it removed automatically via the server API.
|
||||||
|
- **SSL error dialog** — When the plugin can't connect to stools.cc due to TLS/SSL issues, a one-time dialog now explains possible causes (antivirus HTTPS scanning, firewall, VPN) with the detailed error message.
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
- **Fixed SSL connect errors** — API calls to `stools.cc` failed with "SSL connect error" because libcurl (built with OpenSSL) couldn't find CA certificates on Windows. Added `CURLSSLOPT_NATIVE_CA` to all curl calls so OpenSSL uses the Windows certificate store.
|
- **Fixed SSL connect errors** — Disabled certificate verification and forced TLS 1.2 to work around Schannel `SEC_E_INVALID_TOKEN` errors caused by antivirus HTTPS inspection or TLS 1.3 incompatibilities. Added `CURLOPT_ERRORBUFFER` for detailed error diagnostics in the OBS log.
|
||||||
|
|
||||||
|
### Improvements
|
||||||
|
- **Patreon hint in FAQ** — The Help & FAQ dialog now includes an entry explaining the watermark and linking directly to the Patreon checkout page.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -16,11 +16,6 @@ OBS Studio plugin for IRL streamers. Receives an RTMP or SRT stream directly in
|
|||||||
|
|
||||||
For setup instructions, FAQ and downloads visit **[stools.cc/p/easy-irl-stream](https://stools.cc/p/easy-irl-stream)**.
|
For setup instructions, FAQ and downloads visit **[stools.cc/p/easy-irl-stream](https://stools.cc/p/easy-irl-stream)**.
|
||||||
|
|
||||||
|
|
||||||
## AI-Powered Development
|
|
||||||
|
|
||||||
This project was built with the support of **Anthropic Claude Opus 4.6**. We embrace AI-assisted development — this is an AI-friendly project. The future is now.
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This project is licensed under the [GNU General Public License v2.0](LICENSE).
|
This project is licensed under the [GNU General Public License v2.0](LICENSE).
|
||||||
|
|||||||
+2
-2
@@ -374,7 +374,7 @@ void event_handler_tick(struct irl_source_data *data)
|
|||||||
if (state == CONN_STATE_CONNECTED)
|
if (state == CONN_STATE_CONNECTED)
|
||||||
check_quality(data);
|
check_quality(data);
|
||||||
|
|
||||||
if (state != CONN_STATE_DISCONNECTED)
|
if (state != CONN_STATE_DISCONNECTED && state != CONN_STATE_LISTENING)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
pthread_mutex_lock(&data->mutex);
|
pthread_mutex_lock(&data->mutex);
|
||||||
@@ -383,7 +383,7 @@ void event_handler_tick(struct irl_source_data *data)
|
|||||||
int timeout = data->disconnect_timeout_sec;
|
int timeout = data->disconnect_timeout_sec;
|
||||||
pthread_mutex_unlock(&data->mutex);
|
pthread_mutex_unlock(&data->mutex);
|
||||||
|
|
||||||
if (already_fired || timeout <= 0)
|
if (already_fired || timeout <= 0 || disc_time == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
uint64_t elapsed_ns = os_gettime_ns() - disc_time;
|
uint64_t elapsed_ns = os_gettime_ns() - disc_time;
|
||||||
|
|||||||
@@ -431,3 +431,68 @@ extern "C" void forced_update_show(const char *new_version, const char *locale)
|
|||||||
obf_dash_downloads_path());
|
obf_dash_downloads_path());
|
||||||
open_url(url);
|
open_url(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C" void ssl_error_dialog_show(const char *detail, const char *locale)
|
||||||
|
{
|
||||||
|
bool is_de = locale && (strncmp(locale, "de", 2) == 0);
|
||||||
|
|
||||||
|
QWidget *parent = (QWidget *)obs_frontend_get_main_window();
|
||||||
|
|
||||||
|
QString title = is_de ? "Verbindungsfehler"
|
||||||
|
: "Connection Error";
|
||||||
|
|
||||||
|
QString err = detail && detail[0] ? QString(detail) : QString("SSL connect error");
|
||||||
|
bool is_sec_e = err.contains("SEC_E_INVALID_TOKEN");
|
||||||
|
|
||||||
|
QString hint_de = is_sec_e
|
||||||
|
? QString::fromUtf8(
|
||||||
|
"Dein Antivirus-Programm f\xc3\xa4""ngt HTTPS-Verbindungen ab "
|
||||||
|
"und st\xc3\xb6""rt den TLS-Handshake.\n\n"
|
||||||
|
"So behebst du das Problem:\n"
|
||||||
|
"1. \xc3\x96""ffne dein Antivirus-Programm (z.B. Panda Dome, "
|
||||||
|
"Kaspersky, Avast, ESET, Bitdefender)\n"
|
||||||
|
"2. Gehe zu Einstellungen \xe2\x86\x92 Schutz / Webschutz\n"
|
||||||
|
"3. Deaktiviere \"HTTPS-Scanning\" / \"SSL-Inspektion\" / "
|
||||||
|
"\"Webschutz\" / \"Safe Browsing\"\n"
|
||||||
|
"4. Oder f\xc3\xbc""ge stools.cc als Ausnahme hinzu\n"
|
||||||
|
"5. Starte OBS neu")
|
||||||
|
: QString::fromUtf8(
|
||||||
|
"M\xc3\xb6""gliche Ursachen:\n"
|
||||||
|
"\xe2\x80\xa2 Antivirus-Software blockiert die Verbindung "
|
||||||
|
"(HTTPS-Scanning / SSL-Inspektion deaktivieren)\n"
|
||||||
|
"\xe2\x80\xa2 Firewall oder Proxy blockiert stools.cc\n"
|
||||||
|
"\xe2\x80\xa2 VPN-Verbindung aktiv");
|
||||||
|
|
||||||
|
QString hint_en = is_sec_e
|
||||||
|
? QString(
|
||||||
|
"Your antivirus software is intercepting HTTPS connections "
|
||||||
|
"and breaking the TLS handshake.\n\n"
|
||||||
|
"How to fix this:\n"
|
||||||
|
"1. Open your antivirus program (e.g. Panda Dome, "
|
||||||
|
"Kaspersky, Avast, ESET, Bitdefender)\n"
|
||||||
|
"2. Go to Settings \xe2\x86\x92 Protection / Web Protection\n"
|
||||||
|
"3. Disable \"HTTPS Scanning\" / \"SSL Inspection\" / "
|
||||||
|
"\"Web Protection\" / \"Safe Browsing\"\n"
|
||||||
|
"4. Or add stools.cc as an exception\n"
|
||||||
|
"5. Restart OBS")
|
||||||
|
: QString(
|
||||||
|
"Possible causes:\n"
|
||||||
|
"\xe2\x80\xa2 Antivirus software blocking the connection "
|
||||||
|
"(disable HTTPS scanning / SSL inspection)\n"
|
||||||
|
"\xe2\x80\xa2 Firewall or proxy blocking stools.cc\n"
|
||||||
|
"\xe2\x80\xa2 VPN connection active");
|
||||||
|
|
||||||
|
QString text = is_de
|
||||||
|
? QString::fromUtf8(
|
||||||
|
"Easy IRL Stream konnte keine sichere Verbindung "
|
||||||
|
"zu stools.cc herstellen.\n\n%1\n\n"
|
||||||
|
"Fehler: %2")
|
||||||
|
.arg(hint_de, err)
|
||||||
|
: QString(
|
||||||
|
"Easy IRL Stream could not establish a secure "
|
||||||
|
"connection to stools.cc.\n\n%1\n\n"
|
||||||
|
"Error: %2")
|
||||||
|
.arg(hint_en, err);
|
||||||
|
|
||||||
|
QMessageBox::warning(parent, title, text, QMessageBox::Ok);
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ void help_dialog_show(const char *local_ip, const char *external_ip,
|
|||||||
|
|
||||||
void update_dialog_show(const char *new_version, const char *locale);
|
void update_dialog_show(const char *new_version, const char *locale);
|
||||||
void forced_update_show(const char *new_version, const char *locale);
|
void forced_update_show(const char *new_version, const char *locale);
|
||||||
|
void ssl_error_dialog_show(const char *detail, const char *locale);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|||||||
+68
-2
@@ -299,6 +299,44 @@ struct update_ctx {
|
|||||||
#include "help-dialog.hpp"
|
#include "help-dialog.hpp"
|
||||||
#include "stats-dialog.hpp"
|
#include "stats-dialog.hpp"
|
||||||
|
|
||||||
|
static int curl_debug_cb(CURL *handle, curl_infotype type, char *data,
|
||||||
|
size_t size, void *userptr)
|
||||||
|
{
|
||||||
|
(void)handle;
|
||||||
|
(void)userptr;
|
||||||
|
|
||||||
|
const char *prefix;
|
||||||
|
switch (type) {
|
||||||
|
case CURLINFO_TEXT:
|
||||||
|
prefix = "* ";
|
||||||
|
break;
|
||||||
|
case CURLINFO_SSL_DATA_IN:
|
||||||
|
case CURLINFO_SSL_DATA_OUT:
|
||||||
|
return 0;
|
||||||
|
case CURLINFO_HEADER_IN:
|
||||||
|
prefix = "< ";
|
||||||
|
break;
|
||||||
|
case CURLINFO_HEADER_OUT:
|
||||||
|
prefix = "> ";
|
||||||
|
break;
|
||||||
|
case CURLINFO_DATA_IN:
|
||||||
|
case CURLINFO_DATA_OUT:
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
char buf[1024];
|
||||||
|
size_t len = size < sizeof(buf) - 1 ? size : sizeof(buf) - 1;
|
||||||
|
memcpy(buf, data, len);
|
||||||
|
while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r'))
|
||||||
|
len--;
|
||||||
|
buf[len] = '\0';
|
||||||
|
|
||||||
|
blog(LOG_INFO, "[%s] curl: %s%s", PLUGIN_NAME, prefix, buf);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static bool check_update_blocking(void)
|
static bool check_update_blocking(void)
|
||||||
{
|
{
|
||||||
char url[256];
|
char url[256];
|
||||||
@@ -313,21 +351,40 @@ static bool check_update_blocking(void)
|
|||||||
if (!curl) return false;
|
if (!curl) return false;
|
||||||
|
|
||||||
struct update_mem_buf buf = {NULL, 0};
|
struct update_mem_buf buf = {NULL, 0};
|
||||||
|
char errbuf[CURL_ERROR_SIZE] = "";
|
||||||
|
|
||||||
curl_easy_setopt(curl, CURLOPT_URL, url);
|
curl_easy_setopt(curl, CURLOPT_URL, url);
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, update_write_cb);
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, update_write_cb);
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buf);
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buf);
|
||||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 5L);
|
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 5L);
|
||||||
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 3L);
|
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 3L);
|
||||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, ua);
|
curl_easy_setopt(curl, CURLOPT_USERAGENT, ua);
|
||||||
#ifdef CURLSSLOPT_NATIVE_CA
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
|
||||||
curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, (long)CURLSSLOPT_NATIVE_CA);
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
|
||||||
|
#ifdef _WIN32
|
||||||
|
curl_easy_setopt(curl, CURLOPT_SSLVERSION,
|
||||||
|
CURL_SSLVERSION_TLSv1_2 | CURL_SSLVERSION_MAX_TLSv1_2);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, (long)CURLSSLOPT_NO_REVOKE);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_HTTP_VERSION,
|
||||||
|
(long)CURL_HTTP_VERSION_1_1);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_SSL_ENABLE_ALPN, 0L);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_IPRESOLVE, (long)CURL_IPRESOLVE_V4);
|
||||||
#endif
|
#endif
|
||||||
|
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, curl_debug_cb);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf);
|
||||||
|
|
||||||
CURLcode res = curl_easy_perform(curl);
|
CURLcode res = curl_easy_perform(curl);
|
||||||
long http_code = 0;
|
long http_code = 0;
|
||||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
|
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
|
||||||
curl_easy_cleanup(curl);
|
curl_easy_cleanup(curl);
|
||||||
|
|
||||||
|
if (res != CURLE_OK) {
|
||||||
|
blog(LOG_WARNING, "[%s] Update check failed: %s (%s)",
|
||||||
|
PLUGIN_NAME, curl_easy_strerror(res),
|
||||||
|
errbuf[0] ? errbuf : "no details");
|
||||||
|
}
|
||||||
|
|
||||||
if (res != CURLE_OK || http_code != 200 || !buf.data) {
|
if (res != CURLE_OK || http_code != 200 || !buf.data) {
|
||||||
free(buf.data);
|
free(buf.data);
|
||||||
return false;
|
return false;
|
||||||
@@ -377,6 +434,15 @@ bool obs_module_load(void)
|
|||||||
{
|
{
|
||||||
curl_global_init(CURL_GLOBAL_DEFAULT);
|
curl_global_init(CURL_GLOBAL_DEFAULT);
|
||||||
|
|
||||||
|
curl_version_info_data *vi = curl_version_info(CURLVERSION_NOW);
|
||||||
|
if (vi) {
|
||||||
|
blog(LOG_INFO,
|
||||||
|
"[%s] curl %s, SSL: %s, features: 0x%x",
|
||||||
|
PLUGIN_NAME, vi->version,
|
||||||
|
vi->ssl_version ? vi->ssl_version : "none",
|
||||||
|
(unsigned)vi->features);
|
||||||
|
}
|
||||||
|
|
||||||
if (check_update_blocking()) {
|
if (check_update_blocking()) {
|
||||||
g_update_required = true;
|
g_update_required = true;
|
||||||
blog(LOG_WARNING,
|
blog(LOG_WARNING,
|
||||||
|
|||||||
+103
-10
@@ -14,6 +14,94 @@
|
|||||||
|
|
||||||
#include <curl/curl.h>
|
#include <curl/curl.h>
|
||||||
|
|
||||||
|
#include "help-dialog.hpp"
|
||||||
|
|
||||||
|
/* ---- SSL error dialog (shown once per session) ---- */
|
||||||
|
|
||||||
|
static volatile bool g_ssl_error_shown = false;
|
||||||
|
|
||||||
|
struct ssl_error_ctx {
|
||||||
|
char detail[CURL_ERROR_SIZE];
|
||||||
|
};
|
||||||
|
|
||||||
|
static void task_show_ssl_error(void *param)
|
||||||
|
{
|
||||||
|
struct ssl_error_ctx *ctx = param;
|
||||||
|
ssl_error_dialog_show(ctx->detail, obs_get_locale());
|
||||||
|
free(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void maybe_show_ssl_error(CURLcode res, const char *errbuf)
|
||||||
|
{
|
||||||
|
if (res != CURLE_SSL_CONNECT_ERROR || g_ssl_error_shown)
|
||||||
|
return;
|
||||||
|
|
||||||
|
g_ssl_error_shown = true;
|
||||||
|
struct ssl_error_ctx *ctx = malloc(sizeof(*ctx));
|
||||||
|
if (ctx) {
|
||||||
|
snprintf(ctx->detail, sizeof(ctx->detail), "%s",
|
||||||
|
errbuf && errbuf[0] ? errbuf : "SSL connect error");
|
||||||
|
obs_queue_task(OBS_TASK_UI, task_show_ssl_error, ctx, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- cURL debug callback ---- */
|
||||||
|
|
||||||
|
static int curl_debug_cb(CURL *handle, curl_infotype type, char *data,
|
||||||
|
size_t size, void *userptr)
|
||||||
|
{
|
||||||
|
(void)handle;
|
||||||
|
(void)userptr;
|
||||||
|
|
||||||
|
const char *prefix;
|
||||||
|
switch (type) {
|
||||||
|
case CURLINFO_TEXT:
|
||||||
|
prefix = "* ";
|
||||||
|
break;
|
||||||
|
case CURLINFO_SSL_DATA_IN:
|
||||||
|
case CURLINFO_SSL_DATA_OUT:
|
||||||
|
return 0;
|
||||||
|
case CURLINFO_HEADER_IN:
|
||||||
|
prefix = "< ";
|
||||||
|
break;
|
||||||
|
case CURLINFO_HEADER_OUT:
|
||||||
|
prefix = "> ";
|
||||||
|
break;
|
||||||
|
case CURLINFO_DATA_IN:
|
||||||
|
case CURLINFO_DATA_OUT:
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
char buf[1024];
|
||||||
|
size_t len = size < sizeof(buf) - 1 ? size : sizeof(buf) - 1;
|
||||||
|
memcpy(buf, data, len);
|
||||||
|
while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r'))
|
||||||
|
len--;
|
||||||
|
buf[len] = '\0';
|
||||||
|
|
||||||
|
blog(LOG_INFO, "[%s] curl: %s%s", PLUGIN_NAME, prefix, buf);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void curl_set_ssl_opts(CURL *curl)
|
||||||
|
{
|
||||||
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
|
||||||
|
#ifdef _WIN32
|
||||||
|
curl_easy_setopt(curl, CURLOPT_SSLVERSION,
|
||||||
|
CURL_SSLVERSION_TLSv1_2 | CURL_SSLVERSION_MAX_TLSv1_2);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, (long)CURLSSLOPT_NO_REVOKE);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_HTTP_VERSION,
|
||||||
|
(long)CURL_HTTP_VERSION_1_1);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_SSL_ENABLE_ALPN, 0L);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_IPRESOLVE, (long)CURL_IPRESOLVE_V4);
|
||||||
|
#endif
|
||||||
|
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, curl_debug_cb);
|
||||||
|
}
|
||||||
|
|
||||||
/* ---- cURL helpers ---- */
|
/* ---- cURL helpers ---- */
|
||||||
|
|
||||||
struct mem_buf {
|
struct mem_buf {
|
||||||
@@ -53,6 +141,7 @@ static char *api_get(const char *path, const char *token)
|
|||||||
snprintf(ua, sizeof(ua), "%s%s", obf_ua_prefix(), PLUGIN_VERSION);
|
snprintf(ua, sizeof(ua), "%s%s", obf_ua_prefix(), PLUGIN_VERSION);
|
||||||
|
|
||||||
struct mem_buf buf = {NULL, 0};
|
struct mem_buf buf = {NULL, 0};
|
||||||
|
char errbuf[CURL_ERROR_SIZE] = "";
|
||||||
|
|
||||||
curl_easy_setopt(curl, CURLOPT_URL, url);
|
curl_easy_setopt(curl, CURLOPT_URL, url);
|
||||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
||||||
@@ -60,9 +149,8 @@ static char *api_get(const char *path, const char *token)
|
|||||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buf);
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buf);
|
||||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 15L);
|
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 15L);
|
||||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, ua);
|
curl_easy_setopt(curl, CURLOPT_USERAGENT, ua);
|
||||||
#ifdef CURLSSLOPT_NATIVE_CA
|
curl_set_ssl_opts(curl);
|
||||||
curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, (long)CURLSSLOPT_NATIVE_CA);
|
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf);
|
||||||
#endif
|
|
||||||
|
|
||||||
CURLcode res = curl_easy_perform(curl);
|
CURLcode res = curl_easy_perform(curl);
|
||||||
long http_code = 0;
|
long http_code = 0;
|
||||||
@@ -71,8 +159,10 @@ static char *api_get(const char *path, const char *token)
|
|||||||
curl_easy_cleanup(curl);
|
curl_easy_cleanup(curl);
|
||||||
|
|
||||||
if (res != CURLE_OK) {
|
if (res != CURLE_OK) {
|
||||||
blog(LOG_WARNING, "[%s] API GET %s failed: %s",
|
blog(LOG_WARNING, "[%s] API GET %s failed: %s (%s)",
|
||||||
PLUGIN_NAME, path, curl_easy_strerror(res));
|
PLUGIN_NAME, path, curl_easy_strerror(res),
|
||||||
|
errbuf[0] ? errbuf : "no details");
|
||||||
|
maybe_show_ssl_error(res, errbuf);
|
||||||
free(buf.data);
|
free(buf.data);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@@ -105,14 +195,15 @@ static bool api_post(const char *path, const char *token, const char *json_body)
|
|||||||
char ua[128];
|
char ua[128];
|
||||||
snprintf(ua, sizeof(ua), "%s%s", obf_ua_prefix(), PLUGIN_VERSION);
|
snprintf(ua, sizeof(ua), "%s%s", obf_ua_prefix(), PLUGIN_VERSION);
|
||||||
|
|
||||||
|
char errbuf[CURL_ERROR_SIZE] = "";
|
||||||
|
|
||||||
curl_easy_setopt(curl, CURLOPT_URL, url);
|
curl_easy_setopt(curl, CURLOPT_URL, url);
|
||||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
||||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_body);
|
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_body);
|
||||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 15L);
|
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 15L);
|
||||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, ua);
|
curl_easy_setopt(curl, CURLOPT_USERAGENT, ua);
|
||||||
#ifdef CURLSSLOPT_NATIVE_CA
|
curl_set_ssl_opts(curl);
|
||||||
curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, (long)CURLSSLOPT_NATIVE_CA);
|
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf);
|
||||||
#endif
|
|
||||||
|
|
||||||
CURLcode res = curl_easy_perform(curl);
|
CURLcode res = curl_easy_perform(curl);
|
||||||
long http_code = 0;
|
long http_code = 0;
|
||||||
@@ -121,8 +212,10 @@ static bool api_post(const char *path, const char *token, const char *json_body)
|
|||||||
curl_easy_cleanup(curl);
|
curl_easy_cleanup(curl);
|
||||||
|
|
||||||
if (res != CURLE_OK) {
|
if (res != CURLE_OK) {
|
||||||
blog(LOG_WARNING, "[%s] API POST %s failed: %s",
|
blog(LOG_WARNING, "[%s] API POST %s failed: %s (%s)",
|
||||||
PLUGIN_NAME, path, curl_easy_strerror(res));
|
PLUGIN_NAME, path, curl_easy_strerror(res),
|
||||||
|
errbuf[0] ? errbuf : "no details");
|
||||||
|
maybe_show_ssl_error(res, errbuf);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (http_code != 200) {
|
if (http_code != 200) {
|
||||||
|
|||||||
+55
-4
@@ -25,6 +25,43 @@ static size_t discard_response(void *ptr, size_t size, size_t nmemb,
|
|||||||
return size * nmemb;
|
return size * nmemb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int webhook_curl_debug_cb(CURL *handle, curl_infotype type, char *data,
|
||||||
|
size_t size, void *userptr)
|
||||||
|
{
|
||||||
|
(void)handle;
|
||||||
|
(void)userptr;
|
||||||
|
|
||||||
|
const char *prefix;
|
||||||
|
switch (type) {
|
||||||
|
case CURLINFO_TEXT:
|
||||||
|
prefix = "* ";
|
||||||
|
break;
|
||||||
|
case CURLINFO_SSL_DATA_IN:
|
||||||
|
case CURLINFO_SSL_DATA_OUT:
|
||||||
|
case CURLINFO_DATA_IN:
|
||||||
|
case CURLINFO_DATA_OUT:
|
||||||
|
return 0;
|
||||||
|
case CURLINFO_HEADER_IN:
|
||||||
|
prefix = "< ";
|
||||||
|
break;
|
||||||
|
case CURLINFO_HEADER_OUT:
|
||||||
|
prefix = "> ";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
char buf[1024];
|
||||||
|
size_t len = size < sizeof(buf) - 1 ? size : sizeof(buf) - 1;
|
||||||
|
memcpy(buf, data, len);
|
||||||
|
while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r'))
|
||||||
|
len--;
|
||||||
|
buf[len] = '\0';
|
||||||
|
|
||||||
|
blog(LOG_INFO, "[Easy IRL Stream] curl: %s%s", prefix, buf);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static void webhook_do_send(const char *url, const char *json_body)
|
static void webhook_do_send(const char *url, const char *json_body)
|
||||||
{
|
{
|
||||||
CURL *curl = curl_easy_init();
|
CURL *curl = curl_easy_init();
|
||||||
@@ -37,6 +74,8 @@ static void webhook_do_send(const char *url, const char *json_body)
|
|||||||
struct curl_slist *headers = NULL;
|
struct curl_slist *headers = NULL;
|
||||||
headers = curl_slist_append(headers, "Content-Type: application/json");
|
headers = curl_slist_append(headers, "Content-Type: application/json");
|
||||||
|
|
||||||
|
char errbuf[CURL_ERROR_SIZE] = "";
|
||||||
|
|
||||||
curl_easy_setopt(curl, CURLOPT_URL, url);
|
curl_easy_setopt(curl, CURLOPT_URL, url);
|
||||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_body);
|
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_body);
|
||||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
||||||
@@ -44,14 +83,26 @@ static void webhook_do_send(const char *url, const char *json_body)
|
|||||||
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 5L);
|
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 5L);
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, discard_response);
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, discard_response);
|
||||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, "easy-irl-stream-webhook/1.0");
|
curl_easy_setopt(curl, CURLOPT_USERAGENT, "easy-irl-stream-webhook/1.0");
|
||||||
#ifdef CURLSSLOPT_NATIVE_CA
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
|
||||||
curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, (long)CURLSSLOPT_NATIVE_CA);
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
|
||||||
|
#ifdef _WIN32
|
||||||
|
curl_easy_setopt(curl, CURLOPT_SSLVERSION,
|
||||||
|
CURL_SSLVERSION_TLSv1_2 | CURL_SSLVERSION_MAX_TLSv1_2);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, (long)CURLSSLOPT_NO_REVOKE);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_HTTP_VERSION,
|
||||||
|
(long)CURL_HTTP_VERSION_1_1);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_SSL_ENABLE_ALPN, 0L);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_IPRESOLVE, (long)CURL_IPRESOLVE_V4);
|
||||||
#endif
|
#endif
|
||||||
|
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, webhook_curl_debug_cb);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf);
|
||||||
|
|
||||||
CURLcode res = curl_easy_perform(curl);
|
CURLcode res = curl_easy_perform(curl);
|
||||||
if (res != CURLE_OK) {
|
if (res != CURLE_OK) {
|
||||||
blog(LOG_WARNING, "[%s] Webhook failed (%s): %s",
|
blog(LOG_WARNING, "[%s] Webhook failed (%s): %s (%s)",
|
||||||
"Easy IRL Stream", url, curl_easy_strerror(res));
|
"Easy IRL Stream", url, curl_easy_strerror(res),
|
||||||
|
errbuf[0] ? errbuf : "no details");
|
||||||
} else {
|
} else {
|
||||||
long http_code = 0;
|
long http_code = 0;
|
||||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
|
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
|
||||||
|
|||||||
Reference in New Issue
Block a user