mirror of https://github.com/OpenVPN/openvpn-gui
PLAP: support for webauth
Instead of opening the browser window in PLAP context (which is insecure), display a dialog with QR code and ask user to scan it on mobile device. QR code generation is taken from MIT-licensed library: https://github.com/nayuki/QR-Code-generator/blob/master/c/qrcodegen.c Before I tried vcpkg libqrencode package but gave up after 10 mins of dependencies building (it requires msys2 etc). Note that you need to add setenv IV_SSO webauth to PLAP profile to make it work. Fixes: https://github.com/OpenVPN/openvpn-gui/issues/687 Signed-off-by: Lev Stipakov <lev@openvpn.net>pull/738/head
parent
2242c1cca4
commit
d8249daf8f
|
@ -39,3 +39,4 @@ plap/credentialprovider.h
|
|||
x64
|
||||
Debug
|
||||
out
|
||||
build
|
|
@ -25,7 +25,10 @@ add_executable(${PROJECT_NAME} WIN32
|
|||
as.c
|
||||
pkcs11.c
|
||||
config_parser.c
|
||||
res/openvpn-gui-res.rc)
|
||||
qr.c
|
||||
qrcodegen/qrcodegen.c
|
||||
res/openvpn-gui-res.rc
|
||||
)
|
||||
|
||||
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -DDEBUG")
|
||||
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS} /guard:cf /Qspectre")
|
||||
|
@ -117,6 +120,8 @@ add_library(${PROJECT_NAME_PLAP} SHARED
|
|||
registry.c
|
||||
config_parser.c
|
||||
service.c
|
||||
qr.c
|
||||
qrcodegen/qrcodegen.c
|
||||
plap/ui_glue.c
|
||||
plap/stub.c
|
||||
plap/plap_common.c
|
||||
|
|
|
@ -110,6 +110,8 @@ openvpn_gui_SOURCES = \
|
|||
as.c as.h \
|
||||
pkcs11.c pkcs11.h \
|
||||
config_parser.c config_parser.h \
|
||||
qr.c qr.h \
|
||||
qrcodegen/qrcodegen.c qrcodegen/qrcodegen.h
|
||||
openvpn-gui-res.h
|
||||
|
||||
openvpn_gui_LDFLAGS = -mwindows
|
||||
|
|
|
@ -158,6 +158,13 @@ screen are setup in the `config-auto` folder, `OpenVPNService`
|
|||
is running, and these connections are visible and controllable
|
||||
from the GUI.
|
||||
|
||||
In addition to that, PLAP profiles must contain::
|
||||
|
||||
setenv IV_SSO webauth
|
||||
|
||||
to indicate that client supports web-based (as QR code for PLAP)
|
||||
authentication method.
|
||||
|
||||
Once those pre-requisites are satisfied, the login screen will
|
||||
display an icon for `Pre-Logon Access Providers`, clicking which
|
||||
will bring up a list of OpenVPN connection profiles available, and
|
||||
|
|
15
misc.c
15
misc.c
|
@ -544,6 +544,21 @@ CheckFileAccess(const TCHAR *path, int access)
|
|||
return ret;
|
||||
}
|
||||
|
||||
char *
|
||||
WCharToUTF8(const WCHAR *wstr)
|
||||
{
|
||||
int utf8_len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL);
|
||||
if (utf8_len == 0)
|
||||
return NULL;
|
||||
|
||||
char *utf8_str = (char *)malloc(utf8_len);
|
||||
if (!utf8_str)
|
||||
return NULL;
|
||||
|
||||
WideCharToMultiByte(CP_UTF8, 0, wstr, -1, utf8_str, utf8_len, NULL, NULL);
|
||||
return utf8_str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a NUL terminated narrow string to wide string using
|
||||
* specified codepage. The caller must free
|
||||
|
|
6
misc.h
6
misc.h
|
@ -58,6 +58,12 @@ WCHAR *Widen(const char *utf8);
|
|||
|
||||
WCHAR *WidenEx(UINT codepage, const char *utf8);
|
||||
|
||||
/**
|
||||
* Convert a wide string to a UTF-8 string. The caller must
|
||||
* free the returned pointer. Return NULL on error.
|
||||
*/
|
||||
char *WCharToUTF8(const WCHAR *wstr);
|
||||
|
||||
BOOL validate_input(const WCHAR *input, const WCHAR *exclude);
|
||||
|
||||
/* Concatenate two wide strings with a separator */
|
||||
|
|
|
@ -166,6 +166,11 @@
|
|||
#define ID_LVW_PKCS11 451
|
||||
#define ID_TXT_PKCS11 452
|
||||
|
||||
/* QR code dialog */
|
||||
#define ID_DLG_QR 500
|
||||
#define ID_STATIC_QR 501
|
||||
#define ID_TXT_QR 502
|
||||
|
||||
/* General settings contd.. */
|
||||
|
||||
#define ID_CHK_CONCAT_OTP 470
|
||||
|
|
20
openvpn.c
20
openvpn.c
|
@ -62,6 +62,7 @@
|
|||
#include "echo.h"
|
||||
#include "pkcs11.h"
|
||||
#include "service.h"
|
||||
#include "qr.h"
|
||||
|
||||
#define OPENVPN_SERVICE_PIPE_NAME_OVPN2 L"\\\\.\\pipe\\openvpn\\service"
|
||||
#define OPENVPN_SERVICE_PIPE_NAME_OVPN3 L"\\\\.\\pipe\\ovpnagent"
|
||||
|
@ -447,6 +448,14 @@ OnStateChange(connection_t *c, char *data)
|
|||
SetDlgItemTextW(c->hwndStatus, ID_TXT_IP, L"");
|
||||
SetStatusWinIcon(c->hwndStatus, ID_ICO_CONNECTING);
|
||||
}
|
||||
|
||||
/* close QR dialog, if any */
|
||||
CloseQRDialog();
|
||||
}
|
||||
else if (strcmp(state, "ASSIGN_IP") == 0)
|
||||
{
|
||||
/* first state after completion of web-based auth, now it is safe to close QR dialog */
|
||||
CloseQRDialog();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1755,9 +1764,16 @@ OnInfoMsg(connection_t *c, char *msg)
|
|||
|
||||
if (url)
|
||||
{
|
||||
if (!open_url(url))
|
||||
if (o.use_qr_for_url)
|
||||
{
|
||||
WriteStatusLog(c, L"GUI> ", L"Error: failed to open url from info msg", false);
|
||||
OpenQRDialog(url, c->config_name);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!open_url(url))
|
||||
{
|
||||
WriteStatusLog(c, L"GUI> ", L"Error: failed to open url from info msg", false);
|
||||
}
|
||||
}
|
||||
free(url);
|
||||
}
|
||||
|
|
|
@ -259,6 +259,8 @@ typedef struct
|
|||
TCHAR *action_arg;
|
||||
HANDLE session_semaphore;
|
||||
HANDLE event_log;
|
||||
DWORD use_qr_for_url; /* display QR code instead of opening a web browser with a URL, used by
|
||||
PLAP */
|
||||
} options_t;
|
||||
|
||||
void InitOptions(options_t *);
|
||||
|
|
|
@ -102,6 +102,10 @@ libopenvpn_plap_la_SOURCES = \
|
|||
$(top_srcdir)/config_parser.c \
|
||||
$(top_srcdir)/pkcs11.c \
|
||||
$(top_srcdir)/service.c \
|
||||
$(top_srcdir)/qr.c \
|
||||
$(top_srcdir)/qr.h \
|
||||
$(top_srcdir)/qrcodegen/qrcodegen.c \
|
||||
$(top_srcdir)/qrcodegen/qrcodegen.h \
|
||||
openvpn-plap-res.rc
|
||||
|
||||
libopenvpn_plap_la_LIBADD = \
|
||||
|
|
|
@ -66,23 +66,16 @@ OnStop_(connection_t *c, UNUSED char *msg)
|
|||
SendMessage(c->hwndStatus, WM_CLOSE, 0, 0);
|
||||
}
|
||||
|
||||
/* Override OnInfoMsg: We filter out anything other
|
||||
* than CR_TEXT: In particular, OPEN_URL is not supported
|
||||
* in PLAP context.
|
||||
*/
|
||||
static void
|
||||
OnInfoMsg_(connection_t *c, char *msg)
|
||||
{
|
||||
if (strbegins(msg, "CR_TEXT:"))
|
||||
if (c == active_profile)
|
||||
{
|
||||
if (c == active_profile)
|
||||
{
|
||||
OnInfoMsg(c, msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
DetachOpenVPN(c); /* next attach will handle it */
|
||||
}
|
||||
OnInfoMsg(c, msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
DetachOpenVPN(c); /* next attach will handle it */
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -237,6 +230,9 @@ InitializeUI(HINSTANCE hinstance)
|
|||
*/
|
||||
o.service_state = service_connected;
|
||||
|
||||
/* PLAP always uses QR */
|
||||
o.use_qr_for_url = TRUE;
|
||||
|
||||
o.hInstance = hinstance;
|
||||
|
||||
DWORD status = WSAStartup(MAKEWORD(2, 2), &wsaData);
|
||||
|
|
|
@ -0,0 +1,207 @@
|
|||
/*
|
||||
* OpenVPN-GUI -- A Windows GUI for OpenVPN.
|
||||
*
|
||||
* Copyright (C) 2025 Lev Stipakov <lstipakov@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program (see the file COPYING included with this
|
||||
* distribution); if not, write to the Free Software Foundation, Inc.,
|
||||
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <windows.h>
|
||||
#include "qrcodegen/qrcodegen.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "main.h"
|
||||
#include "qr.h"
|
||||
#include "misc.h"
|
||||
|
||||
#include "openvpn-gui-res.h"
|
||||
|
||||
struct url_and_config_name
|
||||
{
|
||||
const wchar_t *url;
|
||||
const wchar_t *config_name;
|
||||
};
|
||||
|
||||
typedef struct url_and_config_name url_and_config_name_t;
|
||||
|
||||
/* global handle to be able to close the dialog from outside */
|
||||
static HWND g_hwndQR = NULL;
|
||||
|
||||
/* Generate QR bitmap from wide string */
|
||||
HBITMAP
|
||||
CreateQRBitmapFromWchar(const wchar_t *wtext, int scale)
|
||||
{
|
||||
char *utf8_text = WCharToUTF8(wtext);
|
||||
if (!utf8_text)
|
||||
return NULL;
|
||||
|
||||
uint8_t qrcode[qrcodegen_BUFFER_LEN_MAX];
|
||||
uint8_t temp[qrcodegen_BUFFER_LEN_MAX];
|
||||
|
||||
bool ok = qrcodegen_encodeText(utf8_text,
|
||||
temp,
|
||||
qrcode,
|
||||
qrcodegen_Ecc_LOW,
|
||||
qrcodegen_VERSION_MIN,
|
||||
qrcodegen_VERSION_MAX,
|
||||
qrcodegen_Mask_AUTO,
|
||||
true);
|
||||
free(utf8_text);
|
||||
if (!ok)
|
||||
return NULL;
|
||||
|
||||
int size = qrcodegen_getSize(qrcode);
|
||||
int imgSize = size * scale;
|
||||
|
||||
HDC hdcScreen = GetDC(NULL);
|
||||
HDC hdcMem = CreateCompatibleDC(hdcScreen);
|
||||
HBITMAP hBitmap = CreateCompatibleBitmap(hdcScreen, imgSize, imgSize);
|
||||
SelectObject(hdcMem, hBitmap);
|
||||
|
||||
RECT rc = { 0, 0, imgSize, imgSize };
|
||||
FillRect(hdcMem, &rc, (HBRUSH)GetStockObject(WHITE_BRUSH));
|
||||
|
||||
HBRUSH blackBrush = (HBRUSH)GetStockObject(BLACK_BRUSH);
|
||||
for (int y = 0; y < size; ++y)
|
||||
{
|
||||
for (int x = 0; x < size; ++x)
|
||||
{
|
||||
if (qrcodegen_getModule(qrcode, x, y))
|
||||
{
|
||||
RECT pixelRect = { x * scale, y * scale, (x + 1) * scale, (y + 1) * scale };
|
||||
FillRect(hdcMem, &pixelRect, blackBrush);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DeleteDC(hdcMem);
|
||||
ReleaseDC(NULL, hdcScreen);
|
||||
return hBitmap;
|
||||
}
|
||||
|
||||
INT_PTR CALLBACK
|
||||
QrDialogProc(HWND hwnd, UINT msg, UNUSED WPARAM wp, LPARAM lp)
|
||||
{
|
||||
static HBITMAP hQRBitmap = NULL;
|
||||
|
||||
switch (msg)
|
||||
{
|
||||
case WM_INITDIALOG:
|
||||
{
|
||||
url_and_config_name_t *uc = (url_and_config_name_t *)lp;
|
||||
|
||||
const int padding = 15;
|
||||
const int qrMaxSize = 300; /* max QR bitmap size in pixels */
|
||||
|
||||
/* Generate QR initially at largest acceptable size */
|
||||
int scale = 6;
|
||||
HBITMAP hQRBitmap = CreateQRBitmapFromWchar(uc->url, scale);
|
||||
if (!hQRBitmap)
|
||||
{
|
||||
MsgToEventLog(EVENTLOG_ERROR_TYPE, L"Failed to generate QR");
|
||||
EndDialog(hwnd, 0);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BITMAP bm;
|
||||
GetObject(hQRBitmap, sizeof(BITMAP), &bm);
|
||||
|
||||
/* Adjust scale down if bitmap too big */
|
||||
while ((bm.bmWidth > qrMaxSize) && scale > 1)
|
||||
{
|
||||
DeleteObject(hQRBitmap);
|
||||
scale--;
|
||||
hQRBitmap = CreateQRBitmapFromWchar(uc->url, scale);
|
||||
if (!hQRBitmap)
|
||||
{
|
||||
MsgToEventLog(EVENTLOG_ERROR_TYPE, L"Failed to generate QR");
|
||||
EndDialog(hwnd, 0);
|
||||
return TRUE;
|
||||
}
|
||||
GetObject(hQRBitmap, sizeof(BITMAP), &bm);
|
||||
}
|
||||
|
||||
int qrWidth = bm.bmWidth;
|
||||
int qrHeight = bm.bmHeight;
|
||||
|
||||
HWND hwndStaticQR = GetDlgItem(hwnd, ID_STATIC_QR);
|
||||
SetWindowPos(hwndStaticQR, NULL, padding, padding, qrWidth, qrHeight, SWP_NOZORDER);
|
||||
SendMessage(hwndStaticQR, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hQRBitmap);
|
||||
|
||||
/* Position text control below QR bitmap */
|
||||
HWND hwndStaticText = GetDlgItem(hwnd, ID_TXT_QR);
|
||||
int textWidth = qrWidth;
|
||||
int textHeight = 50;
|
||||
SetWindowPos(hwndStaticText,
|
||||
NULL,
|
||||
padding,
|
||||
qrHeight + padding + 10,
|
||||
textWidth,
|
||||
textHeight,
|
||||
SWP_NOZORDER);
|
||||
|
||||
/* Calculate total dialog size */
|
||||
int dialogWidth = qrWidth + padding * 2;
|
||||
int dialogHeight = qrHeight + textHeight + padding * 3;
|
||||
|
||||
RECT rcDialog = { 0, 0, dialogWidth, dialogHeight };
|
||||
AdjustWindowRectEx(
|
||||
&rcDialog, GetWindowLong(hwnd, GWL_STYLE), FALSE, GetWindowLong(hwnd, GWL_EXSTYLE));
|
||||
int dlgWidth = rcDialog.right - rcDialog.left;
|
||||
int dlgHeight = rcDialog.bottom - rcDialog.top;
|
||||
|
||||
/* Center the dialog on the screen */
|
||||
int posX = (GetSystemMetrics(SM_CXSCREEN) - dlgWidth) / 2;
|
||||
int posY = (GetSystemMetrics(SM_CYSCREEN) - dlgHeight) / 2;
|
||||
SetWindowPos(hwnd, NULL, posX, posY, dlgWidth, dlgHeight, SWP_NOZORDER);
|
||||
|
||||
SetWindowText(hwnd, uc->config_name);
|
||||
|
||||
g_hwndQR = hwnd;
|
||||
}
|
||||
return TRUE;
|
||||
|
||||
case WM_CLOSE:
|
||||
EndDialog(hwnd, 0);
|
||||
return TRUE;
|
||||
|
||||
case WM_DESTROY:
|
||||
if (hQRBitmap)
|
||||
DeleteObject(hQRBitmap);
|
||||
g_hwndQR = NULL;
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void
|
||||
CloseQRDialog()
|
||||
{
|
||||
if (g_hwndQR && IsWindow(g_hwndQR))
|
||||
PostMessage(g_hwndQR, WM_CLOSE, 0, 0);
|
||||
}
|
||||
|
||||
extern options_t o;
|
||||
|
||||
void
|
||||
OpenQRDialog(const wchar_t *url, const wchar_t *config_name)
|
||||
{
|
||||
url_and_config_name_t uc;
|
||||
uc.url = url;
|
||||
uc.config_name = config_name;
|
||||
DialogBoxParam(o.hInstance, MAKEINTRESOURCE(ID_DLG_QR), o.hWnd, QrDialogProc, (LPARAM)&uc);
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* OpenVPN-GUI -- A Windows GUI for OpenVPN.
|
||||
*
|
||||
* Copyright (C) 2025 Lev Stipakov <lstipakov@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program (see the file COPYING included with this
|
||||
* distribution); if not, write to the Free Software Foundation, Inc.,
|
||||
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
void CloseQRDialog();
|
||||
|
||||
void OpenQRDialog(const wchar_t *url, const wchar_t *config_name);
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,421 @@
|
|||
/*
|
||||
* QR Code generator library (C)
|
||||
*
|
||||
* Copyright (c) Project Nayuki. (MIT License)
|
||||
* https://www.nayuki.io/page/qr-code-generator-library
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* This library creates QR Code symbols, which is a type of two-dimension barcode.
|
||||
* Invented by Denso Wave and described in the ISO/IEC 18004 standard.
|
||||
* A QR Code structure is an immutable square grid of dark and light cells.
|
||||
* The library provides functions to create a QR Code from text or binary data.
|
||||
* The library covers the QR Code Model 2 specification, supporting all versions (sizes)
|
||||
* from 1 to 40, all 4 error correction levels, and 4 character encoding modes.
|
||||
*
|
||||
* Ways to create a QR Code object:
|
||||
* - High level: Take the payload data and call qrcodegen_encodeText() or
|
||||
* qrcodegen_encodeBinary().
|
||||
* - Low level: Custom-make the list of segments and call
|
||||
* qrcodegen_encodeSegments() or qrcodegen_encodeSegmentsAdvanced().
|
||||
* (Note that all ways require supplying the desired error correction level and various byte
|
||||
* buffers.)
|
||||
*/
|
||||
|
||||
|
||||
/*---- Enum and struct types----*/
|
||||
|
||||
/*
|
||||
* The error correction level in a QR Code symbol.
|
||||
*/
|
||||
enum qrcodegen_Ecc
|
||||
{
|
||||
// Must be declared in ascending order of error protection
|
||||
// so that an internal qrcodegen function works properly
|
||||
qrcodegen_Ecc_LOW = 0, // The QR Code can tolerate about 7% erroneous codewords
|
||||
qrcodegen_Ecc_MEDIUM, // The QR Code can tolerate about 15% erroneous codewords
|
||||
qrcodegen_Ecc_QUARTILE, // The QR Code can tolerate about 25% erroneous codewords
|
||||
qrcodegen_Ecc_HIGH, // The QR Code can tolerate about 30% erroneous codewords
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* The mask pattern used in a QR Code symbol.
|
||||
*/
|
||||
enum qrcodegen_Mask
|
||||
{
|
||||
// A special value to tell the QR Code encoder to
|
||||
// automatically select an appropriate mask pattern
|
||||
qrcodegen_Mask_AUTO = -1,
|
||||
// The eight actual mask patterns
|
||||
qrcodegen_Mask_0 = 0,
|
||||
qrcodegen_Mask_1,
|
||||
qrcodegen_Mask_2,
|
||||
qrcodegen_Mask_3,
|
||||
qrcodegen_Mask_4,
|
||||
qrcodegen_Mask_5,
|
||||
qrcodegen_Mask_6,
|
||||
qrcodegen_Mask_7,
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Describes how a segment's data bits are interpreted.
|
||||
*/
|
||||
enum qrcodegen_Mode
|
||||
{
|
||||
qrcodegen_Mode_NUMERIC = 0x1,
|
||||
qrcodegen_Mode_ALPHANUMERIC = 0x2,
|
||||
qrcodegen_Mode_BYTE = 0x4,
|
||||
qrcodegen_Mode_KANJI = 0x8,
|
||||
qrcodegen_Mode_ECI = 0x7,
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* A segment of character/binary/control data in a QR Code symbol.
|
||||
* The mid-level way to create a segment is to take the payload data
|
||||
* and call a factory function such as qrcodegen_makeNumeric().
|
||||
* The low-level way to create a segment is to custom-make the bit buffer
|
||||
* and initialize a qrcodegen_Segment struct with appropriate values.
|
||||
* Even in the most favorable conditions, a QR Code can only hold 7089 characters of data.
|
||||
* Any segment longer than this is meaningless for the purpose of generating QR Codes.
|
||||
* Moreover, the maximum allowed bit length is 32767 because
|
||||
* the largest QR Code (version 40) has 31329 modules.
|
||||
*/
|
||||
struct qrcodegen_Segment
|
||||
{
|
||||
// The mode indicator of this segment.
|
||||
enum qrcodegen_Mode mode;
|
||||
|
||||
// The length of this segment's unencoded data. Measured in characters for
|
||||
// numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode.
|
||||
// Always zero or positive. Not the same as the data's bit length.
|
||||
int numChars;
|
||||
|
||||
// The data bits of this segment, packed in bitwise big endian.
|
||||
// Can be null if the bit length is zero.
|
||||
uint8_t *data;
|
||||
|
||||
// The number of valid data bits used in the buffer. Requires
|
||||
// 0 <= bitLength <= 32767, and bitLength <= (capacity of data array) * 8.
|
||||
// The character count (numChars) must agree with the mode and the bit buffer length.
|
||||
int bitLength;
|
||||
};
|
||||
|
||||
|
||||
/*---- Macro constants and functions ----*/
|
||||
|
||||
#define qrcodegen_VERSION_MIN \
|
||||
1 // The minimum version number supported in the QR Code Model 2 standard
|
||||
#define qrcodegen_VERSION_MAX \
|
||||
40 // The maximum version number supported in the QR Code Model 2 standard
|
||||
|
||||
// Calculates the number of bytes needed to store any QR Code up to and including the given version
|
||||
// number, as a compile-time constant. For example, 'uint8_t
|
||||
// buffer[qrcodegen_BUFFER_LEN_FOR_VERSION(25)];' can store any single QR Code from version 1 to 25
|
||||
// (inclusive). The result fits in an int (or int16). Requires qrcodegen_VERSION_MIN <= n <=
|
||||
// qrcodegen_VERSION_MAX.
|
||||
#define qrcodegen_BUFFER_LEN_FOR_VERSION(n) ((((n) * 4 + 17) * ((n) * 4 + 17) + 7) / 8 + 1)
|
||||
|
||||
// The worst-case number of bytes needed to store one QR Code, up to and including
|
||||
// version 40. This value equals 3918, which is just under 4 kilobytes.
|
||||
// Use this more convenient value to avoid calculating tighter memory bounds for buffers.
|
||||
#define qrcodegen_BUFFER_LEN_MAX qrcodegen_BUFFER_LEN_FOR_VERSION(qrcodegen_VERSION_MAX)
|
||||
|
||||
|
||||
/*---- Functions (high level) to generate QR Codes ----*/
|
||||
|
||||
/*
|
||||
* Encodes the given text string to a QR Code, returning true if successful.
|
||||
* If the data is too long to fit in any version in the given range
|
||||
* at the given ECC level, then false is returned.
|
||||
*
|
||||
* The input text must be encoded in UTF-8 and contain no NULs.
|
||||
* Requires 1 <= minVersion <= maxVersion <= 40.
|
||||
*
|
||||
* The smallest possible QR Code version within the given range is automatically
|
||||
* chosen for the output. Iff boostEcl is true, then the ECC level of the result
|
||||
* may be higher than the ecl argument if it can be done without increasing the
|
||||
* version. The mask is either between qrcodegen_Mask_0 to 7 to force that mask, or
|
||||
* qrcodegen_Mask_AUTO to automatically choose an appropriate mask (which may be slow).
|
||||
*
|
||||
* About the arrays, letting len = qrcodegen_BUFFER_LEN_FOR_VERSION(maxVersion):
|
||||
* - Before calling the function:
|
||||
* - The array ranges tempBuffer[0 : len] and qrcode[0 : len] must allow
|
||||
* reading and writing; hence each array must have a length of at least len.
|
||||
* - The two ranges must not overlap (aliasing).
|
||||
* - The initial state of both ranges can be uninitialized
|
||||
* because the function always writes before reading.
|
||||
* - After the function returns:
|
||||
* - Both ranges have no guarantee on which elements are initialized and what values are
|
||||
* stored.
|
||||
* - tempBuffer contains no useful data and should be treated as entirely uninitialized.
|
||||
* - If successful, qrcode can be passed into qrcodegen_getSize() and qrcodegen_getModule().
|
||||
*
|
||||
* If successful, the resulting QR Code may use numeric,
|
||||
* alphanumeric, or byte mode to encode the text.
|
||||
*
|
||||
* In the most optimistic case, a QR Code at version 40 with low ECC
|
||||
* can hold any UTF-8 string up to 2953 bytes, or any alphanumeric string
|
||||
* up to 4296 characters, or any digit string up to 7089 characters.
|
||||
* These numbers represent the hard upper limit of the QR Code standard.
|
||||
*
|
||||
* Please consult the QR Code specification for information on
|
||||
* data capacities per version, ECC level, and text encoding mode.
|
||||
*/
|
||||
bool qrcodegen_encodeText(const char *text,
|
||||
uint8_t tempBuffer[],
|
||||
uint8_t qrcode[],
|
||||
enum qrcodegen_Ecc ecl,
|
||||
int minVersion,
|
||||
int maxVersion,
|
||||
enum qrcodegen_Mask mask,
|
||||
bool boostEcl);
|
||||
|
||||
|
||||
/*
|
||||
* Encodes the given binary data to a QR Code, returning true if successful.
|
||||
* If the data is too long to fit in any version in the given range
|
||||
* at the given ECC level, then false is returned.
|
||||
*
|
||||
* Requires 1 <= minVersion <= maxVersion <= 40.
|
||||
*
|
||||
* The smallest possible QR Code version within the given range is automatically
|
||||
* chosen for the output. Iff boostEcl is true, then the ECC level of the result
|
||||
* may be higher than the ecl argument if it can be done without increasing the
|
||||
* version. The mask is either between qrcodegen_Mask_0 to 7 to force that mask, or
|
||||
* qrcodegen_Mask_AUTO to automatically choose an appropriate mask (which may be slow).
|
||||
*
|
||||
* About the arrays, letting len = qrcodegen_BUFFER_LEN_FOR_VERSION(maxVersion):
|
||||
* - Before calling the function:
|
||||
* - The array ranges dataAndTemp[0 : len] and qrcode[0 : len] must allow
|
||||
* reading and writing; hence each array must have a length of at least len.
|
||||
* - The two ranges must not overlap (aliasing).
|
||||
* - The input array range dataAndTemp[0 : dataLen] should normally be
|
||||
* valid UTF-8 text, but is not required by the QR Code standard.
|
||||
* - The initial state of dataAndTemp[dataLen : len] and qrcode[0 : len]
|
||||
* can be uninitialized because the function always writes before reading.
|
||||
* - After the function returns:
|
||||
* - Both ranges have no guarantee on which elements are initialized and what values are
|
||||
* stored.
|
||||
* - dataAndTemp contains no useful data and should be treated as entirely uninitialized.
|
||||
* - If successful, qrcode can be passed into qrcodegen_getSize() and qrcodegen_getModule().
|
||||
*
|
||||
* If successful, the resulting QR Code will use byte mode to encode the data.
|
||||
*
|
||||
* In the most optimistic case, a QR Code at version 40 with low ECC can hold any byte
|
||||
* sequence up to length 2953. This is the hard upper limit of the QR Code standard.
|
||||
*
|
||||
* Please consult the QR Code specification for information on
|
||||
* data capacities per version, ECC level, and text encoding mode.
|
||||
*/
|
||||
bool qrcodegen_encodeBinary(uint8_t dataAndTemp[],
|
||||
size_t dataLen,
|
||||
uint8_t qrcode[],
|
||||
enum qrcodegen_Ecc ecl,
|
||||
int minVersion,
|
||||
int maxVersion,
|
||||
enum qrcodegen_Mask mask,
|
||||
bool boostEcl);
|
||||
|
||||
|
||||
/*---- Functions (low level) to generate QR Codes ----*/
|
||||
|
||||
/*
|
||||
* Encodes the given segments to a QR Code, returning true if successful.
|
||||
* If the data is too long to fit in any version at the given ECC level,
|
||||
* then false is returned.
|
||||
*
|
||||
* The smallest possible QR Code version is automatically chosen for
|
||||
* the output. The ECC level of the result may be higher than the
|
||||
* ecl argument if it can be done without increasing the version.
|
||||
*
|
||||
* About the byte arrays, letting len = qrcodegen_BUFFER_LEN_FOR_VERSION(qrcodegen_VERSION_MAX):
|
||||
* - Before calling the function:
|
||||
* - The array ranges tempBuffer[0 : len] and qrcode[0 : len] must allow
|
||||
* reading and writing; hence each array must have a length of at least len.
|
||||
* - The two ranges must not overlap (aliasing).
|
||||
* - The initial state of both ranges can be uninitialized
|
||||
* because the function always writes before reading.
|
||||
* - The input array segs can contain segments whose data buffers overlap with tempBuffer.
|
||||
* - After the function returns:
|
||||
* - Both ranges have no guarantee on which elements are initialized and what values are
|
||||
* stored.
|
||||
* - tempBuffer contains no useful data and should be treated as entirely uninitialized.
|
||||
* - Any segment whose data buffer overlaps with tempBuffer[0 : len]
|
||||
* must be treated as having invalid values in that array.
|
||||
* - If successful, qrcode can be passed into qrcodegen_getSize() and qrcodegen_getModule().
|
||||
*
|
||||
* Please consult the QR Code specification for information on
|
||||
* data capacities per version, ECC level, and text encoding mode.
|
||||
*
|
||||
* This function allows the user to create a custom sequence of segments that switches
|
||||
* between modes (such as alphanumeric and byte) to encode text in less space.
|
||||
* This is a low-level API; the high-level API is qrcodegen_encodeText() and
|
||||
* qrcodegen_encodeBinary().
|
||||
*/
|
||||
bool qrcodegen_encodeSegments(const struct qrcodegen_Segment segs[],
|
||||
size_t len,
|
||||
enum qrcodegen_Ecc ecl,
|
||||
uint8_t tempBuffer[],
|
||||
uint8_t qrcode[]);
|
||||
|
||||
|
||||
/*
|
||||
* Encodes the given segments to a QR Code, returning true if successful.
|
||||
* If the data is too long to fit in any version in the given range
|
||||
* at the given ECC level, then false is returned.
|
||||
*
|
||||
* Requires 1 <= minVersion <= maxVersion <= 40.
|
||||
*
|
||||
* The smallest possible QR Code version within the given range is automatically
|
||||
* chosen for the output. Iff boostEcl is true, then the ECC level of the result
|
||||
* may be higher than the ecl argument if it can be done without increasing the
|
||||
* version. The mask is either between qrcodegen_Mask_0 to 7 to force that mask, or
|
||||
* qrcodegen_Mask_AUTO to automatically choose an appropriate mask (which may be slow).
|
||||
*
|
||||
* About the byte arrays, letting len = qrcodegen_BUFFER_LEN_FOR_VERSION(qrcodegen_VERSION_MAX):
|
||||
* - Before calling the function:
|
||||
* - The array ranges tempBuffer[0 : len] and qrcode[0 : len] must allow
|
||||
* reading and writing; hence each array must have a length of at least len.
|
||||
* - The two ranges must not overlap (aliasing).
|
||||
* - The initial state of both ranges can be uninitialized
|
||||
* because the function always writes before reading.
|
||||
* - The input array segs can contain segments whose data buffers overlap with tempBuffer.
|
||||
* - After the function returns:
|
||||
* - Both ranges have no guarantee on which elements are initialized and what values are
|
||||
* stored.
|
||||
* - tempBuffer contains no useful data and should be treated as entirely uninitialized.
|
||||
* - Any segment whose data buffer overlaps with tempBuffer[0 : len]
|
||||
* must be treated as having invalid values in that array.
|
||||
* - If successful, qrcode can be passed into qrcodegen_getSize() and qrcodegen_getModule().
|
||||
*
|
||||
* Please consult the QR Code specification for information on
|
||||
* data capacities per version, ECC level, and text encoding mode.
|
||||
*
|
||||
* This function allows the user to create a custom sequence of segments that switches
|
||||
* between modes (such as alphanumeric and byte) to encode text in less space.
|
||||
* This is a low-level API; the high-level API is qrcodegen_encodeText() and
|
||||
* qrcodegen_encodeBinary().
|
||||
*/
|
||||
bool qrcodegen_encodeSegmentsAdvanced(const struct qrcodegen_Segment segs[],
|
||||
size_t len,
|
||||
enum qrcodegen_Ecc ecl,
|
||||
int minVersion,
|
||||
int maxVersion,
|
||||
enum qrcodegen_Mask mask,
|
||||
bool boostEcl,
|
||||
uint8_t tempBuffer[],
|
||||
uint8_t qrcode[]);
|
||||
|
||||
|
||||
/*
|
||||
* Tests whether the given string can be encoded as a segment in numeric mode.
|
||||
* A string is encodable iff each character is in the range 0 to 9.
|
||||
*/
|
||||
bool qrcodegen_isNumeric(const char *text);
|
||||
|
||||
|
||||
/*
|
||||
* Tests whether the given string can be encoded as a segment in alphanumeric mode.
|
||||
* A string is encodable iff each character is in the following set: 0 to 9, A to Z
|
||||
* (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon.
|
||||
*/
|
||||
bool qrcodegen_isAlphanumeric(const char *text);
|
||||
|
||||
|
||||
/*
|
||||
* Returns the number of bytes (uint8_t) needed for the data buffer of a segment
|
||||
* containing the given number of characters using the given mode. Notes:
|
||||
* - Returns SIZE_MAX on failure, i.e. numChars > INT16_MAX or the internal
|
||||
* calculation of the number of needed bits exceeds INT16_MAX (i.e. 32767).
|
||||
* - Otherwise, all valid results are in the range [0, ceil(INT16_MAX / 8)], i.e. at most 4096.
|
||||
* - It is okay for the user to allocate more bytes for the buffer than needed.
|
||||
* - For byte mode, numChars measures the number of bytes, not Unicode code points.
|
||||
* - For ECI mode, numChars must be 0, and the worst-case number of bytes is returned.
|
||||
* An actual ECI segment can have shorter data. For non-ECI modes, the result is exact.
|
||||
*/
|
||||
size_t qrcodegen_calcSegmentBufferSize(enum qrcodegen_Mode mode, size_t numChars);
|
||||
|
||||
|
||||
/*
|
||||
* Returns a segment representing the given binary data encoded in
|
||||
* byte mode. All input byte arrays are acceptable. Any text string
|
||||
* can be converted to UTF-8 bytes and encoded as a byte mode segment.
|
||||
*/
|
||||
struct qrcodegen_Segment qrcodegen_makeBytes(const uint8_t data[], size_t len, uint8_t buf[]);
|
||||
|
||||
|
||||
/*
|
||||
* Returns a segment representing the given string of decimal digits encoded in numeric mode.
|
||||
*/
|
||||
struct qrcodegen_Segment qrcodegen_makeNumeric(const char *digits, uint8_t buf[]);
|
||||
|
||||
|
||||
/*
|
||||
* Returns a segment representing the given text string encoded in alphanumeric mode.
|
||||
* The characters allowed are: 0 to 9, A to Z (uppercase only), space,
|
||||
* dollar, percent, asterisk, plus, hyphen, period, slash, colon.
|
||||
*/
|
||||
struct qrcodegen_Segment qrcodegen_makeAlphanumeric(const char *text, uint8_t buf[]);
|
||||
|
||||
|
||||
/*
|
||||
* Returns a segment representing an Extended Channel Interpretation
|
||||
* (ECI) designator with the given assignment value.
|
||||
*/
|
||||
struct qrcodegen_Segment qrcodegen_makeEci(long assignVal, uint8_t buf[]);
|
||||
|
||||
|
||||
/*---- Functions to extract raw data from QR Codes ----*/
|
||||
|
||||
/*
|
||||
* Returns the side length of the given QR Code, assuming that encoding succeeded.
|
||||
* The result is in the range [21, 177]. Note that the length of the array buffer
|
||||
* is related to the side length - every 'uint8_t qrcode[]' must have length at least
|
||||
* qrcodegen_BUFFER_LEN_FOR_VERSION(version), which equals ceil(size^2 / 8 + 1).
|
||||
*/
|
||||
int qrcodegen_getSize(const uint8_t qrcode[]);
|
||||
|
||||
|
||||
/*
|
||||
* Returns the color of the module (pixel) at the given coordinates, which is false
|
||||
* for light or true for dark. The top left corner has the coordinates (x=0, y=0).
|
||||
* If the given coordinates are out of bounds, then false (light) is returned.
|
||||
*/
|
||||
bool qrcodegen_getModule(const uint8_t qrcode[], int x, int y);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
|
@ -319,6 +319,14 @@ BEGIN
|
|||
LTEXT "", ID_TXT_WARNING, 6, 222, 190, 10
|
||||
END
|
||||
|
||||
ID_DLG_QR DIALOGEX 0, 0, 10, 10
|
||||
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
|
||||
FONT 8, "Segoe UI"
|
||||
BEGIN
|
||||
CONTROL "", ID_STATIC_QR, "Static", SS_BITMAP | WS_VISIBLE | SS_CENTERIMAGE, 0, 0, 10, 10
|
||||
LTEXT "Scan this QR code on your mobile to proceed with authentication.", ID_TXT_QR, 0, 0, 10, 10, SS_CENTER | WS_VISIBLE | SS_EDITCONTROL
|
||||
END
|
||||
|
||||
STRINGTABLE
|
||||
LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT
|
||||
BEGIN
|
||||
|
|
Loading…
Reference in New Issue