/** * libpassphrase – Personalisable library for TTY passphrase reading * * Copyright © 2013, 2014 Mattias Andrée (maandree@member.fsf.org) * * 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 3 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. If not, see . */ #include #include #include #include #include "passphrase.h" #define START_PASSPHRASE_LIMIT 32 #if !defined(PASSPHRASE_ECHO) || defined(PASSPHRASE_MOVE) /** * The original TTY settings */ static struct termios saved_stty; #endif #ifndef PASSPHRASE_REALLOC static inline char* xrealloc(char* array, size_t cur_size, size_t new_size) { char* rc = malloc(new_size * sizeof(char)); size_t i; if (rc) for (i = 0; i < cur_size; i++) *(rc + i) = *(array + i); for (i = 0; i < cur_size; i++) *(array + i) = 0; free(array); return rc; } #else #define xrealloc(array, _cur_size, new_size) realloc(array, (new_size) * sizeof(char)) #endif #if defined(PASSPHRASE_MOVE) && !defined(PASSPHRASE_STAR) && !defined(PASSPHRASE_ECHO) # define xprintf(...) /* do nothing */ # define xflush() /* do nothing */ #elif defined(PASSPHRASE_MOVE) || defined(PASSPHRASE_STAR) # define xprintf(FORMAT, ...) fprintf(stderr, FORMAT, __VA_ARGS__) # define xflush() fflush(stderr) #else # define xflush() fflush(stderr) #endif #ifdef PASSPHRASE_MOVE # if defined(PASSPHRASE_STAR) # define xputchar(C) ({ if ((C & 0xC0) != 0x80) fputc('*', stderr); }) # elif defined(PASSPHRASE_ECHO) # define xputchar(C) fputc(C, stderr) # else # define xputchar(C) ({ /* be silent */ }) # endif #endif /** * Reads the passphrase from stdin * * @return The passphrase, should be wiped `free`:ed, `NULL` on error */ char* passphrase_read(void) { char* rc = malloc(START_PASSPHRASE_LIMIT * sizeof(char)); long size = START_PASSPHRASE_LIMIT; long len = 0; #ifdef PASSPHRASE_MOVE long point = 0; long i = 0; # ifdef PASSPHRASE_OVERRIDE # if defined(PASSPHRASE_INSERT) && defined(DEFAULT_INSERT) char insert = 1; # elif defined(PASSPHRASE_INSERT) char insert = 0; # endif # endif #endif int c; if (rc == NULL) return NULL; /* Read password until EOF or Enter, skip all \0 as that is probably not a part of the passphrase (good luck typing that in X.org) and can be echoed into stdin by the kernel. */ for (;;) { c = getchar(); if ((c < 0) || (c == '\n')) break; if (c != 0) { #if defined(PASSPHRASE_MOVE) /* \e[1~ \eOH ^A -1 home */ /* \e[2~ -2 insert */ /* \e[3~ ^D -3 delete */ /* \e[4~ \eOF ^E -4 end */ /* \8 \127 -5 erase */ /* \e[C ^F -6 right */ /* \e[D ^B -7 left */ int cc = 0; #ifdef PASSPHRASE_DEDICATED if (c == '\033') { c = getchar(); if (c == 'O') { c = getchar(); if (c == 'H') cc = -1; else if (c == 'F') cc = -4; } else if (c == '[') { c = getchar(); if (c == 'C') cc = -6; else if (c == 'D') cc = -7; else if (('1' <= c) && (c <= '4') && (getchar() == '~')) cc = -(c - '0'); } } else #endif if ((c == 8) || (c == 127)) cc = -5; else if ((c < 0) || (c >= ' ')) cc = ((int)c) & 255; #ifdef PASSPHRASE_CONTROL else if (c == 'A' - '@') cc = -1; else if (c == 'B' - '@') cc = -7; else if (c == 'D' - '@') cc = -3; else if (c == 'E' - '@') cc = -4; else if (c == 'F' - '@') cc = -6; #endif if (cc > 0) { c = (char)cc; if (point == len) { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpedantic" xputchar(c); #pragma GCC diagnostic pop *(rc + len++) = c; point++; } #ifdef PASSPHRASE_INSERT else #ifdef PASSPHRASE_OVERRIDE if (insert) #endif { if ((c & 0xC0) != 0x80) { xprintf("\033[@"); } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpedantic" xputchar(c); #pragma GCC diagnostic pop for (i = len; i > point; i--) *(rc + i) = *(rc + i - 1); len++; *(rc + point++) = c; } #endif #ifdef PASSPHRASE_OVERRIDE else { long n = 1; char cn = c; while ((*(rc + point + n) & 0xC0) == 0x80) n++; for (i = point + n; i < len; i++) *(rc + i - n) = *(rc + i); for (i = len - n; i < len; i++) *(rc + i) = 0; len -= n; n = 0; while (cn & 0x80) { cn <<= 1; n++; } n = n ? n : 1; if (len + n > size) { if ((rc = xrealloc(rc, (size_t)size, (size_t)size << 1L)) == NULL) return NULL; size <<= 1L; } len += n; for (i = len - 1; i >= point + n; i--) *(rc + i) = *(rc + i - n); for (i = 0; i < n; i++) { if (i) c = getchar(); #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpedantic" xputchar(c); #pragma GCC diagnostic pop *(rc + point++) = c; } } #endif } else if ((cc == -1) && point) /* home */ { long n = 0; for (i = 0; i < point; i++) if ((*(rc + i) & 0xC0) != 0x80) n++; xprintf("\033[%liD", n); point = 0; } #if defined(PASSPHRASE_INSERT) && defined(PASSPHRASE_OVERRIDE) else if (cc == -2) /* insert */ insert ^= 1; #endif #ifdef PASSPHRASE_DELETE else if ((cc == -3) && (len != point)) /* delete */ { xprintf("\033[P"); #ifdef PASSPHRASE_INVALID *(rc + len) = 0; #endif do { for (i = point; i < len; i++) *(rc + i) = *(rc + i + 1); len--; } while ((len != point) && ((*(rc + point) & 0xC0) == 0x80)); } #endif else if ((cc == -4) && (len != point)) /* end */ { long n = 0; for (i = point; i < len; i++) if ((*(rc + i) & 0xC0) != 0x80) n++; xprintf("\033[%liC", n); point = len; } else if ((cc == -5) && point) /* erase */ { char redo = 1; xprintf("\033[D\033[P"); #ifdef PASSPHRASE_INVALID *(rc + len) = 0; #endif while (redo) { redo = (*(rc + point - 1) & 0xC0) == 0x80; for (i = point; i <= len; i++) *(rc + i - 1) = *(rc + i); point--; len--; } } else if ((cc == -6) && (len != point)) /* right */ { xprintf("\033[C"); do point++; while ((len != point) && ((*(rc + point) & 0xC0) == 0x80)); } else if ((cc == -7) && point) /* left */ { xprintf("\033[D"); point--; while (point && ((*(rc + point) & 0xC0) == 0x80)) point--; } #elif defined(PASSPHRASE_STAR) if ((c == 8) || (c == 127)) { if (len == 0) continue; xprintf("%s", "\033[D \033[D"); xflush(); *(rc + --len) = 0; #ifdef DEBUG goto debug; #else continue; #endif } if ((c & 0xC0) != 0x80) fputc('*', stderr); *(rc + len++) = c; #else *(rc + len++) = c; #endif xflush(); if (len == size) { if ((rc = xrealloc(rc, (size_t)size, (size_t)size << 1)) == NULL) return NULL; size <<= 1L; } #ifdef DEBUG #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-label" debug: { long n = 0; for (i = point; i < len; i++) if ((*(rc + i) & 0xC0) != 0x80) n++; *(rc + len) = 0; if (n) fprintf(stderr, "\033[s\033[H\033[K%s\033[%liD\033[01;34m%s\033[00m\033[u", rc, n, rc + point); else fprintf(stderr, "\033[s\033[H\033[K%s\033[01;34m%s\033[00m\033[u", rc, rc + point); fflush(stderr); } #endif } } /* NUL-terminate passphrase */ *(rc + len) = 0; #if !defined(PASSPHRASE_ECHO) || defined(PASSPHRASE_MOVE) fprintf(stderr, "\n"); #endif return rc; } /** * Disable echoing and do anything else to the terminal settnings `passphrase_read` requires */ void passphrase_disable_echo(void) { #if !defined(PASSPHRASE_ECHO) || defined(PASSPHRASE_MOVE) struct termios stty; tcgetattr(STDIN_FILENO, &stty); saved_stty = stty; stty.c_lflag &= (tcflag_t)~ECHO; #if defined(PASSPHRASE_STAR) || defined(PASSPHRASE_MOVE) stty.c_lflag &= (tcflag_t)~ICANON; #endif tcsetattr(STDIN_FILENO, TCSAFLUSH, &stty); #endif } /** * Undo the actions of `passphrase_disable_echo` */ void passphrase_reenable_echo(void) { #if !defined(PASSPHRASE_ECHO) || defined(PASSPHRASE_MOVE) tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_stty); #endif }