1 /* This file is part of GNU Dico.
2    Copyright (C) 1998-2020 Sergey Poznyakoff
3 
4    GNU Dico is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 3, or (at your option)
7    any later version.
8 
9    GNU Dico is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13 
14    You should have received a copy of the GNU General Public License
15    along with GNU Dico.  If not, see <http://www.gnu.org/licenses/>. */
16 
17 #include "dico-priv.h"
18 #include <sys/types.h>
19 #include <pwd.h>
20 #include <md5.h>
21 #include "getpass.h"
22 
23 const char *xscript_prefix[] = { "S:", "C:" };
24 
25 static int
parse_initial_reply(struct dict_connection * conn)26 parse_initial_reply(struct dict_connection *conn)
27 {
28     char *p;
29     size_t n = 0;
30     size_t len;
31 
32     if (!dict_status_p(conn, "220"))
33 	return 1;
34     p = strchr(conn->buf, '<');
35     if (!p)
36 	return 1;
37     p++;
38 
39     while ((len = strcspn(p, ".>"))) {
40 	char *s;
41 	if (conn->capac == n) {
42 	    if (n == 0)
43 		n = 2;
44 	    conn->capav = x2nrealloc(conn->capav, &n, sizeof(conn->capav[0]));
45 	}
46 
47 	s = xmalloc(len+1);
48 	memcpy(s, p, len);
49 	s[len] = 0;
50 	conn->capav[conn->capac++] = s;
51 	p += len + 1;
52 	if (p[-1] == '>')
53 	    break;
54     }
55 
56     p = strchr(p, '<');
57     if (!p)
58 	return 1;
59     len = strcspn(p, ">");
60     if (p[len] != '>')
61 	return 1;
62     len++;
63     conn->msgid = xmalloc(len + 1);
64     memcpy(conn->msgid, p, len);
65     conn->msgid[len] = 0;
66 
67     return 0;
68 }
69 
70 static int
apop_auth(struct dict_connection * conn,struct auth_cred * cred)71 apop_auth(struct dict_connection *conn, struct auth_cred *cred)
72 {
73     int i;
74     struct md5_ctx md5context;
75     unsigned char md5digest[16];
76     char buf[sizeof(md5digest) * 2 + 1];
77     char *p;
78 
79     md5_init_ctx(&md5context);
80     md5_process_bytes(conn->msgid, strlen(conn->msgid), &md5context);
81     md5_process_bytes(cred->pass, strlen(cred->pass), &md5context);
82     md5_finish_ctx(&md5context, md5digest);
83 
84     for (i = 0, p = buf; i < 16; i++, p += 2)
85 	sprintf(p, "%02x", md5digest[i]);
86     *p = 0;
87     stream_printf(conn->str, "AUTH %s %s\r\n", cred->user, buf);
88     if (dict_read_reply(conn)) {
89 	dico_log(L_ERR, 0, _("No reply from server"));
90 	return 1;
91     }
92     return dict_status_p(conn, "230") == 0;
93 }
94 
95 static int
dict_auth(struct dict_connection * conn,dico_url_t url)96 dict_auth(struct dict_connection *conn, dico_url_t url)
97 {
98     int rc = saslauth(conn, url);
99 
100     switch (rc) {
101     case AUTH_OK:
102 	return 0;
103 
104     case AUTH_CONT:
105 	if (dict_capa(conn, "auth")) {
106 	    struct auth_cred cred;
107 
108 	    switch (auth_cred_get(url->host, &cred)) {
109 	    case GETCRED_OK:
110 		XDICO_DEBUG(1, _("Attempting APOP authentication\n"));
111 		rc = apop_auth(conn, &cred);
112 		auth_cred_free(&cred);
113 		return rc;
114 
115 	    case GETCRED_FAIL:
116 		dico_log(L_WARN, 0,
117 			 _("Not enough credentials for authentication"));
118 		break;
119 
120 	    case GETCRED_NOAUTH:
121 		XDICO_DEBUG(1, _("Skipping authentication\n"));
122 		break;
123 	    }
124 	}
125 	return 0;
126 
127     case AUTH_FAIL:
128 	break;
129     }
130     return 1;
131 }
132 
133 char *
get_homedir(void)134 get_homedir(void)
135 {
136     char *homedir = getenv("HOME");
137     if (!homedir) {
138 	struct passwd *pw = getpwuid(geteuid());
139 	homedir = pw->pw_dir;
140     }
141     return homedir;
142 }
143 
144 int
ds_tilde_expand(const char * str,char ** output)145 ds_tilde_expand(const char *str, char **output)
146 {
147     char *dir;
148 
149     if (str[0] != '~')
150 	return 0;
151     if (str[1] == '/') {
152 	dir = get_homedir();
153 	str += 2;
154     } else {
155 	char *p;
156 	size_t len;
157 	char *name;
158 	struct passwd *pw;
159 
160 	str++;
161 	p = strchr(str, '/');
162 	if (!p)
163 	    return 0;
164 	len = p - str;
165 	name = xmalloc(len + 1);
166 	memcpy(name, str, len);
167 	name[len] = 0;
168 	pw = getpwnam(name);
169 	free(name);
170 	if (pw) {
171 	    dir = pw->pw_dir;
172 	    str = p + 1;
173 	}
174     }
175     *output = dico_full_file_name(dir, str);
176     return 1;
177 }
178 
179 static void
auth_cred_dup(struct auth_cred * dst,const struct auth_cred * src)180 auth_cred_dup(struct auth_cred *dst, const struct auth_cred *src)
181 {
182     dst->user = src->user ? xstrdup(src->user) : NULL;
183     dst->pass = src->pass ? xstrdup(src->pass) : NULL;
184 }
185 
186 void
auth_cred_free(struct auth_cred * cred)187 auth_cred_free(struct auth_cred *cred)
188 {
189     free(cred->user);
190     free(cred->pass);
191     dico_list_destroy(&cred->mech);
192     free(cred->service);
193     free(cred->realm);
194     free(cred->hostname);
195 }
196 
197 int
auth_cred_get(char * host,struct auth_cred * cred)198 auth_cred_get(char *host, struct auth_cred *cred)
199 {
200     memset(cred, 0, sizeof(cred[0]));
201     auth_cred_dup(cred, &default_cred);
202     if (default_cred.user && default_cred.pass) {
203 	XDICO_DEBUG(1,
204 		    _("Obtained authentication credentials from the command line\n"));
205 	return GETCRED_OK;
206     } else {
207 	int flags = 0;
208 
209 	if (autologin_file) {
210 	    if (access(autologin_file, F_OK))
211 		dico_log(L_WARN, 0, _("File %s does not exist"),
212 			 autologin_file);
213 	    else
214 		parse_autologin(autologin_file, host, cred, &flags);
215 	}
216 
217 	if (!flags && DEFAULT_AUTOLOGIN_FILE) {
218 	    char *home = get_homedir();
219 	    char *filename = dico_full_file_name(home,
220 						 DEFAULT_AUTOLOGIN_FILE);
221 	    parse_autologin(filename, host, cred, &flags);
222 	    free(filename);
223 	}
224 	if (flags & AUTOLOGIN_NOAUTH)
225 	    return GETCRED_NOAUTH;
226     }
227     if (cred->user && !cred->pass) {
228 	char *p = getpass(_("Password:"));
229 	cred->pass = p ? xstrdup(p) : NULL;
230     }
231     return (cred->user && cred->pass) ? GETCRED_OK : GETCRED_FAIL;
232 }
233 
234 void
dict_transcript(struct dict_connection * conn,int state)235 dict_transcript(struct dict_connection *conn, int state)
236 {
237     if (state == conn->transcript)
238 	return;
239     if (state == 0) {
240 	dico_stream_t transport;
241 	if (dico_stream_ioctl(conn->str, DICO_IOCTL_GET_TRANSPORT,
242 			      &transport)) {
243 	    dico_log(L_CRIT, errno,
244 		     _("INTERNAL ERROR at %s:%d: cannot get stream transport"),
245 		     __FILE__,
246 		     __LINE__);
247 	    return;
248 	}
249 
250 	if (dico_stream_ioctl(conn->str, DICO_IOCTL_SET_TRANSPORT, NULL)) {
251 	    dico_log(L_CRIT, errno,
252 		     _("INTERNAL ERROR at %s:%d: cannot set stream transport"),
253 		     __FILE__,
254 		     __LINE__);
255 	    return;
256 	}
257 
258 	dico_stream_close(conn->str);
259 	dico_stream_destroy(&conn->str);
260 	conn->str = transport;
261 	conn->transcript = state;
262     } else {
263 	dico_stream_t logstr = dico_log_stream_create(L_DEBUG);
264 	if (!logstr)
265 	    xalloc_die();
266 	conn->str = xdico_transcript_stream_create(conn->str, logstr,
267 						   xscript_prefix);
268 	conn->transcript = state;
269     }
270 }
271 
272 static char const *
urlstr(dico_url_t url)273 urlstr(dico_url_t url)
274 {
275     if (!url->string) {
276 	if (!url->proto)
277 	    xdico_assign_string(&url->proto, "dict");
278 	if (!url->port)
279 	    xdico_assign_string(&url->port, DICO_DICT_PORT_STR);
280 
281 	if (url->host) {
282 	    asprintf(&url->string, "%s://%s:%s",
283 		     url->proto,
284 		     url->host,
285 		     url->port);
286 	} else {
287 	    asprintf(&url->string, "%s:///%s", url->proto, url->path);
288 	}
289     }
290 
291     return url->string;
292 }
293 
294 int
dict_connect(struct dict_connection ** pconn,dico_url_t url)295 dict_connect(struct dict_connection **pconn, dico_url_t url)
296 {
297     int fd, rc, family;
298     struct addrinfo hints, *res, *rp;
299     dico_stream_t str;
300     struct dict_connection *conn;
301     char const *port = url->port ? url->port : DICO_DICT_PORT_STR;
302 
303     XDICO_DEBUG_F1(1, _("Connecting to %s\n"), urlstr (url));
304 
305     if (source_addr) {
306 	memset(&hints, 0, sizeof(hints));
307 	hints.ai_socktype = SOCK_STREAM;
308 	rc = getaddrinfo(source_addr, NULL, &hints, &res);
309 	if (rc) {
310 	    dico_log(L_ERR, 0,
311 		     _("bad source address: %s"), gai_strerror(rc));
312 	    return 1;
313 	}
314 
315 	for (rp = res; rp; rp = rp->ai_next) {
316 	    fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
317 	    if (fd == -1)
318 		continue;
319 	    if (bind(fd, rp->ai_addr, rp->ai_addrlen) == 0)
320 		break;
321 	    close(fd);
322 	}
323 
324 	if (!rp) {
325 	    dico_log(L_ERR, 0,
326 		     _("can't bind to the source address"));
327 	    return 1;
328 	}
329     } else {
330 	fd = -1;
331     }
332 
333     memset(&hints, 0, sizeof(hints));
334     hints.ai_socktype = SOCK_STREAM;
335     if (url->host) {
336 	rc = getaddrinfo(url->host, port, &hints, &res);
337 	if (rc) {
338 	    dico_log(L_ERR, 0,
339 		     _("%s: can't get address: %s"),
340 		     url->host, gai_strerror(rc));
341 	    return -1;
342 	}
343     } else {
344 	struct sockaddr_un *s;
345 
346 	if (strlen(url->path) >= sizeof s->sun_path) {
347 	    dico_log(L_ERR, 0, _("%s: UNIX socket name too long"), url->path);
348 	    return -1;
349 	}
350 
351 	hints.ai_family = AF_UNIX;
352 	hints.ai_addrlen = sizeof(struct sockaddr_un);
353 
354 	s = xcalloc(1, hints.ai_addrlen);
355 	s->sun_family = AF_UNIX;
356 	strcpy(s->sun_path, url->path);
357 
358 	hints.ai_addr = (struct sockaddr *)s;
359 
360 	res = &hints;
361     }
362 
363     for (rp = res; rp; rp = rp->ai_next) {
364 	if (fd != -1 && family != rp->ai_family) {
365 	    close(fd);
366 	    fd = -1;
367 	}
368 	if (fd == -1) {
369 	    family = rp->ai_family;
370 	    fd = socket(family, SOCK_STREAM, 0);
371 	    if (fd == -1) {
372 		dico_log(L_ERR, errno,
373 			 _("cannot create dict socket"));
374 		continue;
375 	    }
376 	}
377 
378 	if (connect(fd, rp->ai_addr, rp->ai_addrlen) != -1)
379 	    break;
380     }
381 
382     if (!rp) {
383 	dico_log(L_ERR, 0, _("%s: cannot connect"), urlstr(url));
384 	return 1;
385     }
386 
387     if (res == &hints)
388 	free(res->ai_addr);
389     else
390 	freeaddrinfo(res);
391 
392     if ((str = dico_fd_io_stream_create(fd, fd)) == NULL) {
393 	dico_log(L_ERR, errno,
394 		 _("cannot create dict stream: %s"),
395 		 strerror(errno));
396 	return 1;
397     }
398 
399     conn = xzalloc(sizeof(*conn));
400     conn->str = str;
401     conn->fd = fd;
402     dict_transcript(conn, transcript);
403     if (dict_read_reply(conn)) {
404 	dico_log(L_ERR, 0, _("No reply from server"));
405 	return 1;
406     }
407     if (parse_initial_reply(conn)) {
408 	dico_log(L_ERR, 0, _("Invalid reply from server"));
409 	dict_conn_close(conn);
410 	return 1;
411     }
412 
413     XDICO_DEBUG(1, _("Sending client information\n"));
414     stream_printf(conn->str, "CLIENT \"%s\"\r\n", client);
415     dict_read_reply(conn);
416     if (!dict_status_p(conn, "250"))
417 	dico_log(L_WARN, 0,
418 		 _("Unexpected reply to CLIENT command: `%s'"),
419 		 conn->buf);
420 
421     obstack_init(&conn->stk);
422 
423     if (!noauth_option && dict_auth(conn, url)) {
424 	dico_log(L_ERR, 0, _("Authentication failed"));
425 	dict_conn_close(conn);
426 	return 1;
427     }
428 
429     *pconn = conn;
430 
431     return 0;
432 }
433 
434 int
dict_read_reply(struct dict_connection * conn)435 dict_read_reply(struct dict_connection *conn)
436 {
437     int rc;
438     if (conn->buf)
439 	conn->buf[0] = 0;
440     rc = dico_stream_getline(conn->str, &conn->buf, &conn->size,
441 			     &conn->level);
442     if (rc == 0)
443 	conn->level = dico_trim_nl(conn->buf);
444     return rc;
445 }
446 
447 int
dict_status_p(struct dict_connection * conn,const char * status)448 dict_status_p(struct dict_connection *conn, const char *status)
449 {
450     return conn->level > 3
451 	&& memcmp(conn->buf, status, 3) == 0
452 	&& (isspace(conn->buf[3])
453 	    || (conn->level == 5 && memcmp(conn->buf+3,"\r\n",2) == 0));
454 }
455 
456 int
dict_capa(struct dict_connection * conn,char * capa)457 dict_capa(struct dict_connection *conn, char *capa)
458 {
459     int i;
460 
461     for (i = 0; i < conn->capac; i++)
462 	if (strcmp(conn->capav[i], capa) == 0)
463 	    return 1;
464     return 0;
465 }
466 
467 int
dict_multiline_reply(struct dict_connection * conn)468 dict_multiline_reply(struct dict_connection *conn)
469 {
470     int rc;
471     size_t nlines = 0;
472 
473     while ((rc = dict_read_reply(conn)) == 0) {
474 	char *ptr = conn->buf;
475 	size_t len = conn->level;
476 	if (*ptr == '.') {
477 	    if (ptr[1] == 0)
478 		break;
479 	    else if (ptr[1] == '.') {
480 		ptr++;
481 		len--;
482 	    }
483 	}
484 	obstack_grow(&conn->stk, ptr, len);
485 	obstack_1grow(&conn->stk, '\n');
486 	nlines++;
487     }
488     obstack_1grow(&conn->stk, 0);
489     return rc;
490 }
491 
492 int
dict_define(struct dict_connection * conn,char * database,char * word)493 dict_define(struct dict_connection *conn, char *database, char *word)
494 {
495     int rc;
496 
497     XDICO_DEBUG_F2(1, _("Sending query for word \"%s\" in database \"%s\"\n"),
498 		   word, database);
499     stream_printf(conn->str, "DEFINE \"%s\" \"%s\"\r\n",
500 		  quotearg_n (0, database),
501 		  quotearg_n (1, word));
502     dict_read_reply(conn);
503     if (dict_status_p(conn, "150")) {
504 	unsigned long i, count;
505 	char *p;
506 
507 	count = strtoul (conn->buf + 3, &p, 10);
508 	XDICO_DEBUG_F1(1, ngettext("Reading %lu definition\n",
509 				   "Reading %lu definitions\n", count),
510 		       count);
511 	for (i = 0; i < count; i++) {
512 	    dict_read_reply(conn);
513 	    if (!dict_status_p(conn, "151")) {
514 		dico_log(L_WARN, 0,
515 			 _("Unexpected reply in place of definition %lu"), i);
516 		break;
517 	    }
518 	    obstack_grow(&conn->stk, conn->buf, conn->level);
519 	    obstack_1grow(&conn->stk, 0);
520 	    dict_multiline_reply(conn);
521 	}
522 	dict_read_reply(conn);
523 	dict_result_create(conn, dict_result_define, count,
524 			   obstack_finish(&conn->stk));
525 	rc = 0;
526     } else
527 	rc = 1;
528     return rc;
529 }
530 
531 int
dict_match(struct dict_connection * conn,char * database,char * strategy,char * word)532 dict_match(struct dict_connection *conn, char *database, char *strategy,
533 	   char *word)
534 {
535     int rc;
536     if (levenshtein_threshold && conn->levdist != levenshtein_threshold
537 	&& dict_capa(conn, "xlev")) {
538 	XDICO_DEBUG(1, _("Setting Levenshtein threshold\n"));
539 	stream_printf(conn->str, "XLEV %u\n", levenshtein_threshold);
540 	dict_read_reply(conn);
541 	if (dict_status_p(conn, "250"))
542 	    conn->levdist = levenshtein_threshold;
543 	else {
544 	    dico_log(L_WARN, 0, _("Server rejected XLEV command"));
545 	    dico_log(L_WARN, 0, _("Server reply: %s"), conn->buf);
546 	}
547     }
548     XDICO_DEBUG_F3(1, _("Sending query to match word \"%s\" in "
549 			"database \"%s\", "
550 			"using \"%s\"\n"), word, database, strategy);
551     stream_printf(conn->str, "MATCH \"%s\" \"%s\" \"%s\"\r\n",
552 		  quotearg_n (0, database),
553 		  quotearg_n (1, strategy),
554 		  quotearg_n (2, word));
555     dict_read_reply(conn);
556     if (dict_status_p(conn, "152")) {
557 	unsigned long count;
558 	char *p;
559 
560 	count = strtoul (conn->buf + 3, &p, 10);
561 	XDICO_DEBUG_F1(1, ngettext("Reading %lu match\n",
562 				   "Reading %lu matches\n", count),
563 		       count);
564 
565 	dict_multiline_reply(conn);
566 	dict_result_create(conn, dict_result_match, count,
567 			   obstack_finish(&conn->stk));
568 	dict_read_reply(conn);
569 	rc = 0;
570     } else
571 	rc = 1;
572     return rc;
573 }
574 
575 static size_t
count_lines(char * p)576 count_lines(char *p)
577 {
578     size_t count = 0;
579     while ((p = strchr(p, '\n'))) {
580 	count++;
581 	p++;
582     }
583     return count;
584 }
585 
586 static void
_result_parse_def(struct dict_result * res)587 _result_parse_def(struct dict_result *res)
588 {
589     char *p;
590     size_t i;
591     struct define_result *def = xcalloc(res->count, sizeof(*def));
592     struct dico_tokbuf tb;
593 
594     dico_tokenize_begin(&tb);
595 
596     res->set.def = def;
597     p = res->base;
598     for (i = 0; i < res->count; i++, def++) {
599 	/* FIXME: Provide a destructive version of xdico_tokenize_string? */
600 	xdico_tokenize_string(&tb, p);
601 	def->word = xstrdup(tb.tb_tokv[1]);
602 	def->database = xstrdup(tb.tb_tokv[2]);
603 	def->descr = xstrdup(tb.tb_tokv[3]);
604 	p += strlen(p) + 1;
605 	def->defn = p;
606 	def->nlines = count_lines(p);
607 	p += strlen(p) + 1;
608     }
609     dico_tokenize_end(&tb);
610 }
611 
612 static void
_result_free_def(struct dict_result * res)613 _result_free_def(struct dict_result *res)
614 {
615     size_t i;
616     struct define_result *def = res->set.def;
617 
618     for (i = 0; i < res->count; i++, def++) {
619 	free(def->word);
620 	free(def->database);
621 	free(def->descr);
622     }
623     free(res->set.def);
624 }
625 
626 static void
_result_parse_mat(struct dict_result * res)627 _result_parse_mat(struct dict_result *res)
628 {
629     char *p;
630     size_t i;
631     struct match_result *mat = xcalloc(res->count, sizeof(*mat));
632 
633     res->set.mat = mat;
634     for (i = 0, p = strtok(res->base, "\n"); i < res->count;
635 	 p = strtok(NULL, "\n"), i++, mat++) {
636 	size_t len;
637 
638 	if (!p) {
639 	    dico_log(L_NOTICE, 0, _("Not enough data in the result"));
640 	    res->count = i;
641 	    break;
642 	}
643 
644 	mat->database = p;
645 	len = strcspn(p, " \t");
646 	p[len] = 0;
647 	p += len + 1;
648 	p += strspn(p, " \t");
649 	len = strlen(p);
650 	if (p[0] == '"' && p[len-1] == '"') {
651 	    p[len-1] = 0;
652 	    p++;
653 	}
654 	mat->word = p;
655     }
656 }
657 
658 static void
_result_free_mat(struct dict_result * res)659 _result_free_mat(struct dict_result *res)
660 {
661     free(res->set.mat);
662 }
663 
664 struct dict_result *
dict_result_create(struct dict_connection * conn,enum dict_result_type type,size_t count,char * base)665 dict_result_create(struct dict_connection *conn, enum dict_result_type type,
666 		   size_t count, char *base)
667 {
668     struct dict_result *res = xmalloc(sizeof(*res));
669     res->conn = conn;
670     res->prev = conn->last_result;
671     conn->last_result = res;
672     res->type = type;
673     res->count = count;
674     res->base = base;
675     switch (type) {
676     case dict_result_define:
677 	_result_parse_def(res);
678 	break;
679 
680     case dict_result_match:
681 	_result_parse_mat(res);
682 	break;
683 
684     case dict_result_text:
685 	break;
686     }
687     return res;
688 }
689 
690 void
dict_result_free(struct dict_result * res)691 dict_result_free(struct dict_result *res)
692 {
693     if (!res)
694 	return;
695     /* Detach res from the list and free obstack memory, if it was the
696        last obtained result,  */
697     if (res == res->conn->last_result) {
698 	obstack_free(&res->conn->stk, res->base);
699 	res->conn->last_result = res->prev;
700     } else {
701 	struct dict_result *p;
702 
703 	for (p = res->conn->last_result; p && p->prev != res; p = p->prev)
704 	    ;
705 	if (!p) {
706 	    dico_log(L_CRIT, 0, _("Freeing unlinked result"));
707 	    abort();
708 	}
709 	p->prev = res->prev;
710     }
711     /* Free allocated memory */
712     switch (res->type) {
713     case dict_result_define:
714 	_result_free_def(res);
715 	break;
716 
717     case dict_result_match:
718 	_result_free_mat(res);
719 	break;
720 
721     case dict_result_text:
722 	break;
723     }
724     free(res);
725 }
726 
727 /* FIXME: Split into close/destroy */
728 void
dict_conn_close(struct dict_connection * conn)729 dict_conn_close(struct dict_connection *conn)
730 {
731     struct dict_result *res;
732 
733     dico_stream_close(conn->str);
734     dico_stream_destroy(&conn->str);
735     free(conn->msgid);
736     free(conn->buf);
737     dico_argcv_free(conn->capac, conn->capav);
738     for (res = conn->last_result; res; ) {
739 	struct dict_result *prev = res->prev;
740 	dict_result_free(res);
741 	res = prev;
742     }
743     obstack_free(&conn->stk, NULL);
744     free(conn);
745 }
746 
747