Set client-IP (AKA peername) to "nullpeer" if ->getpeername returns
[yazproxy-moved-to-github.git] / src / yaz-proxy.cpp
1 /* $Id: yaz-proxy.cpp,v 1.67 2006-06-09 09:01:31 adam Exp $
2    Copyright (c) 1998-2006, Index Data.
3
4 This file is part of the yazproxy.
5
6 YAZ proxy 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 YAZ proxy 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 YAZ proxy; 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 #ifdef WIN32
23 #define HAVE_SYS_STAT_H 1
24 #define HAVE_SYS_TYPES_H 1
25 #endif
26
27 #if HAVE_UNISTD_H
28 #include <unistd.h>
29 #endif
30 #if HAVE_SYS_TIME_H
31 #include <sys/time.h>
32 #endif
33 #if HAVE_SYS_TYPES_H
34 #include <sys/types.h>
35 #endif
36 #if HAVE_SYS_STAT_H
37 #include <sys/stat.h>
38 #endif
39
40 #include <assert.h>
41 #include <stdlib.h>
42 #include <time.h>
43 #include <fcntl.h>
44
45 #include <yaz/srw.h>
46 #include <yaz/marcdisp.h>
47 #include <yaz/yaz-iconv.h>
48 #include <yaz/log.h>
49 #include <yaz/diagbib1.h>
50 #include "proxyp.h"
51 #include <yaz/pquery.h>
52 #include <yaz/otherinfo.h>
53 #include <yaz/charneg.h>
54 #include "msg-thread.h"
55
56 using namespace yazpp_1;
57
58 #ifdef WIN32
59 #define strncasecmp _strnicmp
60 #endif
61
62 #define USE_AUTH_MSG 1
63
64 #if USE_AUTH_MSG
65 class YAZ_EXPORT Auth_Msg : public IMsg_Thread {
66 public:
67     int m_ret;
68     IMsg_Thread *handle();
69     void result();
70     Yaz_Proxy *m_proxy;
71     NMEM m_nmem;
72     char *m_apdu_buf;
73     int m_apdu_len;
74     Auth_Msg();
75     virtual ~Auth_Msg();
76 };
77
78 Auth_Msg::Auth_Msg()
79 {
80     m_nmem = nmem_create();
81 }
82
83 Auth_Msg::~Auth_Msg()
84 {
85     nmem_destroy(m_nmem);
86 }
87
88 IMsg_Thread *Auth_Msg::handle()
89 {
90     ODR decode = odr_createmem(ODR_DECODE);
91     Z_APDU *apdu;
92
93     odr_setbuf(decode, m_apdu_buf, m_apdu_len, 0);
94     int r = z_APDU(decode, &apdu, 0, 0);
95     if (!r)
96     {
97         yaz_log(YLOG_WARN, "decode failed in Auth_Msg::handle");
98     }
99     else
100     {
101         m_ret = m_proxy->handle_authentication(apdu);
102     }
103     odr_destroy(decode);
104     return this;
105 }
106
107 void Auth_Msg::result()
108 {
109     if (m_proxy->dec_ref())
110     {
111         yaz_log(YLOG_LOG, "Auth_Msg::proxy deleted meanwhile");
112     }
113     else
114     {
115         odr_setbuf(m_proxy->odr_decode(), m_apdu_buf, m_apdu_len, 0);
116         Z_APDU *apdu = 0;
117         int r = z_APDU(m_proxy->odr_decode(), &apdu, 0, 0);
118         if (!r)
119             yaz_log(YLOG_LOG, "Auth_Msg::result z_APDU failed");
120         m_proxy->result_authentication(apdu, m_ret);
121     }
122     delete this;
123 }
124
125 #endif
126
127 void Yaz_Proxy::result_authentication(Z_APDU *apdu, int ret)
128 {
129     if (apdu == 0 || ret == 0)
130     {
131         Z_APDU *apdu_reject = zget_APDU(odr_encode(), Z_APDU_initResponse);
132         *apdu_reject->u.initResponse->result = 0;
133         send_to_client(apdu_reject);
134         dec_ref();
135     }
136     else
137     {
138         if (apdu->which == Z_APDU_initRequest)
139         {
140             Yaz_ProxyConfig *cfg = check_reconfigure();
141             if (cfg)
142                 cfg->target_authentication(m_default_target, odr_encode(), 
143                                            apdu->u.initRequest);
144         }
145         handle_incoming_Z_PDU_2(apdu);
146     }
147 }
148
149 static const char *apdu_name(Z_APDU *apdu)
150 {
151     switch (apdu->which)
152     {
153     case Z_APDU_initRequest:
154         return "initRequest";
155     case Z_APDU_initResponse:
156         return "initResponse";
157     case Z_APDU_searchRequest:
158         return "searchRequest";
159     case Z_APDU_searchResponse:
160         return "searchResponse";
161     case Z_APDU_presentRequest:
162         return "presentRequest";
163     case Z_APDU_presentResponse:
164         return "presentResponse";
165     case Z_APDU_deleteResultSetRequest:
166         return "deleteResultSetRequest";
167     case Z_APDU_deleteResultSetResponse:
168         return "deleteResultSetResponse";
169     case Z_APDU_scanRequest:
170         return "scanRequest";
171     case Z_APDU_scanResponse:
172         return "scanResponse";
173     case Z_APDU_sortRequest:
174         return "sortRequest";
175     case Z_APDU_sortResponse:
176         return "sortResponse";
177     case Z_APDU_extendedServicesRequest:
178         return "extendedServicesRequest";
179     case Z_APDU_extendedServicesResponse:
180         return "extendedServicesResponse";
181     case Z_APDU_close:
182         return "close";
183     }
184     return "other";
185 }
186
187 static const char *gdu_name(Z_GDU *gdu)
188 {
189     switch(gdu->which)
190     {
191     case Z_GDU_Z3950:
192         return apdu_name(gdu->u.z3950);
193     case Z_GDU_HTTP_Request:
194         return "HTTP Request";
195     case Z_GDU_HTTP_Response:
196         return "HTTP Response";
197     }
198     return "Unknown request/response";
199 }
200
201 Yaz_Proxy::Yaz_Proxy(IPDU_Observable *the_PDU_Observable,
202                      ISocketObservable *the_socket_observable,
203                      Yaz_Proxy *parent)
204     :
205     Z_Assoc(the_PDU_Observable),
206     m_bw_stat(60), m_pdu_stat(60), m_search_stat(60)
207 {
208     m_PDU_Observable = the_PDU_Observable;
209     m_socket_observable = the_socket_observable;
210     m_client = 0;
211     m_parent = parent;
212     m_clientPool = 0;
213     m_seqno = 1;
214     m_keepalive_limit_bw = 500000;
215     m_keepalive_limit_pdu = 1000;
216     m_proxyTarget = 0;
217     m_default_target = 0;
218     m_proxy_negotiation_charset = 0;
219     m_proxy_negotiation_lang = 0;
220     m_proxy_negotiation_default_charset = 0;
221     m_charset_converter = new Yaz_CharsetConverter;
222     m_max_clients = 150;
223     m_log_mask = 0;
224     m_seed = time(0);
225     m_client_idletime = 600;
226     m_target_idletime = 600;
227     m_optimize = xstrdup ("1");
228     strcpy(m_session_str, "0 ");
229     m_session_no = 0;
230     m_bytes_sent = 0;
231     m_bytes_recv = 0;
232     m_bw_max = 0;
233     m_pdu_max = 0;
234     m_search_max = 0;
235     m_max_connect = 0;
236     m_max_connect_period = 0;
237     m_limit_connect = 0;
238     m_limit_connect_period = 0;
239     m_timeout_mode = timeout_normal;
240     m_timeout_gdu = 0;
241     m_max_record_retrieve = 0;
242     m_reconfig_flag = 0;
243     m_config_fname = 0;
244     m_request_no = 0;
245     m_flag_invalid_session = 0;
246     m_referenceId = 0;
247     m_referenceId_mem = nmem_create();
248     m_config = 0;
249     m_marcxml_mode = none;
250     m_stylesheet_xsp = 0;
251     m_stylesheet_nprl = 0;
252     m_stylesheet_apdu = 0;
253     m_s2z_stylesheet = 0;
254     m_s2z_database = 0;
255     m_schema = 0;
256     m_backend_type = 0;
257     m_backend_charset = 0;
258     m_frontend_type = 0;
259     m_initRequest_apdu = 0;
260     m_initRequest_mem = 0;
261     m_initRequest_preferredMessageSize = 0;
262     m_initRequest_maximumRecordSize = 0;
263     m_initRequest_options = 0;
264     m_initRequest_version = 0;
265     m_initRequest_oi_negotiation_charsets = 0;
266     m_initRequest_oi_negotiation_num_charsets = 0;
267     m_initRequest_oi_negotiation_langs = 0;
268     m_initRequest_oi_negotiation_num_langs = 0;
269     m_initRequest_oi_negotiation_selected = 0;
270     m_apdu_invalid_session = 0;
271     m_mem_invalid_session = 0;
272     m_s2z_odr_init = 0;
273     m_s2z_odr_search = 0;
274     m_s2z_init_apdu = 0;
275     m_s2z_search_apdu = 0;
276     m_s2z_present_apdu = 0;
277     m_http_keepalive = 0;
278     m_http_version = 0;
279     m_soap_ns = 0;
280     m_s2z_packing = Z_SRW_recordPacking_string;
281 #if HAVE_GETTIMEOFDAY
282     m_time_tv = xmalloc(sizeof(struct timeval));
283     struct timeval *tv = (struct timeval *) m_time_tv;
284     tv->tv_sec = 0;
285     tv->tv_usec = 0;
286 #else
287     m_time_tv = 0;
288 #endif
289     m_usemarcon_ini_stage1 = 0;
290     m_usemarcon_ini_stage2 = 0;
291     m_usemarcon = new Yaz_usemarcon();
292     if (!m_parent)
293         low_socket_open();
294     m_my_thread = 0;
295     m_ref_count = 1;
296     m_main_ptr_dec = false;
297     m_peername = 0;
298 }
299
300 void Yaz_Proxy::inc_ref()
301 {
302     m_ref_count++;
303 }
304
305 Yaz_Proxy::~Yaz_Proxy()
306 {
307     yaz_log(YLOG_LOG, "%sClosed %d/%d sent/recv bytes total", m_session_str,
308             m_bytes_sent, m_bytes_recv);
309     nmem_destroy(m_initRequest_mem);
310     nmem_destroy(m_mem_invalid_session);
311     nmem_destroy(m_referenceId_mem);
312
313     xfree(m_proxyTarget);
314     xfree(m_default_target);
315     xfree(m_proxy_negotiation_charset);
316     xfree(m_proxy_negotiation_lang);
317     xfree(m_proxy_negotiation_default_charset);
318     delete m_charset_converter;
319     xfree(m_optimize);
320
321 #if HAVE_XSLT
322     if (m_stylesheet_xsp)
323         xsltFreeStylesheet((xsltStylesheetPtr) m_stylesheet_xsp);
324 #endif
325     xfree (m_time_tv);
326
327     xfree (m_peername);
328     xfree (m_schema);
329     xfree (m_backend_type);
330     xfree (m_backend_charset);
331     xfree (m_usemarcon_ini_stage1);
332     xfree (m_usemarcon_ini_stage2);
333     delete m_usemarcon;
334     if (m_s2z_odr_init)
335         odr_destroy(m_s2z_odr_init);
336     if (m_s2z_odr_search)
337         odr_destroy(m_s2z_odr_search);
338     if (!m_parent)
339         low_socket_close();
340     if (!m_parent)
341         delete m_my_thread;
342     delete m_config;
343 }
344
345 void Yaz_Proxy::set_debug_mode(int mode)
346 {
347     m_debug_mode = mode;
348 }
349
350 int Yaz_Proxy::set_config(const char *config)
351 {
352     delete m_config;
353     m_config = new Yaz_ProxyConfig();
354     xfree(m_config_fname);
355     m_config_fname = xstrdup(config);
356     int r = m_config->read_xml(config);
357     if (!r)
358     {
359         int period = 60;
360         m_config->get_generic_info(&m_log_mask, &m_max_clients,
361                                    &m_max_connect, &m_limit_connect, &period);
362         m_connect.set_period(period);
363     }
364     return r;
365 }
366
367 void Yaz_Proxy::set_default_target(const char *target)
368 {
369     xfree (m_default_target);
370     m_default_target = 0;
371     if (target)
372         m_default_target = (char *) xstrdup (target);
373 }
374
375 void Yaz_Proxy::set_proxy_negotiation (const char *charset, const char *lang,
376                                        const char *default_charset)
377 {
378     yaz_log(YLOG_DEBUG, "%sSet the proxy negotiation: charset to '%s', "
379         "default charset to '%s', language to '%s'", m_session_str, 
380         charset?charset:"none",
381         default_charset?default_charset:"none",
382         lang?lang:"none");
383     xfree (m_proxy_negotiation_charset);
384     xfree (m_proxy_negotiation_lang);
385     m_proxy_negotiation_charset = m_proxy_negotiation_lang = 0;
386     if (charset)
387         m_proxy_negotiation_charset = (char *) xstrdup (charset);
388     if (lang)
389         m_proxy_negotiation_lang = (char *) xstrdup (lang);
390     if (default_charset)
391         m_proxy_negotiation_default_charset =
392             (char *) xstrdup (default_charset);
393 }
394
395 Yaz_ProxyConfig *Yaz_Proxy::check_reconfigure()
396 {
397     if (m_parent)
398         return m_parent->check_reconfigure();
399
400     Yaz_ProxyConfig *cfg = m_config;
401     if (m_reconfig_flag)
402     {
403         yaz_log(YLOG_LOG, "reconfigure");
404         yaz_log_reopen();
405         if (m_config_fname && cfg)
406         {
407             yaz_log(YLOG_LOG, "reconfigure config %s", m_config_fname);
408             int r = cfg->read_xml(m_config_fname);
409             if (r)
410                 yaz_log(YLOG_WARN, "reconfigure failed");
411             else
412             {
413                 m_log_mask = 0;
414                 int period = 60;
415                 cfg->get_generic_info(&m_log_mask, &m_max_clients,
416                                       &m_max_connect, &m_limit_connect,
417                                       &period);
418                 m_connect.set_period(period);
419             }
420         }
421         else
422             yaz_log(YLOG_LOG, "reconfigure");
423         m_reconfig_flag = 0;
424     }
425     return cfg;
426 }
427
428 IPDU_Observer *Yaz_Proxy::sessionNotify(IPDU_Observable
429                                         *the_PDU_Observable, int fd)
430 {
431     check_reconfigure();
432
433     char session_str[200];
434     const char *peername = the_PDU_Observable->getpeername();
435     if (!peername)
436         peername = "nullpeer";
437
438     if (m_log_mask & PROXY_LOG_IP_CLIENT)
439         sprintf(session_str, "%ld:%d %.80s %d ",
440                 (long) time(0), m_session_no, peername, 0);
441     else
442         sprintf(session_str, "%ld:%d %d ",
443                 (long) time(0), m_session_no, 0);
444     m_session_no++;
445
446     yaz_log (YLOG_LOG, "%sNew session %s", session_str, peername);
447
448     Yaz_Proxy *new_proxy = new Yaz_Proxy(the_PDU_Observable,
449                                          m_socket_observable, this);
450
451     new_proxy->m_config = 0;
452     new_proxy->m_config_fname = 0;
453     new_proxy->timeout(m_client_idletime);
454     new_proxy->m_target_idletime = m_target_idletime;
455     new_proxy->set_default_target(m_default_target);
456     new_proxy->m_max_clients = m_max_clients;
457     new_proxy->m_log_mask = m_log_mask;
458     new_proxy->m_session_no = m_session_no;
459
460 #if 0
461     // in case we want to watch a particular client..
462     if (!strcmp(peername, "tcp:163.121.19.82")) // NIS GROUP
463         new_proxy->m_log_mask = 255;
464 #endif
465
466     new_proxy->set_APDU_log(get_APDU_log());
467     if (new_proxy->m_log_mask & PROXY_LOG_APDU_CLIENT)
468         new_proxy->set_APDU_yazlog(1);
469     else
470         new_proxy->set_APDU_yazlog(0);
471     strcpy(new_proxy->m_session_str, session_str);
472     new_proxy->m_peername = xstrdup(peername);
473     new_proxy->set_proxy_negotiation(m_proxy_negotiation_charset,
474         m_proxy_negotiation_lang, m_proxy_negotiation_default_charset);
475     // create thread object the first time we get an incoming connection
476     if (!m_my_thread)
477         m_my_thread = new Msg_Thread(m_socket_observable, 1);
478     new_proxy->m_my_thread = m_my_thread;
479     return new_proxy;
480 }
481
482 char *Yaz_Proxy::get_cookie(Z_OtherInformation **otherInfo)
483 {
484     int oid[OID_SIZE];
485     Z_OtherInformationUnit *oi;
486     struct oident ent;
487     ent.proto = PROTO_Z3950;
488     ent.oclass = CLASS_USERINFO;
489     ent.value = (oid_value) VAL_COOKIE;
490     assert (oid_ent_to_oid (&ent, oid));
491
492     if (oid_ent_to_oid (&ent, oid) &&
493         (oi = update_otherInformation(otherInfo, 0, oid, 1, 1)) &&
494         oi->which == Z_OtherInfo_characterInfo)
495         return oi->information.characterInfo;
496     return 0;
497 }
498 char *Yaz_Proxy::get_proxy(Z_OtherInformation **otherInfo)
499 {
500     int oid[OID_SIZE];
501     Z_OtherInformationUnit *oi;
502     struct oident ent;
503     ent.proto = PROTO_Z3950;
504     ent.oclass = CLASS_USERINFO;
505     ent.value = (oid_value) VAL_PROXY;
506     if (oid_ent_to_oid (&ent, oid) &&
507         (oi = update_otherInformation(otherInfo, 0, oid, 1, 1)) &&
508         oi->which == Z_OtherInfo_characterInfo)
509         return oi->information.characterInfo;
510     return 0;
511 }
512 const char *Yaz_Proxy::load_balance(const char **url)
513 {
514     int zurl_in_use[MAX_ZURL_PLEX];
515     int zurl_in_spare[MAX_ZURL_PLEX];
516     Yaz_ProxyClient *c;
517     int i;
518
519     for (i = 0; i<MAX_ZURL_PLEX; i++)
520     {
521         zurl_in_use[i] = 0;
522         zurl_in_spare[i] = 0;
523     }
524     for (c = m_parent->m_clientPool; c; c = c->m_next)
525     {
526         for (i = 0; url[i]; i++)
527             if (!strcmp(url[i], c->get_hostname()))
528             {
529                 zurl_in_use[i]++;
530                 if (c->m_cookie == 0 && c->m_server == 0 && c->m_waiting == 0)
531                     zurl_in_spare[i]++;
532             }
533     }
534     int min_use = 100000;
535     int spare_for_min = 0;
536     int max_spare = 0;
537     const char *ret_min = 0;
538     const char *ret_spare = 0;
539     for (i = 0; url[i]; i++)
540     {
541         yaz_log(YLOG_DEBUG, "%szurl=%s use=%d spare=%d",
542                 m_session_str, url[i], zurl_in_use[i], zurl_in_spare[i]);
543         if (min_use > zurl_in_use[i])
544         {
545             ret_min = url[i];
546             min_use = zurl_in_use[i];
547             spare_for_min = zurl_in_spare[i];
548         }
549         if (max_spare < zurl_in_spare[i])
550         {
551             ret_spare = url[i];
552             max_spare = zurl_in_spare[i];
553         }
554     }
555     return ret_min;
556 }
557
558 Yaz_ProxyClient *Yaz_Proxy::get_client(Z_APDU *apdu, const char *cookie,
559                                        const char *proxy_host)
560 {
561     assert (m_parent);
562     Yaz_Proxy *parent = m_parent;
563     Yaz_ProxyClient *c = m_client;
564
565     if (!m_proxyTarget)
566     {
567         const char *url[MAX_ZURL_PLEX];
568         Yaz_ProxyConfig *cfg = check_reconfigure();
569         if (proxy_host)
570         {
571             if (parent && parent->m_debug_mode)
572             {
573                 // only to be enabled for debugging...
574                 if (!strcmp(proxy_host, "stop"))
575                     exit(0);
576             }
577             xfree(m_default_target);
578             m_default_target = xstrdup(proxy_host);
579         }
580         proxy_host = m_default_target;
581         int client_idletime = -1;
582         const char *cql2rpn_fname = 0;
583         const char *negotiation_charset = 0;
584         const char *negotiation_lang = 0;
585         const char *query_charset = 0;
586         const char *default_client_query_charset = 0;
587         url[0] = m_default_target;
588         url[1] = 0;
589         if (cfg)
590         {
591             int pre_init = 0;
592             cfg->get_target_info(proxy_host, url, &m_bw_max,
593                                  &m_pdu_max, &m_max_record_retrieve,
594                                  &m_search_max,
595                                  &m_target_idletime, &client_idletime,
596                                  &parent->m_max_clients,
597                                  &m_keepalive_limit_bw,
598                                  &m_keepalive_limit_pdu,
599                                  &pre_init,
600                                  &cql2rpn_fname,
601                                  &negotiation_charset,
602                                  &negotiation_lang,
603                                  &query_charset,
604                                  &default_client_query_charset);
605         }
606         if (client_idletime != -1)
607         {
608             m_client_idletime = client_idletime;
609             timeout(m_client_idletime);
610         }
611
612         // get those FILE descriptors available 
613         m_parent->low_socket_close();
614         if (cql2rpn_fname)
615             m_cql2rpn.set_pqf_file(cql2rpn_fname);
616         // reserve them again
617         m_parent->low_socket_open();
618         
619         if (negotiation_charset || negotiation_lang || default_client_query_charset)
620         {
621             set_proxy_negotiation(negotiation_charset,
622                 negotiation_lang, default_client_query_charset);
623         }
624         m_charset_converter->set_target_query_charset(query_charset);
625         if (!url[0])
626         {
627             yaz_log(YLOG_LOG, "%sNo default target", m_session_str);
628             return 0;
629         }
630         // we don't handle multiplexing for cookie session, so we just
631         // pick the first one in this case (anonymous users will be able
632         // to use any backend)
633         if (cookie && *cookie)
634             m_proxyTarget = (char*) xstrdup(url[0]);
635         else
636             m_proxyTarget = (char*) xstrdup(load_balance(url));
637     }
638     if (cookie && *cookie)
639     {   // search in sessions with a cookie
640         for (c = parent->m_clientPool; c; c = c->m_next)
641         {
642             assert (c->m_prev);
643             assert (*c->m_prev == c);
644             if (c->m_cookie && !strcmp(cookie,c->m_cookie) &&
645                 !strcmp(m_proxyTarget, c->get_hostname()))
646             {
647                 // Found it in cache
648                 // The following handles "cancel"
649                 // If connection is busy (waiting for PDU) and
650                 // we have an initRequest we can safely do re-open
651                 if (c->m_waiting && apdu->which == Z_APDU_initRequest)
652                 {
653                     yaz_log (YLOG_LOG, "%s REOPEN target=%s", m_session_str,
654                              c->get_hostname());
655                     c->close();
656                     c->m_init_flag = 0;
657
658                     c->m_last_ok = 0;
659                     c->m_cache.clear();
660                     c->m_last_resultCount = 0;
661                     c->m_sr_transform = 0;
662                     c->m_waiting = 0;
663                     c->m_resultSetStartPoint = 0;
664                     c->m_target_idletime = m_target_idletime;
665                     if (c->client(m_proxyTarget))
666                     {
667                         delete c;
668                         return 0;
669                     }
670                     c->timeout(30);
671                 }
672                 c->m_seqno = parent->m_seqno;
673                 if (c->m_server && c->m_server != this)
674                     c->m_server->m_client = 0;
675                 c->m_server = this;
676                 (parent->m_seqno)++;
677                 yaz_log (YLOG_DEBUG, "get_client 1 %p %p", this, c);
678                 return c;
679             }
680         }
681     }
682     else if (!c && apdu->which == Z_APDU_initRequest )
683     {
684         // anonymous sessions without cookie.
685         // if authentication is set it is NOT anonymous se we can't share them.
686         // If charset and lang negotiation is use it is NOT anonymous session too.
687         for (c = parent->m_clientPool; c; c = c->m_next)
688         {
689             assert(c->m_prev);
690             assert(*c->m_prev == c);
691             if (c->m_server == 0 && c->m_cookie == 0 &&  c->m_waiting == 0 
692                 && c->compare_idAuthentication(apdu)
693                 && c->compare_charset(apdu)
694                 && !strcmp(m_proxyTarget, c->get_hostname()))
695             {
696                 // found it in cache
697                 yaz_log (YLOG_LOG, "%sREUSE %d %s",
698                          m_session_str, parent->m_seqno, c->get_hostname());
699                 
700                 c->m_seqno = parent->m_seqno;
701                 assert(c->m_server == 0);
702                 c->m_server = this;
703
704                 if (parent->m_log_mask & PROXY_LOG_APDU_SERVER)
705                     c->set_APDU_yazlog(1);
706                 else
707                     c->set_APDU_yazlog(0);
708
709                 (parent->m_seqno)++;
710
711                 parent->pre_init();
712
713                 return c;
714             }
715         }
716     }
717     if (!m_client)
718     {
719         if (apdu->which != Z_APDU_initRequest)
720         {
721             yaz_log (YLOG_LOG, "%sno init request as first PDU", m_session_str);
722             return 0;
723         }
724         // go through list of clients - and find the lowest/oldest one.
725         Yaz_ProxyClient *c_min = 0;
726         int min_seq = -1;
727         int no_of_clients = 0;
728         if (parent->m_clientPool)
729             yaz_log (YLOG_DEBUG, "Existing sessions");
730         for (c = parent->m_clientPool; c; c = c->m_next)
731         {
732             yaz_log (YLOG_DEBUG, " Session %-3d wait=%d %s cookie=%s", c->m_seqno,
733                                c->m_waiting, c->get_hostname(),
734                                c->m_cookie ? c->m_cookie : "");
735             no_of_clients++;
736             if (min_seq < 0 || c->m_seqno < min_seq)
737             {
738                 min_seq = c->m_seqno;
739                 c_min = c;
740             }
741         }
742         if (no_of_clients >= parent->m_max_clients)
743         {
744             c = c_min;
745             if (c->m_waiting || strcmp(m_proxyTarget, c->get_hostname()))
746             {
747                 yaz_log (YLOG_LOG, "%sMAXCLIENTS %d Destroy %d",
748                          m_session_str, parent->m_max_clients, c->m_seqno);
749                 if (c->m_server && c->m_server != this)
750                     c->m_server->dec_ref();
751             }
752             else
753             {
754                 yaz_log (YLOG_LOG, "%sMAXCLIENTS %d Reuse %d %d %s",
755                          m_session_str, parent->m_max_clients,
756                          c->m_seqno, parent->m_seqno, c->get_hostname());
757                 xfree (c->m_cookie);
758                 c->m_cookie = 0;
759                 if (cookie)
760                     c->m_cookie = xstrdup(cookie);
761                 c->m_seqno = parent->m_seqno;
762                 if (c->m_server && c->m_server != this)
763                 {
764                     c->m_server->m_client = 0;
765                     c->m_server->dec_ref();
766                 }
767                 (parent->m_seqno)++;
768                 c->m_target_idletime = m_target_idletime;
769                 c->timeout(m_target_idletime);
770
771                 if (parent->m_log_mask & PROXY_LOG_APDU_SERVER)
772                     c->set_APDU_yazlog(1);
773                 else
774                     c->set_APDU_yazlog(0);
775
776                 return c;
777             }
778         }
779         else
780         {
781             yaz_log (YLOG_LOG, "%sNEW %d %s",
782                      m_session_str, parent->m_seqno, m_proxyTarget);
783             c = new Yaz_ProxyClient(m_PDU_Observable->clone(), parent);
784             c->m_next = parent->m_clientPool;
785             if (c->m_next)
786                 c->m_next->m_prev = &c->m_next;
787             parent->m_clientPool = c;
788             c->m_prev = &parent->m_clientPool;
789         }
790
791         xfree (c->m_cookie);
792         c->m_cookie = 0;
793         if (cookie)
794             c->m_cookie = xstrdup(cookie);
795
796         c->m_seqno = parent->m_seqno;
797         c->m_init_flag = 0;
798         c->m_last_resultCount = 0;
799         c->m_last_ok = 0;
800         c->m_cache.clear();
801         c->m_sr_transform = 0;
802         c->m_waiting = 0;
803         c->m_resultSetStartPoint = 0;
804         (parent->m_seqno)++;
805         if (c->client(m_proxyTarget))
806         {
807             delete c;
808             return 0;
809         }
810         c->m_target_idletime = m_target_idletime;
811         c->timeout(30);
812
813         if (parent->m_log_mask & PROXY_LOG_APDU_SERVER)
814             c->set_APDU_yazlog(1);
815         else
816             c->set_APDU_yazlog(0);
817
818         c->set_idAuthentication(apdu);
819     }
820     yaz_log (YLOG_DEBUG, "get_client 3 %p %p", this, c);
821     return c;
822 }
823
824 void Yaz_Proxy::display_diagrecs(Z_DiagRec **pp, int num)
825 {
826     int i;
827     for (i = 0; i<num; i++)
828     {
829         oident *ent;
830         Z_DefaultDiagFormat *r;
831         Z_DiagRec *p = pp[i];
832         if (p->which != Z_DiagRec_defaultFormat)
833         {
834             yaz_log(YLOG_LOG, "%sError no diagnostics", m_session_str);
835             return;
836         }
837         else
838             r = p->u.defaultFormat;
839         if (!(ent = oid_getentbyoid(r->diagnosticSetId)) ||
840             ent->oclass != CLASS_DIAGSET || ent->value != VAL_BIB1)
841             yaz_log(YLOG_LOG, "%sError unknown diagnostic set", m_session_str);
842         switch (r->which)
843         {
844         case Z_DefaultDiagFormat_v2Addinfo:
845             yaz_log(YLOG_LOG, "%sError %d %s:%s",
846                     m_session_str,
847                     *r->condition, diagbib1_str(*r->condition),
848                     r->u.v2Addinfo);
849             break;
850         case Z_DefaultDiagFormat_v3Addinfo:
851             yaz_log(YLOG_LOG, "%sError %d %s:%s",
852                     m_session_str,
853                     *r->condition, diagbib1_str(*r->condition),
854                     r->u.v3Addinfo);
855             break;
856         }
857     }
858 }
859
860 int Yaz_Proxy::convert_xsl(Z_NamePlusRecordList *p, Z_APDU *apdu)
861 {
862     if (!m_stylesheet_xsp || p->num_records <= 0)
863     {
864         return 0;  /* no XSLT to be done ... */
865     }
866
867     m_stylesheet_offset = 0;
868     m_stylesheet_nprl = p;
869     m_stylesheet_apdu = apdu;
870     m_timeout_mode = timeout_xsl;
871
872     timeout(0);
873     return 1;
874 }
875
876 void Yaz_Proxy::convert_xsl_delay()
877 {
878 #if HAVE_XSLT
879     Z_NamePlusRecord *npr = m_stylesheet_nprl->records[m_stylesheet_offset];
880     if (npr->which == Z_NamePlusRecord_databaseRecord)
881     {
882         Z_External *r = npr->u.databaseRecord;
883         if (r->which == Z_External_octet)
884         {
885 #if 0
886             fwrite((char*) r->u.octet_aligned->buf, 1, r->u.octet_aligned->len, stdout);
887 #endif
888             xmlDocPtr res, doc = xmlParseMemory(
889                 (char*) r->u.octet_aligned->buf,
890                 r->u.octet_aligned->len);
891
892
893             yaz_log(YLOG_LOG, "%sXSLT convert %d",
894                     m_session_str, m_stylesheet_offset);
895             res = xsltApplyStylesheet((xsltStylesheetPtr) m_stylesheet_xsp,
896                                       doc, 0);
897
898             if (res)
899             {
900                 xmlChar *out_buf;
901                 int out_len;
902                 xmlDocDumpFormatMemory (res, &out_buf, &out_len, 1);
903
904                 m_stylesheet_nprl->records[m_stylesheet_offset]->
905                     u.databaseRecord =
906                     z_ext_record(odr_encode(), VAL_TEXT_XML,
907                                  (char*) out_buf, out_len);
908                 xmlFree(out_buf);
909                 xmlFreeDoc(res);
910             }
911
912             xmlFreeDoc(doc);
913         }
914     }
915 #endif
916     m_stylesheet_offset++;
917     if (m_stylesheet_offset == m_stylesheet_nprl->num_records)
918     {
919         m_timeout_mode = timeout_normal;
920         m_stylesheet_nprl = 0;
921 #if HAVE_XSLT
922         if (m_stylesheet_xsp)
923             xsltFreeStylesheet((xsltStylesheetPtr) m_stylesheet_xsp);
924 #endif
925         m_stylesheet_xsp = 0;
926         timeout(m_client_idletime);
927         send_PDU_convert(m_stylesheet_apdu);
928     }
929     else
930         timeout(0);
931 }
932
933 void Yaz_Proxy::convert_to_frontend_type(Z_NamePlusRecordList *p)
934 {
935     if (m_frontend_type != VAL_NONE)
936     {
937         int i;
938         for (i = 0; i < p->num_records; i++)
939         {
940             Z_NamePlusRecord *npr = p->records[i];
941             if (npr->which == Z_NamePlusRecord_databaseRecord)
942             {
943                 Z_External *r = npr->u.databaseRecord;
944                 if (r->which == Z_External_octet)
945                 {
946 #if HAVE_USEMARCON
947                     if (m_usemarcon_ini_stage1 && *m_usemarcon_ini_stage1)
948                     {
949                         if (!m_usemarcon->m_stage1)
950                         {
951                             m_usemarcon->m_stage1 = new CDetails();
952                         }
953                         m_usemarcon->m_stage1->SetIniFileName(m_usemarcon_ini_stage1);
954                         m_usemarcon->m_stage1->SetMarcRecord((char*) r->u.octet_aligned->buf, r->u.octet_aligned->len);
955                         int res = m_usemarcon->m_stage1->Start();
956                         if (res == 0)
957                         {
958                             char *converted;
959                             int convlen;
960                             m_usemarcon->m_stage1->GetMarcRecord(converted, convlen);
961                             if (m_usemarcon_ini_stage2 && *m_usemarcon_ini_stage2)
962                             {
963                                 if (!m_usemarcon->m_stage2)
964                                 {
965                                     m_usemarcon->m_stage2 = new CDetails();
966                                 }
967                                 m_usemarcon->m_stage2->SetIniFileName(m_usemarcon_ini_stage2);
968                                 m_usemarcon->m_stage2->SetMarcRecord(converted, convlen);
969                                 res = m_usemarcon->m_stage2->Start();
970                                 if (res == 0)
971                                 {
972                                     free(converted);
973                                     m_usemarcon->m_stage2->GetMarcRecord(converted, convlen);
974                                 }
975                                 else
976                                 {
977                                     yaz_log(YLOG_LOG, "%sUSEMARCON stage 2 error %d", m_session_str, res);
978                                 }
979                             }
980                             npr->u.databaseRecord =
981                                 z_ext_record(odr_encode(),
982                                              m_frontend_type,
983                                              converted,
984                                              strlen(converted));
985                             free(converted);
986                         }
987                         else
988                         {
989                             yaz_log(YLOG_LOG, "%sUSEMARCON stage 1 error %d", m_session_str, res);
990                         }
991                         continue;
992                     }
993 #endif
994 /* HAVE_USEMARCON */
995                     npr->u.databaseRecord =
996                         z_ext_record(odr_encode(),
997                                      m_frontend_type,
998                                      (char*) r->u.octet_aligned->buf,
999                                      r->u.octet_aligned->len);
1000                 }
1001             }
1002         }
1003     }
1004 }
1005
1006 void Yaz_Proxy::convert_records_charset(Z_NamePlusRecordList *p,
1007                                         const char *backend_charset)
1008 {
1009     int sel =   m_charset_converter->get_client_charset_selected();
1010     const char *client_record_charset =
1011         m_charset_converter->get_client_query_charset();
1012     if (sel && backend_charset && client_record_charset &&
1013         strcmp(backend_charset, client_record_charset))
1014     {
1015         int i;
1016         yaz_iconv_t cd = yaz_iconv_open(client_record_charset,
1017                                         backend_charset);
1018         yaz_marc_t mt = yaz_marc_create();
1019         yaz_marc_xml(mt, YAZ_MARC_ISO2709);
1020         yaz_marc_iconv(mt, cd);
1021         for (i = 0; i < p->num_records; i++)
1022         {
1023             Z_NamePlusRecord *npr = p->records[i];
1024             if (npr->which == Z_NamePlusRecord_databaseRecord)
1025             {
1026                 Z_External *r = npr->u.databaseRecord;
1027                 oident *ent = oid_getentbyoid(r->direct_reference);
1028                 if (!ent || ent->value == VAL_NONE)
1029                     continue;
1030
1031                 if (ent->value == VAL_SUTRS)
1032                 {
1033                     WRBUF w = wrbuf_alloc();
1034
1035                     wrbuf_iconv_write(w, cd,  (char*) r->u.octet_aligned->buf,
1036                                       r->u.octet_aligned->len);
1037                     npr->u.databaseRecord =
1038                         z_ext_record(odr_encode(), ent->value, wrbuf_buf(w),
1039                                      wrbuf_len(w));
1040                     wrbuf_free(w, 1);
1041                 }
1042                 else if (ent->value == VAL_TEXT_XML)
1043                 {
1044                     ;
1045                 }
1046                 else if (r->which == Z_External_octet)
1047                 {
1048                     int rlen;
1049                     char *result;
1050                     if (yaz_marc_decode_buf(mt,
1051                                             (char*) r->u.octet_aligned->buf,
1052                                             r->u.octet_aligned->len,
1053                                             &result, &rlen))
1054                     {
1055                         npr->u.databaseRecord =
1056                             z_ext_record(odr_encode(), ent->value, result, rlen);
1057                         yaz_log(YLOG_LOG, "%sRecoding MARC record",
1058                                 m_session_str);
1059                     }
1060                 }
1061             }
1062         }
1063         if (cd)
1064             yaz_iconv_close(cd);
1065         yaz_marc_destroy(mt);
1066     }
1067 }
1068
1069 void Yaz_Proxy::convert_to_marcxml(Z_NamePlusRecordList *p,
1070                                    const char *backend_charset)
1071 {
1072     int i;
1073     if (!backend_charset)
1074         backend_charset = "MARC-8";
1075     yaz_iconv_t cd = yaz_iconv_open("UTF-8", backend_charset);
1076     yaz_marc_t mt = yaz_marc_create();
1077     yaz_marc_xml(mt, YAZ_MARC_MARCXML);
1078     yaz_marc_iconv(mt, cd);
1079     for (i = 0; i < p->num_records; i++)
1080     {
1081         Z_NamePlusRecord *npr = p->records[i];
1082         if (npr->which == Z_NamePlusRecord_databaseRecord)
1083         {
1084             Z_External *r = npr->u.databaseRecord;
1085             if (r->which == Z_External_OPAC)
1086             {
1087                 WRBUF w = wrbuf_alloc();
1088
1089                 yaz_opac_decode_wrbuf(mt, r->u.opac, w);
1090                 npr->u.databaseRecord = z_ext_record(
1091                     odr_encode(), VAL_TEXT_XML,
1092                     wrbuf_buf(w), wrbuf_len(w)
1093                     );
1094                 wrbuf_free(w, 1);
1095             }
1096             else if (r->which == Z_External_octet)
1097             {
1098                 int rlen;
1099                 char *result;
1100                 if (yaz_marc_decode_buf(mt, (char*) r->u.octet_aligned->buf,
1101                                         r->u.octet_aligned->len,
1102                                         &result, &rlen))
1103                 {
1104                     npr->u.databaseRecord =
1105                         z_ext_record(odr_encode(), VAL_TEXT_XML, result, rlen);
1106                 }
1107             }
1108         }
1109     }
1110     if (cd)
1111         yaz_iconv_close(cd);
1112     yaz_marc_destroy(mt);
1113 }
1114
1115 void Yaz_Proxy::logtime()
1116 {
1117 #if HAVE_GETTIMEOFDAY
1118     struct timeval *tv = (struct timeval*) m_time_tv;
1119     if (tv->tv_sec)
1120     {
1121         struct timeval tv1;
1122         gettimeofday(&tv1, 0);
1123         long diff = (tv1.tv_sec - tv->tv_sec)*1000000 +
1124             (tv1.tv_usec - tv->tv_usec);
1125         if (diff >= 0)
1126             yaz_log(YLOG_LOG, "%sElapsed %ld.%03ld", m_session_str,
1127                     diff/1000000, (diff/1000)%1000);
1128     }
1129     tv->tv_sec = 0;
1130     tv->tv_usec = 0;
1131 #endif
1132 }
1133
1134 int Yaz_Proxy::send_http_response(int code)
1135 {
1136     ODR o = odr_encode();
1137     Z_GDU *gdu = z_get_HTTP_Response(o, code);
1138     Z_HTTP_Response *hres = gdu->u.HTTP_Response;
1139     if (m_http_version)
1140         hres->version = odr_strdup(o, m_http_version);
1141     if (m_http_keepalive)
1142         z_HTTP_header_add(o, &hres->headers, "Connection", "Keep-Alive");
1143     else
1144         timeout(0);
1145
1146     if (m_log_mask & PROXY_LOG_REQ_CLIENT)
1147     {
1148         yaz_log (YLOG_LOG, "%sSending %s to client", m_session_str,
1149                  gdu_name(gdu));
1150     }
1151     int len;
1152     int r = send_GDU(gdu, &len);
1153     m_bytes_sent += len;
1154     m_bw_stat.add_bytes(len);
1155     logtime();
1156
1157     recv_GDU_more(true);
1158
1159     return r;
1160 }
1161
1162 int Yaz_Proxy::send_srw_response(Z_SRW_PDU *srw_pdu, int http_code /* = 200 */)
1163 {
1164     ODR o = odr_encode();
1165     const char *ctype = "text/xml";
1166     Z_GDU *gdu = z_get_HTTP_Response(o, http_code);
1167     Z_HTTP_Response *hres = gdu->u.HTTP_Response;
1168     if (m_http_version)
1169         hres->version = odr_strdup(o, m_http_version);
1170     z_HTTP_header_add(o, &hres->headers, "Content-Type", ctype);
1171     if (m_http_keepalive)
1172         z_HTTP_header_add(o, &hres->headers, "Connection", "Keep-Alive");
1173     else
1174         timeout(0);
1175     if (http_code == 401)
1176         z_HTTP_header_add(o, &hres->headers, "WWW-Authenticate", "Basic realm=\"YAZ Proxy\"");
1177
1178     static Z_SOAP_Handler soap_handlers[2] = {
1179 #if HAVE_XSLT
1180         {"http://www.loc.gov/zing/srw/", 0,
1181          (Z_SOAP_fun) yaz_srw_codec},
1182 #endif
1183         {0, 0, 0}
1184     };
1185
1186     Z_SOAP *soap_package = (Z_SOAP*) odr_malloc(o, sizeof(Z_SOAP));
1187     soap_package->which = Z_SOAP_generic;
1188     soap_package->u.generic =
1189         (Z_SOAP_Generic *) odr_malloc(o,  sizeof(*soap_package->u.generic));
1190     soap_package->u.generic->no = 0;
1191     soap_package->u.generic->ns = soap_handlers[0].ns;
1192     soap_package->u.generic->p = (void *) srw_pdu;
1193     soap_package->ns = m_soap_ns;
1194     z_soap_codec_enc_xsl(o, &soap_package,
1195                          &hres->content_buf, &hres->content_len,
1196                          soap_handlers, 0, m_s2z_stylesheet);
1197     if (m_log_mask & PROXY_LOG_REQ_CLIENT)
1198     {
1199         yaz_log (YLOG_LOG, "%sSending %s to client", m_session_str,
1200                  gdu_name(gdu));
1201     }
1202     int len;
1203     int r = send_GDU(gdu, &len);
1204     m_bytes_sent += len;
1205     m_bw_stat.add_bytes(len);
1206     logtime();
1207
1208     recv_GDU_more(true);
1209
1210     return r;
1211 }
1212
1213 int Yaz_Proxy::send_to_srw_client_error(int srw_error, const char *add)
1214 {
1215     ODR o = odr_encode();
1216     Z_SRW_diagnostic *diagnostic = (Z_SRW_diagnostic *)
1217         odr_malloc(o, sizeof(*diagnostic));
1218     int num_diagnostic = 1;
1219     yaz_mk_std_diagnostic(o, diagnostic, srw_error, add);
1220     return send_srw_search_response(diagnostic, num_diagnostic, srw_error == 3 ? 401 : 200);
1221 }
1222
1223 int Yaz_Proxy::z_to_srw_diag(ODR o, Z_SRW_searchRetrieveResponse *srw_res,
1224                              Z_DefaultDiagFormat *ddf)
1225 {
1226     int bib1_code = *ddf->condition;
1227     if (bib1_code == 109)
1228         return 404;
1229     srw_res->num_diagnostics = 1;
1230     srw_res->diagnostics = (Z_SRW_diagnostic *)
1231         odr_malloc(o, sizeof(*srw_res->diagnostics));
1232     yaz_mk_std_diagnostic(o, srw_res->diagnostics,
1233                           yaz_diag_bib1_to_srw(*ddf->condition),
1234                           ddf->u.v2Addinfo);
1235     return 0;
1236 }
1237
1238 int Yaz_Proxy::send_to_srw_client_ok(int hits, Z_Records *records, int start)
1239 {
1240     ODR o = odr_encode();
1241     Z_SRW_PDU *srw_pdu = yaz_srw_get(o, Z_SRW_searchRetrieve_response);
1242     Z_SRW_searchRetrieveResponse *srw_res = srw_pdu->u.response;
1243
1244     srw_res->numberOfRecords = odr_intdup (o, hits);
1245     if (records && records->which == Z_Records_DBOSD)
1246     {
1247         srw_res->num_records =
1248             records->u.databaseOrSurDiagnostics->num_records;
1249         int i;
1250         srw_res->records = (Z_SRW_record *)
1251             odr_malloc(o, srw_res->num_records * sizeof(Z_SRW_record));
1252         for (i = 0; i < srw_res->num_records; i++)
1253         {
1254             Z_NamePlusRecord *npr = records->u.databaseOrSurDiagnostics->records[i];
1255             if (npr->which != Z_NamePlusRecord_databaseRecord)
1256             {
1257                 srw_res->records[i].recordSchema = "diagnostic";
1258                 srw_res->records[i].recordPacking = m_s2z_packing;
1259                 srw_res->records[i].recordData_buf = "67";
1260                 srw_res->records[i].recordData_len = 2;
1261                 srw_res->records[i].recordPosition = odr_intdup(o, i+start);
1262                 continue;
1263             }
1264             Z_External *r = npr->u.databaseRecord;
1265             oident *ent = oid_getentbyoid(r->direct_reference);
1266             if (r->which == Z_External_octet && ent->value == VAL_TEXT_XML)
1267             {
1268                 srw_res->records[i].recordSchema = m_schema;
1269                 srw_res->records[i].recordPacking = m_s2z_packing;
1270                 srw_res->records[i].recordData_buf = (char*)
1271                     r->u.octet_aligned->buf;
1272                 srw_res->records[i].recordData_len = r->u.octet_aligned->len;
1273                 srw_res->records[i].recordPosition = odr_intdup(o, i+start);
1274             }
1275             else
1276             {
1277                 srw_res->records[i].recordSchema = "diagnostic";
1278                 srw_res->records[i].recordPacking = m_s2z_packing;
1279                 srw_res->records[i].recordData_buf = "67";
1280                 srw_res->records[i].recordData_len = 2;
1281                 srw_res->records[i].recordPosition = odr_intdup(o, i+start);
1282             }
1283         }
1284     }
1285     if (records && records->which == Z_Records_NSD)
1286     {
1287         int http_code;
1288         http_code = z_to_srw_diag(odr_encode(), srw_res,
1289                                    records->u.nonSurrogateDiagnostic);
1290         if (http_code)
1291             return send_http_response(http_code);
1292     }
1293     return send_srw_response(srw_pdu);
1294
1295 }
1296
1297 int Yaz_Proxy::send_srw_search_response(Z_SRW_diagnostic *diagnostics,
1298                                         int num_diagnostics, int http_code /* = 200 */)
1299 {
1300     ODR o = odr_encode();
1301     Z_SRW_PDU *srw_pdu = yaz_srw_get(o, Z_SRW_searchRetrieve_response);
1302     Z_SRW_searchRetrieveResponse *srw_res = srw_pdu->u.response;
1303
1304     srw_res->num_diagnostics = num_diagnostics;
1305     srw_res->diagnostics = diagnostics;
1306     return send_srw_response(srw_pdu, http_code);
1307 }
1308
1309 int Yaz_Proxy::send_srw_explain_response(Z_SRW_diagnostic *diagnostics,
1310                                         int num_diagnostics)
1311 {
1312     Yaz_ProxyConfig *cfg = check_reconfigure();
1313     if (cfg)
1314     {
1315         int len;
1316         char *b = cfg->get_explain_doc(odr_encode(), 0 /* target */,
1317                                        m_s2z_database, &len);
1318         if (b)
1319         {
1320             Z_SRW_PDU *res = yaz_srw_get(odr_encode(), Z_SRW_explain_response);
1321             Z_SRW_explainResponse *er = res->u.explain_response;
1322
1323             er->record.recordData_buf = b;
1324             er->record.recordData_len = len;
1325             er->record.recordPacking = m_s2z_packing;
1326             er->record.recordSchema = "http://explain.z3950.org/dtd/2.0/";
1327
1328             er->diagnostics = diagnostics;
1329             er->num_diagnostics = num_diagnostics;
1330             return send_srw_response(res);
1331         }
1332     }
1333     return send_http_response(404);
1334 }
1335
1336 int Yaz_Proxy::send_PDU_convert(Z_APDU *apdu)
1337 {
1338     if (m_http_version)
1339     {
1340         if (apdu->which == Z_APDU_initResponse)
1341         {
1342             Z_InitResponse *res = apdu->u.initResponse;
1343             if (*res->result == 0)
1344             {
1345                 send_to_srw_client_error(3, 0);
1346             }
1347             else if (!m_s2z_search_apdu)
1348             {
1349                 send_srw_explain_response(0, 0);
1350             }
1351             else
1352             {
1353                 handle_incoming_Z_PDU(m_s2z_search_apdu);
1354             }
1355         }
1356         else if (m_s2z_search_apdu && apdu->which == Z_APDU_searchResponse)
1357         {
1358             m_s2z_search_apdu = 0;
1359             Z_SearchResponse *res = apdu->u.searchResponse;
1360             m_s2z_hit_count = *res->resultCount;
1361             if (res->records && res->records->which == Z_Records_NSD)
1362             {
1363                 send_to_srw_client_ok(0, res->records, 1);
1364             }
1365             else if (m_s2z_present_apdu && m_s2z_hit_count > 0)
1366             {
1367                 // adjust
1368                 Z_PresentRequest *pr = m_s2z_present_apdu->u.presentRequest;
1369
1370                 if (*pr->resultSetStartPoint <= m_s2z_hit_count)
1371                 {
1372                     if (*pr->numberOfRecordsRequested+ *pr->resultSetStartPoint
1373                         > m_s2z_hit_count)
1374                         *pr->numberOfRecordsRequested =
1375                             1 + m_s2z_hit_count - *pr->resultSetStartPoint;
1376                 }
1377                 handle_incoming_Z_PDU(m_s2z_present_apdu);
1378             }
1379             else
1380             {
1381                 m_s2z_present_apdu = 0;
1382                 send_to_srw_client_ok(m_s2z_hit_count, res->records, 1);
1383             }
1384         }
1385         else if (m_s2z_present_apdu && apdu->which == Z_APDU_presentResponse)
1386         {
1387             int start =
1388                 *m_s2z_present_apdu->u.presentRequest->resultSetStartPoint;
1389
1390             m_s2z_present_apdu = 0;
1391             Z_PresentResponse *res = apdu->u.presentResponse;
1392             send_to_srw_client_ok(m_s2z_hit_count, res->records, start);
1393         }
1394     }
1395     else
1396     {
1397         int len = 0;
1398         if (m_log_mask & PROXY_LOG_REQ_CLIENT)
1399             yaz_log (YLOG_LOG, "%sSending %s to client", m_session_str,
1400                      apdu_name(apdu));
1401         int r = send_Z_PDU(apdu, &len);
1402         m_bytes_sent += len;
1403         m_bw_stat.add_bytes(len);
1404         logtime();
1405         return r;
1406     }
1407     return 0;
1408 }
1409
1410 int Yaz_Proxy::send_to_client(Z_APDU *apdu)
1411 {
1412     int kill_session = 0;
1413     Z_ReferenceId **new_id = get_referenceIdP(apdu);
1414
1415     if (new_id)
1416         *new_id = m_referenceId;
1417
1418     if (apdu->which == Z_APDU_searchResponse)
1419     {
1420         Z_SearchResponse *sr = apdu->u.searchResponse;
1421         Z_Records *p = sr->records;
1422         if (p && p->which == Z_Records_NSD)
1423         {
1424             Z_DiagRec dr, *dr_p = &dr;
1425             dr.which = Z_DiagRec_defaultFormat;
1426             dr.u.defaultFormat = p->u.nonSurrogateDiagnostic;
1427
1428             *sr->searchStatus = 0;
1429             display_diagrecs(&dr_p, 1);
1430         }
1431         else
1432         {
1433             if (p && p->which == Z_Records_DBOSD)
1434             {
1435                 if (m_backend_type
1436 #if HAVE_USEMARCON
1437                     || m_usemarcon_ini_stage1 || m_usemarcon_ini_stage2
1438 #endif
1439                     )
1440                     convert_to_frontend_type(p->u.databaseOrSurDiagnostics);
1441                 if (m_marcxml_mode == marcxml)
1442                     convert_to_marcxml(p->u.databaseOrSurDiagnostics,
1443                                        m_backend_charset);
1444                 else
1445                     convert_records_charset(p->u.databaseOrSurDiagnostics,
1446                                             m_backend_charset);
1447                 if (convert_xsl(p->u.databaseOrSurDiagnostics, apdu))
1448                     return 0;
1449
1450             }
1451             if (sr->resultCount)
1452             {
1453                 yaz_log(YLOG_LOG, "%s%d hits", m_session_str,
1454                         *sr->resultCount);
1455                 if (*sr->resultCount < 0)
1456                 {
1457                     m_flag_invalid_session = 1;
1458                     kill_session = 1;
1459
1460                     *sr->searchStatus = 0;
1461                     sr->records =
1462                         create_nonSurrogateDiagnostics(odr_encode(), 2, 0);
1463                     *sr->resultCount = 0;
1464                 }
1465             }
1466         }
1467     }
1468     else if (apdu->which == Z_APDU_presentResponse)
1469     {
1470         Z_PresentResponse *sr = apdu->u.presentResponse;
1471         Z_Records *p = sr->records;
1472         if (p && p->which == Z_Records_NSD)
1473         {
1474             Z_DiagRec dr, *dr_p = &dr;
1475             dr.which = Z_DiagRec_defaultFormat;
1476             dr.u.defaultFormat = p->u.nonSurrogateDiagnostic;
1477             if (*sr->presentStatus == Z_PresentStatus_success)
1478                 *sr->presentStatus = Z_PresentStatus_failure;
1479             display_diagrecs(&dr_p, 1);
1480         }
1481         if (p && p->which == Z_Records_DBOSD)
1482         {
1483             if (m_backend_type
1484 #if HAVE_USEMARCON
1485                 || m_usemarcon_ini_stage1 || m_usemarcon_ini_stage2
1486 #endif
1487                 )
1488                 convert_to_frontend_type(p->u.databaseOrSurDiagnostics);
1489             if (m_marcxml_mode == marcxml)
1490                 convert_to_marcxml(p->u.databaseOrSurDiagnostics,
1491                                    m_backend_charset);
1492             else
1493                 convert_records_charset(p->u.databaseOrSurDiagnostics,
1494                                         m_backend_charset);
1495             if (convert_xsl(p->u.databaseOrSurDiagnostics, apdu))
1496                 return 0;
1497         }
1498     }
1499     else if (apdu->which == Z_APDU_initResponse)
1500     {
1501         //Get and check negotiation record
1502         //from init response.
1503         handle_charset_lang_negotiation(apdu);
1504
1505         if (m_initRequest_options)
1506         {
1507             Z_Options *nopt =
1508                 (Odr_bitmask *)odr_malloc(odr_encode(),
1509                                           sizeof(Odr_bitmask));
1510             ODR_MASK_ZERO(nopt);
1511
1512             int i;
1513             for (i = 0; i<24; i++)
1514                 if (ODR_MASK_GET(m_initRequest_options, i) &&
1515                     ODR_MASK_GET(apdu->u.initResponse->options, i))
1516                     ODR_MASK_SET(nopt, i);
1517             apdu->u.initResponse->options = nopt;
1518         }
1519         if (m_initRequest_version)
1520         {
1521             Z_ProtocolVersion *nopt =
1522                 (Odr_bitmask *)odr_malloc(odr_encode(),
1523                                           sizeof(Odr_bitmask));
1524             ODR_MASK_ZERO(nopt);
1525
1526             int i;
1527             for (i = 0; i<8; i++)
1528                 if (ODR_MASK_GET(m_initRequest_version, i) &&
1529                     ODR_MASK_GET(apdu->u.initResponse->protocolVersion, i))
1530                     ODR_MASK_SET(nopt, i);
1531             apdu->u.initResponse->protocolVersion = nopt;
1532         }
1533         apdu->u.initResponse->preferredMessageSize =
1534             odr_intdup(odr_encode(),
1535                        m_client->m_initResponse_preferredMessageSize >
1536                        m_initRequest_preferredMessageSize ?
1537                        m_initRequest_preferredMessageSize :
1538                        m_client->m_initResponse_preferredMessageSize);
1539         apdu->u.initResponse->maximumRecordSize =
1540             odr_intdup(odr_encode(),
1541                        m_client->m_initResponse_maximumRecordSize >
1542                        m_initRequest_maximumRecordSize ?
1543                        m_initRequest_maximumRecordSize :
1544                        m_client->m_initResponse_maximumRecordSize);
1545     }
1546
1547     int r = send_PDU_convert(apdu);
1548     if (r)
1549         return r;
1550     if (kill_session)
1551     {
1552         delete m_client;
1553         m_client = 0;
1554         m_parent->pre_init();
1555     }
1556     return r;
1557 }
1558
1559 void Yaz_ProxyClient::set_idAuthentication(Z_APDU *apdu)
1560 {
1561     Z_IdAuthentication *t = apdu->u.initRequest->idAuthentication;
1562     
1563     odr_reset(m_idAuthentication_odr);
1564     z_IdAuthentication(m_idAuthentication_odr, &t, 1, 0);
1565     m_idAuthentication_ber_buf =
1566         odr_getbuf(m_idAuthentication_odr, 
1567                    &m_idAuthentication_ber_size, 0);
1568 }
1569
1570 bool Yaz_ProxyClient::compare_charset(Z_APDU *apdu)
1571 {
1572     return true;
1573 }
1574
1575 bool Yaz_ProxyClient::compare_idAuthentication(Z_APDU *apdu)
1576 {
1577     Z_IdAuthentication *t = apdu->u.initRequest->idAuthentication;
1578     ODR odr = odr_createmem(ODR_ENCODE);
1579
1580     z_IdAuthentication(odr, &t, 1, 0);
1581     int sz;
1582     char *buf = odr_getbuf(odr, &sz, 0);
1583     if (buf && m_idAuthentication_ber_buf
1584         && sz == m_idAuthentication_ber_size
1585         && !memcmp(m_idAuthentication_ber_buf, buf, sz))
1586     {
1587         odr_destroy(odr);
1588         return true;
1589     }
1590     odr_destroy(odr);
1591     if (!buf && !m_idAuthentication_ber_buf)
1592         return true;
1593     return false;
1594 }
1595
1596 int Yaz_ProxyClient::send_to_target(Z_APDU *apdu)
1597 {
1598     int len = 0;
1599     const char *apdu_name_tmp = apdu_name(apdu);
1600     int r = send_Z_PDU(apdu, &len);
1601     if (m_root->get_log_mask() & PROXY_LOG_REQ_SERVER)
1602         yaz_log (YLOG_LOG, "%sSending %s to %s %d bytes",
1603                  get_session_str(),
1604                  apdu_name_tmp, get_hostname(), len);
1605     m_bytes_sent += len;
1606     return r;
1607 }
1608
1609 Z_APDU *Yaz_Proxy::result_set_optimize(Z_APDU *apdu)
1610 {
1611     if (apdu->which == Z_APDU_presentRequest)
1612     {
1613         Z_PresentRequest *pr = apdu->u.presentRequest;
1614         int toget = *pr->numberOfRecordsRequested;
1615         int start = *pr->resultSetStartPoint;
1616
1617         yaz_log(YLOG_LOG, "%sPresent %s %d+%d", m_session_str,
1618                 pr->resultSetId, start, toget);
1619
1620         if (*m_parent->m_optimize == '0')
1621             return apdu;
1622
1623         if (!m_client->m_last_resultSetId)
1624         {
1625             Z_APDU *new_apdu = create_Z_PDU(Z_APDU_presentResponse);
1626             new_apdu->u.presentResponse->records =
1627                 create_nonSurrogateDiagnostics(
1628                     odr_encode(), 
1629                     YAZ_BIB1_SPECIFIED_RESULT_SET_DOES_NOT_EXIST,
1630                     pr->resultSetId);
1631             send_to_client(new_apdu);
1632             return 0;
1633         }
1634         if (start < 1 || toget < 0)
1635         {
1636             Z_APDU *new_apdu = create_Z_PDU(Z_APDU_presentResponse);
1637             new_apdu->u.presentResponse->records =
1638                 create_nonSurrogateDiagnostics(
1639                     odr_encode(), 
1640                     YAZ_BIB1_PRESENT_REQUEST_OUT_OF_RANGE, 
1641                     0);
1642             send_to_client(new_apdu);
1643             return 0;
1644         }
1645         if (!strcmp(m_client->m_last_resultSetId, pr->resultSetId))
1646         {
1647             if (start+toget-1 > m_client->m_last_resultCount)
1648             {
1649                 Z_APDU *new_apdu = create_Z_PDU(Z_APDU_presentResponse);
1650                 new_apdu->u.presentResponse->records =
1651                     create_nonSurrogateDiagnostics(
1652                         odr_encode(), 
1653                         YAZ_BIB1_PRESENT_REQUEST_OUT_OF_RANGE,
1654                         0);
1655                 send_to_client(new_apdu);
1656                 return 0;
1657             }
1658             Z_NamePlusRecordList *npr;
1659 #if 0
1660             yaz_log(YLOG_LOG, "%sCache lookup %d+%d syntax=%s",
1661                     m_session_str, start, toget, yaz_z3950oid_to_str(
1662                         pr->preferredRecordSyntax, &oclass));
1663 #endif
1664             if (m_client->m_cache.lookup (odr_encode(), &npr, start, toget,
1665                                           pr->preferredRecordSyntax,
1666                                           pr->recordComposition))
1667             {
1668                 yaz_log (YLOG_LOG, "%sReturned cached records for present request",
1669                          m_session_str);
1670                 Z_APDU *new_apdu = create_Z_PDU(Z_APDU_presentResponse);
1671                 new_apdu->u.presentResponse->referenceId = pr->referenceId;
1672
1673                 new_apdu->u.presentResponse->numberOfRecordsReturned
1674                     = odr_intdup(odr_encode(), toget);
1675
1676                 new_apdu->u.presentResponse->records = (Z_Records*)
1677                     odr_malloc(odr_encode(), sizeof(Z_Records));
1678                 new_apdu->u.presentResponse->records->which = Z_Records_DBOSD;
1679                 new_apdu->u.presentResponse->records->u.databaseOrSurDiagnostics = npr;
1680                 new_apdu->u.presentResponse->nextResultSetPosition =
1681                     odr_intdup(odr_encode(), start+toget);
1682
1683                 send_to_client(new_apdu);
1684                 return 0;
1685             }
1686         }
1687     }
1688
1689     if (apdu->which != Z_APDU_searchRequest)
1690         return apdu;
1691     Z_SearchRequest *sr = apdu->u.searchRequest;
1692     Yaz_Z_Query *this_query = new Yaz_Z_Query;
1693     Yaz_Z_Databases this_databases;
1694
1695     this_databases.set(sr->num_databaseNames, (const char **)
1696                        sr->databaseNames);
1697
1698     this_query->set_Z_Query(sr->query);
1699
1700     // Check for non-negative piggyback params.
1701     if (*sr->smallSetUpperBound < 0
1702         || *sr->largeSetLowerBound < 0
1703         || *sr->mediumSetPresentNumber < 0)
1704     {
1705         Z_APDU *new_apdu = create_Z_PDU(Z_APDU_searchResponse);
1706         // Not a present request.. But can't find better diagnostic
1707         new_apdu->u.searchResponse->records =
1708             create_nonSurrogateDiagnostics(
1709                 odr_encode(), 
1710                 YAZ_BIB1_PRESENT_REQUEST_OUT_OF_RANGE, 0);
1711         send_to_client(new_apdu);
1712         return 0;
1713     }
1714
1715     char query_str[120];
1716     this_query->print(query_str, sizeof(query_str)-1);
1717     yaz_log(YLOG_LOG, "%sSearch %s", m_session_str, query_str);
1718
1719     if (*m_parent->m_optimize != '0' &&
1720         m_client->m_last_ok && m_client->m_last_query &&
1721         m_client->m_last_query->match(this_query) &&
1722         !strcmp(m_client->m_last_resultSetId, sr->resultSetName) &&
1723         m_client->m_last_databases.match(this_databases))
1724     {
1725         delete this_query;
1726         if (m_client->m_last_resultCount > *sr->smallSetUpperBound &&
1727             m_client->m_last_resultCount < *sr->largeSetLowerBound)
1728         {
1729             Z_NamePlusRecordList *npr;
1730             int toget = *sr->mediumSetPresentNumber;
1731             Z_RecordComposition *comp = 0;
1732
1733             if (toget > m_client->m_last_resultCount)
1734                 toget = m_client->m_last_resultCount;
1735             
1736             if (sr->mediumSetElementSetNames)
1737             {
1738                 comp = (Z_RecordComposition *)
1739                     odr_malloc(odr_encode(), sizeof(Z_RecordComposition));
1740                 comp->which = Z_RecordComp_simple;
1741                 comp->u.simple = sr->mediumSetElementSetNames;
1742             }
1743
1744             if (m_client->m_cache.lookup (odr_encode(), &npr, 1, toget,
1745                                           sr->preferredRecordSyntax, comp))
1746             {
1747                 yaz_log (YLOG_LOG, "%sReturned cached records for medium set",
1748                          m_session_str);
1749                 Z_APDU *new_apdu = create_Z_PDU(Z_APDU_searchResponse);
1750                 new_apdu->u.searchResponse->referenceId = sr->referenceId;
1751                 new_apdu->u.searchResponse->resultCount =
1752                     &m_client->m_last_resultCount;
1753
1754                 new_apdu->u.searchResponse->numberOfRecordsReturned
1755                     = odr_intdup(odr_encode(), toget);
1756
1757                 new_apdu->u.searchResponse->presentStatus =
1758                     odr_intdup(odr_encode(), Z_PresentStatus_success);
1759                 new_apdu->u.searchResponse->records = (Z_Records*)
1760                     odr_malloc(odr_encode(), sizeof(Z_Records));
1761                 new_apdu->u.searchResponse->records->which = Z_Records_DBOSD;
1762                 new_apdu->u.searchResponse->records->u.databaseOrSurDiagnostics = npr;
1763                 new_apdu->u.searchResponse->nextResultSetPosition =
1764                     odr_intdup(odr_encode(), toget+1);
1765                 send_to_client(new_apdu);
1766                 return 0;
1767             }
1768             else
1769             {
1770                 // medium Set
1771                 // send present request (medium size)
1772                 yaz_log (YLOG_LOG, "%sOptimizing search for medium set",
1773                          m_session_str);
1774
1775                 Z_APDU *new_apdu = create_Z_PDU(Z_APDU_presentRequest);
1776                 Z_PresentRequest *pr = new_apdu->u.presentRequest;
1777                 pr->referenceId = sr->referenceId;
1778                 pr->resultSetId = sr->resultSetName;
1779                 pr->preferredRecordSyntax = sr->preferredRecordSyntax;
1780                 *pr->numberOfRecordsRequested = toget;
1781                 pr->recordComposition = comp;
1782                 m_client->m_sr_transform = 1;
1783                 return new_apdu;
1784             }
1785         }
1786         else if (m_client->m_last_resultCount >= *sr->largeSetLowerBound ||
1787             m_client->m_last_resultCount <= 0)
1788         {
1789             // large set. Return pseudo-search response immediately
1790             yaz_log (YLOG_LOG, "%sOptimizing search for large set",
1791                      m_session_str);
1792             Z_APDU *new_apdu = create_Z_PDU(Z_APDU_searchResponse);
1793             new_apdu->u.searchResponse->referenceId = sr->referenceId;
1794             new_apdu->u.searchResponse->resultCount =
1795                 &m_client->m_last_resultCount;
1796             send_to_client(new_apdu);
1797             return 0;
1798         }
1799         else
1800         {
1801             Z_NamePlusRecordList *npr;
1802             int toget = m_client->m_last_resultCount;
1803             Z_RecordComposition *comp = 0;
1804             // small set
1805             // send a present request (small set)
1806
1807             if (sr->smallSetElementSetNames)
1808             {
1809                 comp = (Z_RecordComposition *)
1810                     odr_malloc(odr_encode(), sizeof(Z_RecordComposition));
1811                 comp->which = Z_RecordComp_simple;
1812                 comp->u.simple = sr->smallSetElementSetNames;
1813             }
1814
1815             if (m_client->m_cache.lookup (odr_encode(), &npr, 1, toget,
1816                                           sr->preferredRecordSyntax, comp))
1817             {
1818                 yaz_log (YLOG_LOG, "%sReturned cached records for small set",
1819                          m_session_str);
1820                 Z_APDU *new_apdu = create_Z_PDU(Z_APDU_searchResponse);
1821                 new_apdu->u.searchResponse->referenceId = sr->referenceId;
1822                 new_apdu->u.searchResponse->resultCount =
1823                     &m_client->m_last_resultCount;
1824
1825                 new_apdu->u.searchResponse->numberOfRecordsReturned
1826                     = odr_intdup(odr_encode(), toget);
1827
1828                 new_apdu->u.searchResponse->presentStatus =
1829                     odr_intdup(odr_encode(), Z_PresentStatus_success);
1830                 new_apdu->u.searchResponse->records = (Z_Records*)
1831                     odr_malloc(odr_encode(), sizeof(Z_Records));
1832                 new_apdu->u.searchResponse->records->which = Z_Records_DBOSD;
1833                 new_apdu->u.searchResponse->records->u.databaseOrSurDiagnostics = npr;
1834                 new_apdu->u.searchResponse->nextResultSetPosition =
1835                     odr_intdup(odr_encode(), toget+1);
1836                 send_to_client(new_apdu);
1837                 return 0;
1838             }
1839             else
1840             {
1841                 yaz_log (YLOG_LOG, "%sOptimizing search for small set",
1842                          m_session_str);
1843                 Z_APDU *new_apdu = create_Z_PDU(Z_APDU_presentRequest);
1844                 Z_PresentRequest *pr = new_apdu->u.presentRequest;
1845                 pr->referenceId = sr->referenceId;
1846                 pr->resultSetId = sr->resultSetName;
1847                 pr->preferredRecordSyntax = sr->preferredRecordSyntax;
1848                 *pr->numberOfRecordsRequested = toget;
1849                 pr->recordComposition = comp;
1850                 m_client->m_sr_transform = 1;
1851                 return new_apdu;
1852             }
1853         }
1854     }
1855     else  // query doesn't match
1856     {
1857         delete m_client->m_last_query;
1858         m_client->m_last_query = this_query;
1859         m_client->m_last_ok = 0;
1860         m_client->m_cache.clear();
1861         m_client->m_resultSetStartPoint = 0;
1862
1863         xfree (m_client->m_last_resultSetId);
1864         m_client->m_last_resultSetId = xstrdup (sr->resultSetName);
1865
1866         m_client->m_last_databases.set(sr->num_databaseNames,
1867                                        (const char **) sr->databaseNames);
1868     }
1869     return apdu;
1870 }
1871
1872
1873 void Yaz_Proxy::inc_request_no()
1874 {
1875     m_request_no++;
1876     char *cp = m_session_str + strlen(m_session_str)-1;
1877     if (*cp == ' ')
1878         cp--;
1879     while (*cp && *cp != ' ')
1880         cp--;
1881     if (*cp)
1882         sprintf(cp+1, "%d ", m_request_no);
1883 }
1884
1885 void Yaz_Proxy::recv_GDU(Z_GDU *apdu, int len)
1886 {
1887     inc_request_no();
1888
1889     m_bytes_recv += len;
1890
1891     if (m_log_mask & PROXY_LOG_REQ_CLIENT)
1892         yaz_log (YLOG_LOG, "%sReceiving %s from client %d bytes",
1893                  m_session_str, gdu_name(apdu), len);
1894
1895 #if 0
1896     // try to make a _bad_ attribute set ID .. Don't enable this in prod.
1897     if (apdu->which == Z_GDU_Z3950 
1898         && apdu->u.z3950->which == Z_APDU_searchRequest)
1899     {
1900         Z_SearchRequest *req = apdu->u.z3950->u.searchRequest;
1901         if (req->query && req->query->which == Z_Query_type_1)
1902         {
1903             Z_RPNQuery *rpnquery = req->query->u.type_1;
1904             if (rpnquery->attributeSetId)
1905             {
1906                 rpnquery->attributeSetId[0] = -2;
1907                 rpnquery->attributeSetId[1] = -1;
1908                 yaz_log(YLOG_WARN, "%sBAD FIXUP TEST", m_session_str);
1909             }
1910         }
1911     }
1912 #endif
1913
1914 #if HAVE_GETTIMEOFDAY
1915     gettimeofday((struct timeval *) m_time_tv, 0);
1916 #endif
1917     m_bw_stat.add_bytes(len);
1918     m_pdu_stat.add_bytes(1);
1919
1920     GDU *gdu = new GDU(apdu);
1921
1922     if (gdu->get() == 0)
1923     {
1924         delete gdu;
1925         yaz_log(YLOG_LOG, "%sUnable to encode package", m_session_str);
1926         m_in_queue.clear();
1927         dec_ref();
1928         return;
1929     }
1930     m_in_queue.enqueue(gdu);
1931     recv_GDU_more(false);
1932 }
1933
1934 void Yaz_Proxy::HTTP_Forwarded(Z_GDU *z_gdu)
1935 {
1936     if (z_gdu->which == Z_GDU_HTTP_Request)
1937     {
1938         Z_HTTP_Request *hreq = z_gdu->u.HTTP_Request;
1939         const char *x_forwarded_for =
1940             z_HTTP_header_lookup(hreq->headers, "X-Forwarded-For");
1941         if (x_forwarded_for)
1942         {
1943             xfree(m_peername);
1944             m_peername = (char*) xmalloc(strlen(x_forwarded_for)+5);
1945             sprintf(m_peername, "tcp:%s", x_forwarded_for);
1946             
1947             yaz_log(YLOG_LOG, "%sHTTP Forwarded from %s", m_session_str,
1948                     m_peername);
1949             if (m_log_mask & PROXY_LOG_IP_CLIENT)
1950                 sprintf(m_session_str, "%ld:%d %.80s %d ",
1951                         (long) time(0), m_session_no, m_peername, m_request_no);
1952             else
1953                 sprintf(m_session_str, "%ld:%d %d ",
1954                         (long) time(0), m_session_no, m_request_no);
1955         }
1956     }
1957 }
1958
1959 void Yaz_Proxy::connect_stat(bool &block, int &reduce)
1960 {
1961
1962     m_parent->m_connect.cleanup(false);
1963     m_parent->m_connect.add_connect(m_peername);
1964
1965     int connect_total = m_parent->m_connect.get_total(m_peername);
1966     int max_connect = m_parent->m_max_connect;
1967
1968     if (max_connect && connect_total > max_connect)
1969     {
1970         yaz_log(YLOG_LOG, "%sconnect not accepted total=%d max=%d",
1971                 m_session_str, connect_total, max_connect);
1972         block = true;
1973     }
1974     else 
1975         block = false;
1976     yaz_log(YLOG_LOG, "%sconnect accepted total=%d", m_session_str,
1977             connect_total);
1978     
1979     int limit_connect = m_parent->m_limit_connect;
1980     if (limit_connect)
1981         reduce = connect_total / limit_connect;
1982     else
1983         reduce = 0;
1984 }
1985
1986 void Yaz_Proxy::recv_GDU_reduce(GDU *gdu)
1987 {
1988     HTTP_Forwarded(gdu->get());
1989
1990     int reduce = 0;
1991     
1992     if (m_request_no == 1)
1993     {
1994         bool block = false;
1995         
1996         connect_stat(block, reduce);
1997
1998         if (block)
1999         {
2000             m_timeout_mode = timeout_busy;
2001             timeout(0);
2002             return;
2003         }
2004     }
2005
2006     int bw_total = m_bw_stat.get_total();
2007     int pdu_total = m_pdu_stat.get_total();
2008     int search_total = m_search_stat.get_total();
2009
2010     assert(m_timeout_mode == timeout_busy);
2011     assert(m_timeout_gdu == 0);
2012
2013     if (m_search_max)
2014         reduce += search_total / m_search_max;
2015     if (m_bw_max)
2016         reduce += (bw_total/m_bw_max);
2017     if (m_pdu_max)
2018     {
2019         if (pdu_total > m_pdu_max)
2020         {
2021             int nreduce = (m_pdu_max >= 60) ? 1 : 60/m_pdu_max;
2022             reduce = (reduce > nreduce) ? reduce : nreduce;
2023         }
2024     }
2025     m_http_version = 0;
2026
2027 #if 0
2028     /* uncomment to force a big reduce */
2029     m_timeout_mode = timeout_reduce;
2030     m_timeout_gdu = gdu;
2031     timeout(3);       // call us reduce seconds later
2032     return;
2033 #endif
2034     if (reduce)
2035     {
2036         yaz_log(YLOG_LOG, "%sdelay=%d bw=%d pdu=%d search=%d limit-bw=%d limit-pdu=%d limit-search=%d",
2037                 m_session_str, reduce, bw_total, pdu_total, search_total,
2038                 m_bw_max, m_pdu_max, m_search_max);
2039
2040         m_timeout_mode = timeout_reduce;
2041         m_timeout_gdu = gdu;
2042         timeout(reduce);       // call us reduce seconds later
2043     }
2044     else
2045         recv_GDU_normal(gdu);
2046 }
2047
2048 void Yaz_Proxy::recv_GDU_more(bool normal)
2049 {
2050     GDU *g;
2051     if (normal && m_timeout_mode == timeout_busy)
2052         m_timeout_mode = timeout_normal;
2053     while (m_timeout_mode == timeout_normal && (g = m_in_queue.dequeue()))
2054     {
2055         m_timeout_mode = timeout_busy;
2056         inc_ref();
2057         recv_GDU_reduce(g);
2058         if (dec_ref())
2059             break;
2060     }
2061 }
2062
2063 void Yaz_Proxy::recv_GDU_normal(GDU *gdu)
2064 {
2065     Z_GDU *apdu = 0;
2066     gdu->move_away_gdu(odr_decode(), &apdu);
2067     delete gdu;
2068
2069     if (apdu->which == Z_GDU_Z3950)
2070         handle_incoming_Z_PDU(apdu->u.z3950);
2071     else if (apdu->which == Z_GDU_HTTP_Request)
2072         handle_incoming_HTTP(apdu->u.HTTP_Request);
2073 }
2074
2075 void Yaz_Proxy::handle_max_record_retrieve(Z_APDU *apdu)
2076 {
2077     if (m_max_record_retrieve)
2078     {
2079         if (apdu->which == Z_APDU_presentRequest)
2080         {
2081             Z_PresentRequest *pr = apdu->u.presentRequest;
2082             if (pr->numberOfRecordsRequested &&
2083                 *pr->numberOfRecordsRequested > m_max_record_retrieve)
2084                 *pr->numberOfRecordsRequested = m_max_record_retrieve;
2085         }
2086     }
2087 }
2088
2089 void Yaz_Proxy::handle_charset_lang_negotiation(Z_APDU *apdu)
2090 {
2091     if (apdu->which == Z_APDU_initRequest)
2092     {
2093         if (m_initRequest_options &&
2094             !ODR_MASK_GET(m_initRequest_options, Z_Options_negotiationModel) &&
2095             (m_proxy_negotiation_charset || m_proxy_negotiation_lang))
2096         {
2097             // There is no negotiation proposal from
2098             // client's side. OK. The proxy negotiation
2099             // in use, only.
2100             Z_InitRequest *initRequest = apdu->u.initRequest;
2101             Z_OtherInformation **otherInfo;
2102             Z_OtherInformationUnit *oi;
2103             get_otherInfoAPDU(apdu, &otherInfo);
2104             oi = update_otherInformation(otherInfo, 1, NULL, 0, 0);
2105             if (oi)
2106             {
2107                 ODR_MASK_SET(initRequest->options,
2108                     Z_Options_negotiationModel);
2109                 oi->which = Z_OtherInfo_externallyDefinedInfo;
2110                 oi->information.externallyDefinedInfo =
2111                 yaz_set_proposal_charneg(odr_encode(),
2112                     (const char**)&m_proxy_negotiation_charset,
2113                     m_proxy_negotiation_charset ? 1:0,
2114                     (const char**)&m_proxy_negotiation_lang,
2115                     m_proxy_negotiation_lang ? 1:0,
2116                     1);
2117             }
2118         }
2119         else if (m_initRequest_options &&
2120                  ODR_MASK_GET(m_initRequest_options,
2121                               Z_Options_negotiationModel) &&
2122                  m_charset_converter->get_target_query_charset())
2123         {
2124             yaz_log(YLOG_LOG, "%sManaged charset negotiation: charset=%s",
2125                     m_session_str,
2126                     m_charset_converter->get_target_query_charset());
2127             Z_InitRequest *initRequest = apdu->u.initRequest;
2128             Z_CharSetandLanguageNegotiation *negotiation =
2129                 yaz_get_charneg_record (initRequest->otherInfo);
2130             if (negotiation &&
2131                 negotiation->which == Z_CharSetandLanguageNegotiation_proposal)
2132             {
2133                 NMEM nmem = nmem_create();
2134                 char **charsets = 0;
2135                 int num_charsets = 0;
2136                 char **langs = 0;
2137                 int num_langs = 0;
2138                 int selected = 0;
2139                 yaz_get_proposal_charneg (nmem, negotiation,
2140                                           &charsets, &num_charsets,
2141                                           &langs, &num_langs, &selected);
2142                 int i;
2143                 for (i = 0; i<num_charsets; i++)
2144                     yaz_log(YLOG_LOG, "%scharset %s", m_session_str,
2145                             charsets[i]);
2146                 for (i = 0; i<num_langs; i++)
2147                     yaz_log(YLOG_LOG, "%slang %s", m_session_str,
2148                             langs[i]);
2149
2150                 const char *t_charset =
2151                     m_charset_converter->get_target_query_charset();
2152                 // sweep through charsets and pick the first supported
2153                 // conversion
2154                 for (i = 0; i<num_charsets; i++)
2155                 {
2156                     const char *c_charset = charsets[i];
2157                     if (!odr_set_charset(odr_decode(), t_charset, c_charset))
2158                         break;
2159                 }
2160                 if (i != num_charsets)
2161                 {
2162                     // got one .. set up ODR for reverse direction
2163                     const char *c_charset = charsets[i];
2164                     odr_set_charset(odr_encode(), c_charset, t_charset);
2165                     m_charset_converter->set_client_query_charset(c_charset);
2166                     m_charset_converter->set_client_charset_selected(selected);
2167                 }
2168                 nmem_destroy(nmem);
2169                 ODR_MASK_CLEAR(m_initRequest_options,
2170                                Z_Options_negotiationModel);
2171                 yaz_del_charneg_record(&initRequest->otherInfo);
2172             }
2173             else
2174             {
2175                 yaz_log(YLOG_WARN, "%sUnable to decode charset package",
2176                         m_session_str);
2177             }
2178         }
2179         else if (m_charset_converter->get_target_query_charset() &&
2180             m_proxy_negotiation_default_charset)
2181         {
2182             m_charset_converter->
2183                 set_client_query_charset(m_proxy_negotiation_default_charset);
2184         }
2185     }
2186     else if (apdu->which == Z_APDU_initResponse)
2187     {
2188         Z_InitResponse *initResponse = apdu->u.initResponse;
2189         Z_OtherInformation **otherInfo;
2190         get_otherInfoAPDU(apdu, &otherInfo);
2191         
2192         Z_CharSetandLanguageNegotiation *charneg = 0;
2193
2194         if (otherInfo && *otherInfo && 
2195             ODR_MASK_GET(initResponse->options, Z_Options_negotiationModel)
2196             && (charneg = yaz_get_charneg_record(*otherInfo)))
2197         {
2198             char *charset = 0;
2199             char *lang = 0;
2200             int selected = 0;
2201
2202             yaz_get_response_charneg(m_referenceId_mem, charneg,
2203                 &charset, &lang, &selected);
2204
2205             yaz_log(YLOG_LOG, "%sAccepted charset - '%s' and lang - '%s'",
2206                 m_session_str, (charset)?charset:"none", (lang)?lang:"none");
2207
2208             if (m_initRequest_options &&
2209                 ODR_MASK_GET(m_initRequest_options, Z_Options_negotiationModel))
2210             {
2211                 yaz_log(YLOG_LOG, "%sClient's negotiation record in use",
2212                     m_session_str);
2213             }
2214             else if (m_proxy_negotiation_charset || m_proxy_negotiation_lang)
2215             {
2216                 // negotiation-charset, negotiation-lang
2217                 // elements of config file in use.
2218
2219                 yaz_log(YLOG_LOG, "%sProxy's negotiation record in use",
2220                     m_session_str);
2221
2222                 // clear negotiation option.
2223                 ODR_MASK_CLEAR(initResponse->options, Z_Options_negotiationModel);
2224
2225                 // Delete negotiation (charneg-3) entry.
2226                 yaz_del_charneg_record(otherInfo);
2227             }
2228         }
2229         else
2230         {
2231             if (m_proxy_negotiation_charset || m_proxy_negotiation_lang)
2232             {
2233                 yaz_log(YLOG_LOG, "%sTarget did not honor negotiation",
2234                         m_session_str);
2235             }
2236             else if (m_charset_converter->get_client_query_charset())
2237             {
2238                 Z_OtherInformation **otherInfo;
2239                 Z_OtherInformationUnit *oi;
2240                 get_otherInfoAPDU(apdu, &otherInfo);
2241                 oi = update_otherInformation(otherInfo, 1, NULL, 0, 0);
2242                 if (oi)
2243                 {
2244                     ODR_MASK_SET(initResponse->options,
2245                                  Z_Options_negotiationModel);
2246                     if (m_initRequest_options)
2247                         ODR_MASK_SET(m_initRequest_options,
2248                                      Z_Options_negotiationModel);
2249                     
2250                     oi->which = Z_OtherInfo_externallyDefinedInfo;    
2251                     oi->information.externallyDefinedInfo =
2252                         yaz_set_response_charneg(
2253                             odr_encode(),
2254                             m_charset_converter->get_client_query_charset(),
2255                             0 /* no lang */,
2256                             m_charset_converter->get_client_charset_selected());
2257                 }
2258             }
2259         }
2260     }
2261 }
2262
2263 Z_Records *Yaz_Proxy::create_nonSurrogateDiagnostics(ODR odr,
2264                                                      int error,
2265                                                      const char *addinfo)
2266 {
2267     Z_Records *rec = (Z_Records *)
2268         odr_malloc (odr, sizeof(*rec));
2269     int *err = (int *)
2270         odr_malloc (odr, sizeof(*err));
2271     Z_DiagRec *drec = (Z_DiagRec *)
2272         odr_malloc (odr, sizeof(*drec));
2273     Z_DefaultDiagFormat *dr = (Z_DefaultDiagFormat *)
2274         odr_malloc (odr, sizeof(*dr));
2275     *err = error;
2276     rec->which = Z_Records_NSD;
2277     rec->u.nonSurrogateDiagnostic = dr;
2278     dr->diagnosticSetId =
2279         yaz_oidval_to_z3950oid (odr, CLASS_DIAGSET, VAL_BIB1);
2280     dr->condition = err;
2281     dr->which = Z_DefaultDiagFormat_v2Addinfo;
2282     dr->u.v2Addinfo = odr_strdup (odr, addinfo ? addinfo : "");
2283     return rec;
2284 }
2285
2286 Z_APDU *Yaz_Proxy::handle_query_transformation(Z_APDU *apdu)
2287 {
2288     if (apdu->which == Z_APDU_searchRequest &&
2289         apdu->u.searchRequest->query &&
2290         apdu->u.searchRequest->query->which == Z_Query_type_104 &&
2291         apdu->u.searchRequest->query->u.type_104->which == Z_External_CQL)
2292     {
2293         Z_RPNQuery *rpnquery = 0;
2294         Z_SearchRequest *sr = apdu->u.searchRequest;
2295         char *addinfo = 0;
2296
2297         yaz_log(YLOG_LOG, "%sCQL: %s", m_session_str,
2298                 sr->query->u.type_104->u.cql);
2299
2300         int r = m_cql2rpn.query_transform(sr->query->u.type_104->u.cql,
2301                                           &rpnquery, odr_encode(),
2302                                           &addinfo);
2303         if (r == -3)
2304             yaz_log(YLOG_LOG, "%sNo CQL to RPN table", m_session_str);
2305         else if (r)
2306         {
2307             yaz_log(YLOG_LOG, "%sCQL Conversion error %d", m_session_str, r);
2308             Z_APDU *new_apdu = create_Z_PDU(Z_APDU_searchResponse);
2309
2310             new_apdu->u.searchResponse->referenceId = sr->referenceId;
2311             new_apdu->u.searchResponse->records =
2312                 create_nonSurrogateDiagnostics(odr_encode(),
2313                                                yaz_diag_srw_to_bib1(r),
2314                                                addinfo);
2315             *new_apdu->u.searchResponse->searchStatus = 0;
2316
2317             send_to_client(new_apdu);
2318
2319             return 0;
2320         }
2321         else
2322         {
2323             sr->query->which = Z_Query_type_1;
2324             sr->query->u.type_1 = rpnquery;
2325         }
2326         return apdu;
2327     }
2328     return apdu;
2329 }
2330
2331 Z_APDU *Yaz_Proxy::handle_target_charset_conversion(Z_APDU *apdu)
2332 {
2333     if (apdu->which == Z_APDU_searchRequest &&
2334         apdu->u.searchRequest->query)
2335     {
2336         if (apdu->u.searchRequest->query->which == Z_Query_type_1
2337             || apdu->u.searchRequest->query->which == Z_Query_type_101)
2338         {
2339             if (m_http_version)
2340                 m_charset_converter->set_client_query_charset("UTF-8");
2341             Z_RPNQuery *rpnquery = apdu->u.searchRequest->query->u.type_1;
2342             m_charset_converter->convert_type_1(rpnquery, odr_encode());
2343         }
2344     }
2345     return apdu;
2346 }
2347
2348
2349 Z_APDU *Yaz_Proxy::handle_query_validation(Z_APDU *apdu)
2350 {
2351     if (apdu->which == Z_APDU_searchRequest)
2352     {
2353         Z_SearchRequest *sr = apdu->u.searchRequest;
2354         int err = 0;
2355         char *addinfo = 0;
2356
2357         Yaz_ProxyConfig *cfg = check_reconfigure();
2358         if (cfg)
2359             err = cfg->check_query(odr_encode(), m_default_target,
2360                                    sr->query, &addinfo);
2361         if (err)
2362         {
2363             Z_APDU *new_apdu = create_Z_PDU(Z_APDU_searchResponse);
2364
2365             new_apdu->u.searchResponse->referenceId = sr->referenceId;
2366             new_apdu->u.searchResponse->records =
2367                 create_nonSurrogateDiagnostics(odr_encode(), err, addinfo);
2368             *new_apdu->u.searchResponse->searchStatus = 0;
2369
2370             send_to_client(new_apdu);
2371
2372             return 0;
2373         }
2374     }
2375     return apdu;
2376 }
2377
2378 int Yaz_Proxy::handle_authentication(Z_APDU *apdu)
2379 {
2380     if (apdu->which != Z_APDU_initRequest)
2381         return 1;  // pass if no init request
2382     Z_InitRequest *req = apdu->u.initRequest;
2383
2384     Yaz_ProxyConfig *cfg = check_reconfigure();
2385     if (!cfg)
2386         return 1;  // pass if no config
2387
2388     int ret;
2389     if (req->idAuthentication == 0)
2390     {
2391         ret = cfg->client_authentication(m_default_target, 0, 0, 0,
2392                                          m_peername);
2393     }
2394     else if (req->idAuthentication->which == Z_IdAuthentication_idPass)
2395     {
2396         ret = cfg->client_authentication(
2397             m_default_target,
2398             req->idAuthentication->u.idPass->userId,
2399             req->idAuthentication->u.idPass->groupId,
2400             req->idAuthentication->u.idPass->password,
2401             m_peername);
2402     }
2403     else if (req->idAuthentication->which == Z_IdAuthentication_open)
2404     {
2405         char user[64], pass[64];
2406         *user = '\0';
2407         *pass = '\0';
2408         sscanf(req->idAuthentication->u.open, "%63[^/]/%63s", user, pass);
2409         ret = cfg->client_authentication(m_default_target, user, 0, pass,
2410                                          m_peername);
2411     }
2412     else
2413         ret = cfg->client_authentication(m_default_target, 0, 0, 0,
2414                                          m_peername);
2415     return ret;
2416 }
2417
2418 Z_APDU *Yaz_Proxy::handle_syntax_validation(Z_APDU *apdu)
2419 {
2420     m_marcxml_mode = none;
2421     if (apdu->which == Z_APDU_searchRequest)
2422     {
2423         Z_SearchRequest *sr = apdu->u.searchRequest;
2424         int err = 0;
2425         char *addinfo = 0;
2426         Yaz_ProxyConfig *cfg = check_reconfigure();
2427
2428         Z_RecordComposition rc_temp, *rc = 0;
2429         if (sr->smallSetElementSetNames)
2430         {
2431             rc_temp.which = Z_RecordComp_simple;
2432             rc_temp.u.simple = sr->smallSetElementSetNames;
2433             rc = &rc_temp;
2434         }
2435
2436         if (sr->preferredRecordSyntax)
2437         {
2438             struct oident *ent;
2439             ent = oid_getentbyoid(sr->preferredRecordSyntax);
2440             m_frontend_type = ent->value;
2441         }
2442         else
2443             m_frontend_type = VAL_NONE;
2444
2445         char *stylesheet_name = 0;
2446         if (cfg)
2447             err = cfg->check_syntax(odr_encode(),
2448                                     m_default_target,
2449                                     sr->preferredRecordSyntax, rc,
2450                                     &addinfo, &stylesheet_name, &m_schema,
2451                                     &m_backend_type, &m_backend_charset,
2452                                     &m_usemarcon_ini_stage1,
2453                                     &m_usemarcon_ini_stage2);
2454         if (stylesheet_name)
2455         {
2456             m_parent->low_socket_close();
2457
2458 #if HAVE_XSLT
2459             if (m_stylesheet_xsp)
2460                 xsltFreeStylesheet((xsltStylesheetPtr) m_stylesheet_xsp);
2461             m_stylesheet_xsp = xsltParseStylesheetFile((const xmlChar*)
2462                                                        stylesheet_name);
2463 #endif
2464             m_stylesheet_offset = 0;
2465             xfree(stylesheet_name);
2466
2467             m_parent->low_socket_open();
2468         }
2469         if (err == -1)
2470         {
2471             sr->smallSetElementSetNames = 0;
2472             sr->mediumSetElementSetNames = 0;
2473             m_marcxml_mode = marcxml;
2474             if (m_backend_type)
2475             {
2476
2477                 sr->preferredRecordSyntax =
2478                     yaz_str_to_z3950oid(odr_encode(), CLASS_RECSYN,
2479                                         m_backend_type);
2480             }
2481             else
2482                 sr->preferredRecordSyntax =
2483                     yaz_oidval_to_z3950oid(odr_encode(), CLASS_RECSYN,
2484                                            VAL_USMARC);
2485         }
2486         else if (err)
2487         {
2488             Z_APDU *new_apdu = create_Z_PDU(Z_APDU_searchResponse);
2489
2490             new_apdu->u.searchResponse->referenceId = sr->referenceId;
2491             new_apdu->u.searchResponse->records =
2492                 create_nonSurrogateDiagnostics(odr_encode(), err, addinfo);
2493             *new_apdu->u.searchResponse->searchStatus = 0;
2494
2495             send_to_client(new_apdu);
2496
2497             return 0;
2498         }
2499         else if (m_backend_type)
2500         {
2501             sr->preferredRecordSyntax =
2502                 yaz_str_to_z3950oid(odr_encode(), CLASS_RECSYN, m_backend_type);
2503         }
2504     }
2505     else if (apdu->which == Z_APDU_presentRequest)
2506     {
2507         Z_PresentRequest *pr = apdu->u.presentRequest;
2508         int err = 0;
2509         char *addinfo = 0;
2510         Yaz_ProxyConfig *cfg = check_reconfigure();
2511
2512         if (pr->preferredRecordSyntax)
2513         {
2514             struct oident *ent;
2515             ent = oid_getentbyoid(pr->preferredRecordSyntax);
2516             m_frontend_type = ent->value;
2517         }
2518         else
2519             m_frontend_type = VAL_NONE;
2520
2521         char *stylesheet_name = 0;
2522         if (cfg)
2523             err = cfg->check_syntax(odr_encode(), m_default_target,
2524                                     pr->preferredRecordSyntax,
2525                                     pr->recordComposition,
2526                                     &addinfo, &stylesheet_name, &m_schema,
2527                                     &m_backend_type, &m_backend_charset,
2528                                     &m_usemarcon_ini_stage1,
2529                                     &m_usemarcon_ini_stage2
2530                                     );
2531         if (stylesheet_name)
2532         {
2533             m_parent->low_socket_close();
2534
2535 #if HAVE_XSLT
2536             if (m_stylesheet_xsp)
2537                 xsltFreeStylesheet((xsltStylesheetPtr) m_stylesheet_xsp);
2538             m_stylesheet_xsp = xsltParseStylesheetFile((const xmlChar*)
2539                                                        stylesheet_name);
2540 #endif
2541             m_stylesheet_offset = 0;
2542             xfree(stylesheet_name);
2543
2544             m_parent->low_socket_open();
2545         }
2546         if (err == -1)
2547         {
2548             pr->recordComposition = 0;
2549             m_marcxml_mode = marcxml;
2550             if (m_backend_type)
2551             {
2552
2553                 pr->preferredRecordSyntax =
2554                     yaz_str_to_z3950oid(odr_encode(), CLASS_RECSYN,
2555                                         m_backend_type);
2556             }
2557             else
2558                 pr->preferredRecordSyntax =
2559                     yaz_oidval_to_z3950oid(odr_encode(), CLASS_RECSYN,
2560                                            VAL_USMARC);
2561         }
2562         else if (err)
2563         {
2564             Z_APDU *new_apdu = create_Z_PDU(Z_APDU_presentResponse);
2565
2566             new_apdu->u.presentResponse->referenceId = pr->referenceId;
2567             new_apdu->u.presentResponse->records =
2568                 create_nonSurrogateDiagnostics(odr_encode(), err, addinfo);
2569             *new_apdu->u.presentResponse->presentStatus =
2570                 Z_PresentStatus_failure;
2571
2572             send_to_client(new_apdu);
2573
2574             return 0;
2575         }
2576         else if (m_backend_type)
2577         {
2578             pr->preferredRecordSyntax =
2579                 yaz_str_to_z3950oid(odr_encode(), CLASS_RECSYN, m_backend_type);
2580         }
2581     }
2582     return apdu;
2583 }
2584
2585 Z_ElementSetNames *Yaz_Proxy::mk_esn_from_schema(ODR o, const char *schema)
2586 {
2587     if (!schema)
2588         return 0;
2589     Z_ElementSetNames *esn = (Z_ElementSetNames *)
2590         odr_malloc(o, sizeof(Z_ElementSetNames));
2591     esn->which = Z_ElementSetNames_generic;
2592     esn->u.generic = odr_strdup(o, schema);
2593     return esn;
2594 }
2595
2596 void Yaz_Proxy::srw_get_client(const char *db, const char **backend_db)
2597 {
2598     const char *t = 0;
2599     Yaz_ProxyConfig *cfg = check_reconfigure();
2600     if (cfg)
2601         t = cfg->get_explain_name(db, backend_db);
2602
2603     if (m_client && m_default_target && t && strcmp(m_default_target, t))
2604     {
2605         releaseClient();
2606     }
2607
2608     if (t)
2609     {
2610         xfree(m_default_target);
2611         m_default_target = xstrdup(t);
2612     }
2613 }
2614
2615 int Yaz_Proxy::file_access(Z_HTTP_Request *hreq)
2616 {
2617     struct stat sbuf;
2618     if (strcmp(hreq->method, "GET"))
2619         return 0;
2620     if (hreq->path[0] != '/')
2621         return 0;
2622     const char *cp = hreq->path;
2623     while (*cp)
2624     {
2625         if (*cp == '/' && strchr("/.", cp[1]))
2626             return 0;
2627         cp++;
2628     }
2629
2630     Yaz_ProxyConfig *cfg = check_reconfigure();
2631
2632     if (!cfg->get_file_access_info(hreq->path+1))
2633         return 0;
2634
2635     const char *fname = hreq->path+1;
2636     if (stat(fname, &sbuf))
2637     {
2638         yaz_log(YLOG_LOG|YLOG_ERRNO, "%sstat failed for %s", m_session_str,
2639                 fname);
2640         return 0;
2641     }
2642     if ((sbuf.st_mode & S_IFMT) != S_IFREG)
2643     {
2644         yaz_log(YLOG_LOG, "%sNot a regular file %s", m_session_str, fname);
2645         return 0;
2646     }
2647     if (sbuf.st_size > (off_t) 1000000)
2648     {
2649         yaz_log(YLOG_WARN, "%sFile %s too large for transfer", m_session_str,
2650                 fname);
2651         return 0;
2652     }
2653
2654     ODR o = odr_encode();
2655
2656     const char *ctype = cfg->check_mime_type(fname);
2657     Z_GDU *gdu = z_get_HTTP_Response(o, 200);
2658     Z_HTTP_Response *hres = gdu->u.HTTP_Response;
2659     if (m_http_version)
2660         hres->version = odr_strdup(o, m_http_version);
2661     z_HTTP_header_add(o, &hres->headers, "Content-Type", ctype);
2662     if (m_http_keepalive)
2663         z_HTTP_header_add(o, &hres->headers, "Connection", "Keep-Alive");
2664     else
2665         timeout(0);
2666
2667     hres->content_len = sbuf.st_size;
2668     hres->content_buf = (char*) odr_malloc(o, hres->content_len);
2669     FILE *f = fopen(fname, "rb");
2670     if (f)
2671     {
2672         fread(hres->content_buf, 1, hres->content_len, f);
2673         fclose(f);
2674     }
2675     else
2676     {
2677         return 0;
2678     }
2679     if (m_log_mask & PROXY_LOG_REQ_CLIENT)
2680     {
2681         yaz_log (YLOG_LOG, "%sSending file %s to client", m_session_str,
2682                  fname);
2683     }
2684     int len;
2685     send_GDU(gdu, &len);
2686     recv_GDU_more(true);
2687     return 1;
2688 }
2689
2690 void Yaz_Proxy::handle_incoming_HTTP(Z_HTTP_Request *hreq)
2691 {
2692     if (m_s2z_odr_init)
2693     {
2694         odr_destroy(m_s2z_odr_init);
2695         m_s2z_odr_init = 0;
2696     }
2697     if (m_s2z_odr_search)
2698     {
2699         odr_destroy(m_s2z_odr_search);
2700         m_s2z_odr_search = 0;
2701     }
2702
2703     m_http_keepalive = 0;
2704     m_http_version = 0;
2705     if (!strcmp(hreq->version, "1.0"))
2706     {
2707         const char *v = z_HTTP_header_lookup(hreq->headers, "Connection");
2708         if (v && !strcmp(v, "Keep-Alive"))
2709             m_http_keepalive = 1;
2710         else
2711             m_http_keepalive = 0;
2712         m_http_version = "1.0";
2713     }
2714     else
2715     {
2716         const char *v = z_HTTP_header_lookup(hreq->headers, "Connection");
2717         if (v && !strcmp(v, "close"))
2718             m_http_keepalive = 0;
2719         else
2720             m_http_keepalive = 1;
2721         m_http_version = "1.1";
2722     }
2723
2724     const char *a = z_HTTP_header_lookup(hreq->headers, "Authorization");
2725     char authorization_str[255];
2726     *authorization_str = '\0';
2727     if (a && strncasecmp(a, "Basic ", 6) == 0)
2728         base64_decode(a + 6, authorization_str, 254);
2729
2730     Z_SRW_PDU *srw_pdu = 0;
2731     Z_SOAP *soap_package = 0;
2732     char *charset = 0;
2733     Z_SRW_diagnostic *diagnostic = 0;
2734     int num_diagnostic = 0;
2735
2736     yaz_log(YLOG_LOG, "%s%s %s", m_session_str, hreq->method, hreq->path);
2737
2738     if (file_access(hreq))
2739     {
2740         return;
2741     }
2742     else if (yaz_srw_decode(hreq, &srw_pdu, &soap_package, odr_decode(),
2743                             &charset) == 0
2744              || yaz_sru_decode(hreq, &srw_pdu, &soap_package, odr_decode(),
2745                                &charset, &diagnostic, &num_diagnostic) == 0)
2746     {
2747         m_s2z_odr_init = odr_createmem(ODR_ENCODE);
2748         m_s2z_odr_search = odr_createmem(ODR_ENCODE);
2749         m_soap_ns = odr_strdup(m_s2z_odr_search, soap_package->ns);
2750         m_s2z_init_apdu = 0;
2751         m_s2z_search_apdu = 0;
2752         m_s2z_present_apdu = 0;
2753
2754         m_s2z_stylesheet = 0;
2755
2756         Z_IdAuthentication *auth = NULL;
2757         if (*authorization_str)
2758         {
2759             auth = (Z_IdAuthentication *) odr_malloc(m_s2z_odr_init, sizeof(Z_IdAuthentication));
2760             auth->which = Z_IdAuthentication_idPass;
2761             auth->u.idPass = (Z_IdPass *) odr_malloc(m_s2z_odr_init, sizeof(Z_IdPass));
2762             auth->u.idPass->groupId = NULL;
2763             char *p = strchr(authorization_str, ':');
2764             if (p)
2765             {
2766                 *p = '\0';
2767                 p++;
2768                 auth->u.idPass->password = odr_strdup(m_s2z_odr_init, p);
2769             }
2770             auth->u.idPass->userId = odr_strdup(m_s2z_odr_init, authorization_str);
2771         }
2772
2773         if (srw_pdu->which == Z_SRW_searchRetrieve_request)
2774         {
2775
2776             Z_SRW_searchRetrieveRequest *srw_req = srw_pdu->u.request;
2777
2778             const char *backend_db = srw_req->database;
2779             srw_get_client(srw_req->database, &backend_db);
2780
2781             m_s2z_database = odr_strdup(m_s2z_odr_init, srw_req->database);
2782             // recordXPath unsupported.
2783             if (srw_req->recordXPath)
2784             {
2785                 yaz_add_srw_diagnostic(odr_decode(),
2786                                        &diagnostic, &num_diagnostic,
2787                                        72, 0);
2788             }
2789             // sort unsupported
2790             if (srw_req->sort_type != Z_SRW_sort_type_none)
2791             {
2792                 yaz_add_srw_diagnostic(odr_decode(),
2793                                        &diagnostic, &num_diagnostic,
2794                                        80, 0);
2795             }
2796             // save stylesheet
2797             if (srw_req->stylesheet)
2798                 m_s2z_stylesheet =
2799                     odr_strdup(m_s2z_odr_init, srw_req->stylesheet);
2800
2801             // set packing for response records ..
2802             if (srw_req->recordPacking &&
2803                 !strcmp(srw_req->recordPacking, "xml"))
2804                 m_s2z_packing = Z_SRW_recordPacking_XML;
2805             else
2806                 m_s2z_packing = Z_SRW_recordPacking_string;
2807
2808             if (num_diagnostic)
2809             {
2810                 Z_SRW_PDU *srw_pdu =
2811                     yaz_srw_get(odr_encode(),
2812                                 Z_SRW_searchRetrieve_response);
2813                 Z_SRW_searchRetrieveResponse *srw_res = srw_pdu->u.response;
2814
2815                 srw_res->diagnostics = diagnostic;
2816                 srw_res->num_diagnostics = num_diagnostic;
2817                 send_srw_response(srw_pdu);
2818                 return;
2819             }
2820
2821             // prepare search PDU
2822             m_s2z_search_apdu = zget_APDU(m_s2z_odr_search,
2823                                           Z_APDU_searchRequest);
2824             Z_SearchRequest *z_searchRequest =
2825                 m_s2z_search_apdu->u.searchRequest;
2826
2827             z_searchRequest->num_databaseNames = 1;
2828             z_searchRequest->databaseNames = (char**)
2829                 odr_malloc(m_s2z_odr_search, sizeof(char *));
2830             z_searchRequest->databaseNames[0] = odr_strdup(m_s2z_odr_search,
2831                                                            backend_db);
2832
2833             // query transformation
2834             Z_Query *query = (Z_Query *)
2835                 odr_malloc(m_s2z_odr_search, sizeof(Z_Query));
2836             z_searchRequest->query = query;
2837
2838             if (srw_req->query_type == Z_SRW_query_type_cql)
2839             {
2840                 Z_External *ext = (Z_External *)
2841                     odr_malloc(m_s2z_odr_search, sizeof(*ext));
2842                 ext->direct_reference =
2843                     odr_getoidbystr(m_s2z_odr_search, "1.2.840.10003.16.2");
2844                 ext->indirect_reference = 0;
2845                 ext->descriptor = 0;
2846                 ext->which = Z_External_CQL;
2847                 ext->u.cql = srw_req->query.cql;
2848
2849                 query->which = Z_Query_type_104;
2850                 query->u.type_104 =  ext;
2851             }
2852             else if (srw_req->query_type == Z_SRW_query_type_pqf)
2853             {
2854                 Z_RPNQuery *RPNquery;
2855                 YAZ_PQF_Parser pqf_parser;
2856
2857                 pqf_parser = yaz_pqf_create ();
2858
2859                 RPNquery = yaz_pqf_parse (pqf_parser, m_s2z_odr_search,
2860                                           srw_req->query.pqf);
2861                 if (!RPNquery)
2862                 {
2863                     const char *pqf_msg;
2864                     size_t off;
2865                     int code = yaz_pqf_error (pqf_parser, &pqf_msg, &off);
2866                     int ioff = off;
2867                     yaz_log(YLOG_LOG, "%*s^\n", ioff+4, "");
2868                     yaz_log(YLOG_LOG, "Bad PQF: %s (code %d)\n", pqf_msg, code);
2869
2870                     send_to_srw_client_error(10, 0);
2871                     return;
2872                 }
2873                 query->which = Z_Query_type_1;
2874                 query->u.type_1 =  RPNquery;
2875
2876                 yaz_pqf_destroy (pqf_parser);
2877             }
2878             else
2879             {
2880                 send_to_srw_client_error(7, "query");
2881                 return;
2882             }
2883
2884             // present
2885             m_s2z_present_apdu = 0;
2886             int max = 0;
2887             if (srw_req->maximumRecords)
2888                 max = *srw_req->maximumRecords;
2889             int start = 1;
2890             if (srw_req->startRecord)
2891                 start = *srw_req->startRecord;
2892             if (max > 0)
2893             {
2894                 // Some backend, such as Voyager doesn't honor piggyback
2895                 // So we use present always (0 &&).
2896                 if (0 && start <= 1)  // Z39.50 piggyback
2897                 {
2898                     *z_searchRequest->smallSetUpperBound = max;
2899                     *z_searchRequest->mediumSetPresentNumber = max;
2900                     *z_searchRequest->largeSetLowerBound = 2000000000; // 2e9
2901
2902                     z_searchRequest->preferredRecordSyntax =
2903                         yaz_oidval_to_z3950oid(m_s2z_odr_search, CLASS_RECSYN,
2904                                                VAL_TEXT_XML);
2905                     if (srw_req->recordSchema)
2906                     {
2907                         z_searchRequest->smallSetElementSetNames =
2908                             z_searchRequest->mediumSetElementSetNames =
2909                             mk_esn_from_schema(m_s2z_odr_search,
2910                                                srw_req->recordSchema);
2911                     }
2912                 }
2913                 else   // Z39.50 present
2914                 {
2915                     m_s2z_present_apdu = zget_APDU(m_s2z_odr_search,
2916                                                    Z_APDU_presentRequest);
2917                     Z_PresentRequest *z_presentRequest =
2918                         m_s2z_present_apdu->u.presentRequest;
2919                     *z_presentRequest->resultSetStartPoint = start;
2920                     *z_presentRequest->numberOfRecordsRequested = max;
2921                     z_presentRequest->preferredRecordSyntax =
2922                         yaz_oidval_to_z3950oid(m_s2z_odr_search, CLASS_RECSYN,
2923                                                VAL_TEXT_XML);
2924                     if (srw_req->recordSchema)
2925                     {
2926                         z_presentRequest->recordComposition =
2927                             (Z_RecordComposition *)
2928                             odr_malloc(m_s2z_odr_search,
2929                                        sizeof(Z_RecordComposition));
2930                         z_presentRequest->recordComposition->which =
2931                             Z_RecordComp_simple;
2932                         z_presentRequest->recordComposition->u.simple =
2933                             mk_esn_from_schema(m_s2z_odr_search,
2934                                                srw_req->recordSchema);
2935                     }
2936                 }
2937             }
2938             if (!m_client)
2939             {
2940                 m_s2z_init_apdu = zget_APDU(m_s2z_odr_init,
2941                                             Z_APDU_initRequest);
2942
2943                 m_s2z_init_apdu->u.initRequest->idAuthentication = auth;
2944
2945                 // prevent m_initRequest_apdu memory from being grabbed
2946                 // in Yaz_Proxy::handle_incoming_Z_PDU
2947                 m_initRequest_apdu = m_s2z_init_apdu;
2948                 handle_incoming_Z_PDU(m_s2z_init_apdu);
2949                 return;
2950             }
2951             else
2952             {
2953                 handle_incoming_Z_PDU(m_s2z_search_apdu);
2954                 return;
2955             }
2956         }
2957         else if (srw_pdu->which == Z_SRW_explain_request)
2958         {
2959             Z_SRW_explainRequest *srw_req = srw_pdu->u.explain_request;
2960
2961             const char *backend_db = srw_req->database;
2962             srw_get_client(srw_req->database, &backend_db);
2963
2964             m_s2z_database = odr_strdup(m_s2z_odr_init, srw_req->database);
2965
2966             // save stylesheet
2967             if (srw_req->stylesheet)
2968                 m_s2z_stylesheet =
2969                     odr_strdup(m_s2z_odr_init, srw_req->stylesheet);
2970
2971             if (srw_req->recordPacking &&
2972                 !strcmp(srw_req->recordPacking, "xml"))
2973                 m_s2z_packing = Z_SRW_recordPacking_XML;
2974             else
2975                 m_s2z_packing = Z_SRW_recordPacking_string;
2976
2977             if (num_diagnostic)
2978             {
2979                 send_srw_explain_response(diagnostic, num_diagnostic);
2980                 return;
2981             }
2982
2983             if (!m_client)
2984             {
2985                 m_s2z_init_apdu = zget_APDU(m_s2z_odr_init,
2986                                             Z_APDU_initRequest);
2987
2988                 m_s2z_init_apdu->u.initRequest->idAuthentication = auth;
2989                 
2990                 // prevent m_initRequest_apdu memory from being grabbed
2991                 // in Yaz_Proxy::handle_incoming_Z_PDU
2992                 m_initRequest_apdu = m_s2z_init_apdu;
2993                 handle_incoming_Z_PDU(m_s2z_init_apdu);
2994             }
2995             else
2996                 send_srw_explain_response(0, 0);
2997             return;
2998         }
2999         else if (srw_pdu->which == Z_SRW_scan_request)
3000         {
3001             m_s2z_database = odr_strdup(m_s2z_odr_init,
3002                                         srw_pdu->u.scan_request->database);
3003
3004             yaz_add_srw_diagnostic(odr_decode(),
3005                                    &diagnostic, &num_diagnostic,
3006                                    4, "scan");
3007             Z_SRW_PDU *srw_pdu =
3008                 yaz_srw_get(odr_encode(),
3009                             Z_SRW_scan_response);
3010             Z_SRW_scanResponse *srw_res = srw_pdu->u.scan_response;
3011
3012             srw_res->diagnostics = diagnostic;
3013             srw_res->num_diagnostics = num_diagnostic;
3014             send_srw_response(srw_pdu);
3015             return;
3016         }
3017         else
3018         {
3019             m_s2z_database = 0;
3020
3021             send_to_srw_client_error(4, 0);
3022         }
3023     }
3024     send_http_response(400);
3025 }
3026
3027 void Yaz_Proxy::handle_init(Z_APDU *apdu)
3028 {
3029
3030     Z_OtherInformation **oi;
3031     get_otherInfoAPDU(apdu, &oi);
3032
3033     if (apdu->u.initRequest->implementationId)
3034         yaz_log(YLOG_LOG, "%simplementationId: %s",
3035                 m_session_str, apdu->u.initRequest->implementationId);
3036     if (apdu->u.initRequest->implementationName)
3037         yaz_log(YLOG_LOG, "%simplementationName: %s",
3038                 m_session_str, apdu->u.initRequest->implementationName);
3039     if (apdu->u.initRequest->implementationVersion)
3040         yaz_log(YLOG_LOG, "%simplementationVersion: %s",
3041                 m_session_str, apdu->u.initRequest->implementationVersion);
3042     if (m_initRequest_apdu == 0)
3043     {
3044         if (m_initRequest_mem)
3045             nmem_destroy(m_initRequest_mem);
3046
3047         m_initRequest_apdu = apdu;
3048         m_initRequest_mem = odr_extract_mem(odr_decode());
3049
3050         m_initRequest_preferredMessageSize = *apdu->u.initRequest->
3051             preferredMessageSize;
3052         *apdu->u.initRequest->preferredMessageSize = 1024*1024;
3053         m_initRequest_maximumRecordSize = *apdu->u.initRequest->
3054             maximumRecordSize;
3055         *apdu->u.initRequest->maximumRecordSize = 1024*1024;
3056
3057         Z_CharSetandLanguageNegotiation *charSetandLangRecord =
3058             yaz_get_charneg_record(*oi);
3059
3060         // Save proposal charsets and langs.
3061         if (ODR_MASK_GET(apdu->u.initRequest->options,
3062                          Z_Options_negotiationModel)
3063             && charSetandLangRecord)
3064         {
3065
3066             yaz_get_proposal_charneg(m_referenceId_mem,
3067                                      charSetandLangRecord,
3068                                      &m_initRequest_oi_negotiation_charsets,
3069                                      &m_initRequest_oi_negotiation_num_charsets,
3070                                      &m_initRequest_oi_negotiation_langs,
3071                                      &m_initRequest_oi_negotiation_num_langs,
3072                                      &m_initRequest_oi_negotiation_selected);
3073
3074             for (int i = 0; i<m_initRequest_oi_negotiation_num_charsets; i++)
3075             {
3076                 yaz_log(YLOG_LOG, "%scharacters set proposal: %s",
3077                         m_session_str,(m_initRequest_oi_negotiation_charsets[i])?
3078                         m_initRequest_oi_negotiation_charsets[i]:"none");
3079             }
3080             for (int i=0; i<m_initRequest_oi_negotiation_num_langs; i++)
3081             {
3082                 yaz_log(YLOG_LOG, "%slanguages proposal: %s",
3083                         m_session_str, (m_initRequest_oi_negotiation_langs[i])?
3084                         m_initRequest_oi_negotiation_langs[i]:"none");
3085             }
3086             yaz_log(YLOG_LOG, "%sselected proposal: %d (boolean)",
3087                     m_session_str, m_initRequest_oi_negotiation_selected);
3088         }
3089         // save init options for the response..
3090         m_initRequest_options = apdu->u.initRequest->options;
3091
3092         apdu->u.initRequest->options =
3093             (Odr_bitmask *)nmem_malloc(m_initRequest_mem,
3094                                        sizeof(Odr_bitmask));
3095         ODR_MASK_ZERO(apdu->u.initRequest->options);
3096         int i;
3097         for (i = 0; i<= 24; i++)
3098             ODR_MASK_SET(apdu->u.initRequest->options, i);
3099         // check negotiation option
3100         if (!ODR_MASK_GET(m_initRequest_options,
3101                           Z_Options_negotiationModel))
3102         {
3103             ODR_MASK_CLEAR(apdu->u.initRequest->options,
3104                            Z_Options_negotiationModel);
3105         }
3106         ODR_MASK_CLEAR(apdu->u.initRequest->options,
3107                        Z_Options_concurrentOperations);
3108         // make new version
3109         m_initRequest_version = apdu->u.initRequest->protocolVersion;
3110         apdu->u.initRequest->protocolVersion =
3111             (Odr_bitmask *)nmem_malloc(m_initRequest_mem,
3112                                        sizeof(Odr_bitmask));
3113         ODR_MASK_ZERO(apdu->u.initRequest->protocolVersion);
3114
3115         for (i = 0; i<= 8; i++)
3116             ODR_MASK_SET(apdu->u.initRequest->protocolVersion, i);
3117     }
3118     handle_charset_lang_negotiation(apdu);
3119     if (m_client->m_init_flag)
3120     {
3121         if (handle_init_response_for_invalid_session(apdu))
3122             return;
3123         if (m_client->m_initResponse)
3124         {
3125             Z_APDU *apdu2 = m_client->m_initResponse;
3126             apdu2->u.initResponse->otherInfo = 0;
3127             if (m_client->m_cookie && *m_client->m_cookie)
3128                 set_otherInformationString(apdu2, VAL_COOKIE, 1,
3129                                            m_client->m_cookie);
3130             apdu2->u.initResponse->referenceId =
3131                 apdu->u.initRequest->referenceId;
3132             apdu2->u.initResponse->options = m_client->m_initResponse_options;
3133             apdu2->u.initResponse->protocolVersion =
3134                 m_client->m_initResponse_version;
3135
3136             handle_charset_lang_negotiation(apdu2);
3137
3138             if (m_timeout_mode == timeout_busy)
3139                 m_timeout_mode = timeout_normal;
3140             send_to_client(apdu2);
3141             return;
3142         }
3143     }
3144     m_client->m_init_flag = 1;
3145
3146 #if USE_AUTH_MSG
3147     Auth_Msg *m = new Auth_Msg;
3148     m->m_proxy = this;
3149     z_APDU(odr_encode(), &apdu, 0, "encode");
3150     char *apdu_buf = odr_getbuf(odr_encode(), &m->m_apdu_len, 0);
3151     m->m_apdu_buf = (char*) nmem_malloc(m->m_nmem, m->m_apdu_len);
3152     memcpy(m->m_apdu_buf, apdu_buf, m->m_apdu_len);
3153     odr_reset(odr_encode());
3154
3155     inc_ref();
3156     m_my_thread->put(m);
3157 #else
3158     int ret = handle_authentication(apdu);
3159     result_authentication(apdu, ret);
3160 #endif
3161 }
3162
3163 void Yaz_Proxy::handle_incoming_Z_PDU(Z_APDU *apdu)
3164 {
3165     Z_ReferenceId **refid = get_referenceIdP(apdu);
3166     nmem_reset(m_referenceId_mem);
3167     if (refid && *refid)
3168     {
3169         m_referenceId = (Z_ReferenceId *)
3170             nmem_malloc(m_referenceId_mem, sizeof(*m_referenceId));
3171         m_referenceId->len = m_referenceId->size = (*refid)->len;
3172         m_referenceId->buf = (unsigned char *)
3173             nmem_malloc(m_referenceId_mem, (*refid)->len);
3174         memcpy(m_referenceId->buf, (*refid)->buf, (*refid)->len);
3175     }
3176     else
3177         m_referenceId = 0;
3178
3179     if (!m_client && m_flag_invalid_session)
3180     {
3181         // Got request for a session that is invalid..
3182         m_apdu_invalid_session = apdu; // save package
3183         m_mem_invalid_session = odr_extract_mem(odr_decode());
3184         apdu = m_initRequest_apdu;     // but throw an init to the target
3185     }
3186
3187     if (apdu->which == Z_APDU_searchRequest)
3188         m_search_stat.add_bytes(1);
3189
3190     // Determine our client.
3191     Z_OtherInformation **oi;
3192     get_otherInfoAPDU(apdu, &oi);
3193     m_client = get_client(apdu, get_cookie(oi), get_proxy(oi));
3194     if (!m_client)
3195     {
3196         if (m_http_version)
3197         {   // HTTP. Send not found
3198             send_http_response(404);
3199             return;
3200         }
3201         else
3202         {
3203             // Z39.50 just shutdown
3204             timeout(0);
3205             return;
3206         }
3207     }
3208
3209     m_client->m_server = this;
3210
3211     if (apdu->which == Z_APDU_initRequest)
3212         handle_init(apdu);
3213     else
3214         handle_incoming_Z_PDU_2(apdu);
3215 }
3216
3217 void Yaz_Proxy::handle_incoming_Z_PDU_2(Z_APDU *apdu)
3218 {
3219     handle_max_record_retrieve(apdu);
3220
3221     if (apdu)
3222         apdu = handle_syntax_validation(apdu);
3223
3224     if (apdu)
3225         apdu = handle_query_transformation(apdu);
3226
3227     if (apdu)
3228         apdu = handle_target_charset_conversion(apdu);
3229
3230     if (apdu)
3231         apdu = handle_query_validation(apdu);
3232
3233     if (apdu)
3234         apdu = result_set_optimize(apdu);
3235
3236     if (!apdu)
3237     {
3238         m_client->timeout(m_target_idletime);  // mark it active even
3239         recv_GDU_more(true);
3240         // though we didn't use it
3241         return;
3242     }
3243
3244     // delete other info construct completely if 0 elements
3245     Z_OtherInformation **oi;
3246     get_otherInfoAPDU(apdu, &oi);
3247     if (oi && *oi && (*oi)->num_elements == 0)
3248         *oi = 0;
3249
3250     if (apdu->which == Z_APDU_presentRequest &&
3251         m_client->m_resultSetStartPoint == 0)
3252     {
3253         Z_PresentRequest *pr = apdu->u.presentRequest;
3254         m_client->m_resultSetStartPoint = *pr->resultSetStartPoint;
3255         m_client->m_cache.copy_presentRequest(apdu->u.presentRequest);
3256     } else {
3257         m_client->m_resultSetStartPoint = 0;
3258     }
3259     if (m_client->send_to_target(apdu) < 0)
3260     {
3261         m_client->shutdown();
3262     }
3263     else
3264         m_client->m_waiting = 1;
3265 }
3266
3267 void Yaz_Proxy::connectNotify()
3268 {
3269 }
3270
3271 void Yaz_Proxy::releaseClient()
3272 {
3273     xfree(m_proxyTarget);
3274     m_proxyTarget = 0;
3275     m_flag_invalid_session = 0;
3276     // only keep if keep_alive flag is set...
3277     if (m_client &&
3278         m_client->m_pdu_recv < m_keepalive_limit_pdu &&
3279         m_client->m_bytes_recv+m_client->m_bytes_sent < m_keepalive_limit_bw &&
3280         m_client->m_waiting == 0)
3281     {
3282         yaz_log(YLOG_LOG, "%sShutdown (client to proxy) keepalive %s",
3283                  m_session_str,
3284                  m_client->get_hostname());
3285         yaz_log(YLOG_LOG, "%sbw=%d pdu=%d limit-bw=%d limit-pdu=%d",
3286                 m_session_str, m_client->m_pdu_recv,
3287                 m_client->m_bytes_sent + m_client->m_bytes_recv,
3288                 m_keepalive_limit_bw, m_keepalive_limit_pdu);
3289         assert (m_client->m_waiting != 2);
3290         // Tell client (if any) that no server connection is there..
3291         m_client->m_server = 0;
3292         m_client = 0;
3293     }
3294     else if (m_client)
3295     {
3296         yaz_log (YLOG_LOG, "%sShutdown (client to proxy) close %s",
3297                  m_session_str,
3298                  m_client->get_hostname());
3299         assert (m_client->m_waiting != 2);
3300         delete m_client;
3301         m_client = 0;
3302     }
3303     else if (!m_parent)
3304     {
3305         yaz_log (YLOG_LOG, "%sshutdown (client to proxy) bad state",
3306                  m_session_str);
3307         assert (m_parent);
3308     }
3309     else
3310     {
3311         yaz_log (YLOG_LOG, "%sShutdown (client to proxy)",
3312                  m_session_str);
3313     }
3314     if (m_parent)
3315         m_parent->pre_init();
3316 }
3317
3318 bool Yaz_Proxy::dec_ref()
3319 {
3320     m_http_keepalive = 0;
3321
3322     --m_ref_count;
3323     if (m_ref_count > 0)
3324         return false;
3325
3326     releaseClient();
3327
3328     delete this;
3329     return true;
3330 }
3331
3332 const char *Yaz_ProxyClient::get_session_str()
3333 {
3334     if (!m_server)
3335         return "0 ";
3336     return m_server->get_session_str();
3337 }
3338
3339 void Yaz_ProxyClient::shutdown()
3340 {
3341     yaz_log (YLOG_LOG, "%sShutdown (proxy to target) %s", get_session_str(),
3342              get_hostname());
3343
3344     if (m_server)
3345     {
3346         m_waiting = 1;   // ensure it's released from Yaz_Proxy::releaseClient
3347         m_server->dec_ref();
3348     }
3349     else
3350         delete this;
3351 }
3352
3353 void Yaz_Proxy::failNotify()
3354 {
3355     inc_request_no();
3356     yaz_log (YLOG_LOG, "%sConnection closed by client", get_session_str());
3357     dec_ref();
3358 }
3359
3360 void Yaz_Proxy::send_response_fail_client(const char *addr)
3361 {
3362     if (m_http_version)
3363     {
3364         Z_SRW_diagnostic *diagnostic = 0;
3365         int num_diagnostic = 0;
3366         
3367         yaz_add_srw_diagnostic(odr_encode(),
3368                                &diagnostic, &num_diagnostic,
3369                                YAZ_SRW_SYSTEM_TEMPORARILY_UNAVAILABLE, addr);
3370         if (m_s2z_search_apdu)
3371             send_srw_search_response(diagnostic, num_diagnostic);
3372         else
3373             send_srw_explain_response(diagnostic, num_diagnostic);
3374     }            
3375 }
3376 void Yaz_ProxyClient::failNotify()
3377 {
3378     if (m_server)
3379         m_server->inc_request_no();
3380     yaz_log (YLOG_LOG, "%sConnection closed by target %s",
3381              get_session_str(), get_hostname());
3382
3383     if (m_server)
3384         m_server->send_response_fail_client(get_hostname());
3385     shutdown();
3386 }
3387
3388 void Yaz_ProxyClient::connectNotify()
3389 {
3390     const char *s = get_session_str();
3391     const char *h = get_hostname();
3392     yaz_log (YLOG_LOG, "%sConnection accepted by %s timeout=%d", s, h,
3393              m_target_idletime);
3394     timeout(m_target_idletime);
3395     if (!m_server)
3396         pre_init_client();
3397 }
3398
3399 IPDU_Observer *Yaz_ProxyClient::sessionNotify(IPDU_Observable
3400                                               *the_PDU_Observable, int fd)
3401 {
3402     return new Yaz_ProxyClient(the_PDU_Observable, 0);
3403 }
3404
3405 Yaz_ProxyClient::~Yaz_ProxyClient()
3406 {
3407     if (m_prev)
3408         *m_prev = m_next;
3409     if (m_next)
3410         m_next->m_prev = m_prev;
3411     m_waiting = 2;     // for debugging purposes only.
3412     odr_destroy(m_init_odr);
3413     odr_destroy(m_idAuthentication_odr);
3414     delete m_last_query;
3415     xfree (m_last_resultSetId);
3416     xfree (m_cookie);
3417 }
3418
3419 void Yaz_ProxyClient::pre_init_client()
3420 {
3421     Z_APDU *apdu = create_Z_PDU(Z_APDU_initRequest);
3422     Z_InitRequest *req = apdu->u.initRequest;
3423
3424     int i;
3425     for (i = 0; i<= 24; i++)
3426         ODR_MASK_SET(req->options, i);
3427     ODR_MASK_CLEAR(apdu->u.initRequest->options,
3428                    Z_Options_negotiationModel);
3429     ODR_MASK_CLEAR(apdu->u.initRequest->options,
3430                    Z_Options_concurrentOperations);
3431     for (i = 0; i<= 10; i++)
3432         ODR_MASK_SET(req->protocolVersion, i);
3433
3434     if (send_to_target(apdu) < 0)
3435     {
3436         delete this;
3437     }
3438     else
3439     {
3440         m_waiting = 1;
3441         m_init_flag = 1;
3442     }
3443 }
3444
3445 void Yaz_Proxy::pre_init()
3446 {
3447     int i;
3448     const char *name = 0;
3449     const char *zurl_in_use[MAX_ZURL_PLEX];
3450     int limit_bw, limit_pdu, limit_req, limit_search;
3451     int target_idletime, client_idletime;
3452     int max_clients;
3453     int keepalive_limit_bw, keepalive_limit_pdu;
3454     int pre_init;
3455     const char *cql2rpn = 0;
3456     const char *authentication = 0;
3457     const char *negotiation_charset = 0;
3458     const char *negotiation_lang = 0;
3459
3460     Yaz_ProxyConfig *cfg = check_reconfigure();
3461
3462     zurl_in_use[0] = 0;
3463
3464     if (m_log_mask & PROXY_LOG_APDU_CLIENT)
3465         set_APDU_yazlog(1);
3466     else
3467         set_APDU_yazlog(0);
3468
3469     for (i = 0; cfg && cfg->get_target_no(i, &name, zurl_in_use,
3470                                           &limit_bw, &limit_pdu, &limit_req,
3471                                           &limit_search,
3472                                           &target_idletime, &client_idletime,
3473                                           &max_clients,
3474                                           &keepalive_limit_bw,
3475                                           &keepalive_limit_pdu,
3476                                           &pre_init,
3477                                           &cql2rpn,
3478                                           &authentication,
3479                                           &negotiation_charset,
3480                                           &negotiation_lang,
3481                                           0,
3482                                           0) ; i++)
3483     {
3484         if (pre_init)
3485         {
3486             int j;
3487             for (j = 0; zurl_in_use[j]; j++)
3488             {
3489                 Yaz_ProxyClient *c;
3490                 int spare = 0;
3491                 int spare_waiting = 0;
3492                 int in_use = 0;
3493                 int other = 0;
3494                 for (c = m_clientPool; c; c = c->m_next)
3495                 {
3496                     if (!strcmp(zurl_in_use[j], c->get_hostname()))
3497                     {
3498                         if (c->m_cookie == 0)
3499                         {
3500                             if (c->m_server == 0)
3501                                 if (c->m_waiting)
3502                                     spare_waiting++;
3503                                 else
3504                                     spare++;
3505                             else
3506                                 in_use++;
3507                         }
3508                         else
3509                             other++;
3510                     }
3511                 }
3512                 yaz_log(YLOG_LOG, "%spre-init %s %s use=%d other=%d spare=%d "
3513                         "sparew=%d preinit=%d",m_session_str,
3514                         name, zurl_in_use[j], in_use, other,
3515                         spare, spare_waiting, pre_init);
3516                 if (spare + spare_waiting < pre_init)
3517                 {
3518                     c = new Yaz_ProxyClient(m_PDU_Observable->clone(), this);
3519                     c->m_next = m_clientPool;
3520                     if (c->m_next)
3521                         c->m_next->m_prev = &c->m_next;
3522                     m_clientPool = c;
3523                     c->m_prev = &m_clientPool;
3524
3525                     if (m_log_mask & PROXY_LOG_APDU_SERVER)
3526                         c->set_APDU_yazlog(1);
3527                     else
3528                         c->set_APDU_yazlog(0);
3529
3530                     if (c->client(zurl_in_use[j]))
3531                     {
3532                         timeout(60);
3533                         delete c;
3534                         return;
3535                     }
3536                     c->timeout(30);
3537                     c->m_waiting = 1;
3538                     c->m_target_idletime = target_idletime;
3539                     c->m_seqno = m_seqno++;
3540                 }
3541             }
3542         }
3543     }
3544 }
3545
3546 void Yaz_Proxy::timeoutNotify()
3547 {
3548     if (m_parent)
3549     {
3550         GDU *gdu;
3551         switch(m_timeout_mode)
3552         {
3553         case timeout_normal:
3554         case timeout_busy:
3555             inc_request_no();
3556             m_in_queue.clear();
3557             yaz_log (YLOG_LOG, "%sTimeout (client to proxy)", m_session_str);
3558             dec_ref();
3559             break;
3560         case timeout_reduce:
3561             timeout(m_client_idletime);
3562             m_timeout_mode = timeout_busy;
3563             gdu = m_timeout_gdu;
3564             m_timeout_gdu = 0;
3565             recv_GDU_normal(gdu);
3566             break;
3567         case timeout_xsl:
3568             assert(m_stylesheet_nprl);
3569             convert_xsl_delay();
3570             recv_GDU_more(true);
3571         }
3572     }
3573     else
3574     {
3575         timeout(600);
3576         pre_init();
3577     }
3578 }
3579
3580 void Yaz_Proxy::markInvalid()
3581 {
3582     m_client = 0;
3583     m_flag_invalid_session = 1;
3584 }
3585
3586 void Yaz_ProxyClient::timeoutNotify()
3587 {
3588     if (m_server)
3589         m_server->inc_request_no();
3590
3591     yaz_log (YLOG_LOG, "%sTimeout (proxy to target) %s", get_session_str(),
3592              get_hostname());
3593
3594     if (m_server)
3595         m_server->send_response_fail_client(get_hostname());
3596
3597     Yaz_Proxy *proxy_root = m_root;
3598
3599     shutdown();
3600
3601     proxy_root->pre_init();
3602 }
3603
3604 Yaz_ProxyClient::Yaz_ProxyClient(IPDU_Observable *the_PDU_Observable,
3605                                  Yaz_Proxy *parent) :
3606     Z_Assoc (the_PDU_Observable)
3607 {
3608     m_cookie = 0;
3609     m_next = 0;
3610     m_prev = 0;
3611     m_init_flag = 0;
3612     m_last_query = 0;
3613     m_last_resultSetId = 0;
3614     m_last_resultCount = 0;
3615     m_last_ok = 0;
3616     m_sr_transform = 0;
3617     m_waiting = 0;
3618     m_init_odr = odr_createmem (ODR_DECODE);
3619     m_initResponse = 0;
3620     m_initResponse_options = 0;
3621     m_initResponse_version = 0;
3622     m_initResponse_preferredMessageSize = 0;
3623     m_initResponse_maximumRecordSize = 0;
3624     m_resultSetStartPoint = 0;
3625     m_bytes_sent = m_bytes_recv = 0;
3626     m_pdu_recv = 0;
3627     m_server = 0;
3628     m_seqno = 0;
3629     m_target_idletime = 600;
3630     m_root = parent;
3631     m_idAuthentication_odr = odr_createmem(ODR_ENCODE);
3632     m_idAuthentication_ber_buf = 0;
3633     m_idAuthentication_ber_size = 0;
3634 }
3635
3636 const char *Yaz_Proxy::option(const char *name, const char *value)
3637 {
3638     if (!strcmp (name, "optimize")) {
3639         if (value) {
3640             xfree (m_optimize);
3641             m_optimize = xstrdup (value);
3642         }
3643         return m_optimize;
3644     }
3645     return 0;
3646 }
3647
3648 void Yaz_ProxyClient::recv_HTTP_response(Z_HTTP_Response *apdu, int len)
3649 {
3650
3651 }
3652
3653 void Yaz_ProxyClient::recv_GDU(Z_GDU *apdu, int len)
3654 {
3655     if (apdu->which == Z_GDU_Z3950)
3656         recv_Z_PDU(apdu->u.z3950, len);
3657     else if (apdu->which == Z_GDU_HTTP_Response)
3658         recv_HTTP_response(apdu->u.HTTP_Response, len);
3659     else
3660         shutdown();
3661 }
3662
3663 int Yaz_Proxy::handle_init_response_for_invalid_session(Z_APDU *apdu)
3664 {
3665     if (!m_flag_invalid_session)
3666         return 0;
3667     m_flag_invalid_session = 0;
3668     handle_incoming_Z_PDU(m_apdu_invalid_session);
3669     assert (m_mem_invalid_session);
3670     nmem_destroy(m_mem_invalid_session);
3671     m_mem_invalid_session = 0;
3672     return 1;
3673 }
3674
3675 void Yaz_ProxyClient::recv_Z_PDU(Z_APDU *apdu, int len)
3676 {
3677     m_bytes_recv += len;
3678
3679     m_pdu_recv++;
3680     m_waiting = 0;
3681     if (m_root->get_log_mask() & PROXY_LOG_REQ_SERVER)
3682         yaz_log (YLOG_LOG, "%sReceiving %s from %s %d bytes", get_session_str(),
3683                  apdu_name(apdu), get_hostname(), len);
3684     if (apdu->which == Z_APDU_initResponse)
3685     {
3686         if (!m_server)  // if this is a pre init session , check for more
3687             m_root->pre_init();
3688         NMEM nmem = odr_extract_mem (odr_decode());
3689         odr_reset (m_init_odr);
3690         nmem_transfer (m_init_odr->mem, nmem);
3691         m_initResponse = apdu;
3692         m_initResponse_options = apdu->u.initResponse->options;
3693         m_initResponse_version = apdu->u.initResponse->protocolVersion;
3694         m_initResponse_preferredMessageSize =
3695             *apdu->u.initResponse->preferredMessageSize;
3696         m_initResponse_maximumRecordSize =
3697             *apdu->u.initResponse->maximumRecordSize;
3698
3699         Z_InitResponse *ir = apdu->u.initResponse;
3700        
3701         // apply YAZ Proxy version
3702         char *imv0 = ir->implementationVersion;
3703         char *imv1 = (char*)
3704             odr_malloc(m_init_odr, 20 + (imv0 ? strlen(imv0) : 0));
3705         *imv1 = '\0';
3706         if (imv0)
3707             strcat(imv1, imv0);
3708 #ifdef VERSION
3709         strcat(imv1, "/" VERSION);
3710 #endif
3711         ir->implementationVersion = imv1;
3712         
3713         // apply YAZ Proxy implementation name
3714         char *im0 = ir->implementationName;
3715         char *im1 = (char*)
3716             odr_malloc(m_init_odr, 20 + (im0 ? strlen(im0) : 0));
3717         *im1 = '\0';
3718         if (im0)
3719         {
3720             strcat(im1, im0);
3721             strcat(im1, " ");
3722         }
3723         strcat(im1, "(YAZ Proxy)");
3724         ir->implementationName = im1;
3725
3726         nmem_destroy (nmem);
3727
3728         if (m_server && m_server->handle_init_response_for_invalid_session(apdu))
3729             return;
3730     }
3731     if (apdu->which == Z_APDU_searchResponse)
3732     {
3733         Z_SearchResponse *sr = apdu->u.searchResponse;
3734         m_last_resultCount = *sr->resultCount;
3735         int status = *sr->searchStatus;
3736         if (status && (!sr->records || sr->records->which == Z_Records_DBOSD))
3737         {
3738             m_last_ok = 1;
3739
3740             if (sr->records && sr->records->which == Z_Records_DBOSD)
3741             {
3742                 m_cache.add(odr_decode(),
3743                             sr->records->u.databaseOrSurDiagnostics, 1,
3744                             *sr->resultCount);
3745             }
3746         }
3747     }
3748     if (apdu->which == Z_APDU_presentResponse)
3749     {
3750         Z_PresentResponse *pr = apdu->u.presentResponse;
3751         if (m_sr_transform)
3752         {
3753             m_sr_transform = 0;
3754             Z_APDU *new_apdu = create_Z_PDU(Z_APDU_searchResponse);
3755             Z_SearchResponse *sr = new_apdu->u.searchResponse;
3756             sr->referenceId = pr->referenceId;
3757             *sr->resultCount = m_last_resultCount;
3758             sr->records = pr->records;
3759             sr->nextResultSetPosition = pr->nextResultSetPosition;
3760             sr->numberOfRecordsReturned = pr->numberOfRecordsReturned;
3761             apdu = new_apdu;
3762         }
3763         if (pr->records &&
3764             pr->records->which == Z_Records_DBOSD && m_resultSetStartPoint)
3765         {
3766             m_cache.add(odr_decode(),
3767                         pr->records->u.databaseOrSurDiagnostics,
3768                         m_resultSetStartPoint, -1);
3769             m_resultSetStartPoint = 0;
3770         }
3771     }
3772     if (m_cookie)
3773         set_otherInformationString (apdu, VAL_COOKIE, 1, m_cookie);
3774
3775     Yaz_Proxy *server = m_server; // save it. send_to_client may destroy us
3776
3777     if (server)
3778         server->send_to_client(apdu);
3779     if (apdu->which == Z_APDU_close)
3780         shutdown();
3781     else if (server)
3782         server->recv_GDU_more(true);
3783 }
3784
3785 void Yaz_Proxy::low_socket_close()
3786 {
3787 #if WIN32
3788 #else
3789     int i;
3790     for (i = 0; i<NO_SPARE_SOLARIS_FD; i++)
3791         if  (m_lo_fd[i] >= 0)
3792             ::close(m_lo_fd[i]);
3793 #endif
3794 }
3795
3796 void Yaz_Proxy::low_socket_open()
3797 {
3798 #if WIN32
3799 #else
3800     int i;
3801     for (i = 0; i<NO_SPARE_SOLARIS_FD; i++)
3802         m_lo_fd[i] = open("/dev/null", O_RDONLY);
3803 #endif
3804 }
3805
3806 int Yaz_Proxy::server(const char *addr)
3807 {
3808     int r = Z_Assoc::server(addr);
3809     if (!r)
3810     {
3811         yaz_log(YLOG_LOG, "%sStarted proxy "
3812 #ifdef VERSION
3813             VERSION
3814 #endif
3815             " on %s", m_session_str, addr);
3816         timeout(1);
3817     }
3818     return r;
3819 }
3820
3821 void Yaz_Proxy::base64_decode(const char *base64, char *buf, int buf_len)
3822 {
3823     const char *base64_chars =
3824         "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
3825     int len = strlen(base64);
3826     int buf_pos = 0;
3827     int index = 1;
3828
3829     for (int pos = 0; pos <= len; pos++)
3830     {
3831         if (base64[pos] == '=' || buf_pos + 1 >= buf_len)
3832             break;
3833
3834         const char *ch_ptr = strchr(base64_chars, base64[pos]);
3835         if (!ch_ptr)
3836             break;
3837         char ch = (char) (ch_ptr - base64_chars);
3838         switch (index)
3839         {
3840             case 1:
3841                 buf[buf_pos] = ch << 2;
3842                 break;
3843             case 2:
3844                 buf[buf_pos++] += (ch & 0x30) >> 4;
3845                 buf[buf_pos] = (ch & 0x0f) << 4;
3846                 break;
3847             case 3:
3848                 buf[buf_pos++] += (ch & 0x3c) >> 2;
3849                 buf[buf_pos] = (ch & 0x03) << 6;
3850                 break;
3851             case 4:
3852                 buf[buf_pos++] += ch;
3853         }
3854         if (index < 4)
3855             index++;
3856         else
3857             index = 1;
3858     }
3859     buf[buf_pos] = '\0';
3860 }
3861
3862 /*
3863  * Local variables:
3864  * c-basic-offset: 4
3865  * indent-tabs-mode: nil
3866  * End:
3867  * vim: shiftwidth=4 tabstop=8 expandtab
3868  */
3869