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 }