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