Fixed bug #1319: Pazpar2 goes to infinite loop.. Seems resolver related.
[pazpar2-moved-to-github.git] / src / connection.c
1 /* $Id: connection.c,v 1.9 2007-07-25 13:27:06 adam Exp $
2    Copyright (c) 2006-2007, Index Data.
3
4 This file is part of Pazpar2.
5
6 Pazpar2 is free software; you can redistribute it and/or modify it under
7 the terms of the GNU General Public License as published by the Free
8 Software Foundation; either version 2, or (at your option) any later
9 version.
10
11 Pazpar2 is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Pazpar2; see the file LICENSE.  If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19 02111-1307, USA.
20  */
21
22 /** \file connection.c
23     \brief Z39.50 connection (low-level client)
24 */
25
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <string.h>
29 #include <sys/time.h>
30 #include <unistd.h>
31 #include <sys/socket.h>
32 #include <netdb.h>
33 #include <signal.h>
34 #include <ctype.h>
35 #include <assert.h>
36
37 #if HAVE_CONFIG_H
38 #include "cconfig.h"
39 #endif
40
41 #include <yaz/log.h>
42 #include <yaz/comstack.h>
43 #include <yaz/tcpip.h>
44 #include "connection.h"
45 #include "eventl.h"
46 #include "pazpar2.h"
47 #include "host.h"
48 #include "client.h"
49 #include "settings.h"
50 #include "parameters.h"
51
52
53 /** \brief Represents a physical, reusable  connection to a remote Z39.50 host
54  */
55 struct connection {
56     IOCHAN iochan;
57     COMSTACK link;
58     struct host *host;
59     struct client *client;
60     char *ibuf;
61     int ibufsize;
62     char *authentication; // Empty string or authentication string if set
63     enum {
64         Conn_Resolving,
65         Conn_Connecting,
66         Conn_Open,
67         Conn_Waiting,
68     } state;
69     struct connection *next; // next for same host or next in free list
70 };
71
72 static struct connection *connection_freelist = 0;
73
74 static void remove_connection_from_host(struct connection *con)
75 {
76     struct connection **conp = &con->host->connections;
77     assert(con);
78     while (*conp)
79     {
80         if (*conp == con)
81         {
82             *conp = (*conp)->next;
83             return;
84         }
85         conp = &(*conp)->next;
86     }
87     assert(*conp == 0);
88 }
89
90 // Close connection and recycle structure
91 void connection_destroy(struct connection *co)
92 {
93     if (co->link)
94     {
95         cs_close(co->link);
96         iochan_destroy(co->iochan);
97     }
98
99     yaz_log(YLOG_DEBUG, "Connection destroy %s", co->host->hostport);
100
101     remove_connection_from_host(co);
102     if (co->client)
103     {
104         client_disconnect(co->client);
105     }
106     co->next = connection_freelist;
107     connection_freelist = co;
108 }
109
110 // Creates a new connection for client, associated with the host of 
111 // client's database
112 struct connection *connection_create(struct client *cl)
113 {
114     struct connection *new;
115     struct host *host = client_get_host(cl);
116
117     if ((new = connection_freelist))
118         connection_freelist = new->next;
119     else
120     {
121         new = xmalloc(sizeof (struct connection));
122         new->ibuf = 0;
123         new->ibufsize = 0;
124     }
125     new->host = host;
126     new->next = new->host->connections;
127     new->host->connections = new;
128     new->client = cl;
129     new->authentication = "";
130     client_set_connection(cl, new);
131     new->link = 0;
132     new->state = Conn_Resolving;
133     if (host->ipport)
134         connection_connect(new);
135     return new;
136 }
137
138 static void connection_handler(IOCHAN i, int event)
139 {
140     struct connection *co = iochan_getdata(i);
141     struct client *cl = co->client;
142     struct session *se = 0;
143
144     if (cl)
145         se = client_get_session(cl);
146     else
147     {
148         connection_destroy(co);
149         return;
150     }
151
152     if (event & EVENT_TIMEOUT)
153     {
154         if (co->state == Conn_Connecting)
155         {
156             yaz_log(YLOG_WARN,  "connect timeout %s", client_get_url(cl));
157             client_fatal(cl);
158         }
159         else
160         {
161             yaz_log(YLOG_LOG,  "idle timeout %s", client_get_url(cl));
162             connection_destroy(co);
163         }
164         return;
165     }
166     if (co->state == Conn_Connecting && event & EVENT_OUTPUT)
167     {
168         int errcode;
169         socklen_t errlen = sizeof(errcode);
170
171         if (getsockopt(cs_fileno(co->link), SOL_SOCKET, SO_ERROR, &errcode,
172             &errlen) < 0 || errcode != 0)
173         {
174             client_fatal(cl);
175             return;
176         }
177         else
178         {
179             yaz_log(YLOG_DEBUG, "Connect OK");
180             co->state = Conn_Open;
181             if (cl)
182                 client_set_state(cl, Client_Connected);
183             iochan_settimeout(i, global_parameters.z3950_session_timeout);
184         }
185     }
186
187     else if (event & EVENT_INPUT)
188     {
189         int len = cs_get(co->link, &co->ibuf, &co->ibufsize);
190
191         if (len < 0)
192         {
193             yaz_log(YLOG_WARN|YLOG_ERRNO, "Error reading from %s", 
194                     client_get_url(cl));
195             connection_destroy(co);
196             return;
197         }
198         else if (len == 0)
199         {
200             yaz_log(YLOG_WARN, "EOF reading from %s", client_get_url(cl));
201             connection_destroy(co);
202             return;
203         }
204         else if (len > 1) // We discard input if we have no connection
205         {
206             co->state = Conn_Open;
207
208             if (client_is_our_response(cl))
209             {
210                 Z_APDU *a;
211                 struct session_database *sdb = client_get_database(cl);
212                 const char *apdulog = session_setting_oneval(sdb, PZ_APDULOG);
213
214                 odr_reset(global_parameters.odr_in);
215                 odr_setbuf(global_parameters.odr_in, co->ibuf, len, 0);
216                 if (!z_APDU(global_parameters.odr_in, &a, 0, 0))
217                 {
218                     client_fatal(cl);
219                     return;
220                 }
221
222                 if (apdulog && *apdulog && *apdulog != '0')
223                 {
224                     ODR p = odr_createmem(ODR_PRINT);
225                     yaz_log(YLOG_LOG, "recv APDU %s", client_get_url(cl));
226                     
227                     odr_setprint(p, yaz_log_file());
228                     z_APDU(p, &a, 0, 0);
229                     odr_setprint(p, stderr);
230                     odr_destroy(p);
231                 }
232                 switch (a->which)
233                 {
234                     case Z_APDU_initResponse:
235                         client_init_response(cl, a);
236                         break;
237                     case Z_APDU_searchResponse:
238                         client_search_response(cl, a);
239                         break;
240                     case Z_APDU_presentResponse:
241                         client_present_response(cl, a);
242                         break;
243                     case Z_APDU_close:
244                         client_close_response(cl, a);
245                         break;
246                     default:
247                         yaz_log(YLOG_WARN, 
248                                 "Unexpected Z39.50 response from %s",  
249                                 client_get_url(cl));
250                         client_fatal(cl);
251                         return;
252                 }
253                 // We aren't expecting staggered output from target
254                 // if (cs_more(t->link))
255                 //    iochan_setevent(i, EVENT_INPUT);
256             }
257             else  // we throw away response and go to idle mode
258             {
259                 yaz_log(YLOG_DEBUG, "Ignoring result of expired operation");
260                 client_set_state(cl, Client_Idle);
261             }
262         }
263         /* if len==1 we do nothing but wait for more input */
264     }
265     client_continue(cl);
266 }
267
268 // Disassociate connection from client
269 void connection_release(struct connection *co)
270 {
271     struct client *cl = co->client;
272
273     yaz_log(YLOG_DEBUG, "Connection release %s", co->host->hostport);
274     if (!cl)
275         return;
276     client_set_connection(cl, 0);
277     co->client = 0;
278 }
279
280 void connect_resolver_host(struct host *host)
281 {
282     struct connection *con = host->connections;
283     while (con)
284     {
285         if (con->state == Conn_Resolving)
286         {
287             if (!host->ipport) /* unresolved */
288             {
289                 connection_destroy(con);
290                 /* start all over .. at some point it will be NULL */
291                 con = host->connections;
292                 continue;
293             }
294             else if (!con->client)
295             {
296                 connection_destroy(con);
297                 /* start all over .. at some point it will be NULL */
298                 con = host->connections;
299                 continue;
300             }
301             else
302             {
303                 connection_connect(con);
304             }
305         }
306         else
307         {
308             yaz_log(YLOG_LOG, "connect_resolver_host: state=%d", con->state);
309         }
310         con = con->next;
311     }
312 }
313
314 int connection_send_apdu(struct connection *co, Z_APDU *a)
315 {
316     char *buf;
317     int len, r;
318
319     if (!z_APDU(global_parameters.odr_out, &a, 0, 0))
320     {
321         odr_perror(global_parameters.odr_out, "Encoding APDU");
322         abort();
323     }
324     buf = odr_getbuf(global_parameters.odr_out, &len, 0);
325     r = cs_put(co->link, buf, len);
326     if (r < 0)
327     {
328         yaz_log(YLOG_WARN, "cs_put: %s", cs_errmsg(cs_errno(co->link)));
329         return -1;
330     }
331     else if (r == 1)
332     {
333         fprintf(stderr, "cs_put incomplete (ParaZ does not handle that)\n");
334         exit(1);
335     }
336     odr_reset(global_parameters.odr_out); /* release the APDU structure  */
337     co->state = Conn_Waiting;
338     iochan_setflags(co->iochan, EVENT_INPUT);
339     return 0;
340 }
341
342 struct host *connection_get_host(struct connection *con)
343 {
344     return con->host;
345 }
346
347 int connection_connect(struct connection *con)
348 {
349     COMSTACK link = 0;
350     struct host *host = connection_get_host(con);
351     void *addr;
352     int res;
353
354     struct session_database *sdb = client_get_database(con->client);
355     char *zproxy = session_setting_oneval(sdb, PZ_ZPROXY);
356
357     assert(host->ipport);
358     assert(con);
359
360     if (!(link = cs_create(tcpip_type, 0, PROTO_Z3950)))
361     {
362         yaz_log(YLOG_FATAL|YLOG_ERRNO, "Failed to create comstack");
363         return -1;
364     }
365     
366     if (!zproxy || 0 == strlen(zproxy)){
367         /* no Z39.50 proxy needed - direct connect */
368         yaz_log(YLOG_DEBUG, "Connection create %s", connection_get_url(con));
369         
370         if (!(addr = cs_straddr(link, host->ipport)))
371         {
372             yaz_log(YLOG_WARN|YLOG_ERRNO, 
373                     "Lookup of IP address %s failed", host->ipport);
374             return -1;
375         }
376         
377     } else {
378         /* Z39.50 proxy connect */
379         yaz_log(YLOG_DEBUG, "Connection create %s proxy %s", 
380                 connection_get_url(con), zproxy);
381         
382         if (!(addr = cs_straddr(link, zproxy)))
383         {
384             yaz_log(YLOG_WARN|YLOG_ERRNO, 
385                     "Lookup of ZProxy IP address %s failed", 
386                     zproxy);
387             return -1;
388         }
389     }
390     
391     res = cs_connect(link, addr);
392     if (res < 0)
393     {
394         yaz_log(YLOG_WARN|YLOG_ERRNO, "cs_connect %s",
395                 connection_get_url(con));
396         return -1;
397     }
398     con->link = link;
399     con->state = Conn_Connecting;
400     con->iochan = iochan_create(cs_fileno(link), connection_handler, 0);
401     iochan_settimeout(con->iochan, global_parameters.z3950_connect_timeout);
402     iochan_setdata(con->iochan, con);
403     pazpar2_add_channel(con->iochan);
404
405     /* this fragment is bad DRY: from client_prep_connection */
406     client_set_state(con->client, Client_Connecting);
407     iochan_setflag(con->iochan, EVENT_OUTPUT);
408     return 0;
409 }
410
411 const char *connection_get_url(struct connection *co)
412 {
413     return client_get_url(co->client);
414 }
415
416 void connection_set_authentication(struct connection *co, char *auth)
417 {
418     co->authentication = auth;
419 }
420
421 // Ensure that client has a connection associated
422 int client_prep_connection(struct client *cl)
423 {
424     struct connection *co;
425     struct session *se = client_get_session(cl);
426     struct host *host = client_get_host(cl);
427
428     co = client_get_connection(cl);
429
430     yaz_log(YLOG_DEBUG, "Client prep %s", client_get_url(cl));
431
432     if (!co)
433     {
434         // See if someone else has an idle connection
435         // We should look at timestamps here to select the longest-idle connection
436         for (co = host->connections; co; co = co->next)
437             if (co->state == Conn_Open &&
438                 (!co->client || client_get_session(co->client) != se) &&
439                 !strcmp(co->authentication,
440                     session_setting_oneval(client_get_database(cl),
441                     PZ_AUTHENTICATION)))
442                 break;
443         if (co)
444         {
445             connection_release(co);
446             client_set_connection(cl, co);
447             co->client = cl;
448         }
449         else
450             co = connection_create(cl);
451     }
452     if (co)
453     {
454         if (co->state == Conn_Connecting)
455         {
456             client_set_state(cl, Client_Connecting);
457             iochan_setflag(co->iochan, EVENT_OUTPUT);
458         }
459         else if (co->state == Conn_Open)
460         {
461             if (client_get_state(cl) == Client_Error 
462                 || client_get_state(cl) == Client_Disconnected)
463                 client_set_state(cl, Client_Idle);
464             iochan_setflag(co->iochan, EVENT_OUTPUT);
465         }
466         return 1;
467     }
468     else
469         return 0;
470 }
471
472
473
474 /*
475  * Local variables:
476  * c-basic-offset: 4
477  * indent-tabs-mode: nil
478  * End:
479  * vim: shiftwidth=4 tabstop=8 expandtab
480  */