Skip to content

Instantly share code, notes, and snippets.

@ageis
Last active November 23, 2022 02:08
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ageis/025736fbfb296c8729f6461a607ec90e to your computer and use it in GitHub Desktop.
Save ageis/025736fbfb296c8729f6461a607ec90e to your computer and use it in GitHub Desktop.
A utility to display QR codes in the CLI/terminal. To build, run `gcc showqrcode.c -ldl -o showqrcode`
// showqrcode: A utility to display QR codes in the terminal.
// Copyright © 2019 Kevin Gallagher <kevingallagher@gmail.com>
// Modified and based upon original code from libpam-google-authenticator:
// https://github.com/google/google-authenticator-libpam
// The original license is printed below.
//
// Helper program to generate a new secret for use in two-factor
// authentication.
//
// Copyright 2010 Google Inc.
// Author: Markus Gutschke
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <dlfcn.h>
#include <getopt.h>
#include <stdio.h>
#include <stdio_ext.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <regex.h>
#include <locale.h>
#define ANSI_RESET "\x1B[0m"
#define ANSI_BLACKONGREY "\x1B[30;47;27m"
#define ANSI_WHITE "\x1B[27m"
#define ANSI_BLACK "\x1B[7m"
#define UTF8_BOTH "\xE2\x96\x88"
#define UTF8_TOPHALF "\xE2\x96\x80"
#define UTF8_BOTTOMHALF "\xE2\x96\x84"
static enum { QR_UNSET=0, QR_NONE, QR_ANSI, QR_UTF8 } qr_mode = QR_UTF8;
static int displayQRCode(const char* url) {
FILE * out = stdout;
setbuf(out, NULL);
void *qrencode = dlopen("libqrencode.so.4", RTLD_NOW | RTLD_LOCAL);
if (!qrencode) {
qrencode = dlopen("libqrencode.so.3", RTLD_NOW | RTLD_LOCAL);
}
if (!qrencode) {
qrencode = dlopen("libqrencode.3.dylib", RTLD_NOW | RTLD_LOCAL);
}
if (!qrencode) {
qrencode = dlopen("libqrencode.so.2", RTLD_NOW | RTLD_LOCAL);
}
if (!qrencode) {
return 0;
}
typedef struct {
int version;
int width;
unsigned char *data;
} QRcode;
QRcode *(*QRcode_encodeString8bit)(const char *, int, int) =
(QRcode *(*)(const char *, int, int))
dlsym(qrencode, "QRcode_encodeString8bit");
void (*QRcode_free)(QRcode *qrcode) =
(void (*)(QRcode *))dlsym(qrencode, "QRcode_free");
if (!QRcode_encodeString8bit || !QRcode_free) {
dlclose(qrencode);
return 0;
}
QRcode *qrcode = QRcode_encodeString8bit(url, 0, 1);
const char *ptr = (char *)qrcode->data;
if (qr_mode == QR_UTF8) {
fprintf(out, "%s", ANSI_BLACKONGREY);
for (int i = 0; i < qrcode->width + 4; ++i) {
printf(" ");
}
puts(ANSI_RESET);
for (int y = 0; y < qrcode->width; y += 2) {
fprintf(out, "%s", ANSI_BLACKONGREY" ");
for (int x = 0; x < qrcode->width; ++x) {
const int top = qrcode->data[y*qrcode->width + x] & 1;
int bottom = 0;
if (y+1 < qrcode->width) {
bottom = qrcode->data[(y+1)*qrcode->width + x] & 1;
}
if (top) {
if (bottom) {
fprintf(out, "%s", UTF8_BOTH);
} else {
fprintf(out, "%s", UTF8_TOPHALF);
}
} else {
if (bottom) {
fprintf(out, "%s", UTF8_BOTTOMHALF);
} else {
printf(" ");
}
}
}
puts(" "ANSI_RESET);
}
fprintf(out, "%s", ANSI_BLACKONGREY);
for (int i = 0; i < qrcode->width + 4; ++i) {
printf(" ");
}
puts(ANSI_RESET);
fflush(out);
}
QRcode_free(qrcode);
dlclose(qrencode);
return 1;
}
static void usage(void) {
puts(
"showqrcode [<options>]\n"
" -h, --help Print this message\n"
" -d, --data=<string> Encode arbitrary strings\n"
" -p, --pipe Accept input from STDIN\n"
" -u, --url=<otpauth://> Supply the \"otpauth://\" URL to encode\n");
}
int main(int argc, char *argv[]) {
int opt;
int idx = -1;
char *url = NULL;
char *data = NULL;
char str[256];
int otp_regex;
int d = 0, p = 0, u = 0;
regex_t regex;
regmatch_t rematch[2];
size_t nmatch = 2;
setlocale(LC_ALL, "en_US.UTF-8");
if(argc <= 1) {
fprintf(stderr, "Error: Expected at least one argument.\n");
usage();
_exit(1);
}
otp_regex = regcomp(&regex, "^otpauth://.*", 0);
static struct option options[] = {
{ "help", 0, 0, 'h' },
{ "data", 1, 0, 'd' },
{ "pipe", 0, 0, 'p' },
{ "url", 1, 0, 'u' },
{ 0, 0, 0, 0 }
};
static const char optstring[] = "+h:d:pu:";
while (opt = getopt_long(argc, argv, "+h:d:pu:", options, NULL), opt != -1) {
switch (opt) {
case '0':
if (options[opt].val != 0) {
break;
}
if (optarg) {
break;
}
case '-':
break;
case 'h':
usage();
exit(0);
break;
case 'd':
data = strdup(optarg);
d = 1;
break;
case 'p':
data = fgets(str, 256, stdin);
p = 1;
break;
case 'u':
url = strdup(optarg);
u = 1;
otp_regex = regexec(&regex, url, 0, NULL, 0);
if (otp_regex) {
fprintf(stderr, "The input URL %s is not in the correct format.\n", url);
_exit(1);
}
if (strlen(url) < 16) {
fprintf(stderr, "The input URL %s does not contain enough data.\n", url);
_exit(1);
}
break;
case '?':
usage();
_exit(1);
break;
default:
fprintf(stderr, "Error: getopt returned unexpected value.\n");
}
if ((d && u) || (d && p) || (p && u)) {
fprintf(stderr, "Error: you may only specify one mode at a time.\n");
usage();
_exit(1);
}
}
if (data) {
url = strdup(data);
} else if ((!url) && (!data)) {
fprintf(stderr, "Error: No input to encode.\n");
_exit(1);
}
//if (isatty(1)) {
if (!displayQRCode(url)) {
printf(
"Failed to use libqrencode to show QR code visually for scanning.\n"
"Consider typing the OTP secret into your app manually.\n");
_exit(1);
}
//} else {
// _exit(1);
//}
fclose(stdout);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment