a75066e40f1ed044787d20b446b45a5a1f619f79
[pazpar2-moved-to-github.git] / src / connection.c
1 /* $Id: connection.c,v 1.6 2007-07-03 11:21:48 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         yaz_log(YLOG_WARN, "Destroying orphan connection");
149         connection_destroy(co);
150         return;
151     }
152
153     if (event & EVENT_TIMEOUT)
154     {
155         if (co->state == Conn_Connecting)
156         {
157             yaz_log(YLOG_WARN,  "connect timeout %s", client_get_url(cl));
158             client_fatal(cl);
159         }
160         else
161         {
162             yaz_log(YLOG_LOG,  "idle timeout %s", client_get_url(cl));
163             connection_destroy(co);
164         }
165         return;
166     }
167     if (co->state == Conn_Connecting && event & EVENT_OUTPUT)
168     {
169         int errcode;
170         socklen_t errlen = sizeof(errcode);
171
172         if (getsockopt(cs_fileno(co->link), SOL_SOCKET, SO_ERROR, &errcode,
173             &errlen) < 0 || errcode != 0)
174         {
175             client_fatal(cl);
176             return;
177         }
178         else
179         {
180             yaz_log(YLOG_DEBUG, "Connect OK");
181             co->state = Conn_Open;
182             if (cl)
183                 client_set_state(cl, Client_Connected);
184             iochan_settimeout(i, 180);
185         }
186     }
187
188     else if (event & EVENT_INPUT)
189     {
190         int len = cs_get(co->link, &co->ibuf, &co->ibufsize);
191
192         if (len < 0)
193         {
194             yaz_log(YLOG_WARN|YLOG_ERRNO, "Error reading from %s", 
195                     client_get_url(cl));
196             connection_destroy(co);
197             return;
198         }
199         else if (len == 0)
200         {
201             yaz_log(YLOG_WARN, "EOF reading from %s", client_get_url(cl));
202             connection_destroy(co);
203             return;
204         }
205         else if (len > 1) // We discard input if we have no connection
206         {
207             co->state = Conn_Open;
208
209             if (client_is_our_response(cl))
210             {
211                 Z_APDU *a;
212                 struct session_database *sdb = client_get_database(cl);
213                 const char *apdulog = session_setting_oneval(sdb, PZ_APDULOG);
214
215                 odr_reset(global_parameters.odr_in);
216                 odr_setbuf(global_parameters.odr_in, co->ibuf, len, 0);
217                 if (!z_APDU(global_parameters.odr_in, &a, 0, 0))
218                 {
219                     client_fatal(cl);
220                     return;
221                 }
222
223                 if (apdulog && *apdulog && *apdulog != '0')
224                 {
225                     ODR p = odr_createmem(ODR_PRINT);
226                     yaz_log(YLOG_LOG, "recv APDU %s", client_get_url(cl));
227                     
228                     odr_setprint(p, yaz_log_file());
229                     z_APDU(p, &a, 0, 0);
230                     odr_setprint(p, stderr);
231                     odr_destroy(p);
232                 }
233                 switch (a->which)
234                 {
235                     case Z_APDU_initResponse:
236                         client_init_response(cl, a);
237                         break;
238                     case Z_APDU_searchResponse:
239                         client_search_response(cl, a);
240                         break;
241                     case Z_APDU_presentResponse:
242                         client_present_response(cl, a);
243                         break;
244                     case Z_APDU_close:
245                         client_close_response(cl, a);
246                         break;
247                     default:
248                         yaz_log(YLOG_WARN, 
249                                 "Unexpected Z39.50 response from %s",  
250                                 client_get_url(cl));
251                         client_fatal(cl);
252                         return;
253                 }
254                 // We aren't expecting staggered output from target
255                 // if (cs_more(t->link))
256                 //    iochan_setevent(i, EVENT_INPUT);
257             }
258             else  // we throw away response and go to idle mode
259             {
260                 yaz_log(YLOG_DEBUG, "Ignoring result of expired operation");
261                 client_set_state(cl, Client_Idle);
262             }
263         }
264         /* if len==1 we do nothing but wait for more input */
265     }
266     client_continue(cl);
267 }
268
269 // Disassociate connection from client
270 void connection_release(struct connection *co)
271 {
272     struct client *cl = co->client;
273
274     yaz_log(YLOG_DEBUG, "Connection release %s", co->host->hostport);
275     if (!cl)
276         return;
277     client_set_connection(cl, 0);
278     co->client = 0;
279 }
280
281 void connect_resolver_host(struct host *host)
282 {
283     struct connection *con = host->connections;
284     while (con)
285     {
286         if (con->state == Conn_Resolving)
287         {
288             if (!host->ipport) /* unresolved */
289             {
290                 connection_destroy(con);
291                 /* start all over .. at some point it will be NULL */
292                 con = host->connections;
293             }
294             else if (!con->client)
295             {
296                 yaz_log(YLOG_WARN, "connect_unresolved_host : ophan client");
297                 connection_destroy(con);
298                 /* start all over .. at some point it will be NULL */
299                 con = host->connections;
300             }
301             else
302             {
303                 connection_connect(con);
304                 con = con->next;
305             }
306         }
307     }
308 }
309
310 int connection_send_apdu(struct connection *co, Z_APDU *a)
311 {
312     char *buf;
313     int len, r;
314
315     if (!z_APDU(global_parameters.odr_out, &a, 0, 0))
316     {
317         odr_perror(global_parameters.odr_out, "Encoding APDU");
318         abort();
319     }
320     buf = odr_getbuf(global_parameters.odr_out, &len, 0);
321     r = cs_put(co->link, buf, len);
322     if (r < 0)
323     {
324         yaz_log(YLOG_WARN, "cs_put: %s", cs_errmsg(cs_errno(co->link)));
325         return -1;
326     }
327     else if (r == 1)
328     {
329         fprintf(stderr, "cs_put incomplete (ParaZ does not handle that)\n");
330         exit(1);
331     }
332     odr_reset(global_parameters.odr_out); /* release the APDU structure  */
333     co->state = Conn_Waiting;
334     iochan_setflags(co->iochan, EVENT_INPUT);
335     return 0;
336 }
337
338 struct host *connection_get_host(struct connection *con)
339 {
340     return con->host;
341 }
342
343 int connection_connect(struct connection *con)
344 {
345     COMSTACK link = 0;
346     struct host *host = connection_get_host(con);
347     void *addr;
348     int res;
349
350     struct session_database *sdb = client_get_database(con->client);
351     char *zproxy = session_setting_oneval(sdb, PZ_ZPROXY);
352
353     assert(host->ipport);
354     assert(con);
355
356     if (!(link = cs_create(tcpip_type, 0, PROTO_Z3950)))
357     {
358         yaz_log(YLOG_FATAL|YLOG_ERRNO, "Failed to create comstack");
359         return -1;
360     }
361     
362     if (!zproxy || 0 == strlen(zproxy)){
363         /* no Z39.50 proxy needed - direct connect */
364         yaz_log(YLOG_DEBUG, "Connection create %s", connection_get_url(con));
365         
366         if (!(addr = cs_straddr(link, host->ipport)))
367         {
368             yaz_log(YLOG_WARN|YLOG_ERRNO, 
369                     "Lookup of IP address %s failed", host->ipport);
370             return -1;
371         }
372         
373     } else {
374         /* Z39.50 proxy connect */
375         yaz_log(YLOG_DEBUG, "Connection create %s proxy %s", 
376                 connection_get_url(con), zproxy);
377         
378         if (!(addr = cs_straddr(link, zproxy)))
379         {
380             yaz_log(YLOG_WARN|YLOG_ERRNO, 
381                     "Lookup of ZProxy IP address %s failed", 
382                     zproxy);
383             return -1;
384         }
385     }
386     
387     res = cs_connect(link, addr);
388     if (res < 0)
389     {
390         yaz_log(YLOG_WARN|YLOG_ERRNO, "cs_connect %s",
391                 connection_get_url(con));
392         return -1;
393     }
394     con->link = link;
395     con->state = Conn_Connecting;
396     con->iochan = iochan_create(cs_fileno(link), connection_handler, 0);
397     iochan_settimeout(con->iochan, 30);
398     iochan_setdata(con->iochan, con);
399     pazpar2_add_channel(con->iochan);
400
401     /* this fragment is bad DRY: from client_prep_connection */
402     client_set_state(con->client, Client_Connecting);
403     iochan_setflag(con->iochan, EVENT_OUTPUT);
404     return 0;
405 }
406
407 const char *connection_get_url(struct connection *co)
408 {
409     return client_get_url(co->client);
410 }
411
412 void connection_set_authentication(struct connection *co, char *auth)
413 {
414     co->authentication = auth;
415 }
416
417 // Ensure that client has a connection associated
418 int client_prep_connection(struct client *cl)
419 {
420     struct connection *co;
421     struct session *se = client_get_session(cl);
422     struct host *host = client_get_host(cl);
423
424     co = client_get_connection(cl);
425
426     yaz_log(YLOG_DEBUG, "Client prep %s", client_get_url(cl));
427
428     if (!co)
429     {
430         // See if someone else has an idle connection
431         // We should look at timestamps here to select the longest-idle connection
432         for (co = host->connections; co; co = co->next)
433             if (co->state == Conn_Open &&
434                 (!co->client || client_get_session(co->client) != se) &&
435                 !strcmp(co->authentication,
436                     session_setting_oneval(client_get_database(cl),
437                     PZ_AUTHENTICATION)))
438                 break;
439         if (co)
440         {
441             connection_release(co);
442             client_set_connection(cl, co);
443             co->client = cl;
444         }
445         else
446             co = connection_create(cl);
447     }
448     if (co)
449     {
450         if (co->state == Conn_Connecting)
451         {
452             client_set_state(cl, Client_Connecting);
453             iochan_setflag(co->iochan, EVENT_OUTPUT);
454         }
455         else if (co->state == Conn_Open)
456         {
457             if (client_get_state(cl) == Client_Error 
458                 || client_get_state(cl) == Client_Disconnected)
459                 client_set_state(cl, Client_Idle);
460             iochan_setflag(co->iochan, EVENT_OUTPUT);
461         }
462         return 1;
463     }
464     else
465         return 0;
466 }
467
468
469
470 /*
471  * Local variables:
472  * c-basic-offset: 4
473  * indent-tabs-mode: nil
474  * End:
475  * vim: shiftwidth=4 tabstop=8 expandtab
476  */