apm

Simple pass(1) implementation
git clone https://codeberg.org/night0721/apm
Log | Files | Refs | README | LICENSE

apm.c (11342B)


      1 #include <stdio.h>
      2 #include <stdlib.h>
      3 #include <string.h>
      4 #include <unistd.h>
      5 #include <dirent.h>
      6 #include <libgen.h>
      7 #include <time.h>
      8 #include <sys/stat.h>
      9 #include <sys/resource.h>
     10 #include <sodium.h>
     11 
     12 #include "arg.h"
     13 
     14 #define KEY_SIZE crypto_secretbox_KEYBYTES
     15 #define NONCE_SIZE crypto_secretbox_NONCEBYTES
     16 #define SALT_SIZE crypto_pwhash_SALTBYTES
     17 
     18 char *argv0;
     19 
     20 void die(char *str);
     21 
     22 void *memalloc(size_t size)
     23 {
     24     void *ptr = malloc(size);
     25     if (!ptr) {
     26         perror("apm");
     27         exit(EXIT_FAILURE);
     28     }
     29     return ptr;
     30 }
     31 
     32 char *get_master_key();
     33 
     34 void usage()
     35 {
     36     printf("Usage: %s [-vhL] [[-e | -R | -I | -Q] <password>] [-M <file>] [-G <password> <length>]\n", argv0);
     37     exit(EXIT_SUCCESS);
     38 }
     39 
     40 int compare(const void *a, const void *b)
     41 {
     42     return strcmp(*(const char **)a, *(const char **)b);
     43 }
     44 
     45 void tree(const char *basepath, int depth)
     46 {
     47     char path[PATH_MAX];
     48     struct dirent *dp;
     49     DIR *dir = opendir(basepath);
     50 
     51     if (!dir)
     52         return;
     53 
     54     /* max 1024 files */
     55     char *files[1024];
     56     int file_count = 0;
     57 
     58     while ((dp = readdir(dir)) != NULL) {
     59         if (strcmp(dp->d_name, ".") != 0 && strcmp(dp->d_name, "..") != 0) {
     60             files[file_count] = strdup(dp->d_name);
     61             file_count++;
     62         }
     63     }
     64 
     65     qsort(files, file_count, sizeof(char *), compare);
     66 
     67     for (int i = 0; i < file_count; i++) {
     68         for (int j = 0; j < depth - 1; j++) {
     69             printf("│   ");
     70         }
     71 
     72         if (depth > 0) {
     73             printf("├── ");
     74         }
     75 
     76         printf("%s\n", files[i]);
     77 
     78         strcpy(path, basepath);
     79         strcat(path, "/");
     80         strcat(path, files[i]);
     81 
     82         tree(path, depth + 1);
     83 
     84         free(files[i]);
     85     }
     86 
     87     closedir(dir);
     88 }
     89 
     90 char *get_apm()
     91 {
     92     char dirname[] = "apm";
     93     const char *apm_dir = getenv("APM_DIR");
     94     /* for / and null */
     95     size_t len = 2;
     96     if (apm_dir == NULL) {
     97         apm_dir = getenv("XDG_DATA_HOME");
     98         if (apm_dir == NULL) {
     99             apm_dir = getenv("HOME");
    100             if (apm_dir == NULL) {
    101                 die("HOME not defined");
    102             }
    103         }
    104         len += strlen(dirname);
    105     } else {
    106         /* no / */
    107         len -= 1;
    108     }
    109     size_t dir_len = strlen(apm_dir);
    110     len += dir_len;
    111     char *dir = memalloc(len * sizeof(char));
    112 
    113     /* check if it is apm_DIR or other */
    114     if (len > dir_len) {
    115         snprintf(dir, len, "%s/%s", apm_dir, dirname);
    116     } else {
    117         strncpy(dir, apm_dir, len);
    118     }
    119     struct stat stats;
    120     /* check defined path is directory */
    121     if (!((stat(dir, &stats) == 0) && S_ISDIR(stats.st_mode))) {
    122         if (mkdir(dir, S_IRWXU)) { /* 700 */
    123             die("Cannot initialize directory");
    124         }
    125     }
    126     return dir;
    127 }
    128 
    129 char *get_passfile(const char *key)
    130 {
    131     char *dir = get_apm();
    132     /* concat file name */
    133     /* / and null */
    134     size_t len = strlen(dir) + strlen(key) + 2; 
    135     char *path = memalloc(len * sizeof(char));
    136     snprintf(path, len, "%s/%s", dir, key);
    137     free(dir);
    138     return path;
    139 }
    140 
    141 char *get_password()
    142 {
    143     size_t len;
    144     char *password = NULL;
    145     printf("Enter password to encrypt: \n");
    146     getline(&password, &len, stdin);
    147     /* remove newline character */
    148     password[strcspn(password, "\n")] = '\0';
    149     return password;
    150 }
    151 
    152 void encrypt_password(const char *name, char *password)
    153 {
    154     char *m_key = get_master_key();
    155     unsigned char salt[SALT_SIZE];
    156     unsigned char key[KEY_SIZE];
    157     unsigned char nonce[NONCE_SIZE];
    158 
    159     /* generate random bytes for salt and nonce(number used once) */
    160     randombytes_buf(salt, sizeof(salt));
    161     randombytes_buf(nonce, sizeof(nonce));
    162     /* hash master password to give us the key for encrypting the password */
    163     if (crypto_pwhash(key, sizeof(key), m_key, strlen(m_key), salt,
    164                 crypto_pwhash_OPSLIMIT_INTERACTIVE,
    165                 crypto_pwhash_MEMLIMIT_INTERACTIVE,
    166                   crypto_pwhash_ALG_DEFAULT) != 0) {
    167         sodium_free(m_key);
    168         perror("apm");
    169         die("Cannot create key");
    170     }
    171     size_t pw_len = strlen(password);
    172     /* Include space for authentication tag */
    173     size_t ciphered_len = crypto_secretbox_MACBYTES + pw_len;
    174     char ciphered[ciphered_len];
    175 
    176     /* encrypt password */
    177     if (crypto_secretbox_easy((unsigned char *) ciphered, 
    178                 (unsigned char *) password, pw_len,
    179                 nonce, key) != 0) {
    180         sodium_free(m_key);
    181         die("Error encrypting password");
    182     }
    183 
    184     char *filepath = get_passfile(name);
    185     FILE *file = fopen(filepath, "wb");
    186     if (file == NULL) {
    187         sodium_free(m_key);
    188         free(filepath);
    189         die("Error opening pass file to write");
    190     }
    191    
    192     fwrite(salt, sizeof(salt), 1, file);
    193     fwrite(nonce, sizeof(nonce), 1, file);
    194     fwrite(ciphered, ciphered_len, 1, file);
    195 
    196     fclose(file);
    197     free(filepath);
    198     sodium_free(m_key);
    199 }
    200 
    201 void decrypt_password(const char *name, int open)
    202 {
    203     char *m_key = get_master_key();
    204     unsigned char salt[SALT_SIZE];
    205     unsigned char nonce[NONCE_SIZE];
    206     unsigned char key[KEY_SIZE];
    207 
    208     char *filepath = get_passfile(name);
    209     FILE *file = fopen(filepath, "rb");
    210     if (file == NULL) {
    211         sodium_free(m_key);
    212         free(filepath);
    213         die("Error opening pass file to read");
    214     }
    215     
    216     /* get salt and nonce from file */
    217     fread(salt, sizeof(char), sizeof(salt), file);
    218     fread(nonce, sizeof(char), sizeof(nonce), file);
    219 
    220     fseek(file, 0, SEEK_END);
    221     size_t ciphered_len = ftell(file) - sizeof(salt) - sizeof(nonce);
    222     fseek(file, sizeof(salt) + sizeof(nonce), SEEK_SET);
    223 
    224     char ciphered[ciphered_len];
    225     fread(ciphered, sizeof(char), ciphered_len, file);
    226     if (crypto_pwhash(key, sizeof(key), m_key, strlen(m_key), salt,
    227                 crypto_pwhash_OPSLIMIT_INTERACTIVE,
    228                 crypto_pwhash_MEMLIMIT_INTERACTIVE,
    229                   crypto_pwhash_ALG_DEFAULT) != 0) {
    230         sodium_free(m_key);
    231         free(filepath);
    232         perror("apm");
    233         die("Cannot create key");
    234     }
    235     /* take authentication bytes away */
    236     size_t deciphered_len = ciphered_len - crypto_secretbox_MACBYTES;
    237     char deciphered[deciphered_len];
    238     if (crypto_secretbox_open_easy((unsigned char *) deciphered, 
    239                 (unsigned char *) ciphered, ciphered_len, nonce, key) != 0) {
    240         sodium_free(m_key);
    241         free(filepath);
    242         fclose(file);
    243         die("Error decrypting password");
    244     }
    245 
    246     deciphered[deciphered_len] = '\0';
    247     if (open) {
    248         char *editor = getenv("EDITOR");
    249         if (editor == NULL) {
    250             die("EDITOR not defined");
    251         }
    252         char tmp_f[] = "/tmp/apm";
    253         FILE *tmp = fopen(tmp_f, "w+");
    254         fprintf(tmp, "%s\n", deciphered);
    255         fclose(tmp);
    256         char *cmd = memalloc((strlen(editor) + strlen(tmp_f) + 1) * sizeof(char));
    257         sprintf(cmd, "%s %s", editor, tmp_f);
    258         system(cmd);
    259         free(cmd);
    260         tmp = fopen(tmp_f, "r");
    261         fseek(tmp, 0, SEEK_END);
    262         long tmp_size = ftell(tmp);
    263         fseek(tmp, 0, SEEK_SET);
    264         char content[tmp_size + 1];
    265         fgets(content, tmp_size, tmp);
    266         encrypt_password(name, content);
    267         fclose(tmp);
    268     } else {
    269         printf("%s\n", deciphered);
    270     }
    271 
    272     sodium_free(m_key);
    273     fclose(file);
    274 }
    275 
    276 char *get_master_key()
    277 {
    278     char *key_path = getenv("APM_KEY");
    279     char *m_key = NULL;
    280     if (key_path != NULL) {
    281         FILE *key_file = fopen(key_path, "r");
    282         if (key_file == NULL) {
    283             perror("apm");
    284             exit(EXIT_FAILURE);
    285         }
    286         struct stat st;
    287         if ((fstat(fileno(key_file), &st) == 0) || (S_ISREG(st.st_mode))) {
    288             size_t pass_size = st.st_size;
    289             m_key = (char *) sodium_malloc(pass_size * sizeof(char));
    290             if (m_key == NULL) {
    291                 perror("apm");
    292                 exit(EXIT_FAILURE);
    293             }
    294             if (fgets(m_key, pass_size, key_file) == NULL) {
    295                 perror("apm");
    296                 exit(EXIT_FAILURE);
    297             }
    298         }
    299     } else {
    300         fprintf(stderr, "apm: You are required to set APM_KEY to pass file\n");
    301         exit(EXIT_FAILURE);
    302     }
    303     return m_key;
    304 }
    305 
    306 void generate_password(char*name, int length)
    307 {
    308     srand(time(NULL));
    309     const char *characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890123456789~`!@#$%^&*()-_+=[]{}|/,.<>;:'";
    310     size_t characters_len = strlen(characters);
    311     char *random_string = memalloc(length + 1);
    312     for (int i = 0; i < length; i++) {
    313         random_string[i] = characters[rand() % (characters_len - 1)];
    314     }
    315     random_string[length] = '\0';
    316     printf("The generated password for %s is: %s\n", name, random_string);
    317     encrypt_password(name, random_string);
    318     free(random_string);
    319 }
    320 
    321 void die(char *str)
    322 {
    323     fprintf(stderr, "apm: %s\n", str);
    324     exit(EXIT_FAILURE);
    325 }
    326 
    327 int main(int argc, char *argv[])
    328 {
    329     if (sodium_init() == -1) {
    330         die("Error initializing sodium");
    331     }
    332 
    333     /* disable core dump for security */
    334     setrlimit(RLIMIT_CORE, &(struct rlimit) {0, 0});
    335     
    336     ARGBEGIN {
    337         case 'h':
    338             usage();
    339             break;
    340         case 'v':
    341             printf("apm 1.0.0\n");
    342             exit(EXIT_SUCCESS);
    343             break;
    344         case 'e':
    345             decrypt_password(EARGF(usage()), 1);
    346             exit(EXIT_SUCCESS);
    347             break;
    348         case 'R':;
    349             char *pass_file = get_passfile(EARGF(usage()));
    350             if (remove(pass_file)) {
    351                 perror("apm");
    352             } else {
    353                 printf("Removed %s\n", basename(pass_file));
    354             }
    355             free(pass_file);
    356             exit(EXIT_SUCCESS);
    357             break;
    358         case 'I':;
    359             char *pw = get_password();
    360             encrypt_password(EARGF(usage()), pw);
    361             free(pw);
    362             exit(EXIT_SUCCESS);
    363             break;
    364         case 'Q':
    365             decrypt_password(EARGF(usage()), 0);
    366             exit(EXIT_SUCCESS);
    367             break;
    368         case 'L':;
    369             char *apm = get_apm();
    370             tree(apm, 0);
    371             free(apm);
    372             exit(EXIT_SUCCESS);
    373             break;
    374         case 'M':;
    375             char *filename = EARGF(usage());
    376             FILE *file = fopen(filename, "r");
    377             if (file == NULL) {
    378                 die("Cannot open file to read");
    379             }
    380             fseek(file, 0, SEEK_END);
    381             long file_size = ftell(file);
    382             fseek(file, 0, SEEK_SET);
    383             char *content = memalloc(file_size * sizeof(char));
    384             fread(content, sizeof(char), file_size, file);
    385             char *f_basename = basename(filename);
    386             char *dot = strrchr(f_basename, '.');
    387             if (dot != NULL) {
    388                 *dot = '\0';
    389             }
    390             encrypt_password(f_basename, content);
    391             exit(EXIT_SUCCESS);
    392             break;
    393         case 'G':;
    394             if (argc > 0)
    395                 --argc, ++argv;
    396             goto run;
    397         default:
    398             usage();
    399     } ARGEND;
    400 
    401     run:
    402         switch (argc) {
    403             case 0:
    404                 usage();
    405                 break;
    406             case 1:
    407                 decrypt_password(argv[0], 0);
    408                 break;
    409             case 2:
    410                 generate_password(argv[0], atoi(argv[1]));
    411                 break;
    412         }
    413 
    414     return 0;
    415 
    416 }