main shrub/shrubtools / randomart / randomart.c
  1/* somewhat derived from OpenSSH */
  2#include "defs"
  3
  4#include <ctype.h>
  5#include <stdlib.h>
  6#include <string.h>
  7
  8#include <alloc.h>
  9#include <byte.h>
 10#include <fmt.h>
 11#include <str.h>
 12#include <stralloc.h>
 13#include <subgetopt.h>
 14#include <buffer.h>
 15#include <strerr.h>
 16#include <openreadclose.h>
 17
 18#include "base64.h"
 19#include "bsd-sha2.h"
 20#include "md5.h"
 21
 22#define FLDBASE 8
 23#define FLDSIZE_Y (FLDBASE + 1)
 24#define FLDSIZE_X (FLDBASE * 2 + 1)
 25
 26struct options {
 27	int hashalg;
 28};
 29
 30static void
 31usage(void)
 32{
 33	buffer_puts(buffer_2, "usage: randomart [-E md5|sha256] [file ...]\n");
 34	buffer_puts(buffer_2,
 35	    "       randomart [-E md5|sha256] -k public_key_line\n");
 36	buffer_flush(buffer_2);
 37	exit(1);
 38}
 39
 40static void
 41warnx1(const char *a, const char *b)
 42{
 43	strerr_warn3(a, b, "", 0);
 44}
 45
 46static void
 47warnx2(const char *a, const char *b, const char *c)
 48{
 49	strerr_warn4(a, b, c, "", 0);
 50}
 51
 52static void
 53warnsys1(const char *a, const char *b)
 54{
 55	strerr_warn3(a, b, "", &strerr_sys);
 56}
 57
 58static char *
 59alloc_nz(unsigned int size)
 60{
 61	char *ret;
 62
 63	ret = alloc(size ? size : 1);
 64	if (ret == NULL) {
 65		strerr_die2sys(111, "randomart: ", "alloc");
 66		exit(1);
 67	}
 68	return ret;
 69}
 70
 71static char *
 72dup_string(const char *s)
 73{
 74	char *ret;
 75	unsigned int len;
 76
 77	len = str_len(s) + 1;
 78	ret = alloc_nz(len);
 79	byte_copy(ret, len, (char *)s);
 80	return ret;
 81}
 82
 83static char *
 84trim(char *s)
 85{
 86	char *end;
 87
 88	while (*s != '\0' && isspace((unsigned char)*s))
 89		s++;
 90	if (*s == '\0')
 91		return s;
 92	end = s + str_len(s) - 1;
 93	while (end > s && isspace((unsigned char)*end))
 94		*end-- = '\0';
 95	return s;
 96}
 97
 98static unsigned int
 99format_box_label(char *buf, unsigned int buf_len, const char *text,
100    const char *fallback)
101{
102	int r;
103	unsigned int n;
104	const char *src;
105
106	if (buf_len < 3)
107		return 0;
108	src = text;
109	n = str_len(text);
110	if (n + 2 >= buf_len) {
111		src = fallback;
112		n = str_len(fallback);
113		if (n + 2 >= buf_len)
114			n = buf_len - 3;
115	}
116	buf[0] = '[';
117	n = fmt_str(buf + 1, (char *)src);
118	if (n + 2 >= buf_len)
119		n = buf_len - 3;
120	buf[n + 1] = ']';
121	buf[n + 2] = '\0';
122	return n + 2;
123}
124
125static int
126compute_digest(const u_char *data, size_t len, int hashalg,
127    u_char *digest, size_t *digest_len, const char **label)
128{
129	MD5_CTX md5;
130	SHA2_CTX sha256;
131
132	switch (hashalg) {
133	case 0:
134		MD5Init(&md5);
135		MD5Update(&md5, data, len);
136		MD5Final(digest, &md5);
137		*digest_len = MD5_DIGEST_LENGTH;
138		*label = "MD5";
139		return 0;
140	case 1:
141		SHA256Init(&sha256);
142		SHA256Update(&sha256, data, len);
143		SHA256Final(digest, &sha256);
144		*digest_len = SHA256_DIGEST_LENGTH;
145		*label = "SHA256";
146		return 0;
147	default:
148		return -1;
149	}
150}
151
152static int
153sa_catc(stralloc *sa, char c)
154{
155	return stralloc_catb(sa, &c, 1);
156}
157
158static int
159sa_catn(stralloc *sa, char c, unsigned int n)
160{
161	while (n-- > 0) {
162		if (!sa_catc(sa, c))
163			return 0;
164	}
165	return 1;
166}
167
168static char *
169render_randomart(const char *title, const char *subtitle,
170    const u_char *dgst_raw, size_t dgst_raw_len)
171{
172	static const char augmentation_string[] = " .o+=*BOX@%&#/^SE";
173	char title_buf[FLDSIZE_X];
174	char subtitle_buf[FLDSIZE_X];
175	u_char field[FLDSIZE_X][FLDSIZE_Y];
176	stralloc out = { 0 };
177	unsigned int i, title_len, subtitle_len;
178	u_int b;
179	int x, y;
180	unsigned int len = str_len(augmentation_string) - 1;
181
182	byte_zero(field, sizeof(field));
183	x = FLDSIZE_X / 2;
184	y = FLDSIZE_Y / 2;
185
186	for (i = 0; i < dgst_raw_len; i++) {
187		int input = dgst_raw[i];
188
189		for (b = 0; b < 4; b++) {
190			x += (input & 0x1) ? 1 : -1;
191			y += (input & 0x2) ? 1 : -1;
192			if (x < 0)
193				x = 0;
194			if (y < 0)
195				y = 0;
196			if (x >= FLDSIZE_X)
197				x = FLDSIZE_X - 1;
198			if (y >= FLDSIZE_Y)
199				y = FLDSIZE_Y - 1;
200			if (field[x][y] < len - 2)
201				field[x][y]++;
202			input >>= 2;
203		}
204	}
205
206	field[FLDSIZE_X / 2][FLDSIZE_Y / 2] = len - 1;
207	field[x][y] = len;
208
209	title_len = format_box_label(title_buf, sizeof(title_buf), title, "key");
210	subtitle_len = format_box_label(subtitle_buf, sizeof(subtitle_buf),
211	    subtitle, "hash");
212
213	if (!sa_catc(&out, '+') ||
214	    !sa_catn(&out, '-', (FLDSIZE_X - title_len) / 2) ||
215	    !stralloc_catb(&out, title_buf, title_len) ||
216	    !sa_catn(&out, '-', FLDSIZE_X - ((FLDSIZE_X - title_len) / 2 + title_len)) ||
217	    !stralloc_catb(&out, "+\n", 2))
218		goto fail;
219
220	for (y = 0; y < FLDSIZE_Y; y++) {
221		if (!sa_catc(&out, '|'))
222			goto fail;
223		for (x = 0; x < FLDSIZE_X; x++) {
224			char ch = augmentation_string[field[x][y] > len ?
225			    len : field[x][y]];
226			if (!sa_catc(&out, ch))
227				goto fail;
228		}
229		if (!stralloc_catb(&out, "|\n", 2))
230			goto fail;
231	}
232
233	if (!sa_catc(&out, '+') ||
234	    !sa_catn(&out, '-', (FLDSIZE_X - subtitle_len) / 2) ||
235	    !stralloc_catb(&out, subtitle_buf, subtitle_len) ||
236	    !sa_catn(&out, '-', FLDSIZE_X - ((FLDSIZE_X - subtitle_len) / 2 + subtitle_len)) ||
237	    !sa_catc(&out, '+') ||
238	    !stralloc_0(&out))
239		goto fail;
240
241	return out.s;
242
243fail:
244	alloc_free(out.s);
245	return NULL;
246}
247
248static int
249parse_public_key_line(char *line, char **typep, u_char **blobp,
250    size_t *blob_lenp)
251{
252	char *type, *blob_text, *rest;
253	int decoded_len;
254	unsigned int blob_cap;
255	u_char *blob;
256
257	line = trim(line);
258	if (*line == '\0' || *line == '#')
259		return 1;
260
261	type = strtok(line, " \t");
262	blob_text = strtok(NULL, " \t");
263	rest = strtok(NULL, "");
264	(void)rest;
265	if (type == NULL || blob_text == NULL)
266		return -1;
267
268	blob_cap = (str_len(blob_text) / 4) * 3 + 4;
269	blob = (u_char *)alloc_nz(blob_cap);
270	decoded_len = b64_pton(blob_text, blob, blob_cap);
271	if (decoded_len < 0) {
272		alloc_free(blob);
273		return -1;
274	}
275
276	*typep = dup_string(type);
277	*blobp = blob;
278	*blob_lenp = (size_t)decoded_len;
279	return 0;
280}
281
282static int
283print_art(const char *label, const char *art)
284{
285	if (buffer_puts(buffer_1, label) == -1 ||
286	    buffer_puts(buffer_1, "\n") == -1 ||
287	    buffer_puts(buffer_1, art) == -1 ||
288	    buffer_puts(buffer_1, "\n") == -1 ||
289	    buffer_flush(buffer_1) == -1)
290		return -1;
291	return 0;
292}
293
294static int
295process_key_line(char *line, const char *label, const struct options *opts)
296{
297	char *type = NULL, *art = NULL;
298	u_char *blob = NULL;
299	u_char digest[SHA256_DIGEST_LENGTH];
300	size_t blob_len = 0, digest_len = 0;
301	const char *hash_label = NULL;
302	int ret = 1;
303
304	ret = parse_public_key_line(line, &type, &blob, &blob_len);
305	if (ret == 1) {
306		ret = 2;
307		goto out;
308	}
309	if (ret != 0) {
310		warnx2(label, ": ", "unable to parse OpenSSH public key line");
311		goto out;
312	}
313	if (compute_digest(blob, blob_len, opts->hashalg, digest,
314	    &digest_len, &hash_label) != 0) {
315		warnx1(label, ": unsupported digest");
316		goto out;
317	}
318	art = render_randomart(type, hash_label, digest, digest_len);
319	if (art == NULL) {
320		warnx1(label, ": unable to render randomart");
321		goto out;
322	}
323	ret = print_art(label, art);
324out:
325	alloc_free(type);
326	alloc_free(blob);
327	alloc_free(art);
328	return ret;
329}
330
331static int
332process_blob(char *blob, const char *label, const struct options *opts)
333{
334	char *line = blob;
335	char *next;
336	int ret = 1;
337	int line_ret;
338
339	while (*line) {
340		next = line + str_chr(line, '\n');
341		if (*next != '\0')
342			*next = '\0';
343		else
344			next = 0;
345		line_ret = process_key_line(line, label, opts);
346		if (line_ret == 0) {
347			ret = 0;
348			break;
349		}
350		if (next == NULL)
351			break;
352		line = next + 1;
353	}
354	if (ret != 0)
355		warnx1(label, ": no usable public key line found");
356	return ret;
357}
358
359static int
360process_path(const char *path, const struct options *opts)
361{
362	stralloc sa = { 0 };
363	int ret;
364
365	ret = openreadclose(path, &sa, 8192);
366	if (ret == 0) {
367		warnsys1(path, ": ");
368		alloc_free(sa.s);
369		return 1;
370	}
371	if (ret == -1) {
372		warnsys1(path, ": ");
373		alloc_free(sa.s);
374		return 1;
375	}
376	if (!stralloc_0(&sa))
377		strerr_die2sys(111, "randomart: ", "stralloc_0");
378	ret = process_blob(sa.s, path, opts);
379	alloc_free(sa.s);
380	return ret;
381}
382
383static int
384process_stdin(const struct options *opts)
385{
386	stralloc sa = { 0 };
387	char ch;
388	int r;
389	int ret = 1;
390
391	for (;;) {
392		r = buffer_get(buffer_0, &ch, 1);
393		if (r < 0) {
394			warnsys1("<stdin>", ": ");
395			break;
396		}
397		if (r == 0) {
398			if (sa.len != 0) {
399				if (!stralloc_0(&sa))
400					strerr_die2sys(111, "randomart: ",
401					    "stralloc_0");
402				ret = process_key_line(sa.s, "<stdin>", opts);
403			}
404			break;
405		}
406		if (ch == '\n') {
407			if (!stralloc_0(&sa))
408				strerr_die2sys(111, "randomart: ", "stralloc_0");
409			ret = process_key_line(sa.s, "<stdin>", opts);
410			if (ret == 0)
411				break;
412			sa.len = 0;
413		} else if (!stralloc_append(&sa, &ch)) {
414			strerr_die2sys(111, "randomart: ", "stralloc_append");
415		}
416	}
417	if (ret != 0)
418		warnx1("<stdin>", ": no usable public key line found");
419	alloc_free(sa.s);
420	return ret;
421}
422
423int
424main(int argc, char **argv)
425{
426	struct options opts;
427	char *inline_key = NULL;
428	int ch;
429	int ret = 0;
430
431	opts.hashalg = 1;
432
433	while ((ch = subgetopt(argc, (const char * const *)argv, "E:k:h")) !=
434	    SUBGETOPTDONE) {
435		switch (ch) {
436		case 'E':
437			if (str_equal(subgetoptarg, "md5"))
438				opts.hashalg = 0;
439			else if (str_equal(subgetoptarg, "sha256"))
440				opts.hashalg = 1;
441			else
442				usage();
443			break;
444		case 'k':
445			inline_key = (char *)subgetoptarg;
446			break;
447		case 'h':
448		default:
449			usage();
450		}
451	}
452
453	argc -= subgetoptind;
454	argv += subgetoptind;
455
456	if (inline_key != NULL) {
457		char *buf = dup_string(inline_key);
458
459		ret = process_key_line(buf, "<inline>", &opts);
460		alloc_free(buf);
461		return ret;
462	}
463	if (argc == 0)
464		return process_stdin(&opts);
465	for (; *argv != NULL; argv++)
466		ret |= process_path(*argv, &opts);
467	return ret;
468}