1 /* This file is part of GNU Dico.
2    Copyright (C) 2008-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 <dicod.h>
18 
19 char *msg_id;
20 
21 
22 struct capa_print {
23     size_t num;
24     dico_stream_t stream;
25 };
26 
27 static int
print_capa(const char * name,int enabled,void * data)28 print_capa(const char *name, int enabled, void *data)
29 {
30     struct capa_print *cp = data;
31     if (enabled) {
32 	if (cp->num++)
33 	    dico_stream_write(cp->stream, ".", 1);
34 	stream_writez(cp->stream, (char*)name);
35     }
36     return 0;
37 }
38 
39 static void
output_capabilities(dico_stream_t str)40 output_capabilities(dico_stream_t str)
41 {
42     struct capa_print cp;
43     cp.num = 0;
44     cp.stream = str;
45     dico_stream_write(str, "<", 1);
46     dicod_capa_iterate(print_capa, &cp);
47     dico_stream_write(str, ">", 1);
48 }
49 
50 /*
51   3.1.  Initial Connection
52 
53    When a client initially connects to a DICT server, a code 220 is sent
54    if the client's IP is allowed to connect:
55 
56              220 text capabilities msg-id
57 */
58 static void
initial_banner(dico_stream_t str)59 initial_banner(dico_stream_t str)
60 {
61     dico_stream_write(str, "220 ", 4);
62     stream_writez(str, hostname);
63     dico_stream_write(str, " ", 1);
64     if (initial_banner_text)
65 	stream_writez(str, initial_banner_text);
66     else
67 	stream_writez(str, (char*) program_version);
68     dico_stream_write(str, " ", 1);
69     output_capabilities(str);
70     dico_stream_write(str, " ", 1);
71     asprintf(&msg_id, "<%lu.%lu@%s>",
72 	     (unsigned long) getpid(),
73 	     (unsigned long) time(NULL),
74 	     hostname);
75     stream_writez(str, msg_id);
76     dico_stream_write(str, "\n", 1);
77 }
78 
79 int
get_input_line(dico_stream_t str,char ** buf,size_t * size,size_t * rdbytes)80 get_input_line(dico_stream_t str, char **buf, size_t *size, size_t *rdbytes)
81 {
82     int rc;
83     alarm(inactivity_timeout);
84     rc = dico_stream_getline(str, buf, size, rdbytes);
85     alarm(0);
86     return rc;
87 }
88 
89 RETSIGTYPE
sig_alarm(int sig)90 sig_alarm(int sig)
91 {
92     exit(EXIT_TIMEOUT);
93 }
94 
95 static void
load_modules(void)96 load_modules(void)
97 {
98     dico_iterator_t itr = xdico_list_iterator(modinst_list);
99     dicod_module_instance_t *inst;
100 
101     for (inst = dico_iterator_first(itr); inst;
102 	 inst = dico_iterator_next(itr)) {
103 	if (dicod_load_module(inst)) {
104 	    database_remove_dependent(inst);
105 	    dico_iterator_remove_current(itr, NULL);
106 	}
107     }
108     dico_iterator_destroy(&itr);
109 }
110 
111 static void
init_databases(void)112 init_databases(void)
113 {
114     dico_iterator_t itr = xdico_list_iterator(database_list);
115     dicod_database_t *dp;
116 
117     for (dp = dico_iterator_first(itr); dp; dp = dico_iterator_next(itr)) {
118 	if (dicod_database_init(dp)) {
119 	    dico_log(L_NOTICE, 0, _("removing database %s"), dp->name);
120 	    dico_iterator_remove_current(itr, NULL);
121 	    dicod_database_free(dp);
122 	}
123     }
124     dico_iterator_destroy(&itr);
125 }
126 
127 static void
open_databases(void)128 open_databases(void)
129 {
130     dico_iterator_t itr = xdico_list_iterator(database_list);
131     dicod_database_t *dp;
132 
133     for (dp = dico_iterator_first(itr); dp; dp = dico_iterator_next(itr)) {
134 	if (dicod_database_open(dp) == 0) {
135 	    dp->flags |= dicod_database_flags(dp);
136 	} else {
137 	    dico_log(L_NOTICE, 0, _("removing database %s"), dp->name);
138 	    dico_iterator_remove_current(itr, NULL);
139 	    dicod_database_free(dp);
140 	}
141     }
142     dico_iterator_destroy(&itr);
143 }
144 
145 static void
close_databases(void)146 close_databases(void)
147 {
148     dico_iterator_t itr = xdico_list_iterator(database_list);
149     dicod_database_t *dp;
150 
151     for (dp = dico_iterator_first(itr); dp; dp = dico_iterator_next(itr)) {
152 	if (dicod_database_close(dp))
153 	    dico_log(L_NOTICE, 0, _("error closing database %s"), dp->name);
154     }
155     dico_iterator_destroy(&itr);
156 }
157 
158 /* Default selectors for some commonly used methods. Modules should
159    override these. */
160 int
exact_sel(int cmd,dico_key_t key,const char * dict_word)161 exact_sel(int cmd, dico_key_t key, const char *dict_word)
162 {
163     switch (cmd) {
164     case DICO_SELECT_BEGIN:
165 	break;
166 
167     case DICO_SELECT_RUN:
168 	return utf8_strcasecmp(key->word, (char*)dict_word) == 0;
169 
170     case DICO_SELECT_END:
171 	break;
172     }
173     return 0;
174 }
175 
176 int
prefix_sel(int cmd,dico_key_t key,const char * dict_word)177 prefix_sel(int cmd, dico_key_t key, const char *dict_word)
178 {
179     size_t wordlen, keylen;
180 
181     switch (cmd) {
182     case DICO_SELECT_BEGIN:
183 	key->call_data = (void*) utf8_strlen(key->word);
184 	break;
185 
186     case DICO_SELECT_RUN:
187 	keylen = (size_t)key->call_data;
188 	wordlen = utf8_strlen(dict_word);
189 	return (wordlen >= keylen &&
190 		utf8_strncasecmp((char*)dict_word, key->word, keylen) == 0);
191 
192     case DICO_SELECT_END:
193 	break;
194     }
195     return 0;
196 }
197 
198 struct suffix {
199     size_t charlen;
200     size_t bytelen;
201 };
202 
203 int
suffix_sel(int cmd,dico_key_t key,const char * dict_word)204 suffix_sel(int cmd, dico_key_t key, const char *dict_word)
205 {
206     size_t wordlen;
207     struct suffix *suf;
208 
209     switch (cmd) {
210     case DICO_SELECT_BEGIN:
211 	suf = malloc(sizeof(*suf));
212 	if (!suf) {
213 	    dico_log(L_ERR, ENOMEM, "stdstrat_suffix");
214 	    return -1;
215 	}
216 	suf->bytelen = utf8_strlen(key->word);
217 	suf->charlen = strlen(key->word);
218 	key->call_data = suf;
219 	break;
220 
221     case DICO_SELECT_RUN:
222 	suf = (struct suffix*)key->call_data;
223 	wordlen = strlen(dict_word);
224 	return (wordlen >= suf->bytelen &&
225 		utf8_strncasecmp((char*)dict_word + (wordlen - suf->bytelen),
226 				 key->word, suf->bytelen) == 0);
227 
228     case DICO_SELECT_END:
229 	free(key->call_data);
230 	break;
231     }
232     return 0;
233 }
234 
235 /* Soundex selector */
236 int
soundex_sel(int cmd,dico_key_t key,const char * dict_word)237 soundex_sel(int cmd, dico_key_t key, const char *dict_word)
238 {
239     char dcode[DICO_SOUNDEX_SIZE];
240 
241     switch (cmd) {
242     case DICO_SELECT_BEGIN:
243 	key->call_data = malloc(DICO_SOUNDEX_SIZE);
244 	if (!key->call_data)
245 	    return 1;
246 	dico_soundex(key->word, key->call_data);
247 	break;
248 
249     case DICO_SELECT_RUN:
250 	dico_soundex(dict_word, dcode);
251 	return strcmp(dcode, key->call_data) == 0;
252 
253     case DICO_SELECT_END:
254 	free(key->call_data);
255 	break;
256     }
257     return 0;
258 }
259 
260 void
dicod_init_strategies(void)261 dicod_init_strategies(void)
262 {
263     static struct dico_strategy defstrat[] = {
264 	{ "exact", "Match words exactly", exact_sel },
265 	{ "prefix", "Match word prefixes", prefix_sel },
266 	{ "suffix", "Match word suffixes", suffix_sel },
267 	{ "soundex", "Match using SOUNDEX algorithm", soundex_sel, NULL },
268     };
269     int i;
270     for (i = 0; i < DICO_ARRAY_SIZE(defstrat); i++)
271 	dico_strategy_add(defstrat + i);
272 }
273 
274 void
dicod_server_init(void)275 dicod_server_init(void)
276 {
277     load_modules();
278     init_databases();
279     if (!dico_get_default_strategy())
280 	dico_set_default_strategy(DICTD_DEFAULT_STRATEGY);
281 }
282 
283 void
dicod_server_cleanup(void)284 dicod_server_cleanup(void)
285 {
286     dico_iterator_t itr = xdico_list_iterator(database_list);
287     dicod_database_t *dp;
288 
289     for (dp = dico_iterator_first(itr); dp; dp = dico_iterator_next(itr)) {
290 	if (dicod_database_deinit(dp))
291 	    dico_log(L_NOTICE, 0, _("error freeing database %s"), dp->name);
292     }
293     dico_iterator_destroy(&itr);
294 }
295 
296 static void
log_connection(const char * msg)297 log_connection(const char *msg)
298 {
299     if (debug_level) {
300 	char *p = sockaddr_to_astr((struct sockaddr*)&client_addr,
301 				   client_addrlen);
302 	if (debug_source_info)
303 	    DICO_DEBUG_SINFO(debug_stream);
304 	stream_writez(debug_stream, msg);
305 	dico_stream_write(debug_stream, " ", 1);
306 	if (identity_name)
307 	    stream_printf(debug_stream, "%s@", identity_name);
308 	stream_writez(debug_stream, p);
309 	dico_stream_write(debug_stream, "\n", 1);
310 	free(p);
311     }
312 }
313 
314 static dico_stream_t iostr;
315 
316 void
replace_io_stream(dico_stream_t str)317 replace_io_stream(dico_stream_t str)
318 {
319     iostr = str;
320 }
321 
322 int
dicod_loop(dico_stream_t str)323 dicod_loop(dico_stream_t str)
324 {
325     char *buf = NULL;
326     size_t size = 0;
327     size_t rdbytes;
328     struct dico_tokbuf tb;
329 
330     begin_timing("dicod");
331     signal(SIGALRM, sig_alarm);
332 
333     got_quit = 0;
334     if (transcript) {
335 	dico_stream_t logstr = dico_log_stream_create(L_DEBUG);
336 	if (!logstr)
337 	    xalloc_die();
338 	str = xdico_transcript_stream_create(str, logstr, NULL);
339     }
340     replace_io_stream(str);
341 
342     if (identity_check && server_addr.sa_family == AF_INET)
343 	identity_name = query_ident_name((struct sockaddr_in *)&server_addr,
344 					 (struct sockaddr_in *)&client_addr);
345     log_connection(_("connection from"));
346 
347     open_databases();
348     check_db_visibility();
349     initial_banner(iostr);
350 
351     dico_tokenize_begin(&tb);
352     while (!got_quit && get_input_line(iostr, &buf, &size, &rdbytes) == 0) {
353 	trimnl(buf, rdbytes);
354 	xdico_tokenize_string(&tb, buf);
355 	if (tb.tb_tokc == 0)
356 	    continue;
357 	dicod_handle_command(iostr, tb.tb_tokc, tb.tb_tokv);
358     }
359     dico_tokenize_end(&tb);
360     close_databases();
361     init_auth_data();
362     access_log_free_cache();
363     /* FIXME: destroy stream here, or at least do it if transcript is set */
364     log_connection(_("session finished:"));
365     return 0;
366 }
367 
368 int
dicod_inetd(void)369 dicod_inetd(void)
370 {
371     dico_stream_t str = dicod_iostream(0, 1);
372     if (!str)
373 	return 1;
374 
375     client_addrlen = sizeof(client_addr);
376     if (getsockname (0, (struct sockaddr*)&client_addr, &client_addrlen) == -1)
377 	client_addrlen = 0;
378 
379     server_addrlen = sizeof(server_addr);
380     if (getsockname (1, &server_addr, &server_addrlen) == -1)
381 	server_addrlen = 0;
382 
383     return dicod_loop(str);
384 }
385 
386 dico_stream_t
dicod_iostream(int ifd,int ofd)387 dicod_iostream(int ifd, int ofd)
388 {
389     dico_stream_t str = dico_fd_io_stream_create(ifd, ofd);
390 
391     if (!str)
392         return NULL;
393     dico_stream_set_buffer(str, dico_buffer_line, DICO_MAX_BUFFER);
394     if (!isatty(ifd)) {
395 	dico_stream_t s = dico_crlf_stream(str,
396 					   DICO_STREAM_READ|DICO_STREAM_WRITE,
397 					   0);
398 	if (!s) {
399 	    dico_stream_destroy(&str);
400 	    return NULL;
401 	}
402 	str = s;
403 	dico_stream_set_buffer(str, dico_buffer_line, DICO_MAX_BUFFER);
404     }
405     return str;
406 }
407 
408