Restore openssl dependency (required by curl anyway) and use openssl to
do the MD5 hashes. Remove more code obsoleted by curl; base64encode,
decodechunked, and the HTTP auth code in digcalc.
This commit is contained in:
Mike Sharov 2021-04-11 12:52:08 -04:00
parent e41256a853
commit c8df7b657d
11 changed files with 26 additions and 396 deletions

View File

@ -2,7 +2,7 @@ Snownews
========
Snownews is a command-line RSS feed reader, originally written by
[Oliver Feiler](https://github.com/kouya) (@kouya).
[Oliver Feiler](https://github.com/kouya).
It is designed to be simple and lightweight, and integrates well with
other command-line tools, for both generating and filtering the feeds
it reads.
@ -14,9 +14,8 @@ Features
* Fast and very resource friendly.
* Downloads feeds using libcurl to support a variety of URL types.
* Uses local cache for minimal network traffic.
* Supports cookies.
* A help menu available throughout the program.
* Few dependencies on external libraries; ncurses, libcurl, and libxml2.
* Few dependencies on external libraries; ncurses, libcurl, openssl, and libxml2.
* Import feature for OPML subscription lists.
* Fully customizable key bindings of all program functions.
* Type Ahead Find for quick and easy navigation.
@ -32,19 +31,17 @@ You will need the following:
- ncurses 5.0+
- libcurl
- libxml2
- openssl 1.1+
- gettext
Once you have the above dependencies installed:
Once you have these dependencies installed:
```sh
./configure --prefix=/usr
make install
```
By default, this will install Snownews into `/usr/local`. If you
prefer it to go somewhere else, set the `./configure --prefix=DIR`
parameter. `configure --help` will list other options that you may
find interesting.
`configure --help` will list other options that you may find interesting.
Localization
------------

View File

@ -33,9 +33,6 @@
#define SNOWNEWS_CONFIG_DIR "%s/.snownews/"
#define SNOWNEWS_CACHE_DIR SNOWNEWS_CONFIG_DIR "cache/"
// Define to use experimental code
#undef USE_UNSUPPORTED_AND_BROKEN_CODE
// Define to output of uname
#undef OS

4
configure vendored
View File

@ -34,9 +34,9 @@ seds=[s/^#undef \(USE_UNSUPPORTED_AND_BROKEN_CODE\)/#define \1/]
progs="CC=gcc CC=clang CC=cc INSTALL=install MSGFMT=msgfmt"
# Libs found using pkg-config
pkgs="libcurl libxml-2.0 ncurses"
pkgs="libcurl libxml-2.0 libcrypto ncurses"
# Default pkg flags to substitute when pkg-config is not found
pkg_libs="-lcurl -lxml2 -lncursesw"
pkg_libs="-lcurl -lxml2 -lcrypto -lncursesw"
pkg_cflags="-I\/usr\/include\/libxml2"
pkg_ldflags=""

View File

@ -2,6 +2,7 @@
//
// Copyright (c) 2003-2009 Oliver Feiler <kiza@kcore.de>
// Copyright (c) 2003-2009 Rene Puls <rpuls@gmx.net>
// Copyright (c) 2021 Mike Sharov <msharov@users.sourceforge.net>
//
// Snownews is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 3
@ -17,11 +18,12 @@
#include "main.h"
#include "conversions.h"
#include "md5.h"
#include "ui-support.h"
#include <iconv.h>
#include <libxml/HTMLparser.h>
#include <langinfo.h>
#include <openssl/evp.h>
#include <openssl/md5.h>
//----------------------------------------------------------------------
@ -342,86 +344,6 @@ char* WrapText (const char* text, unsigned width)
return newtext;
}
char* base64encode (const char* inbuf, unsigned inbuf_size)
{
static unsigned char const alphabet[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
unsigned inbuf_pos = 0, outbuf_pos = 0, outbuf_size = 0, bits = 0, char_count = 0;
char* outbuf = malloc (1);
while (inbuf_pos < inbuf_size) {
bits |= *inbuf;
++char_count;
if (char_count == 3) {
outbuf = realloc (outbuf, outbuf_size + 4);
outbuf_size += 4;
outbuf[outbuf_pos + 0] = alphabet[bits >> 18];
outbuf[outbuf_pos + 1] = alphabet[(bits >> 12) & 0x3f];
outbuf[outbuf_pos + 2] = alphabet[(bits >> 6) & 0x3f];
outbuf[outbuf_pos + 3] = alphabet[bits & 0x3f];
outbuf_pos += 4;
bits = 0;
char_count = 0;
}
++inbuf;
++inbuf_pos;
bits <<= 8;
}
if (char_count > 0) {
bits <<= 16 - (8 * char_count);
outbuf = realloc (outbuf, outbuf_size + 4);
outbuf_size += 4;
outbuf[outbuf_pos + 0] = alphabet[bits >> 18];
outbuf[outbuf_pos + 1] = alphabet[(bits >> 12) & 0x3f];
if (char_count == 1) {
outbuf[outbuf_pos + 2] = '=';
outbuf[outbuf_pos + 3] = '=';
} else {
outbuf[outbuf_pos + 2] = alphabet[(bits >> 6) & 0x3f];
outbuf[outbuf_pos + 3] = '=';
}
outbuf_pos += 4;
}
outbuf = realloc (outbuf, outbuf_size + 1);
outbuf[outbuf_pos] = 0;
return outbuf;
}
#ifdef USE_UNSUPPORTED_AND_BROKEN_CODE
// Returns NULL on invalid input
char* decodechunked (char* chunked, unsigned int* inputlen)
{
#warning ===The function decodedechunked() is not safe for binary data. Since you specifically requested it to be compiled in you probably know better what you are doing than me. Do not report bugs for this code.===
char* orig = chunked, *dest = chunked;
unsigned long chunklen;
// We can reuse the same buffer to dechunkify it:
// the data size will never increase.
while ((chunklen = strtoul (orig, &orig, 16))) {
// process one more chunk:
// skip chunk-extension part
while (*orig && (*orig != '\r'))
++orig;
// skip '\r\n' after chunk length
orig += 2;
if ((chunklen > (chunked + *inputlen - orig)))
// insane chunk length. Well...
return NULL;
memmove (dest, orig, chunklen);
dest += chunklen;
orig += chunklen;
// and go to the next chunk
}
*dest = '\0';
*inputlen = dest - chunked;
return chunked;
}
#endif
// Remove leading whitspaces, newlines, tabs.
// This function should be safe for working on UTF-8 strings.
// tidyness: 0 = only suck chars from beginning of string
@ -484,14 +406,21 @@ char* Hashify (const char* url)
char* genItemHash (const char* const* hashitems, unsigned items)
{
struct HashMD5 hash;
hash_md5_init (&hash);
EVP_MD_CTX* mdctx = EVP_MD_CTX_new();
EVP_DigestInit (mdctx, EVP_md5());
for (unsigned i = 0; i < items; ++i)
if (hashitems[i])
hash_md5_data (&hash, hashitems[i], strlen (hashitems[i]));
hash_md5_finish (&hash);
char hashtext[HASH_SIZE_MD5 * 2 + 1];
hash_md5_to_text (&hash, hashtext);
EVP_DigestUpdate (mdctx, hashitems[i], strlen (hashitems[i]));
unsigned char md_value[EVP_MAX_MD_SIZE];
unsigned md_len = 0;
EVP_DigestFinal_ex (mdctx, md_value, &md_len);
EVP_MD_CTX_free (mdctx);
char hashtext [MD5_DIGEST_LENGTH*2 + 1];
for (unsigned i = 0; i < md_len; ++i)
sprintf (&hashtext[2*i], "%02hhx", md_value[i]);
return strdup (hashtext);
}

View File

@ -2,6 +2,7 @@
//
// Copyright (c) 2003-2004 Oliver Feiler <kiza@kcore.de>
// Copyright (c) 2003-2004 Rene Puls <rpuls@gmx.net>
// Copyright (c) 2021 Mike Sharov <msharov@users.sourceforge.net>
//
// Snownews is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 3
@ -20,14 +21,9 @@
char* iconvert (const char* inbuf);
char* UIDejunk (const char* feed_description);
char* WrapText (const char* text, unsigned width);
char* base64encode (const char* inbuf, unsigned int inbuf_size);
void CleanupString (char* string, int tidyness);
char* Hashify (const char* url);
char* genItemHash (const char* const* hashitems, unsigned items);
time_t ISODateToUnix (const char* ISODate);
time_t pubDateToUnix (const char* pubDate);
char* unixToPostDateString (time_t unixDate);
#ifdef USE_UNSUPPORTED_AND_BROKEN_CODE
char* decodechunked (char* chunked, unsigned int* inputlen);
#endif

View File

@ -1,89 +0,0 @@
// This file is part of Snownews - A lightweight console RSS newsreader
//
// Copyright (c) 2003-2004 Oliver Feiler <kiza@kcore.de>
//
// Snownews is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 3
// as published by the Free Software Foundation.
//
// Snownews 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 Snownews. If not, see http://www.gnu.org/licenses/.
// This is the sample implementation from RFC 2617.
// The code has been modified to work with Colin Plumb's
// MD5 implementation rather than using RSA's.
#include "digcalc.h"
// calculate H(A1) as per spec
void DigestCalcHA1 (const char* pszAlg, const char* pszUserName, const char* pszRealm, const char* pszPassword, const char* pszNonce, const char* pszCNonce, HASHHEX SessionKey)
{
struct HashMD5 hash;
hash_md5_init (&hash);
hash_md5_data (&hash, pszUserName, strlen (pszUserName));
hash_md5_data (&hash, ":", 1);
hash_md5_data (&hash, pszRealm, strlen (pszRealm));
hash_md5_data (&hash, ":", 1);
hash_md5_data (&hash, pszPassword, strlen (pszPassword));
hash_md5_finish (&hash);
if (strcmp (pszAlg, "md5-sess") == 0) {
hash_md5_init (&hash);
hash_md5_data (&hash, hash.hash, sizeof (hash.hash));
hash_md5_data (&hash, ":", 1);
hash_md5_data (&hash, pszNonce, strlen (pszNonce));
hash_md5_data (&hash, ":", 1);
hash_md5_data (&hash, pszCNonce, strlen (pszCNonce));
hash_md5_finish (&hash);
}
hash_md5_to_text (&hash, SessionKey);
}
// calculate request-digest/response-digest as per HTTP Digest spec
void DigestCalcResponse (const HASHHEX HA1, // H(A1)
const char* pszNonce, // nonce from server
const char* pszNonceCount, // 8 hex digits
const char* pszCNonce, // client nonce
const char* pszQop, // qop-value: "", "auth", "auth-int"
const char* pszMethod, // method from the request
const char* pszDigestUri, // requested URL
const HASHHEX HEntity, // H(entity body) if qop="auth-int"
HASHHEX Response // request-digest or response-digest
)
{
// calculate H(A2)
struct HashMD5 hash;
hash_md5_init (&hash);
hash_md5_data (&hash, pszMethod, strlen (pszMethod));
hash_md5_data (&hash, ":", 1);
hash_md5_data (&hash, pszDigestUri, strlen (pszDigestUri));
if (strcmp (pszQop, "auth-int") == 0) {
hash_md5_data (&hash, ":", 1);
hash_md5_data (&hash, HEntity, HASHHEXLEN);
}
hash_md5_finish (&hash);
HASHHEX HA2Hex;
hash_md5_to_text (&hash, HA2Hex);
// calculate response
hash_md5_init (&hash);
hash_md5_data (&hash, HA1, HASHHEXLEN);
hash_md5_data (&hash, ":", 1);
hash_md5_data (&hash, pszNonce, strlen (pszNonce));
hash_md5_data (&hash, ":", 1);
if (*pszQop) {
hash_md5_data (&hash, pszNonceCount, strlen (pszNonceCount));
hash_md5_data (&hash, ":", 1);
hash_md5_data (&hash, pszCNonce, strlen (pszCNonce));
hash_md5_data (&hash, ":", 1);
hash_md5_data (&hash, pszQop, strlen (pszQop));
hash_md5_data (&hash, ":", 1);
};
hash_md5_data (&hash, HA2Hex, HASHHEXLEN);
hash_md5_finish (&hash);
hash_md5_to_text (&hash, Response);
}

View File

@ -1,40 +0,0 @@
// This file is part of Snownews - A lightweight console RSS newsreader
//
// Copyright (c) 2003-2004 Oliver Feiler <kiza@kcore.de>
//
// Snownews is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 3
// as published by the Free Software Foundation.
//
// Snownews 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 Snownews. If not, see http://www.gnu.org/licenses/.
// This is the sample implementation from RFC 2617.
// The code has been modified to work with Colin Plumb's
// MD5 implementation rather than using RSA's.
#pragma once
#include "md5.h"
enum { HASHHEXLEN = HASH_SIZE_MD5 * 2 };
typedef char HASHHEX[HASHHEXLEN + 1];
// calculate H(A1) as per HTTP Digest spec
void DigestCalcHA1 (const char* pszAlg, const char* pszUserName, const char* pszRealm, const char* pszPassword, const char* pszNonce, const char* pszCNonce, HASHHEX SessionKey);
// calculate request-digest/response-digest as per HTTP Digest spec
void DigestCalcResponse (const HASHHEX HA1, // H(A1)
const char* pszNonce, // nonce from server
const char* pszNonceCount, // 8 hex digits
const char* pszCNonce, // client nonce
const char* pszQop, // qop-value: "", "auth", "auth-int"
const char* pszMethod, // method from the request
const char* pszDigestUri, // requested URL
const HASHHEX HEntity, // H(entity body) if qop="auth-int"
HASHHEX Response // request-digest or response-digest
);

View File

@ -1,6 +1,7 @@
// This file is part of Snownews - A lightweight console RSS newsreader
//
// Copyright (c) 2003-2004 Oliver Feiler <kiza@kcore.de>
// Copyright (c) 2021 Mike Sharov <msharov@users.sourceforge.net>
//
// Snownews is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 3

122
md5.c
View File

@ -1,122 +0,0 @@
// This file is part of Snownews - A lightweight console RSS newsreader
//
// Copyright (c) 2018 Mike Sharov <msharov@users.sourceforge.net>
//
// Snownews is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 3
// as published by the Free Software Foundation.
//
// Snownews 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 Snownews. If not, see http://www.gnu.org/licenses/.
#include "md5.h"
static inline uint32_t remove_bits (uint32_t v, uint8_t f, uint8_t n)
{
return ((v >> (f + n)) << f) | (v & ((1 << f) - 1));
}
static inline uint32_t Rol (uint32_t v, uint32_t n)
{
return (v << n) | (v >> (32 - n));
}
static void hash_md5_block (struct HashMD5* h)
{
static const uint8_t r[16] = { 7, 12, 17, 22, 5, 9, 14, 20, 4, 11, 16, 23, 6, 10, 15, 21 };
//{{{ K table
static const uint32_t K[64] = {
0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05,
0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
};
//}}}
uint32_t a = h->hash[0], b = h->hash[1], c = h->hash[2], d = h->hash[3], f, g;
for (uint32_t i = 0; i < 64; ++i) {
if (i < 16) {
f = d ^ (b & (c ^ d));
g = i;
} else if (i < 32) {
f = c ^ (d & (b ^ c));
g = (5 * i + 1) % 16;
} else if (i < 48) {
f = b ^ c ^ d;
g = (3 * i + 5) % 16;
} else {
f = c ^ (b | ~d);
g = (7 * i) % 16;
}
f = Rol (a + f + K[i] + h->words[g], r[remove_bits (i, 2, 2)]);
g = d;
d = c;
c = b;
b += f;
a = g;
}
h->hash[0] += a;
h->hash[1] += b;
h->hash[2] += c;
h->hash[3] += d;
}
void hash_md5_init (struct HashMD5* h)
{
memset (h, 0, sizeof (*h));
static const uint32_t hash_md5_initial_value[HASH_SIZE_MD5_WORDS]
= { 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476 };
memcpy (h->hash, hash_md5_initial_value, sizeof (h->hash));
}
void hash_md5_data (struct HashMD5* h, const void* d, size_t n)
{
while (n) {
uint32_t blockOffset = h->offset % HASH_BLOCK_SIZE_MD5;
uint32_t copied = HASH_BLOCK_SIZE_MD5 - blockOffset;
if (copied > n)
copied = n;
memcpy (h->bytes + blockOffset, d, copied);
d = (const char*) d + copied;
n -= copied;
h->offset += copied;
blockOffset = (blockOffset + copied) % HASH_BLOCK_SIZE_MD5;
if (!blockOffset)
hash_md5_block (h);
}
}
void hash_md5_finish (struct HashMD5* h)
{
uint64_t size = h->offset;
uint8_t pad = 0x80; // End message with one bit
do {
hash_md5_data (h, &pad, sizeof (pad));
pad = 0; // Pad with zeroes until there is enough space for offset at the end
} while (h->offset % HASH_BLOCK_SIZE_MD5 != HASH_BLOCK_SIZE_MD5 - sizeof (h->offset));
h->quads[7] = size * 8; // Write size in bits at end of block
hash_md5_block (h);
}
void hash_md5_to_text (const struct HashMD5* h, char* text)
{
for (unsigned i = 0; i < HASH_SIZE_MD5_WORDS; ++i)
for (unsigned j = 0; j < 4; ++j)
sprintf (&text[i * 8 + j * 2], "%02hhx", (uint8_t) (h->hash[i] >> (j * 8)));
}

39
md5.h
View File

@ -1,39 +0,0 @@
// This file is part of Snownews - A lightweight console RSS newsreader
//
// Copyright (c) 2018 Mike Sharov <msharov@users.sourceforge.net>
//
// Snownews is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 3
// as published by the Free Software Foundation.
//
// Snownews 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 Snownews. If not, see http://www.gnu.org/licenses/.
#pragma once
#include "config.h"
enum {
HASH_SIZE_MD5 = 16,
HASH_SIZE_MD5_WORDS = HASH_SIZE_MD5 / sizeof (uint32_t),
HASH_BLOCK_SIZE_MD5 = 64
};
struct HashMD5 {
uint32_t hash[HASH_SIZE_MD5_WORDS];
uint64_t offset;
union {
uint8_t bytes[HASH_BLOCK_SIZE_MD5];
uint32_t words[HASH_BLOCK_SIZE_MD5 / sizeof (uint32_t)];
uint64_t quads[HASH_BLOCK_SIZE_MD5 / sizeof (uint64_t)];
};
};
void hash_md5_init (struct HashMD5* h);
void hash_md5_data (struct HashMD5* h, const void* d, size_t n);
void hash_md5_finish (struct HashMD5* h);
void hash_md5_to_text (const struct HashMD5* h, char* text);

View File

@ -116,7 +116,7 @@ void DownloadFeed (const char* url, struct feed* fp)
// Check if failed because already up-to-date
long unmet = 0;
if (CURLE_OK == curl_easy_getinfo (curl, CURLINFO_CONDITION_UNMET, &unmet)) {
if (CURLE_OK == curl_easy_getinfo (curl, CURLINFO_CONDITION_UNMET, &unmet) && unmet) {
fp->lasterror = strdup (_("Feed already up to date"));
UIStatus (fp->lasterror, 0, 0);
fp->problem = false;