1 /*
2  * Copyright (C) 2013 Nikos Mavrogiannopoulos
3  *
4  * This file is part of ocserv.
5  *
6  * ocserv is free software: you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * ocserv is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include <config.h>
21 
22 #include <stdio.h>
23 #include <string.h>
24 #include <stdlib.h>
25 #include <stdint.h>
26 #ifndef _XOPEN_SOURCE
27 # define _XOPEN_SOURCE
28 #endif
29 #include <unistd.h>
30 #include <gnutls/gnutls.h>
31 #include <gnutls/crypto.h>	/* for random */
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <getopt.h>
35 #ifdef HAVE_CRYPT_H
36   /* libcrypt in Fedora28 does not provide prototype
37    * in unistd.h */
38 # include <crypt.h>
39 #endif
40 
41 /* Gnulib portability files. */
42 #include <getpass.h>
43 #include <minmax.h>
44 
45 #define DEFAULT_OCPASSWD "/usr/local/etc/ocserv/ocpasswd"
46 
47 static const char alphabet[] =
48 	"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ./";
49 
50 #define SALT_SIZE 16
51 static void
crypt_int(const char * fpasswd,const char * username,const char * groupname,const char * passwd)52 crypt_int(const char *fpasswd, const char *username, const char *groupname,
53 	  const char *passwd)
54 {
55 	uint8_t _salt[SALT_SIZE];
56 	char salt[SALT_SIZE+16];
57 	char *p, *cr_passwd;
58 	char *tmp_passwd;
59 	unsigned i;
60 	unsigned fpasswd_len = strlen(fpasswd);
61 	unsigned tmp_passwd_len;
62 	unsigned username_len = strlen(username);
63 	struct stat st;
64 	FILE *fd, *fd2;
65 	char *line = NULL;
66 	size_t line_size;
67 	ssize_t len, l;
68 	int ret;
69 
70 	ret = gnutls_rnd(GNUTLS_RND_NONCE, _salt, sizeof(_salt));
71 	if (ret < 0) {
72 		fprintf(stderr, "Error generating nonce: %s\n",
73 			gnutls_strerror(ret));
74 		exit(1);
75 	}
76 
77 #ifdef TRY_SHA2_CRYPT
78 	strcpy(salt, "$5$");
79 #else
80 	strcpy(salt, "$1$");
81 #endif
82 	p = salt + 3;
83 
84 	for (i = 0; i < sizeof(_salt); i++) {
85 		*p = alphabet[_salt[i] % (sizeof(alphabet) - 1)];
86 		p++;
87 	}
88 	*p = '$';
89 	p++;
90 	*p = 0;
91 	p++;
92 
93 	cr_passwd = crypt(passwd, salt);
94 	if (cr_passwd == NULL) { /* try MD5 */
95 		salt[1] = '1';
96 		cr_passwd = crypt(passwd, salt);
97 	}
98 	if (cr_passwd == NULL) {
99 		fprintf(stderr, "Error in crypt().\n");
100 		exit(1);
101 	}
102 
103 	tmp_passwd_len = fpasswd_len + 5;
104 	tmp_passwd = malloc(tmp_passwd_len);
105 	if (tmp_passwd == NULL) {
106 		fprintf(stderr, "memory error\n");
107 		exit(1);
108 	}
109 
110 	snprintf(tmp_passwd, tmp_passwd_len, "%s.tmp", fpasswd);
111 	if (stat(tmp_passwd, &st) != -1) {
112 		fprintf(stderr, "file '%s' is locked.\n", fpasswd);
113 		exit(1);
114 	}
115 
116 	fd2 = fopen(tmp_passwd, "w");
117 	if (fd2 == NULL) {
118 		fprintf(stderr, "Cannot open '%s' for writing.\n", tmp_passwd);
119 		exit(1);
120 	}
121 
122 	fd = fopen(fpasswd, "r");
123 	if (fd == NULL) {
124 		fprintf(fd2, "%s:%s:%s\n", username, groupname, cr_passwd);
125 	} else {
126 		int found = 0;
127 		while ((len = getline(&line, &line_size, fd)) > 0) {
128 			p = strchr(line, ':');
129 			if (p == NULL)
130 				continue;
131 
132 			l = p-line;
133 			if (l == username_len && strncmp(line, username, l) == 0) {
134 				fprintf(fd2, "%s:%s:%s\n", username, groupname, cr_passwd);
135 				found = 1;
136 			} else {
137 				fwrite(line, 1, len, fd2);
138 			}
139 		}
140 		free(line);
141 		fclose(fd);
142 
143 		if (found == 0)
144 			fprintf(fd2, "%s:%s:%s\n", username, groupname, cr_passwd);
145 	}
146 
147 	fclose(fd2);
148 
149 	ret = rename(tmp_passwd, fpasswd);
150 	if (ret < 0) {
151 		fprintf(stderr, "Cannot write to '%s'.\n", fpasswd);
152 		exit(1);
153 	}
154 	free(tmp_passwd);
155 }
156 
157 static void
delete_user(const char * fpasswd,const char * username)158 delete_user(const char *fpasswd, const char *username)
159 {
160 	FILE * fd, *fd2;
161 	char *tmp_passwd;
162 	char *line, *p;
163 	unsigned fpasswd_len = strlen(fpasswd);
164 	unsigned tmp_passwd_len;
165 	unsigned username_len = strlen(username);
166 	int ret;
167 	ssize_t len, l;
168 	size_t line_size;
169 	struct stat st;
170 
171 	tmp_passwd_len = fpasswd_len + 5;
172 	tmp_passwd = malloc(tmp_passwd_len);
173 
174 	snprintf(tmp_passwd, tmp_passwd_len, "%s.tmp", fpasswd);
175 	if (stat(tmp_passwd, &st) != -1) {
176 		fprintf(stderr, "file '%s' is locked.\n", fpasswd);
177 		exit(1);
178 	}
179 
180 	fd = fopen(fpasswd, "r");
181 	if (fd == NULL) {
182 		fprintf(stderr, "Cannot open '%s' for reading.\n", fpasswd);
183 		exit(1);
184 	}
185 
186 	fd2 = fopen(tmp_passwd, "w");
187 	if (fd2 == NULL) {
188 		fprintf(stderr, "Cannot open '%s' for writing.\n", tmp_passwd);
189 		exit(1);
190 	}
191 
192 	line = NULL;
193 	while ((len = getline(&line, &line_size, fd)) > 0) {
194 		p = strchr(line, ':');
195 		if (p == NULL)
196 			continue;
197 
198 		l = p-line;
199 		if (l == username_len && strncmp(line, username, l) == 0) {
200 			continue;
201 		} else {
202 			fwrite(line, 1, len, fd2);
203 		}
204 	}
205 
206 	free(line);
207 	fclose(fd);
208 	fclose(fd2);
209 
210 	ret = rename(tmp_passwd, fpasswd);
211 	if (ret == -1) {
212 		fprintf(stderr, "Cannot write to '%s'.\n", fpasswd);
213 		exit(1);
214 	}
215 	free(tmp_passwd);
216 }
217 
218 static void
lock_user(const char * fpasswd,const char * username)219 lock_user(const char *fpasswd, const char *username)
220 {
221 	FILE * fd, *fd2;
222 	char *tmp_passwd;
223 	char *line, *p;
224 	unsigned fpasswd_len = strlen(fpasswd);
225 	unsigned tmp_passwd_len;
226 	unsigned username_len = strlen(username);
227 	int ret;
228 	ssize_t len, l;
229 	size_t line_size;
230 	struct stat st;
231 
232 	tmp_passwd_len = fpasswd_len + 5;
233 	tmp_passwd = malloc(tmp_passwd_len);
234 
235 	snprintf(tmp_passwd, tmp_passwd_len, "%s.tmp", fpasswd);
236 	if (stat(tmp_passwd, &st) != -1) {
237 		fprintf(stderr, "file '%s' is locked.\n", fpasswd);
238 		exit(1);
239 	}
240 
241 	fd = fopen(fpasswd, "r");
242 	if (fd == NULL) {
243 		fprintf(stderr, "Cannot open '%s' for reading.\n", fpasswd);
244 		exit(1);
245 	}
246 
247 	fd2 = fopen(tmp_passwd, "w");
248 	if (fd2 == NULL) {
249 		fprintf(stderr, "Cannot open '%s' for writing.\n", tmp_passwd);
250 		exit(1);
251 	}
252 
253 	line = NULL;
254 	while ((len = getline(&line, &line_size, fd)) > 0) {
255 		p = strchr(line, ':');
256 		if (p == NULL)
257 			continue;
258 
259 		l = p-line;
260 		if (l == username_len && strncmp(line, username, l) == 0) {
261 			p = strchr(p+1, ':');
262 			if (p == NULL)
263 				continue;
264 			p++;
265 
266 			if(*p != '!') {
267 				l = p-line;
268 				fwrite(line, 1, l, fd2);
269 				fputc('!', fd2);
270 				fwrite(p, 1, len-l, fd2);
271 			} else {
272 				fwrite(line, 1, len, fd2);
273 			}
274 		} else {
275 			fwrite(line, 1, len, fd2);
276 		}
277 	}
278 
279 	free(line);
280 	fclose(fd);
281 	fclose(fd2);
282 
283 	ret = rename(tmp_passwd, fpasswd);
284 	if (ret == -1) {
285 		fprintf(stderr, "Cannot write to '%s'.\n", fpasswd);
286 		exit(1);
287 	}
288 	free(tmp_passwd);
289 }
290 
291 static void
unlock_user(const char * fpasswd,const char * username)292 unlock_user(const char *fpasswd, const char *username)
293 {
294 	FILE * fd, *fd2;
295 	char *tmp_passwd;
296 	char *line, *p;
297 	unsigned fpasswd_len = strlen(fpasswd);
298 	unsigned tmp_passwd_len;
299 	unsigned username_len = strlen(username);
300 	int ret;
301 	ssize_t len, l;
302 	size_t line_size;
303 	struct stat st;
304 
305 	tmp_passwd_len = fpasswd_len + 5;
306 	tmp_passwd = malloc(tmp_passwd_len);
307 
308 	snprintf(tmp_passwd, tmp_passwd_len, "%s.tmp", fpasswd);
309 	if (stat(tmp_passwd, &st) != -1) {
310 		fprintf(stderr, "file '%s' is locked.\n", fpasswd);
311 		exit(1);
312 	}
313 
314 	fd = fopen(fpasswd, "r");
315 	if (fd == NULL) {
316 		fprintf(stderr, "Cannot open '%s' for reading.\n", fpasswd);
317 		exit(1);
318 	}
319 
320 	fd2 = fopen(tmp_passwd, "w");
321 	if (fd2 == NULL) {
322 		fprintf(stderr, "Cannot open '%s' for writing.\n", tmp_passwd);
323 		exit(1);
324 	}
325 
326 	line = NULL;
327 	while ((len = getline(&line, &line_size, fd)) > 0) {
328 		p = strchr(line, ':');
329 		if (p == NULL)
330 			continue;
331 
332 		l = p-line;
333 		if (l == username_len && strncmp(line, username, l) == 0) {
334 			p = strchr(p+1, ':');
335 			if (p == NULL)
336 				continue;
337 			p++;
338 
339 			l = p-line;
340 			fwrite(line, 1, l, fd2);
341 
342 			if (*p=='!') p++;
343 			l = p-line;
344 			fwrite(p, 1, len-l, fd2);
345 		} else {
346 			fwrite(line, 1, len, fd2);
347 		}
348 	}
349 
350 	free(line);
351 	fclose(fd);
352 	fclose(fd2);
353 
354 	ret = rename(tmp_passwd, fpasswd);
355 	if (ret == -1) {
356 		fprintf(stderr, "Cannot write to '%s'.\n", fpasswd);
357 		exit(1);
358 	}
359 	free(tmp_passwd);
360 }
361 
362 static const struct option long_options[] = {
363 	{"passwd", 1, 0, 'c'},
364 	{"groupname", 1, 0, 'g'},
365 	{"delete", 0, 0, 'd'},
366 	{"lock", 0, 0, 'l'},
367 	{"unlock", 0, 0, 'u'},
368 	{"help", 0, 0, 'h'},
369 	{"version", 0, 0, 'v'},
370 	{NULL, 0, 0, 0}
371 };
372 
373 static
usage(void)374 void usage(void)
375 {
376 	fprintf(stderr, "ocpasswd - OpenConnect server password utility\n");
377 	fprintf(stderr, "Usage:  ocpasswd [ -<flag> [<val>] | --<name>[{=| }<val>] ]... [username]\n");
378 	fprintf(stderr, "\n");
379 	fprintf(stderr, "   -c, --passwd=file          Password file\n");
380 	fprintf(stderr, "   -g, --groupname=str        User's group name\n");
381 	fprintf(stderr, "   -d, --delete               Delete user\n");
382 	fprintf(stderr, "   -l, --lock                 Lock user\n");
383 	fprintf(stderr, "   -u, --unlock               Unlock user\n");
384 	fprintf(stderr, "   -v, --version              output version information and exit\n");
385 	fprintf(stderr, "   -h, --help                 display extended usage information and exit\n");
386 	fprintf(stderr, "\n");
387 	fprintf(stderr, "Options are specified by doubled hyphens and their name or by a single\n");
388 	fprintf(stderr, "hyphen and the flag character.\n\n");
389 	fprintf(stderr, "This program is openconnect password (ocpasswd) utility.  It allows the\n");
390 	fprintf(stderr, "generation and handling of a 'plain' password file used by ocserv.\n\n");
391 	fprintf(stderr, "Please send bug reports to:  "PACKAGE_BUGREPORT"\n");
392 }
393 
394 static
version(void)395 void version(void)
396 {
397 	fprintf(stderr, "ocpasswd - "VERSION"\n");
398 	fprintf(stderr, "Copyright (C) 2013-2017 Nikos Mavrogiannopoulos, all rights reserved.\n");
399 	fprintf(stderr, "This is free software. It is licensed for use, modification and\n");
400 	fprintf(stderr, "redistribution under the terms of the GNU General Public License,\n");
401 	fprintf(stderr, "version 2 <http://gnu.org/licenses/gpl.html>\n\n");
402 	fprintf(stderr, "Please send bug reports to:  "PACKAGE_BUGREPORT"\n");
403 }
404 
405 #define FLAG_DELETE 1
406 #define FLAG_LOCK (1<<1)
407 #define FLAG_UNLOCK (1<<2)
408 
main(int argc,char ** argv)409 int main(int argc, char **argv)
410 {
411 	int ret, c;
412 	const char *username = NULL;
413 	char *groupname = NULL, *fpasswd = NULL;
414 	char* passwd = NULL;
415 	unsigned free_passwd = 0;
416 	size_t l, i;
417 	unsigned flags = 0;
418 
419 	if ((ret = gnutls_global_init()) < 0) {
420 		fprintf(stderr, "global_init: %s\n", gnutls_strerror(ret));
421 		exit(1);
422 	}
423 
424 	umask(066);
425 
426 	while (1) {
427 		c = getopt_long(argc, argv, "c:g:dluvh", long_options, NULL);
428 		if (c == -1)
429 			break;
430 
431 		switch(c) {
432 			case 'c':
433 				if (fpasswd) {
434 					fprintf(stderr, "-c option cannot be specified multiple time\n");
435 					exit(1);
436 				}
437 				fpasswd = strdup(optarg);
438 				break;
439 			case 'g':
440 				if (groupname) {
441 					fprintf(stderr, "-g option cannot be specified multiple time\n");
442 					exit(1);
443 				}
444 				groupname = strdup(optarg);
445 				break;
446 			case 'd':
447 				if (flags) {
448 					usage();
449 					exit(1);
450 				}
451 				flags |= FLAG_DELETE;
452 				break;
453 			case 'u':
454 				if (flags) {
455 					usage();
456 					exit(1);
457 				}
458 				flags |= FLAG_UNLOCK;
459 				break;
460 			case 'l':
461 				if (flags) {
462 					usage();
463 					exit(1);
464 				}
465 				flags |= FLAG_LOCK;
466 				break;
467 			case 'h':
468 				usage();
469 				exit(0);
470 			case 'v':
471 				version();
472 				exit(0);
473 		}
474 	}
475 
476 	if (optind < argc && argc-optind == 1) {
477 		username = argv[optind++];
478 	} else {
479 		usage();
480 		exit(1);
481 	}
482 
483 	if (!groupname)
484 		groupname = strdup("*");
485 	if (!fpasswd)
486 		fpasswd = strdup(DEFAULT_OCPASSWD);
487 
488 	if (flags & FLAG_LOCK) {
489 		lock_user(fpasswd, username);
490 	} else if (flags & FLAG_UNLOCK) {
491 		unlock_user(fpasswd, username);
492 	} else if (flags & FLAG_DELETE) {
493 		delete_user(fpasswd, username);
494 	} else { /* set password */
495 
496 		if (isatty(STDIN_FILENO)) {
497 			char* p2;
498 
499 			passwd = getpass("Enter password: ");
500 			if (passwd == NULL) {
501 				fprintf(stderr, "Please specify a password\n");
502 				exit(1);
503 			}
504 
505 
506 			p2 = strdup(passwd);
507 			passwd = getpass("Re-enter password: ");
508 			if (passwd == NULL) {
509 				fprintf(stderr, "Please specify a password\n");
510 				exit(1);
511 			}
512 
513 			if (p2 == NULL || strcmp(passwd, p2) != 0) {
514 				fprintf(stderr, "Passwords do not match\n");
515 				exit(1);
516 			}
517 			free(p2);
518 		} else {
519 			passwd = NULL;
520 			l = getline(&passwd, &i, stdin);
521 			if (l <= 1) {
522 				fprintf(stderr, "Please specify a password\n");
523 				exit(1);
524 			}
525 
526 			free_passwd = 1;
527 			if (passwd[l-1] == '\n')
528 				passwd[l-1] = 0;
529 		}
530 
531 		crypt_int(fpasswd, username, groupname, passwd);
532 		if (free_passwd)
533 			free(passwd);
534 	}
535 
536 	free(fpasswd);
537 	free(groupname);
538 	gnutls_global_deinit();
539 	return 0;
540 }
541 
542