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