notpass

notpass - a password manager
git clone https://git.sebsite.pw/clone/notpass.git
Log | Files | Refs | README | LICENSE

main.c (8170B)


      1 #define _POSIX_C_SOURCE 200112L
      2 
      3 /* for random() */
      4 #define _XOPEN_SOURCE 500
      5 
      6 #include <sys/stat.h>
      7 #include <sys/types.h>
      8 
      9 #include <crypt.h>
     10 #include <fcntl.h>
     11 #include <stdint.h>
     12 #include <stdio.h>
     13 #include <stdlib.h>
     14 #include <string.h>
     15 #include <termios.h>
     16 #include <time.h>
     17 #include <unistd.h>
     18 
     19 #define AES256 1
     20 #include "aes.h"
     21 
     22 #include "config.h"
     23 
     24 #define USAGEMSG \
     25 	"Usage: np {name}\n" \
     26 	"       np -i\n" \
     27 	"       np -n {name}\n" \
     28 	/*"see np(1) for help\n"*/
     29 
     30 #define OPENERRMSG \
     31 	"Error opening .notpass file\n" \
     32 	"Did you initialize it with `np -i'?\n" \
     33 	/*"see np(1) for more help\n"*/
     34 
     35 #if __STDC_VERSION__ < 201112L
     36 
     37 #define _Noreturn
     38 
     39 #if __STDC_VERSION__ < 199901L
     40 
     41 #define inline
     42 
     43 #endif
     44 #endif
     45 
     46 static _Noreturn void
     47 usage(void)
     48 {
     49 	fputs(USAGEMSG, stderr);
     50 	exit(EXIT_FAILURE);
     51 }
     52 
     53 static void *
     54 xmalloc(size_t size)
     55 {
     56 	void *ret;
     57 	if (!(ret = malloc(size))) {
     58 		fputs("Out of memory\n", stderr);
     59 		exit(EXIT_FAILURE);
     60 	}
     61 	return ret;
     62 }
     63 
     64 static char *
     65 randstr(size_t sz)
     66 {
     67 	char *s = xmalloc(sz + 1);
     68 
     69 	s[sz] = 0;
     70 	while (sz--) {
     71 		s[sz] = random() % 64;
     72 		if (s[sz] < 12)
     73 			s[sz] += '.';
     74 		else if (s[sz] < 38)
     75 			s[sz] += 'A' - 12;
     76 		else
     77 			s[sz] += 'a' - 38;
     78 	}
     79 
     80 	return s;
     81 }
     82 
     83 static char *
     84 gensalt(void)
     85 {
     86 	char *salt = xmalloc(21);
     87 	char *rand = randstr(16);
     88 
     89 	memcpy(salt, "$6$", 3);
     90 	memcpy(salt + 3, rand, 16);
     91 	free(rand);
     92 	memcpy(salt + 19, "$", 2);
     93 
     94 	return salt;
     95 }
     96 
     97 /* this does not set termios; that must be done before calling this function */
     98 static enum { VALID, INVALID, ERR }
     99 validate(char **plain_dest)
    100 {
    101 	char buf[256], hashed[107];
    102 	FILE *f;
    103 
    104 	/* for consistency. everytime the same master password is entered,
    105 	 * the entire buffer contents should be identical
    106 	 */
    107 	memset(buf, 0, 256);
    108 
    109 	if (!(f = fopen(PWDIR ".notpass", "r"))) {
    110 		fputs(OPENERRMSG, stderr);
    111 		return ERR;
    112 	}
    113 	if (fread(hashed, 1, 107, f) < 107) {
    114 		fputs("Error reading .notpass file\n", stderr);
    115 		fclose(f);
    116 		return ERR;
    117 	}
    118 	fclose(f);
    119 
    120 	fputs("Enter master password: ", stderr);
    121 	if (!fgets(buf, 256, stdin)) {
    122 		memset(buf, 0, 256);
    123 		fputs("Failed to read input\n", stderr);
    124 		return ERR;
    125 	}
    126 	putchar('\n');
    127 
    128 	if (strcmp(crypt(buf, hashed), hashed) == 0) {
    129 		*plain_dest = xmalloc(256);
    130 		memcpy(*plain_dest, buf, 256);
    131 		return VALID;
    132 	} else {
    133 		memset(buf, 0, 256);
    134 		return INVALID;
    135 	}
    136 }
    137 
    138 static inline void
    139 init(const char *dir)
    140 {
    141 	struct termios orig_termios, new_termios;
    142 	char buf[256], *salt, *hashed, *filename;
    143 	ssize_t bytes_written = 0;
    144 	size_t len;
    145 	int fd;
    146 
    147 	/* prevent input from being echoed to the screen.
    148 	 * orig_termios is restored after the password is validated.
    149 	 */
    150 	if (tcgetattr(0, &orig_termios) == -1) {
    151 		fputs("tcgetattr error\n", stderr);
    152 		exit(EXIT_FAILURE);
    153 	}
    154 	new_termios = orig_termios;
    155 	new_termios.c_lflag &= ~ECHO;
    156 	if (tcsetattr(0, TCSANOW, &new_termios) == -1) {
    157 		fputs("tcsetattr error\n", stderr);
    158 		exit(EXIT_FAILURE);
    159 	}
    160 
    161 	if (dir) {
    162 		len = strlen(dir);
    163 		filename = xmalloc(len + sizeof(".notpass") + 1);
    164 		strcpy(filename, dir);
    165 		strcpy(filename + len, dir[len - 1] == '/'
    166 		       ? ".notpass"
    167 		       : "/.notpass");
    168 	} else {
    169 		filename = PWDIR ".notpass";
    170 	}
    171 
    172 	if ((fd = creat(filename, 0600)) == -1) {
    173 		tcsetattr(0, TCSAFLUSH, &orig_termios);
    174 		fputs("Error creating .notpass file\n", stderr);
    175 		exit(EXIT_FAILURE);
    176 	}
    177 
    178 	salt = gensalt();
    179 
    180 	fputs("Enter new master password: ", stderr);
    181 	if (!fgets(buf, 256, stdin)) {
    182 		memset(buf, 0, 256);
    183 		tcsetattr(0, TCSAFLUSH, &orig_termios);
    184 		fputs("Failed to read input\n", stderr);
    185 		exit(EXIT_FAILURE);
    186 	}
    187 	putchar('\n');
    188 
    189 	tcsetattr(0, TCSAFLUSH, &orig_termios);
    190 
    191 	hashed = crypt(buf, salt);
    192 	memset(buf, 0, 256);
    193 
    194 	len = strlen(hashed) + 1;
    195 	while ((bytes_written += write(fd, hashed + bytes_written,
    196 					len - bytes_written)) < len) {
    197 		if (bytes_written >= 0)
    198 			continue;
    199 		fputs("Failed to write to .notpass file\n", stderr);
    200 		exit(EXIT_FAILURE);
    201 	}
    202 
    203 	close(fd);
    204 }
    205 
    206 static inline void
    207 get(const char *name, const char *dir)
    208 {
    209 	struct AES_ctx aes_ctx;
    210 	struct termios orig_termios, new_termios;
    211 	char *master, buf[256], iv[AES_BLOCKLEN];
    212 	char *filename = xmalloc(strlen(dir) + strlen(name) + 1);
    213 	FILE *f;
    214 
    215 	/* prevent input from being echoed to the screen.
    216 	 * orig_termios is restored after the password is validated.
    217 	 */
    218 	if (tcgetattr(0, &orig_termios) == -1) {
    219 		fputs("tcgetattr error\n", stderr);
    220 		exit(EXIT_FAILURE);
    221 	}
    222 	new_termios = orig_termios;
    223 	new_termios.c_lflag &= ~ECHO;
    224 	if (tcsetattr(0, TCSANOW, &new_termios) == -1) {
    225 		fputs("tcsetattr error\n", stderr);
    226 		exit(EXIT_FAILURE);
    227 	}
    228 
    229 	switch (validate(&master)) {
    230 	case VALID:
    231 		tcsetattr(0, TCSAFLUSH, &orig_termios);
    232 		break;
    233 	case INVALID:
    234 		fputs("Invalid master password\n", stderr);
    235 		/* fallthrough */
    236 	case ERR:
    237 		/* in case of error, validate fn already printed error msg */
    238 		tcsetattr(0, TCSAFLUSH, &orig_termios);
    239 		exit(EXIT_FAILURE);
    240 	}
    241 
    242 	strcpy(filename, dir);
    243 	strcpy(filename + strlen(dir), name);
    244 
    245 	if (!(f = fopen(filename, "r"))) {
    246 		memset(master, 0, strlen(master));
    247 		fprintf(stderr, "Failed to open file `%s'\n", filename);
    248 		exit(EXIT_FAILURE);
    249 	}
    250 	if (fread(iv, 1, AES_BLOCKLEN, f) < AES_BLOCKLEN)
    251 		goto read_err;
    252 	if (fread(buf, 1, 256, f) < 256)
    253 		goto read_err;
    254 	fclose(f);
    255 
    256 	AES_init_ctx_iv(&aes_ctx, (uint8_t *)master, (uint8_t *)iv);
    257 	memset(master, 0, 256);
    258 	free(master);
    259 	AES_CBC_decrypt_buffer(&aes_ctx, (uint8_t *)buf, 256);
    260 
    261 	printf("%.*s", 256 - (unsigned char)(buf[255]), buf);
    262 	memset(buf, 0, 256);
    263 	return;
    264 
    265 read_err:
    266 	memset(master, 0, 256);
    267 	fprintf(stderr, "Error reading file `%s'\n", filename);
    268 	exit(EXIT_FAILURE);
    269 }
    270 
    271 static inline void
    272 new(const char *name, const char *dir)
    273 {
    274 	struct termios orig_termios, new_termios;
    275 	struct AES_ctx aes_ctx;
    276 	char buf[256], *master, *iv, *filename;
    277 	FILE *f;
    278 	int fd, i;
    279 
    280 	/* prevent input from being echoed to the screen.
    281 	 * orig_termios is restored after the password is validated.
    282 	 */
    283 	if (tcgetattr(0, &orig_termios) == -1) {
    284 		fputs("tcgetattr error\n", stderr);
    285 		exit(EXIT_FAILURE);
    286 	}
    287 	new_termios = orig_termios;
    288 	new_termios.c_lflag &= ~ECHO;
    289 	if (tcsetattr(0, TCSANOW, &new_termios) == -1) {
    290 		fputs("tcsetattr error\n", stderr);
    291 		exit(EXIT_FAILURE);
    292 	}
    293 
    294 	switch (validate(&master)) {
    295 	case VALID:
    296 		break;
    297 	case INVALID:
    298 		fputs("Invalid master password\n", stderr);
    299 		/* fallthrough */
    300 	case ERR:
    301 		/* in case of error, validate fn already printed error msg */
    302 		tcsetattr(0, TCSAFLUSH, &orig_termios);
    303 		exit(EXIT_FAILURE);
    304 	}
    305 
    306 	fputs("Enter new password entry: ", stderr);
    307 
    308 	if (!fgets(buf, 256, stdin)) {
    309 		memset(buf, 0, 256);
    310 		memset(master, 0, 256);
    311 		tcsetattr(0, TCSAFLUSH, &orig_termios);
    312 		fputs("Failed to read input\n", stderr);
    313 		exit(EXIT_FAILURE);
    314 	}
    315 	putchar('\n');
    316 
    317 	tcsetattr(0, TCSAFLUSH, &orig_termios);
    318 
    319 	/* padding for buf: PKCS #7 */
    320 	for (i = 0; buf[i]; i++);
    321 	memset(buf + i, 256 - i, 256 - i);
    322 
    323 	iv = randstr(AES_BLOCKLEN);
    324 	AES_init_ctx_iv(&aes_ctx, (uint8_t *)master, (uint8_t *)iv);
    325 	free(master);
    326 
    327 	AES_CBC_encrypt_buffer(&aes_ctx, (uint8_t *)buf, 256);
    328 
    329 	filename = xmalloc(strlen(dir) + strlen(name) + 1);
    330 	strcpy(filename, dir);
    331 	strcpy(filename + strlen(dir), name);
    332 	if ((fd = creat(filename, 0600)) == -1) {
    333 		fprintf(stderr, "Error creating file `%s'\n", filename);
    334 		exit(EXIT_FAILURE);
    335 	}
    336 
    337 	if (!(f = fdopen(fd, "w"))) {
    338 		fprintf(stderr, "Error opening file `%s'\n", filename);
    339 		exit(EXIT_FAILURE);
    340 	}
    341 	if (fwrite(iv, 1, AES_BLOCKLEN, f) < AES_BLOCKLEN)
    342 		goto write_err;
    343 	free(iv);
    344 	if (fwrite(buf, 1, 256, f) < 256)
    345 		goto write_err;
    346 
    347 	fclose(f);
    348 	return;
    349 
    350 write_err:
    351 	fprintf(stderr, "Failed to write to file `%s'\n", filename);
    352 	exit(EXIT_FAILURE);
    353 }
    354 
    355 int
    356 main(int argc, char **argv)
    357 {
    358 	if (argc < 2)
    359 		usage();
    360 
    361 	srandom(time(NULL));
    362 
    363 	if (argv[1][0] == '-') {
    364 		switch (argv[1][1]) {
    365 		case 'i': /* initialize new key / master password */
    366 			if (argc > 3)
    367 				usage();
    368 
    369 			init((argc == 3) ? argv[2] : NULL);
    370 			return EXIT_SUCCESS;
    371 		case 'n': /* new */
    372 			if (argc > 4 || argv[2][0] == '-')
    373 				usage();
    374 
    375 			new(argv[2], (argc == 4) ? argv[3] : PWDIR);
    376 			return EXIT_SUCCESS;
    377 		default:
    378 			usage();
    379 		}
    380 	}
    381 
    382 	if (argc > 3)
    383 		usage();
    384 
    385 	get(argv[1], (argc == 3) ? argv[2] : PWDIR);
    386 	return EXIT_SUCCESS;
    387 }