/*
* 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;
}