/*
* Compile commandline: cc -std=gnu99 -Wall -Wextra -Os -o cpass pass.c
* Source code:
https://04d.co/dl/software/pass.c
* License: public domain
* TODO: Implement GPGME, support for Windows
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <dirent.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#define defaultPasswordLength 25
#define VER "0.4"
/* Creates a new file or overrides an existing one with a user chosen password */
void createPass(char* path)
{
/* Check if file already exists, if so prompt for override */
struct stat st;
if(stat(path, &st) == 0)
{
char choice;
printf("Password already exists, override? (y/N): ");
scanf("%c", &choice);
if(choice == 'Y' || choice == 'y') {}
else
{
printf("Aborting\n");
exit(EXIT_SUCCESS);
}
}
/* Create a file (or empty it if it already exists) and open it for writing */
FILE *fp = fopen(path, "w");
if(fp == NULL)
{
fprintf(stderr, "Error opening file for writing: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
/* Prompt for password twice, exit if both don't match */
char* fpass = getpass("Password: ");
char* lpass = getpass("Repeat password: ");
if(strcmp(fpass, lpass) == 0)
{
fprintf(fp, "%s", fpass);
printf("Password added\n");
}
else
{
printf("Passwords do not match, try again\n");
exit(EXIT_SUCCESS);
}
/* Flush data to disk */
fflush(fp);
fclose(fp);
return;
}
/* Shows a password from a user provided file */
void showPass(char* path)
{
/* Check if file exists and open the file and read one
* character at a time until EOF */
struct stat st;
FILE *fp = fopen(path, "r");
if(stat(path, &st) == -1 || fp == NULL)
{
fprintf(stderr, "Error opening file: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
char curChar;
while((curChar = fgetc(fp)) != EOF)
printf("%c", curChar);
printf("\n");
/* Clean up */
fflush(fp);
fclose(fp);
return;
}
/* Delete a user provided file */
void deletePass(char* path)
{
/* Check if file exists */
struct stat st;
if(stat(path, &st) == 0)
{
char choice;
printf("Are you sure? (y/N): ");
scanf("%c", &choice);
if(choice == 'Y' || choice == 'y')
{
if(remove(path) == 0)
{
printf("File deleted successfully\n");
exit(EXIT_SUCCESS);
}
else
{
fprintf(stderr, "Error deleting file: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
}
else
{
printf("Aborting\n");
exit(EXIT_SUCCESS);
}
}
else
{
fprintf(stderr, "Error deleting file: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
return;
}
/* Generate a random password and write it to a user provided file */
void generatePass(char* path, unsigned int passLen)
{
const char charSet[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890?!@#$%^&*<>()[]{}|~`,.;";
char password[passLen+1];
/* Check if file already exists, if so prompt for override */
struct stat st;
if(stat(path, &st) == 0)
{
char choice;
printf("Password already exists, override? (y/N): ");
scanf("%c", &choice);
if(choice == 'Y' || choice == 'y') {}
else
{
printf("Aborting\n");
exit(EXIT_SUCCESS);
}
}
/* Seed the RNG with a better source than just time(0) */
unsigned int pid = getpid();
struct timeval t1;
gettimeofday(&t1, NULL);
srand(t1.tv_usec ^ t1.tv_sec ^ pid);
/* For every character position, pick a random char from the set */
for(unsigned int i=0; i < passLen; i++)
{
unsigned int randomIndex = rand() % (sizeof(charSet) - 1);
password[i] = charSet[randomIndex];
}
password[passLen] = '\0'; /* Null terminate the password */
/* Create a file (or empty it if it already exists) and open it for writing */
FILE *fp = fopen(path, "w");
if(fp == NULL)
{
fprintf(stderr, "Error opening file for writing: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
/* Write the newly generated password to a file */
fprintf(fp, "%s", password);
/* Flush data to disk */
fflush(fp);
fclose(fp);
printf("Password added\n");
return;
}
/* Copy or rename a password file */
void cpmvPass(char* src, char* dest, unsigned int removeSource)
{
/* Make a buffer, source and destination pointers */
char buf[4096];
FILE *stream_src = fopen(src, "r");
if(stream_src == NULL)
{
fprintf(stderr, "Error opening source file for reading: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
FILE *stream_dest = fopen(dest, "w");
if(stream_src == NULL)
{
fprintf(stderr, "Error opening destination file for writing: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
/* Copy buf bytes at a time into memory and then to disk */
while(!feof(stream_src))
{
size_t bytes = fread(buf, 1, sizeof(buf), stream_src);
if(bytes)
fwrite(buf, 1, bytes, stream_dest);
else
{
fprintf(stderr, "Error when trying to copy bytes "
"(are source and destination the same file?)\n");
exit(EXIT_FAILURE);
}
}
fclose(stream_src);
fflush(stream_dest);
fclose(stream_dest);
if(removeSource == 1)
{
if(remove(src) == 0)
printf("File moved successfully\n");
else
fprintf(stderr, "Error deleting source file: %s\n", strerror(errno));
}
else
printf("File copied successfully\n");
return;
}
int main(int argc, char *argv[])
{
const char defaultDir[] = "/.local/share/passDir";
char* baseDir;
/* If env variable CPASS_DIR is defined, used that instead of
* the default "passDir" */
if(getenv("CPASS_DIR") == NULL)
{
/* Create the password folder under the user's home dir by default */
baseDir = (char*)malloc(strlen(getenv("HOME")) + strlen(defaultDir) + 1);
strcpy(baseDir, getenv("HOME"));
strcat(baseDir, defaultDir);
}
else
{
baseDir = (char*)malloc(strlen(getenv("CPASS_DIR")) + 1);
strcpy(baseDir, getenv("CPASS_DIR"));
}
char *path, *srcPath, *destPath;
/* Prompt to create baseDir if it doesn't exist already */
struct stat st;
if(stat(baseDir, &st) == -1)
{
char choice;
printf("Password directory %s does not yet exist. "
"Do you want to create it? (y/N): ", baseDir);
scanf("%c", &choice);
if(choice == 'Y' || choice == 'y')
if(mkdir(baseDir, 0700) == 0)
printf("Base directory created successfully\n");
else
{
fprintf(stderr, "Error creating base directory: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
else
{
printf("Aborting\n");
exit(EXIT_SUCCESS);
}
}
/* If no arguments are specified, show stored files */
if(argc == 1)
{
DIR *d = opendir(baseDir);
struct dirent *dir;
if(d == NULL)
{
fprintf(stderr, "Error opening base directory: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
while((dir = readdir(d)) != NULL)
printf("%s\n", dir->d_name);
closedir(d);
}
else if(strcmp(argv[1], "new") == 0)
{
if(argc < 3)
{
printf("No file name provided\n");
return 1;
}
else if(strcmp(argv[2], "new") == 0 ||
strcmp(argv[2], "ver") == 0 ||
strcmp(argv[2], "help") == 0 ||
strcmp(argv[2], "del") == 0 ||
strcmp(argv[2], "copy") == 0 ||
strcmp(argv[2], "move") == 0 ||
strcmp(argv[2], "gen") == 0)
{
printf("File cannot be named after a program command\n");
return 1;
}
/* Construct a path where to write the file */
path = (char*)malloc(strlen(baseDir) + strlen(argv[2]) + 1);
if(path == NULL)
{
fprintf(stderr, "Error allocating memory: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
sprintf(path, "%s/%s", baseDir, argv[2]);
free(baseDir);
/* Do the rest */
createPass(path);
/* Clean up memory */
free(path);
}
else if(strcmp(argv[1], "gen") == 0)
{
if(argc < 3)
{
printf("No file name provided\n");
return 1;
}
else if(strcmp(argv[2], "new") == 0 ||
strcmp(argv[2], "ver") == 0 ||
strcmp(argv[2], "help") == 0 ||
strcmp(argv[2], "del") == 0 ||
strcmp(argv[2], "copy") == 0 ||
strcmp(argv[2], "move") == 0 ||
strcmp(argv[2], "gen") == 0)
{
printf("File cannot be named after a program command\n");
return 1;
}
/* Construct a path where to write the file */
path = (char*)malloc(strlen(baseDir) + strlen(argv[2]) + 1);
if(path == NULL)
{
fprintf(stderr, "Error allocating memory: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
sprintf(path, "%s/%s", baseDir, argv[2]);
free(baseDir);
/* Allow choosing generated password length */
unsigned int passLen = defaultPasswordLength;
if(argc == 4)
{
if(atoi(argv[3]) != 0) /* Check if argument contains only digits */
{
unsigned int tmp = atoi(argv[3]);
if(tmp > 4096) /* Check if user doesn't provide unreasonably long password length */
{
printf("Provided length is larger than 4095, "
"consider choosing a shorter password\n");
exit(EXIT_FAILURE);
}
else
passLen = atoi(argv[3]);
}
else
{
printf("Provided length is not a number\n");
exit(EXIT_FAILURE);
}
}
/* Do the rest */
generatePass(path, passLen);
/* Clean up memory */
free(path);
}
else if(strcmp(argv[1], "del") == 0)
{
if(argc < 3)
{
printf("No file name provided\n");
return 1;
}
/* Construct a path where to write the file */
path = (char*)malloc(strlen(baseDir) + strlen(argv[2]) + 1);
if(path == NULL)
{
fprintf(stderr, "Error allocating memory: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
sprintf(path, "%s/%s", baseDir, argv[2]);
free(baseDir);
/* Do the rest */
deletePass(path);
/* Clean up memory */
free(path);
}
else if(strcmp(argv[1], "copy") == 0 || strcmp(argv[1], "move") == 0)
{
if(argc < 3)
{
printf("No source file name provided\n");
return 1;
}
if(argc < 4)
{
printf("No destination file name provided\n");
return 1;
}
/* Construct a path where to find the source and destination file */
srcPath = (char*)malloc(strlen(baseDir) + strlen(argv[2]) + 1);
if(srcPath == NULL)
{
fprintf(stderr, "Error allocating memory: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
destPath = (char*)malloc(strlen(baseDir) + strlen(argv[3]) + 1);
if(destPath == NULL)
{
fprintf(stderr, "Error allocating memory: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
sprintf(srcPath, "%s/%s", baseDir, argv[2]);
sprintf(destPath, "%s/%s", baseDir, argv[3]);
free(baseDir);
/* Do the rest */
if(strcmp(argv[1], "move") == 0)
cpmvPass(srcPath, destPath, 1);
else
cpmvPass(srcPath, destPath, 0);
/* Clean up memory */
free(srcPath);
free(destPath);
}
else if(strcmp(argv[1], "help") == 0)
printf("Passwords are stored in %s, dictated by the presence"
" or absence of the CPASS_DIR environment variable.\n"
"%s passName\n"
"%s help\n"
"%s ver\n"
"%s new passName\n"
"%s del passName\n"
"%s gen passName [passLength]\n"
"%s copy oldName newName\n"
"%s move oldName newName\n",
baseDir, argv[0], argv[0], argv[0], argv[0], argv[0], argv[0], argv[0], argv[0]);
else if(strcmp(argv[1], "ver") == 0)
printf("cpass version "VER", compiled on %s at %s\n", __DATE__,__TIME__);
else if(argc == 2)
{
/* Construct a path where to find the file */
path = (char*)malloc(strlen(baseDir) + strlen(argv[1]) + 1);
if(path == NULL)
{
fprintf(stderr, "Error allocating memory: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
sprintf(path, "%s/%s", baseDir, argv[1]);
free(baseDir);
/* Do the rest */
showPass(path);
/* Clean up memory */
free(path);
}
return 0;
}