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