Implement 3PTY bridge of two 'join's.
[lcr.git] / apppbx.cpp
index c80c20a..0369bb9 100644 (file)
@@ -609,12 +609,14 @@ void EndpointAppPBX::out_setup(int cfnr)
 //     char                    allowed_ports[256];
 //     char                    exten[256];
        char                    ifname[sizeof(e_ext.interfaces)],
-                               number[256];
+                               *ifname_p,
+                               number[256],
+                               *number_p;
        struct port_settings    port_settings;
 #ifdef WITH_MISDN
        int                     channel = 0;
-       struct admin_list       *admin;
 #endif
+       struct admin_list       *admin;
        int                     earlyb;
        int                     mode = B_MODE_TRANSPARENT;
 
@@ -739,6 +741,23 @@ void EndpointAppPBX::out_setup(int cfnr)
                        }
                        /* found interface */
                        PDEBUG(DEBUG_EPOINT, "EPOINT(%d) calling to interface %s\n", ea_endpoint->ep_serial, ifname);
+                       if (interface->remote) {
+                               admin = admin_first;
+                               while(admin) {
+                                       if (admin->remote_name[0] && !strcmp(admin->remote_name, interface->remote_app))
+                                               break;
+                                       admin = admin->next;
+                               }
+                               if (!admin) {
+                                       trace_header("INTERFACE (remote not connected)", DIRECTION_NONE);
+                                       add_trace("application", NULL, "%s", interface->remote_app);
+                                       end_trace();
+                                       continue;
+                               }
+                               SPRINT(portname, "%s-%d-out", interface->name, 0);
+                               port = new Premote(PORT_TYPE_REMOTE_OUT, portname, &port_settings, interface, admin->sock);
+                               earlyb = (interface->is_earlyb == IS_YES);
+                       } else
 #ifdef WITH_GSM_BS
                        if (interface->gsm_bs) {
                                SPRINT(portname, "%s-%d-out", interface->name, 0);
@@ -777,22 +796,7 @@ void EndpointAppPBX::out_setup(int cfnr)
                                        port = ss5_hunt_line(mISDNport);
                                else
 #endif
-                               if (mISDNport->ifport->remote) {
-                                       admin = admin_first;
-                                       while(admin) {
-                                               if (admin->remote_name[0] && !strcmp(admin->remote_name, mISDNport->ifport->remote_app))
-                                                       break;
-                                               admin = admin->next;
-                                       }
-                                       if (!admin) {
-                                               trace_header("INTERFACE (remote not connected)", DIRECTION_NONE);
-                                               add_trace("application", NULL, "%s", mISDNport->ifport->remote_app);
-                                               end_trace();
-                                               continue;
-                                       }
-                                       port = new Premote(PORT_TYPE_REMOTE_OUT, mISDNport, portname, &port_settings, channel, mISDNport->ifport->channel_force, mode, admin->sock);
-                               } else
-                                       port = new Pdss1((mISDNport->ntmode)?PORT_TYPE_DSS1_NT_OUT:PORT_TYPE_DSS1_TE_OUT, mISDNport, portname, &port_settings, channel, mISDNport->ifport->channel_force, mode);
+                               port = new Pdss1((mISDNport->ntmode)?PORT_TYPE_DSS1_NT_OUT:PORT_TYPE_DSS1_TE_OUT, mISDNport, portname, &port_settings, channel, mISDNport->ifport->channel_force, mode);
                                earlyb = mISDNport->earlyb;
 #else
                        trace_header("INTERFACE (has no function)", DIRECTION_NONE);
@@ -986,128 +990,148 @@ void EndpointAppPBX::out_setup(int cfnr)
                PDEBUG(DEBUG_EPOINT, "EPOINT(%d) dialing external: called='%s' keypad='%s'\n", ea_endpoint->ep_serial, e_dialinginfo.id, e_dialinginfo.keypad);
                /* call to extenal interfaces */
                if (e_dialinginfo.keypad[0])
-                       p = e_dialinginfo.keypad;
+                       number_p = e_dialinginfo.keypad;
                else
-                       p = e_dialinginfo.id;
+                       number_p = e_dialinginfo.id;
                do {
-                       earlyb = 0;
                        number[0] = '\0';
-                       while(*p!=',' && *p!='\0')
-                               SCCAT(number, *p++);
-                       if (*p == ',')
-                               p++;
+                       while(*number_p!=',' && *number_p!='\0')
+                               SCCAT(number, *number_p++);
+                       if (*number_p == ',')
+                               number_p++;
                        /* found number */
-                       PDEBUG(DEBUG_EPOINT, "EPOINT(%d) calling to number '%s' interface '%s'\n", ea_endpoint->ep_serial, number, e_dialinginfo.interfaces[0]?e_dialinginfo.interfaces:"any interface");
-                       /* search interface */
-                       interface = hunt_interface(e_dialinginfo.interfaces[0]?e_dialinginfo.interfaces:NULL);
-                       if (!interface) {
-                               trace_header("INTERFACE (not found)", DIRECTION_NONE);
-                               add_trace("interface", NULL, "%s", ifname);
-                               end_trace();
-                               goto check_anycall_extern;
-                       }
-                       /* found interface */
-#ifdef WITH_GSM_BS
-                       if (interface->gsm_bs) {
-                               SPRINT(portname, "%s-%d-out", interface->name, 0);
-                               port = new Pgsm_bs(PORT_TYPE_GSM_BS_OUT, portname, &port_settings, interface);
-                               earlyb = (interface->is_earlyb == IS_YES);
-                       } else
-#endif
-#ifdef WITH_GSM_MS
-                       if (interface->gsm_ms) {
-                               SPRINT(portname, "%s-%d-out", interface->name, 0);
-                               port = new Pgsm_ms(PORT_TYPE_GSM_MS_OUT, portname, &port_settings, interface);
-                               earlyb = (interface->is_earlyb == IS_YES);
-                       } else
-#endif
-#ifdef WITH_SIP
-                       if (interface->sip) {
-                               SPRINT(portname, "%s-%d-out", interface->name, 0);
-                               port = new Psip(PORT_TYPE_SIP_OUT, portname, &port_settings, interface);
-                               earlyb = (interface->is_earlyb == IS_YES);
-                       } else
-#endif
-                       {
-#ifdef WITH_MISDN
-                               /* hunt for mISDNport and create Port */
-                               mISDNport = hunt_port(e_dialinginfo.interfaces[0]?e_dialinginfo.interfaces:NULL, &channel);
-                               if (!mISDNport) {
-                                       trace_header("INTERFACE (too busy)", DIRECTION_NONE);
-                                       add_trace("interface", NULL, "%s", e_dialinginfo.interfaces[0]?e_dialinginfo.interfaces:"any interface");
+
+                       ifname_p = e_dialinginfo.interfaces;
+                       if (*ifname_p == '+')
+                               ifname_p++;
+                       do {
+                               earlyb = 0;
+                               ifname[0] = '\0';
+                               while(*ifname_p!=',' && *ifname_p!='\0')
+                                       SCCAT(ifname, *ifname_p++);
+                               if (*ifname_p == ',')
+                                       ifname_p++;
+                               /* found interface name */
+
+                               /* search interface */
+                               PDEBUG(DEBUG_EPOINT, "EPOINT(%d) calling to number '%s' interface '%s'\n", ea_endpoint->ep_serial, number, ifname[0]?ifname:"any interface");
+                               interface = hunt_interface(ifname[0]?ifname:NULL);
+                               if (!interface) {
+                                       trace_header("INTERFACE (not found)", DIRECTION_NONE);
+                                       add_trace("interface", NULL, "%s", ifname);
                                        end_trace();
-                                       goto check_anycall_extern;
+                                       continue;
                                }
-                               /* creating EXTERNAL port*/
-                               SPRINT(portname, "%s-%d-out", mISDNport->ifport->interface->name, mISDNport->portnum);
-#ifdef WITH_SS5
-                               if (mISDNport->ss5)
-                                       port = ss5_hunt_line(mISDNport);
-                               else
-#endif
-                               if (mISDNport->ifport->remote) {
+                               /* found interface */
+                               if (interface->remote) {
                                        admin = admin_first;
                                        while(admin) {
-                                               if (admin->remote_name[0] && !strcmp(admin->remote_name, mISDNport->ifport->remote_app))
+                                               if (admin->remote_name[0] && !strcmp(admin->remote_name, interface->remote_app))
                                                        break;
                                                admin = admin->next;
                                        }
                                        if (!admin) {
                                                trace_header("INTERFACE (remote not connected)", DIRECTION_NONE);
-                                               add_trace("application", NULL, "%s", mISDNport->ifport->remote_app);
+                                               add_trace("application", NULL, "%s", interface->remote_app);
                                                end_trace();
                                                continue;
                                        }
-                                       port = new Premote(PORT_TYPE_REMOTE_OUT, mISDNport, portname, &port_settings, channel, mISDNport->ifport->channel_force, mode, admin->sock);
+                                       SPRINT(portname, "%s-%d-out", interface->name, 0);
+                                       port = new Premote(PORT_TYPE_REMOTE_OUT, portname, &port_settings, interface, admin->sock);
+                                       earlyb = (interface->is_earlyb == IS_YES);
                                } else
+#ifdef WITH_GSM_BS
+                               if (interface->gsm_bs) {
+                                       SPRINT(portname, "%s-%d-out", interface->name, 0);
+                                       port = new Pgsm_bs(PORT_TYPE_GSM_BS_OUT, portname, &port_settings, interface);
+                                       earlyb = (interface->is_earlyb == IS_YES);
+                               } else
+#endif
+#ifdef WITH_GSM_MS
+                               if (interface->gsm_ms) {
+                                       SPRINT(portname, "%s-%d-out", interface->name, 0);
+                                       port = new Pgsm_ms(PORT_TYPE_GSM_MS_OUT, portname, &port_settings, interface);
+                                       earlyb = (interface->is_earlyb == IS_YES);
+                               } else
+#endif
+#ifdef WITH_SIP
+                               if (interface->sip) {
+                                       SPRINT(portname, "%s-%d-out", interface->name, 0);
+                                       port = new Psip(PORT_TYPE_SIP_OUT, portname, &port_settings, interface);
+                                       earlyb = (interface->is_earlyb == IS_YES);
+                               } else
+#endif
+                               {
+#ifdef WITH_MISDN
+                                       /* hunt for mISDNport and create Port */
+                                       mISDNport = hunt_port(ifname[0]?ifname:NULL, &channel);
+                                       if (!mISDNport) {
+                                               trace_header("INTERFACE (too busy)", DIRECTION_NONE);
+                                               add_trace("interface", NULL, "%s", ifname[0]?ifname:"any interface");
+                                               end_trace();
+                                               continue;
+                                       }
+                                       /* creating EXTERNAL port*/
+                                       SPRINT(portname, "%s-%d-out", mISDNport->ifport->interface->name, mISDNport->portnum);
+
+#ifdef WITH_SS5
+                                       if (mISDNport->ss5)
+                                               port = ss5_hunt_line(mISDNport);
+                                       else
+#endif
                                        port = new Pdss1((mISDNport->ntmode)?PORT_TYPE_DSS1_NT_OUT:PORT_TYPE_DSS1_TE_OUT, mISDNport, portname, &port_settings, channel, mISDNport->ifport->channel_force, mode);
-                               earlyb = mISDNport->earlyb;
+                                       earlyb = mISDNport->earlyb;
 #else
-                               trace_header("INTERFACE (has no function)", DIRECTION_NONE);
-                               add_trace("interface", NULL, "%s", ifname);
-                               end_trace();
-                               continue;
+                                       trace_header("INTERFACE (has no function)", DIRECTION_NONE);
+                                       add_trace("interface", NULL, "%s", ifname);
+                                       end_trace();
+                                       continue;
 #endif
-                       }
-                       if (!port)
-                               FATAL("No memory for Port instance\n");
-                       PDEBUG(DEBUG_EPOINT, "EPOINT(%d) created port %s\n", ea_endpoint->ep_serial, port->p_name);
-                       memset(&dialinginfo, 0, sizeof(dialinginfo));
-                       if (e_dialinginfo.keypad[0])
-                               SCPY(dialinginfo.keypad, number);
-                       else
-                               SCPY(dialinginfo.id, number);
-                       dialinginfo.itype = INFO_ITYPE_ISDN;
-                       dialinginfo.ntype = e_dialinginfo.ntype;
-                       dialinginfo.sending_complete = e_dialinginfo.sending_complete;
-                       portlist = ea_endpoint->portlist_new(port->p_serial, port->p_type, earlyb);
-                       if (!portlist) {
-                               PERROR("EPOINT(%d) cannot allocate port_list relation\n", ea_endpoint->ep_serial);
-                               delete port;
-                               goto check_anycall_extern;
-                       }
-//printf("EXTERNAL caller=%s,id=%s,dial=%s\n", param.setup.networkid, param.setup.callerinfo.id, param.setup.dialinginfo.id);
-                       message = message_create(ea_endpoint->ep_serial, portlist->port_id, EPOINT_TO_PORT, MESSAGE_SETUP);
-                       memcpy(&message->param.setup.dialinginfo, &dialinginfo, sizeof(struct dialing_info));
-                       memcpy(&message->param.setup.redirinfo, &e_redirinfo, sizeof(struct redir_info));
-                       memcpy(&message->param.setup.callerinfo, &e_callerinfo, sizeof(struct caller_info));
-                       memcpy(&message->param.setup.capainfo, &e_capainfo, sizeof(struct capa_info));
-                       memcpy(&message->param.setup.rtpinfo, &e_rtpinfo, sizeof(struct rtp_info));
-//terminal                     SCPY(message->param.setup.from_terminal, e_ext.number);
-//terminal                     if (e_dialinginfo.id)
-//terminal                             SCPY(message->param.setup.to_terminal, e_dialinginfo.id);
-                               /* handle restricted caller ids */
-                       apply_callerid_restriction(&e_ext, message->param.setup.callerinfo.id, &message->param.setup.callerinfo.ntype, &message->param.setup.callerinfo.present, &message->param.setup.callerinfo.screen, message->param.setup.callerinfo.extension, message->param.setup.callerinfo.name);
-                       apply_callerid_restriction(&e_ext, message->param.setup.callerinfo.id2, &message->param.setup.callerinfo.ntype2, &message->param.setup.callerinfo.present2, &message->param.setup.callerinfo.screen2, message->param.setup.callerinfo.extension, message->param.setup.callerinfo.name);
-                       apply_callerid_restriction(&e_ext, message->param.setup.redirinfo.id, &message->param.setup.redirinfo.ntype, &message->param.setup.redirinfo.present, 0, message->param.setup.redirinfo.extension, NULL);
-                       /* display callerid if desired for extension */
-                       SCPY(message->param.setup.callerinfo.display, apply_callerid_display(message->param.setup.callerinfo.id, message->param.setup.callerinfo.itype, message->param.setup.callerinfo.ntype, message->param.setup.callerinfo.present, message->param.setup.callerinfo.screen, message->param.setup.callerinfo.extension, message->param.setup.callerinfo.name));
-                       message_put(message);
-                       logmessage(message->type, &message->param, portlist->port_id, DIRECTION_OUT);
-                       anycall = 1;
-               } while(*p);
+                               }
+                               if (!port)
+                                       FATAL("No memory for Port instance\n");
+                               PDEBUG(DEBUG_EPOINT, "EPOINT(%d) created port %s\n", ea_endpoint->ep_serial, port->p_name);
+                               memset(&dialinginfo, 0, sizeof(dialinginfo));
+                               if (e_dialinginfo.keypad[0])
+                                       SCPY(dialinginfo.keypad, number);
+                               else
+                                       SCPY(dialinginfo.id, number);
+                               dialinginfo.itype = INFO_ITYPE_ISDN;
+                               dialinginfo.ntype = e_dialinginfo.ntype;
+                               dialinginfo.sending_complete = e_dialinginfo.sending_complete;
+                               portlist = ea_endpoint->portlist_new(port->p_serial, port->p_type, earlyb);
+                               if (!portlist) {
+                                       PERROR("EPOINT(%d) cannot allocate port_list relation\n", ea_endpoint->ep_serial);
+                                       delete port;
+                                       continue;
+                               }
+       //printf("EXTERNAL caller=%s,id=%s,dial=%s\n", param.setup.networkid, param.setup.callerinfo.id, param.setup.dialinginfo.id);
+                               message = message_create(ea_endpoint->ep_serial, portlist->port_id, EPOINT_TO_PORT, MESSAGE_SETUP);
+                               memcpy(&message->param.setup.dialinginfo, &dialinginfo, sizeof(struct dialing_info));
+                               memcpy(&message->param.setup.redirinfo, &e_redirinfo, sizeof(struct redir_info));
+                               memcpy(&message->param.setup.callerinfo, &e_callerinfo, sizeof(struct caller_info));
+                               memcpy(&message->param.setup.capainfo, &e_capainfo, sizeof(struct capa_info));
+                               memcpy(&message->param.setup.rtpinfo, &e_rtpinfo, sizeof(struct rtp_info));
+       //terminal                      SCPY(message->param.setup.from_terminal, e_ext.number);
+       //terminal                      if (e_dialinginfo.id)
+       //terminal                              SCPY(message->param.setup.to_terminal, e_dialinginfo.id);
+                                       /* handle restricted caller ids */
+                               apply_callerid_restriction(&e_ext, message->param.setup.callerinfo.id, &message->param.setup.callerinfo.ntype, &message->param.setup.callerinfo.present, &message->param.setup.callerinfo.screen, message->param.setup.callerinfo.extension, message->param.setup.callerinfo.name);
+                               apply_callerid_restriction(&e_ext, message->param.setup.callerinfo.id2, &message->param.setup.callerinfo.ntype2, &message->param.setup.callerinfo.present2, &message->param.setup.callerinfo.screen2, message->param.setup.callerinfo.extension, message->param.setup.callerinfo.name);
+                               apply_callerid_restriction(&e_ext, message->param.setup.redirinfo.id, &message->param.setup.redirinfo.ntype, &message->param.setup.redirinfo.present, 0, message->param.setup.redirinfo.extension, NULL);
+                               /* display callerid if desired for extension */
+                               SCPY(message->param.setup.callerinfo.display, apply_callerid_display(message->param.setup.callerinfo.id, message->param.setup.callerinfo.itype, message->param.setup.callerinfo.ntype, message->param.setup.callerinfo.present, message->param.setup.callerinfo.screen, message->param.setup.callerinfo.extension, message->param.setup.callerinfo.name));
+                               message_put(message);
+                               logmessage(message->type, &message->param, portlist->port_id, DIRECTION_OUT);
+                               anycall = 1;
+
+                               /* found an interface
+                                * continue only if + is given, so every interface is calles parallel */
+                               if (e_dialinginfo.interfaces[0] != '+')
+                                       break;
+                       } while (*ifname_p);
+               } while(*number_p);
 
-               check_anycall_extern:
                /* now we have all ports created */
                if (!anycall) {
                        trace_header("INTERFACE (no free ports found)", DIRECTION_NONE);
@@ -1344,6 +1368,7 @@ void EndpointAppPBX::port_setup(struct port_list *portlist, int message_type, un
        if (e_callerinfo.interface[0]) {
                do_screen(0, e_callerinfo.id, sizeof(e_callerinfo.id), &e_callerinfo.ntype, &e_callerinfo.present, e_callerinfo.interface);
                do_screen(0, e_callerinfo.id2, sizeof(e_callerinfo.id2), &e_callerinfo.ntype2, &e_callerinfo.present2, e_callerinfo.interface);
+               do_screen(0, e_redirinfo.id, sizeof(e_redirinfo.id), &e_redirinfo.ntype, &e_redirinfo.present, e_callerinfo.interface);
        }
 
        /* process extension */
@@ -1547,8 +1572,7 @@ void EndpointAppPBX::port_information(struct port_list *portlist, int message_ty
        }
        if (e_action)
        if (e_action->index==ACTION_OUTDIAL
-        || e_action->index==ACTION_EXTERNAL
-        || e_action->index==ACTION_REMOTE) {
+        || e_action->index==ACTION_EXTERNAL) {
                if (!e_extdialing)
                        set_tone(portlist, "dialing");
                else if (!e_extdialing[0])
@@ -2370,6 +2394,44 @@ void EndpointAppPBX::port_facility(struct port_list *portlist, int message_type,
        message_put(message);
 }
 
+/* port MESSAGE_3PTY */
+void EndpointAppPBX::port_3pty(struct port_list *portlist, int message_type, union parameter *param)
+{
+       logmessage(message_type, param, portlist->port_id, DIRECTION_IN);
+
+       struct lcr_msg *message;
+       int rc;
+
+#if 0
+       /* bridge for real */
+       if (param->threepty.begin)
+               rc = join_join();
+       else if (param->threepty.end)
+               rc = -ENOTSUP;
+       else
+               return;
+#else
+       /* 3PTY bridge */
+       if (param->threepty.begin)
+               rc = join_3pty();
+       else if (param->threepty.end)
+               rc = split_3pty();
+       else
+               return;
+#endif
+
+       message = message_create(ea_endpoint->ep_serial, portlist->port_id, EPOINT_TO_PORT, MESSAGE_3PTY);
+       message->param.threepty.begin = param->threepty.begin;
+       message->param.threepty.end = param->threepty.end;
+       if (rc < 0)
+               message->param.threepty.error = 1;
+       else
+               message->param.threepty.result = 1;
+       message->param.threepty.invoke_id = param->threepty.invoke_id;
+       logmessage(message->type, &message->param, portlist->port_id, DIRECTION_OUT);
+       message_put(message);
+}
+
 /* port MESSAGE_SUSPEND */
 /* NOTE: before supending, the inactive-notification must be done in order to set call mixer */
 void EndpointAppPBX::port_suspend(struct port_list *portlist, int message_type, union parameter *param)
@@ -2472,6 +2534,11 @@ void EndpointAppPBX::ea_message_port(unsigned int port_id, int message_type, uni
                port_facility(portlist, message_type, param);
                break;
 
+               case MESSAGE_3PTY:
+               PDEBUG(DEBUG_EPOINT, "EPOINT(%d) incoming 3PTY facility (terminal '%s', caller id '%s')\n", ea_endpoint->ep_serial, e_ext.number, e_callerinfo.id);
+               port_3pty(portlist, message_type, param);
+               break;
+
                /* PORT sends DTMF message */
                case MESSAGE_DTMF: /* dtmf digits received */
                PDEBUG(DEBUG_EPOINT, "EPOINT(%d) dtmf digit=%c (terminal '%s', caller id '%s')\n", ea_endpoint->ep_serial, param->dtmf, e_ext.number, e_callerinfo.id);
@@ -3511,49 +3578,50 @@ reject:
 
 /* join calls (look for a join that is on hold (same isdn interface/terminal))
  */
-void EndpointAppPBX::join_join(void)
+int EndpointAppPBX::join_join(void)
 {
 #ifdef WITH_MISDN
        struct lcr_msg *message;
-       struct join_relation *our_relation, *other_relation;
-       struct join_relation **our_relation_pointer, **other_relation_pointer;
-       class Join *our_join, *other_join;
-       class JoinPBX *our_joinpbx, *other_joinpbx;
-       class EndpointAppPBX *other_eapp; class Endpoint *temp_epoint;
+       struct join_relation *add_relation, *remove_relation;
+       struct join_relation **add_relation_pointer, **remove_relation_pointer;
+       class Join *our_join, *other_join, *add_join, *remove_join;
+       class JoinPBX *our_joinpbx, *other_joinpbx, *add_joinpbx, *remove_joinpbx;
+       class EndpointAppPBX *other_eapp, *remove_eapp;
        class Port *our_port, *other_port;
        class Pdss1 *our_pdss1, *other_pdss1;
+       class Endpoint *temp_epoint;
 
        /* are we a candidate to join a join? */
        our_join = find_join_id(ea_endpoint->ep_join_id);
        if (!our_join) {
                PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: our join doesn't exist anymore.\n", ea_endpoint->ep_serial);
-               return;
+               return -1;
        }
        if (our_join->j_type != JOIN_TYPE_PBX) {
                PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: join is not a pbx join.\n", ea_endpoint->ep_serial);
-               return;
+               return -1;
        }
        our_joinpbx = (class JoinPBX *)our_join;
        if (!ea_endpoint->ep_portlist) {
                PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: we have no port.\n", ea_endpoint->ep_serial);
-               return;
+               return -1;
        }
        if (!e_ext.number[0]) {
                PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: we are not internal extension.\n", ea_endpoint->ep_serial);
-               return;
+               return -1;
        }
        our_port = find_port_id(ea_endpoint->ep_portlist->port_id);
        if (!our_port) {
                PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: our port doesn't exist anymore.\n", ea_endpoint->ep_serial);
-               return;
+               return -1;
        }
        if ((our_port->p_type & PORT_CLASS_mISDN_MASK) != PORT_CLASS_DSS1) {
                PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: our port is not isdn.\n", ea_endpoint->ep_serial);
-               return;
+               return -1;
        }
        our_pdss1 = (class Pdss1 *)our_port;
 
-       /* find an endpoint that is on hold and has the same mISDNport that we are on */
+       /* find an endpoint that has the same mISDNport/ces that we are on */
        other_eapp = apppbx_first;
        while(other_eapp) {
                if (other_eapp == this) {
@@ -3570,7 +3638,7 @@ void EndpointAppPBX::join_join(void)
                                 || other_port->p_type==PORT_TYPE_DSS1_NT_IN) { /* port is isdn nt-mode */
                                        other_pdss1 = (class Pdss1 *)other_port;
                                        PDEBUG(DEBUG_EPOINT, "EPOINT(%d) comparing other endpoint's port is of type isdn! comparing our portnum=%d with other's portnum=%d hold=%s ces=%d\n", ea_endpoint->ep_serial, our_pdss1->p_m_mISDNport->portnum, other_pdss1->p_m_mISDNport->portnum, (other_pdss1->p_m_hold)?"YES":"NO", other_pdss1->p_m_d_ces);
-                                       if (other_pdss1->p_m_hold /* port is on hold */
+                                       if (1 //other_pdss1->p_m_hold /* port is on hold */
                                         && other_pdss1->p_m_mISDNport == our_pdss1->p_m_mISDNport /* same isdn interface */
                                         && other_pdss1->p_m_d_ces == our_pdss1->p_m_d_ces) /* same tei+sapi */
                                                break;
@@ -3585,90 +3653,279 @@ void EndpointAppPBX::join_join(void)
        }
        if (!other_eapp) {
                PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: no other endpoint on same isdn terminal.\n", ea_endpoint->ep_serial);
-               return;
+               return -1;
        }
        PDEBUG(DEBUG_EPOINT, "EPOINT(%d) port with same terminal found.\n", ea_endpoint->ep_serial);
 
        /* if we have the same join */
        if (other_eapp->ea_endpoint->ep_join_id == ea_endpoint->ep_join_id) {
                PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: we and the other have the same join.\n", ea_endpoint->ep_serial);
-               return;
+               return -1;
        }
        other_join = find_join_id(other_eapp->ea_endpoint->ep_join_id);
        if (!other_join) {
                PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: other join doesn't exist anymore.\n", ea_endpoint->ep_serial);
-               return;
+               return -1;
        }
        if (other_join->j_type != JOIN_TYPE_PBX) {
                PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: other join is not a pbx join.\n", ea_endpoint->ep_serial);
-               return;
+               return -1;
        }
        other_joinpbx = (class JoinPBX *)other_join;
        if (our_joinpbx->j_partyline && other_joinpbx->j_partyline) {
                PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: both joins are partylines.\n", ea_endpoint->ep_serial);
-               return;
+               return -1;
+       }
+
+       /* now find out which is ACTIVE-IDLE and which is ACTIVE-HELD */
+       if (our_pdss1->p_m_hold && !other_pdss1->p_m_hold) {
+               PDEBUG(DEBUG_EPOINT, "EPOINT(%d) our relation is on hold and other is active, so we move our relations to other relations\n", ea_endpoint->ep_serial);
+               remove_eapp = this;
+               remove_join = our_join;
+               remove_joinpbx = our_joinpbx;
+               add_join = other_join;
+               add_joinpbx = other_joinpbx;
+       } else {
+               PDEBUG(DEBUG_EPOINT, "EPOINT(%d) our relation is active or other is on hold, so we move ohter relations to our relations\n", ea_endpoint->ep_serial);
+               remove_eapp = other_eapp;
+               remove_join = other_join;
+               remove_joinpbx = other_joinpbx;
+               add_join = our_join;
+               add_joinpbx = our_joinpbx;
        }
 
        /* remove relation to endpoint for join on hold */
-       other_relation = other_joinpbx->j_relation;
-       other_relation_pointer = &other_joinpbx->j_relation;
-       while(other_relation) {
-               if (other_relation->epoint_id == other_eapp->ea_endpoint->ep_serial) {
-                       /* detach other endpoint on hold */
-                       *other_relation_pointer = other_relation->next;
-                       FREE(other_relation, sizeof(struct join_relation));
+       remove_relation = remove_joinpbx->j_relation;
+       remove_relation_pointer = &remove_joinpbx->j_relation;
+       while(remove_relation) {
+               if (remove_relation->epoint_id == remove_eapp->ea_endpoint->ep_serial) {
+                       /* detach other endpoint */
+                       *remove_relation_pointer = remove_relation->next;
+                       FREE(remove_relation, sizeof(struct join_relation));
                        cmemuse--;
-                       other_relation = *other_relation_pointer;
-                       other_eapp->ea_endpoint->ep_join_id = 0;
+                       remove_relation = *remove_relation_pointer;
+                       remove_eapp->ea_endpoint->ep_join_id = 0;
                        continue;
                }
 
                /* change join/hold pointer of endpoint to the new join */
-               temp_epoint = find_epoint_id(other_relation->epoint_id);
+               temp_epoint = find_epoint_id(remove_relation->epoint_id);
                if (temp_epoint) {
-                       if (temp_epoint->ep_join_id == other_join->j_serial)
-                               temp_epoint->ep_join_id = our_join->j_serial;
+                       if (temp_epoint->ep_join_id == remove_join->j_serial)
+                               temp_epoint->ep_join_id = add_join->j_serial;
                }
 
-               other_relation_pointer = &other_relation->next;
-               other_relation = other_relation->next;
+               remove_relation_pointer = &remove_relation->next;
+               remove_relation = remove_relation->next;
        }
-       PDEBUG(DEBUG_EPOINT, "EPOINT(%d) endpoint on hold removed, other enpoints on join relinked (to our join).\n", ea_endpoint->ep_serial);
+       PDEBUG(DEBUG_EPOINT, "EPOINT(%d) endpoint removed, other enpoints on join relinked.\n", ea_endpoint->ep_serial);
 
        /* join call relations */
-       our_relation = our_joinpbx->j_relation;
-       our_relation_pointer = &our_joinpbx->j_relation;
-       while(our_relation) {
-               our_relation_pointer = &our_relation->next;
-               our_relation = our_relation->next;
-       }
-       *our_relation_pointer = other_joinpbx->j_relation;
-       other_joinpbx->j_relation = NULL;
+       add_relation = add_joinpbx->j_relation;
+       add_relation_pointer = &add_joinpbx->j_relation;
+       while(add_relation) {
+               add_relation_pointer = &add_relation->next;
+               add_relation = add_relation->next;
+       }
+       *add_relation_pointer = remove_joinpbx->j_relation;
+       remove_joinpbx->j_relation = NULL;
        PDEBUG(DEBUG_EPOINT, "EPOINT(%d) relations joined.\n", ea_endpoint->ep_serial);
 
-       /* release endpoint on hold */
-       message = message_create(other_joinpbx->j_serial, other_eapp->ea_endpoint->ep_serial, JOIN_TO_EPOINT, MESSAGE_RELEASE);
+       /* release endpoint */
+       message = message_create(remove_joinpbx->j_serial, remove_eapp->ea_endpoint->ep_serial, JOIN_TO_EPOINT, MESSAGE_RELEASE);
        message->param.disconnectinfo.cause = CAUSE_NORMAL; /* normal */
        message->param.disconnectinfo.location = LOCATION_PRIVATE_LOCAL;
        message_put(message);
        
        /* if we are not a partyline, we get partyline state from other join */
-       our_joinpbx->j_partyline += other_joinpbx->j_partyline; 
+       add_joinpbx->j_partyline += remove_joinpbx->j_partyline; 
 
        /* remove empty join */
-       delete other_join;
-       PDEBUG(DEBUG_EPOINT, "EPOINT(%d)d-join completely removed!\n");
+       delete remove_join;
+       PDEBUG(DEBUG_EPOINT, "EPOINT(%d)join completely removed!\n", ea_endpoint->ep_serial);
+
+       /* mixer must update */
+       trigger_work(&add_joinpbx->j_updatebridge);
+
+       /* we send a retrieve to that endpoint */
+       // mixer will update the hold-state of the join and send it to the endpoints is changes
+#else
+       PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: no mISDN support anyway.\n", ea_endpoint->ep_serial);
+#endif
+
+       return 0;
+}
+
+int EndpointAppPBX::join_3pty(void)
+{
+#ifdef WITH_MISDN
+       class Join *our_join, *other_join;
+       class JoinPBX *our_joinpbx, *other_joinpbx;
+       class EndpointAppPBX *other_eapp;
+       class Port *our_port, *other_port;
+       class Pdss1 *our_pdss1, *other_pdss1;
+
+       /* are we a candidate to join a join? */
+       our_join = find_join_id(ea_endpoint->ep_join_id);
+       if (!our_join) {
+               PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: our join doesn't exist anymore.\n", ea_endpoint->ep_serial);
+               return -1;
+       }
+       if (our_join->j_type != JOIN_TYPE_PBX) {
+               PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: join is not a pbx join.\n", ea_endpoint->ep_serial);
+               return -1;
+       }
+       our_joinpbx = (class JoinPBX *)our_join;
+       if (!ea_endpoint->ep_portlist) {
+               PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: we have no port.\n", ea_endpoint->ep_serial);
+               return -1;
+       }
+       if (!e_ext.number[0]) {
+               PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: we are not internal extension.\n", ea_endpoint->ep_serial);
+               return -1;
+       }
+       our_port = find_port_id(ea_endpoint->ep_portlist->port_id);
+       if (!our_port) {
+               PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: our port doesn't exist anymore.\n", ea_endpoint->ep_serial);
+               return -1;
+       }
+       if ((our_port->p_type & PORT_CLASS_mISDN_MASK) != PORT_CLASS_DSS1) {
+               PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: our port is not isdn.\n", ea_endpoint->ep_serial);
+               return -1;
+       }
+       our_pdss1 = (class Pdss1 *)our_port;
+
+       /* find an endpoint that has the same mISDNport/ces that we are on */
+       other_eapp = apppbx_first;
+       while(other_eapp) {
+               if (other_eapp == this) {
+                       other_eapp = other_eapp->next;
+                       continue;
+               }
+               PDEBUG(DEBUG_EPOINT, "EPOINT(%d) comparing other endpoint candiate: (ep%d) terminal='%s' port=%s join=%d.\n", ea_endpoint->ep_serial, other_eapp->ea_endpoint->ep_serial, other_eapp->e_ext.number, (other_eapp->ea_endpoint->ep_portlist)?"YES":"NO", other_eapp->ea_endpoint->ep_join_id);
+               if (other_eapp->e_ext.number[0] /* has terminal */
+                && other_eapp->ea_endpoint->ep_portlist /* has port */
+                && other_eapp->ea_endpoint->ep_join_id) { /* has join */
+                       other_port = find_port_id(other_eapp->ea_endpoint->ep_portlist->port_id);
+                       if (other_port) { /* port still exists */
+                               if (other_port->p_type==PORT_TYPE_DSS1_NT_OUT
+                                || other_port->p_type==PORT_TYPE_DSS1_NT_IN) { /* port is isdn nt-mode */
+                                       other_pdss1 = (class Pdss1 *)other_port;
+                                       PDEBUG(DEBUG_EPOINT, "EPOINT(%d) comparing other endpoint's port is of type isdn! comparing our portnum=%d with other's portnum=%d hold=%s ces=%d\n", ea_endpoint->ep_serial, our_pdss1->p_m_mISDNport->portnum, other_pdss1->p_m_mISDNport->portnum, (other_pdss1->p_m_hold)?"YES":"NO", other_pdss1->p_m_d_ces);
+                                       if (1 //other_pdss1->p_m_hold /* port is on hold */
+                                        && other_pdss1->p_m_mISDNport == our_pdss1->p_m_mISDNport /* same isdn interface */
+                                        && other_pdss1->p_m_d_ces == our_pdss1->p_m_d_ces) /* same tei+sapi */
+                                               break;
+                               } else {
+                                       PDEBUG(DEBUG_EPOINT, "EPOINT(%d) comparing other endpoint's port is of other type!\n", ea_endpoint->ep_serial);
+                               }
+                       } else {
+                               PDEBUG(DEBUG_EPOINT, "EPOINT(%d) comparing other endpoint's port doesn't exist enymore.\n", ea_endpoint->ep_serial);
+                       }
+               }
+               other_eapp = other_eapp->next;
+       }
+       if (!other_eapp) {
+               PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: no other endpoint on same isdn terminal.\n", ea_endpoint->ep_serial);
+               return -1;
+       }
+       PDEBUG(DEBUG_EPOINT, "EPOINT(%d) port with same terminal found.\n", ea_endpoint->ep_serial);
+
+       /* if we have the same join */
+       if (other_eapp->ea_endpoint->ep_join_id == ea_endpoint->ep_join_id) {
+               PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: we and the other have the same join.\n", ea_endpoint->ep_serial);
+               return -1;
+       }
+       other_join = find_join_id(other_eapp->ea_endpoint->ep_join_id);
+       if (!other_join) {
+               PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: other join doesn't exist anymore.\n", ea_endpoint->ep_serial);
+               return -1;
+       }
+       if (other_join->j_type != JOIN_TYPE_PBX) {
+               PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: other join is not a pbx join.\n", ea_endpoint->ep_serial);
+               return -1;
+       }
+       other_joinpbx = (class JoinPBX *)other_join;
+       if (our_joinpbx->j_partyline && other_joinpbx->j_partyline) {
+               PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: both joins are partylines.\n", ea_endpoint->ep_serial);
+               return -1;
+       }
+
+       if (our_joinpbx->j_3pty) {
+               PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: our join already doing 3PTY.\n", ea_endpoint->ep_serial);
+               return -1;
+       }
+       if (other_joinpbx->j_3pty) {
+               PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: other join already doing 3PTY.\n", ea_endpoint->ep_serial);
+               return -1;
+       }
+
+       /* set 3PTY bridge */
+       other_joinpbx->j_3pty = our_joinpbx->j_serial;
+       our_joinpbx->j_3pty = other_joinpbx->j_serial;
 
        /* mixer must update */
        trigger_work(&our_joinpbx->j_updatebridge);
+       trigger_work(&other_joinpbx->j_updatebridge);
 
        /* we send a retrieve to that endpoint */
        // mixer will update the hold-state of the join and send it to the endpoints is changes
 #else
        PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: no mISDN support anyway.\n", ea_endpoint->ep_serial);
 #endif
+
+       return 0;
 }
 
+int EndpointAppPBX::split_3pty(void)
+{
+#ifdef WITH_MISDN
+       class Join *our_join, *other_join;
+       class JoinPBX *our_joinpbx, *other_joinpbx;
+
+       /* are we a candidate to join a join? */
+       our_join = find_join_id(ea_endpoint->ep_join_id);
+       if (!our_join) {
+               PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot split: our join doesn't exist anymore.\n", ea_endpoint->ep_serial);
+               return -1;
+       }
+       if (our_join->j_type != JOIN_TYPE_PBX) {
+               PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot split: join is not a pbx join.\n", ea_endpoint->ep_serial);
+               return -1;
+       }
+       our_joinpbx = (class JoinPBX *)our_join;
+
+       if (!our_joinpbx->j_3pty) {
+               PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot split: we don't have a 3PTY.\n", ea_endpoint->ep_serial);
+               return -1;
+       }
+
+       other_join = find_join_id(our_joinpbx->j_3pty);
+       if (!other_join) {
+               PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot split: other join doesn't exist anymore.\n", ea_endpoint->ep_serial);
+               return -1;
+       }
+       if (other_join->j_type != JOIN_TYPE_PBX) {
+               PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot split: join is not a pbx join.\n", ea_endpoint->ep_serial);
+               return -1;
+       }
+       other_joinpbx = (class JoinPBX *)other_join;
+
+       our_joinpbx->j_3pty = 0;
+       other_joinpbx->j_3pty = 0;
+
+       /* mixer must update */
+       trigger_work(&our_joinpbx->j_updatebridge);
+       trigger_work(&other_joinpbx->j_updatebridge);
+
+       /* we send a retrieve to that endpoint */
+       // mixer will update the hold-state of the join and send it to the endpoints is changes
+#else
+       PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot split: no mISDN support anyway.\n", ea_endpoint->ep_serial);
+#endif
+
+       return 0;
+}
 
 /* check if we have an external call
  * this is used to check for encryption ability
@@ -4160,6 +4417,21 @@ void EndpointAppPBX::logmessage(int message_type, union parameter *param, unsign
                break;
 #endif
 
+               case MESSAGE_3PTY:
+               if (param->threepty.begin)
+                       trace_header("Begin3PTY", dir);
+               if (param->threepty.end)
+                       trace_header("End3PTY", dir);
+               if (param->threepty.invoke)
+                       add_trace("action", NULL, "invoke");
+               if (param->threepty.result)
+                       add_trace("action", NULL, "result");
+               if (param->threepty.error)
+                       add_trace("action", NULL, "error");
+               add_trace("invoke-id", NULL, "%d", param->threepty.invoke_id);
+               end_trace();
+               break;
+
                default:
                PERROR("EPOINT(%d) message not of correct type (%d)\n", ea_endpoint->ep_serial, message_type);
        }