2017-08-31 19:30:42 +00:00
|
|
|
//==============================================================================
|
|
|
|
// This file is part of Master Password.
|
|
|
|
// Copyright (c) 2011-2017, Maarten Billemont.
|
|
|
|
//
|
|
|
|
// Master Password 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.
|
|
|
|
//
|
|
|
|
// Master Password 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 can find a copy of the GNU General Public License in the
|
|
|
|
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
|
|
|
//==============================================================================
|
|
|
|
|
2017-09-25 14:33:31 +00:00
|
|
|
#define _POSIX_C_SOURCE 200809L
|
|
|
|
|
2017-08-31 19:30:42 +00:00
|
|
|
#include "mpw-cli-util.h"
|
|
|
|
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <sys/stat.h>
|
2017-09-07 03:54:52 +00:00
|
|
|
#include <sys/wait.h>
|
2017-08-31 19:30:42 +00:00
|
|
|
#include <pwd.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <sysexits.h>
|
|
|
|
|
2017-09-25 22:34:12 +00:00
|
|
|
#define MPW_MAX_INPUT 60
|
|
|
|
|
|
|
|
#if MPW_COLOR
|
|
|
|
#include <curses.h>
|
|
|
|
#include <term.h>
|
|
|
|
#endif
|
|
|
|
|
2017-08-31 19:30:42 +00:00
|
|
|
#include "mpw-util.h"
|
|
|
|
|
|
|
|
const char *mpw_getenv(const char *variableName) {
|
|
|
|
|
|
|
|
char *envBuf = getenv( variableName );
|
2017-09-25 14:51:14 +00:00
|
|
|
return envBuf? mpw_strdup( envBuf ): NULL;
|
2017-08-31 19:30:42 +00:00
|
|
|
}
|
|
|
|
|
2018-09-22 13:47:11 +00:00
|
|
|
const char *mpw_askpass(const char *prompt) {
|
2017-08-31 19:30:42 +00:00
|
|
|
|
|
|
|
const char *askpass = mpw_getenv( MP_ENV_askpass );
|
|
|
|
if (!askpass)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
int pipes[2];
|
|
|
|
if (pipe( pipes ) == ERR) {
|
2018-11-20 16:58:38 +00:00
|
|
|
wrn( "Couldn't create pipes for askpass: %s", strerror( errno ) );
|
2017-08-31 19:30:42 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
pid_t pid = fork();
|
|
|
|
if (pid == ERR) {
|
2019-01-05 18:54:27 +00:00
|
|
|
wrn( "Couldn't fork for askpass:\n %s: %s", askpass, strerror( errno ) );
|
2017-08-31 19:30:42 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!pid) {
|
|
|
|
// askpass fork
|
|
|
|
close( pipes[0] );
|
|
|
|
if (dup2( pipes[1], STDOUT_FILENO ) == ERR)
|
2018-03-24 19:14:41 +00:00
|
|
|
ftl( "Couldn't connect pipe to process: %s", strerror( errno ) );
|
2017-08-31 19:30:42 +00:00
|
|
|
|
|
|
|
else if (execlp( askpass, askpass, prompt, NULL ) == ERR)
|
2018-03-24 19:14:41 +00:00
|
|
|
ftl( "Couldn't execute askpass:\n %s: %s", askpass, strerror( errno ) );
|
2017-08-31 19:30:42 +00:00
|
|
|
|
|
|
|
exit( EX_SOFTWARE );
|
|
|
|
}
|
|
|
|
|
|
|
|
close( pipes[1] );
|
2018-09-22 13:47:11 +00:00
|
|
|
const char *answer = mpw_read_fd( pipes[0] );
|
2017-08-31 19:30:42 +00:00
|
|
|
close( pipes[0] );
|
|
|
|
int status;
|
|
|
|
if (waitpid( pid, &status, 0 ) == ERR) {
|
2018-03-24 19:14:41 +00:00
|
|
|
wrn( "Couldn't wait for askpass: %s", strerror( errno ) );
|
2017-08-31 19:30:42 +00:00
|
|
|
mpw_free_string( &answer );
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2018-11-20 16:58:38 +00:00
|
|
|
if (!WIFEXITED( status ) || WEXITSTATUS( status ) != EXIT_SUCCESS || !answer || !strlen( answer )) {
|
|
|
|
// askpass failed.
|
|
|
|
mpw_free_string( &answer );
|
|
|
|
return NULL;
|
2017-08-31 19:30:42 +00:00
|
|
|
}
|
|
|
|
|
2018-11-20 16:58:38 +00:00
|
|
|
// Remove trailing newline.
|
|
|
|
if (answer[strlen( answer ) - 1] == '\n')
|
|
|
|
mpw_replace_string( answer, mpw_strndup( answer, strlen( answer ) - 1 ) );
|
|
|
|
return answer;
|
2017-08-31 19:30:42 +00:00
|
|
|
}
|
|
|
|
|
2017-09-25 22:34:12 +00:00
|
|
|
static const char *_mpw_getline(const char *prompt, bool silent) {
|
2017-08-31 19:30:42 +00:00
|
|
|
|
|
|
|
// Get answer from askpass.
|
2018-09-22 13:47:11 +00:00
|
|
|
const char *answer = mpw_askpass( prompt );
|
2017-08-31 19:30:42 +00:00
|
|
|
if (answer)
|
|
|
|
return answer;
|
|
|
|
|
2017-09-25 22:34:12 +00:00
|
|
|
#if MPW_COLOR
|
|
|
|
// Initialize a curses screen.
|
2017-10-06 18:59:29 +00:00
|
|
|
SCREEN *screen = newterm( NULL, stderr, stdin );
|
2018-09-22 15:00:08 +00:00
|
|
|
if (screen) {
|
|
|
|
start_color();
|
|
|
|
init_pair( 1, COLOR_WHITE, COLOR_BLUE );
|
|
|
|
init_pair( 2, COLOR_BLACK, COLOR_WHITE );
|
|
|
|
int rows, cols;
|
|
|
|
getmaxyx( stdscr, rows, cols );
|
|
|
|
|
|
|
|
// Display a dialog box.
|
|
|
|
int width = max( prompt? (int)strlen( prompt ): 0, MPW_MAX_INPUT ) + 6;
|
|
|
|
char *version = "mpw v" stringify_def( MP_VERSION );
|
|
|
|
mvprintw( rows - 1, (cols - (int)strlen( version )) / 2, "%s", version );
|
|
|
|
attron( A_BOLD );
|
|
|
|
color_set( 2, NULL );
|
|
|
|
mvprintw( rows / 2 - 1, (cols - width) / 2, "%s%*s%s", "*", width - 2, "", "*" );
|
|
|
|
mvprintw( rows / 2 - 1, (cols - (int)strlen( prompt )) / 2, "%s", prompt );
|
|
|
|
color_set( 1, NULL );
|
|
|
|
mvprintw( rows / 2 + 0, (cols - width) / 2, "%s%*s%s", "|", width - 2, "", "|" );
|
|
|
|
mvprintw( rows / 2 + 1, (cols - width) / 2, "%s%*s%s", "|", width - 2, "", "|" );
|
|
|
|
mvprintw( rows / 2 + 2, (cols - width) / 2, "%s%*s%s", "|", width - 2, "", "|" );
|
|
|
|
|
|
|
|
// Read response.
|
|
|
|
color_set( 2, NULL );
|
|
|
|
attron( A_STANDOUT );
|
2020-01-23 20:55:03 +00:00
|
|
|
int result;
|
2018-09-22 15:00:08 +00:00
|
|
|
char str[MPW_MAX_INPUT + 1];
|
|
|
|
if (silent) {
|
|
|
|
mvprintw( rows / 2 + 1, (cols - 5) / 2, "[ * ]" );
|
|
|
|
refresh();
|
|
|
|
|
|
|
|
noecho();
|
|
|
|
result = mvgetnstr( rows / 2 + 1, (cols - 1) / 2, str, MPW_MAX_INPUT );
|
|
|
|
echo();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
mvprintw( rows / 2 + 1, (cols - (MPW_MAX_INPUT + 2)) / 2, "%*s", MPW_MAX_INPUT + 2, "" );
|
|
|
|
refresh();
|
|
|
|
|
|
|
|
echo();
|
|
|
|
result = mvgetnstr( rows / 2 + 1, (cols - MPW_MAX_INPUT) / 2, str, MPW_MAX_INPUT );
|
|
|
|
}
|
|
|
|
attrset( 0 );
|
|
|
|
endwin();
|
|
|
|
delscreen( screen );
|
|
|
|
|
|
|
|
return result == ERR? NULL: mpw_strndup( str, MPW_MAX_INPUT );
|
2017-09-25 22:34:12 +00:00
|
|
|
}
|
2018-09-22 15:00:08 +00:00
|
|
|
#endif
|
2017-09-25 22:34:12 +00:00
|
|
|
|
2017-08-31 19:30:42 +00:00
|
|
|
// Get password from terminal.
|
|
|
|
fprintf( stderr, "%s ", prompt );
|
|
|
|
|
|
|
|
size_t bufSize = 0;
|
2018-09-22 15:00:08 +00:00
|
|
|
ssize_t lineSize = getline( (char **)&answer, &bufSize, stdin );
|
2017-08-31 19:30:42 +00:00
|
|
|
if (lineSize <= 1) {
|
|
|
|
mpw_free_string( &answer );
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove trailing newline.
|
2018-09-22 15:00:08 +00:00
|
|
|
mpw_replace_string( answer, mpw_strndup( answer, (size_t)lineSize - 1 ) );
|
2017-08-31 19:30:42 +00:00
|
|
|
return answer;
|
|
|
|
}
|
|
|
|
|
2017-09-25 22:34:12 +00:00
|
|
|
const char *mpw_getline(const char *prompt) {
|
2017-08-31 19:30:42 +00:00
|
|
|
|
2017-09-25 22:34:12 +00:00
|
|
|
return _mpw_getline( prompt, false );
|
|
|
|
}
|
2017-08-31 19:30:42 +00:00
|
|
|
|
2017-09-25 22:34:12 +00:00
|
|
|
const char *mpw_getpass(const char *prompt) {
|
2017-08-31 19:30:42 +00:00
|
|
|
|
2017-09-25 22:34:12 +00:00
|
|
|
return _mpw_getline( prompt, true );
|
2017-08-31 19:30:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const char *mpw_path(const char *prefix, const char *extension) {
|
|
|
|
|
2020-01-23 20:55:03 +00:00
|
|
|
if (!prefix || !extension)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
// Compose filename.
|
|
|
|
char *path = mpw_strdup( mpw_str( "%s.%s", prefix, extension ) );
|
|
|
|
if (!path)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
// This is a filename, remove all potential directory separators.
|
|
|
|
for (char *slash; (slash = strstr( path, "/" )); *slash = '_');
|
|
|
|
|
2017-08-31 19:30:42 +00:00
|
|
|
// Resolve user's home directory.
|
|
|
|
char *homeDir = NULL;
|
|
|
|
if (!homeDir)
|
|
|
|
if ((homeDir = getenv( "HOME" )))
|
2017-09-25 14:51:14 +00:00
|
|
|
homeDir = mpw_strdup( homeDir );
|
2017-08-31 19:30:42 +00:00
|
|
|
if (!homeDir)
|
|
|
|
if ((homeDir = getenv( "USERPROFILE" )))
|
2017-09-25 14:51:14 +00:00
|
|
|
homeDir = mpw_strdup( homeDir );
|
2017-08-31 19:30:42 +00:00
|
|
|
if (!homeDir) {
|
|
|
|
const char *homeDrive = getenv( "HOMEDRIVE" ), *homePath = getenv( "HOMEPATH" );
|
|
|
|
if (homeDrive && homePath)
|
2017-09-25 14:51:14 +00:00
|
|
|
homeDir = mpw_strdup( mpw_str( "%s%s", homeDrive, homePath ) );
|
2017-08-31 19:30:42 +00:00
|
|
|
}
|
|
|
|
if (!homeDir) {
|
|
|
|
struct passwd *passwd = getpwuid( getuid() );
|
|
|
|
if (passwd)
|
2017-09-25 14:51:14 +00:00
|
|
|
homeDir = mpw_strdup( passwd->pw_dir );
|
2017-08-31 19:30:42 +00:00
|
|
|
}
|
|
|
|
if (!homeDir)
|
|
|
|
homeDir = getcwd( NULL, 0 );
|
|
|
|
|
|
|
|
// Compose pathname.
|
|
|
|
if (homeDir) {
|
|
|
|
const char *homePath = mpw_str( "%s/.mpw.d/%s", homeDir, path );
|
|
|
|
free( homeDir );
|
|
|
|
free( path );
|
|
|
|
|
|
|
|
if (homePath)
|
2017-09-25 14:51:14 +00:00
|
|
|
path = mpw_strdup( homePath );
|
2017-08-31 19:30:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return path;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool mpw_mkdirs(const char *filePath) {
|
|
|
|
|
|
|
|
if (!filePath)
|
|
|
|
return false;
|
|
|
|
|
2017-09-26 15:07:02 +00:00
|
|
|
// Save the cwd and for absolute paths, start at the root.
|
|
|
|
char *cwd = getcwd( NULL, 0 );
|
|
|
|
if (*filePath == '/')
|
2017-10-06 18:59:29 +00:00
|
|
|
if (chdir( "/" ) == ERR)
|
2017-09-26 15:07:02 +00:00
|
|
|
return false;
|
|
|
|
|
2017-08-31 19:30:42 +00:00
|
|
|
// The path to mkdir is the filePath without the last path component.
|
|
|
|
char *pathEnd = strrchr( filePath, '/' );
|
2017-11-22 03:33:42 +00:00
|
|
|
if (!pathEnd)
|
2017-10-06 18:59:29 +00:00
|
|
|
return true;
|
2017-08-31 19:30:42 +00:00
|
|
|
|
|
|
|
// Walk the path.
|
|
|
|
bool success = true;
|
2017-10-06 18:59:29 +00:00
|
|
|
char *path = mpw_strndup( filePath, (size_t)(pathEnd - filePath) );
|
2017-08-31 19:30:42 +00:00
|
|
|
for (char *dirName = strtok( path, "/" ); success && dirName; dirName = strtok( NULL, "/" )) {
|
|
|
|
if (!strlen( dirName ))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
success &= (mkdir( dirName, 0700 ) != ERR || errno == EEXIST) && chdir( dirName ) != ERR;
|
|
|
|
}
|
|
|
|
free( path );
|
|
|
|
|
|
|
|
if (chdir( cwd ) == ERR)
|
2018-03-24 19:14:41 +00:00
|
|
|
wrn( "Could not restore cwd:\n %s: %s", cwd, strerror( errno ) );
|
2017-08-31 19:30:42 +00:00
|
|
|
free( cwd );
|
|
|
|
|
|
|
|
return success;
|
|
|
|
}
|
|
|
|
|
2018-09-22 13:47:11 +00:00
|
|
|
const char *mpw_read_fd(int fd) {
|
2017-08-31 19:30:42 +00:00
|
|
|
|
|
|
|
char *buf = NULL;
|
|
|
|
size_t blockSize = 4096, bufSize = 0, bufOffset = 0;
|
|
|
|
ssize_t readSize = 0;
|
|
|
|
while ((mpw_realloc( &buf, &bufSize, blockSize )) &&
|
|
|
|
((readSize = read( fd, buf + bufOffset, blockSize )) > 0));
|
|
|
|
if (readSize == ERR)
|
2017-09-01 15:16:09 +00:00
|
|
|
mpw_free( &buf, bufSize );
|
2017-08-31 19:30:42 +00:00
|
|
|
|
|
|
|
return buf;
|
|
|
|
}
|
|
|
|
|
2018-09-22 13:47:11 +00:00
|
|
|
const char *mpw_read_file(FILE *file) {
|
2017-08-31 19:30:42 +00:00
|
|
|
|
|
|
|
if (!file)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
char *buf = NULL;
|
|
|
|
size_t blockSize = 4096, bufSize = 0, bufOffset = 0, readSize = 0;
|
|
|
|
while ((mpw_realloc( &buf, &bufSize, blockSize )) &&
|
|
|
|
(bufOffset += (readSize = fread( buf + bufOffset, 1, blockSize, file ))) &&
|
|
|
|
(readSize == blockSize));
|
2020-01-23 20:55:03 +00:00
|
|
|
if (ferror( file ))
|
|
|
|
mpw_free( &buf, bufSize );
|
2017-08-31 19:30:42 +00:00
|
|
|
|
|
|
|
return buf;
|
|
|
|
}
|
2017-09-25 22:34:12 +00:00
|
|
|
|
|
|
|
#if MPW_COLOR
|
|
|
|
static char *str_tputs;
|
|
|
|
static int str_tputs_cursor;
|
|
|
|
static const int str_tputs_max = 256;
|
|
|
|
|
|
|
|
static bool mpw_setupterm() {
|
|
|
|
|
|
|
|
if (!isatty( STDERR_FILENO ))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
static bool termsetup;
|
|
|
|
if (!termsetup) {
|
|
|
|
int errret;
|
|
|
|
if (!(termsetup = (setupterm( NULL, STDERR_FILENO, &errret ) == OK))) {
|
2018-03-24 19:14:41 +00:00
|
|
|
wrn( "Terminal doesn't support color (setupterm errret %d).", errret );
|
2017-09-25 22:34:12 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int mpw_tputc(int c) {
|
|
|
|
|
|
|
|
if (++str_tputs_cursor < str_tputs_max) {
|
|
|
|
str_tputs[str_tputs_cursor] = (char)c;
|
|
|
|
return OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ERR;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *mpw_tputs(const char *str, int affcnt) {
|
|
|
|
|
|
|
|
if (str_tputs)
|
|
|
|
mpw_free( &str_tputs, str_tputs_max );
|
|
|
|
str_tputs = calloc( str_tputs_max, sizeof( char ) );
|
|
|
|
str_tputs_cursor = -1;
|
|
|
|
|
|
|
|
char *result = tputs( str, affcnt, mpw_tputc ) == ERR? NULL: mpw_strndup( str_tputs, str_tputs_max );
|
|
|
|
if (str_tputs)
|
|
|
|
mpw_free( &str_tputs, str_tputs_max );
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
2020-01-23 20:57:07 +00:00
|
|
|
const char *mpw_identicon_render(MPIdenticon identicon) {
|
2017-09-25 22:34:12 +00:00
|
|
|
|
|
|
|
char *colorString, *resetString;
|
|
|
|
#ifdef MPW_COLOR
|
|
|
|
if (mpw_setupterm()) {
|
|
|
|
colorString = mpw_tputs( tparm( tgetstr( "AF", NULL ), identicon.color ), 1 );
|
|
|
|
resetString = mpw_tputs( tgetstr( "me", NULL ), 1 );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
#endif
|
|
|
|
{
|
|
|
|
colorString = calloc( 1, sizeof( char ) );
|
|
|
|
resetString = calloc( 1, sizeof( char ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *str = mpw_str( "%s%s%s%s%s%s",
|
2020-01-23 20:55:03 +00:00
|
|
|
colorString? colorString: "",
|
|
|
|
identicon.leftArm, identicon.body, identicon.rightArm, identicon.accessory,
|
|
|
|
resetString? resetString: "" );
|
2017-09-25 22:34:12 +00:00
|
|
|
mpw_free_strings( &colorString, &resetString, NULL );
|
|
|
|
|
|
|
|
return mpw_strdup( str );
|
|
|
|
}
|