Minor fix for GSM HR codec negotiation: Add missing 'break'.
[lcr.git] / apppbx.cpp
index b976393..e96b54c 100644 (file)
@@ -522,7 +522,19 @@ void EndpointAppPBX::keypad_function(char digit)
                if ((port->p_type & PORT_CLASS_POTS_MASK) == PORT_CLASS_POTS_FXS)
                        join_join_fxs();
                else if ((port->p_type & PORT_CLASS_mISDN_MASK) == PORT_CLASS_DSS1)
-                       join_join_dss1();
+                       join_join_dss1(-1);
+               break;
+
+               /* VOOTP on */
+               case '1':
+               PDEBUG(DEBUG_EPOINT, "EPOINT(%d) enable VoOTP.\n", ea_endpoint->ep_serial);
+               vootp_on(1);
+               break;
+
+               /* VOOTP off */
+               case '2':
+               PDEBUG(DEBUG_EPOINT, "EPOINT(%d) disable VoOTP.\n", ea_endpoint->ep_serial);
+               vootp_on(0);
                break;
 
 #ifdef WITH_CRYPT
@@ -544,7 +556,6 @@ void EndpointAppPBX::keypad_function(char digit)
                encrypt_off();
                break;
 #endif
-
                default:        
                PDEBUG(DEBUG_EPOINT, "EPOINT(%d) unsupported keypad digit '%c'.\n", ea_endpoint->ep_serial, digit);
        }
@@ -1306,7 +1317,7 @@ int password_timeout(struct lcr_timer *timer, void *instance, int index)
                ea->new_state(EPOINT_STATE_OUT_DISCONNECT);
                portlist = ea->ea_endpoint->ep_portlist;
                if (portlist) {
-                       ea->message_disconnect_port(portlist, CAUSE_NORMAL, LOCATION_PRIVATE_LOCAL, "");
+                       ea->message_disconnect_port(portlist, CAUSE_NORMAL, LOCATION_PRIVATE_LOCAL, "", NULL);
                        ea->set_tone(portlist, "cause_10");
                }
        }
@@ -1413,7 +1424,7 @@ void EndpointAppPBX::port_setup(struct port_list *portlist, int message_type, un
                        trace_header("EXTENSION (not created)", DIRECTION_IN);
                        add_trace("extension", NULL, "%s", e_ext.number);
                        end_trace();
-                       message_disconnect_port(portlist, CAUSE_REJECTED, LOCATION_PRIVATE_LOCAL, "");
+                       message_disconnect_port(portlist, CAUSE_REJECTED, LOCATION_PRIVATE_LOCAL, "", NULL);
                        new_state(EPOINT_STATE_OUT_DISCONNECT);
                        set_tone(portlist, "cause_80"); /* pbx cause: extension not authorized */
                        e_ext.number[0] = '\0'; /* no terminal */
@@ -1959,6 +1970,16 @@ void EndpointAppPBX::port_connect(struct port_list *portlist, int message_type,
                message = message_create(ea_endpoint->ep_serial, ea_endpoint->ep_join_id, EPOINT_TO_JOIN, MESSAGE_AUDIOPATH);
                message->param.audiopath = 1;
                message_put(message);
+               if (e_ext.dov_ident[0]) {
+                       message = message_create(ea_endpoint->ep_serial, ea_endpoint->ep_join_id, EPOINT_TO_JOIN, MESSAGE_DOV_REQUEST);
+                       SPRINT(message->param.dov.data, "%08x ", lcr_random);
+                       SCAT(message->param.dov.data, e_ext.dov_ident);
+                       message->param.dov.length = strlen((char *)message->param.dov.data);
+                       message->param.dov.type = e_ext.dov_type;
+                       message->param.dov.level = e_ext.dov_level;
+                       dov_msg_write(&message->param, 1);
+                       message_put(message);
+               }
        } else if (!e_adminid) {
                /* callback */
                PDEBUG(DEBUG_EPOINT, "EPOINT(%d) we have a callback, so we create a call with cbcaller: \"%s\".\n", ea_endpoint->ep_serial, e_cbcaller);
@@ -1970,7 +1991,7 @@ void EndpointAppPBX::port_connect(struct port_list *portlist, int message_type,
                if (!read_extension(&e_ext, e_ext.number)) {
                        /* extension doesn't exist */
                        PDEBUG(DEBUG_EPOINT, "EPOINT(%d) rejecting callback from not existing extension: '%s'\n", ea_endpoint->ep_serial, e_ext.number);
-                       message_disconnect_port(portlist, CAUSE_REJECTED, LOCATION_PRIVATE_LOCAL, "");
+                       message_disconnect_port(portlist, CAUSE_REJECTED, LOCATION_PRIVATE_LOCAL, "", NULL);
                        new_state(EPOINT_STATE_OUT_DISCONNECT);
                        set_tone(portlist, "cause_80"); /* pbx cause: extension not authorized */
                        return;
@@ -2239,7 +2260,7 @@ void EndpointAppPBX::port_timeout(struct port_list *portlist, int message_type,
        SCPY(e_tone, cause);
        while(portlist) {
                set_tone(portlist, cause);
-               message_disconnect_port(portlist, param->disconnectinfo.cause, param->disconnectinfo.location, "");
+               message_disconnect_port(portlist, param->disconnectinfo.cause, param->disconnectinfo.location, "", NULL);
                portlist = portlist->next;
        }
        release(RELEASE_JOIN, LOCATION_PRIVATE_LOCAL, CAUSE_NORMAL, LOCATION_PRIVATE_LOCAL, CAUSE_NORMAL, 0); /* RELEASE_TYPE, join, port */
@@ -2368,14 +2389,26 @@ void EndpointAppPBX::port_transfer(struct port_list *portlist, int message_type,
        logmessage(message_type, param, portlist->port_id, DIRECTION_IN);
 
        class Port *port;
+       struct lcr_msg *message;
+       int rc;
 
        /* bridge for real */
        if (!(port = find_port_id(portlist->port_id)))
                return;
        if ((port->p_type & PORT_CLASS_POTS_MASK) == PORT_CLASS_POTS_FXS)
                join_join_fxs();
-       else if ((port->p_type & PORT_CLASS_mISDN_MASK) == PORT_CLASS_DSS1)
-               join_join_dss1();
+       else if ((port->p_type & PORT_CLASS_mISDN_MASK) == PORT_CLASS_DSS1
+             || (port->p_type & PORT_CLASS_GSM_MASK) == PORT_CLASS_GSM_BS) {
+               rc = join_join_dss1(param->transfer.invoke_id);
+
+               if (rc < 0) {
+                       message = message_create(ea_endpoint->ep_serial, portlist->port_id, EPOINT_TO_PORT, MESSAGE_TRANSFER);
+                       message->param.transfer.error = 1;
+                       message->param.transfer.invoke_id = param->transfer.invoke_id;
+                       logmessage(message->type, &message->param, portlist->port_id, DIRECTION_OUT);
+                       message_put(message);
+               }
+       }
 }
 
 /* port MESSAGE_SUSPEND */
@@ -2429,6 +2462,36 @@ void EndpointAppPBX::port_disable_dejitter(struct port_list *portlist, int messa
        message_put(message);
 }
 
+/* port MESSAGE_DOV_INDICATION */
+void EndpointAppPBX::port_dov_indication(struct port_list *portlist, int message_type, union parameter *param)
+{
+       struct lcr_msg *message;
+
+       logmessage(message_type, param, portlist->port_id, DIRECTION_IN);
+
+       message = message_create(ea_endpoint->ep_serial, ea_endpoint->ep_join_id, EPOINT_TO_JOIN, MESSAGE_DOV_INDICATION);
+       memcpy(&message->param, param, sizeof(union parameter));
+       message_put(message);
+}
+
+
+/* port MESSAGE_UPDATEBRIDGE  */
+void EndpointAppPBX::port_updatebridge(struct port_list *portlist, int message_type, union parameter *param)
+{
+       struct lcr_msg *message;
+
+       message = message_create(ea_endpoint->ep_serial, ea_endpoint->ep_join_id, EPOINT_TO_JOIN, MESSAGE_UPDATEBRIDGE);
+       message_put(message);
+}
+
+
+/* port MESSAGE_VOOTP  */
+void EndpointAppPBX::port_vootp(struct port_list *portlist, int message_type, union parameter *param)
+{
+       if (param->vootp.failed)
+               set_tone(ea_endpoint->ep_portlist, "crypt_off");
+}
+
 
 /* port sends message to the endpoint
  */
@@ -2626,6 +2689,21 @@ void EndpointAppPBX::ea_message_port(unsigned int port_id, int message_type, uni
                port_disable_dejitter(portlist, message_type, param);
                break;
 
+               case MESSAGE_UPDATEBRIDGE:
+               PDEBUG(DEBUG_EPOINT, "EPOINT(%d) incoming updatebridge message (terminal '%s', caller id '%s')\n", ea_endpoint->ep_serial, e_ext.number, e_callerinfo.id);
+               port_updatebridge(portlist, message_type, param);
+               break;
+
+               case MESSAGE_VOOTP:
+               PDEBUG(DEBUG_EPOINT, "EPOINT(%d) incoming vootp message (terminal '%s', caller id '%s')\n", ea_endpoint->ep_serial, e_ext.number, e_callerinfo.id);
+               port_vootp(portlist, message_type, param);
+               break;
+
+               /* PORT indivated Data-Over-Voice */
+               case MESSAGE_DOV_INDICATION:
+               PDEBUG(DEBUG_EPOINT, "EPOINT(%d) epoint with terminal '%s' (caller id '%s') indicates Data-Over-Voice.\n", ea_endpoint->ep_serial, e_ext.number, e_callerinfo.id);
+               port_dov_indication(portlist, message_type, param);
+               break;
 
                default:
                PDEBUG(DEBUG_EPOINT, "EPOINT(%d) epoint with terminal '%s' (caller id '%s') received a wrong message: %d\n", ea_endpoint->ep_serial, e_ext.number, e_callerinfo.id, message_type);
@@ -2845,6 +2923,13 @@ void EndpointAppPBX::join_connect(struct port_list *portlist, int message_type,
        message_put(message);
        time(&now);
        e_start = now;
+
+       /* if the remote answered, we listen to DOV message */
+       if (e_ext.dov_log[0]) {
+               message = message_create(ea_endpoint->ep_serial, ea_endpoint->ep_join_id, EPOINT_TO_JOIN, MESSAGE_DOV_LISTEN);
+               message->param.dov.type = e_ext.dov_type;
+               message_put(message);
+       }
 }
 
 /* join MESSAGE_DISCONNECT MESSAGE_RELEASE */
@@ -2947,7 +3032,7 @@ void EndpointAppPBX::join_disconnect_release(int message_type, union parameter *
        portlist = ea_endpoint->ep_portlist;
        while(portlist) {
                set_tone(portlist, cause);
-               message_disconnect_port(portlist, param->disconnectinfo.cause, param->disconnectinfo.location, "");
+               message_disconnect_port(portlist, param->disconnectinfo.cause, param->disconnectinfo.location, "", &param->disconnectinfo.transfer);
                portlist = portlist->next;
        }
 }
@@ -3163,6 +3248,43 @@ void EndpointAppPBX::join_disable_dejitter(struct port_list *portlist, int messa
        }
 }
 
+/* join MESSAGE_DOV_INDICATION */
+void EndpointAppPBX::join_dov_indication(struct port_list *portlist, int message_type, union parameter *param)
+{
+       dov_msg_write(param, 0);
+}
+
+/* join MESSAGE_DOV_REQUEST */
+void EndpointAppPBX::join_dov_request(struct port_list *portlist, int message_type, union parameter *param)
+{
+       struct lcr_msg *message;
+
+       /* don't send DOV from estension to extension */
+       if (e_ext.number[0])
+               return;
+
+       while(portlist) {
+               message = message_create(ea_endpoint->ep_serial, portlist->port_id, EPOINT_TO_PORT, MESSAGE_DOV_REQUEST);
+               memcpy(&message->param, param, sizeof(union parameter));
+               message_put(message);
+               logmessage(message_type, param, portlist->port_id, DIRECTION_OUT);
+               portlist = portlist->next;
+       }
+}
+
+/* join MESSAGE_DOV_LISTEN */
+void EndpointAppPBX::join_dov_listen(struct port_list *portlist, int message_type, union parameter *param)
+{
+       struct lcr_msg *message;
+
+       while(portlist) {
+               message = message_create(ea_endpoint->ep_serial, portlist->port_id, EPOINT_TO_PORT, MESSAGE_DOV_LISTEN);
+               memcpy(&message->param, param, sizeof(union parameter));
+               message_put(message);
+               portlist = portlist->next;
+       }
+}
+
 /* JOIN sends messages to the endpoint
  */
 void EndpointAppPBX::ea_message_join(unsigned int join_id, int message_type, union parameter *param)
@@ -3228,7 +3350,9 @@ void EndpointAppPBX::ea_message_join(unsigned int join_id, int message_type, uni
                case MESSAGE_ALERTING:
                PDEBUG(DEBUG_EPOINT, "EPOINT(%d) epoint with terminal '%s' (caller id '%s') received alerting\n", ea_endpoint->ep_serial, e_ext.number, e_callerinfo.id);
                if (e_state!=EPOINT_STATE_IN_OVERLAP
-                && e_state!=EPOINT_STATE_IN_PROCEEDING) {
+                && e_state!=EPOINT_STATE_IN_PROCEEDING
+                && e_state!=EPOINT_STATE_IN_ALERTING /* second alerting */
+                && e_state!=EPOINT_STATE_CONNECT) { /* alerting after transfer */
                        PDEBUG(DEBUG_EPOINT, "EPOINT(%d) ignored because we are not in setup or proceeding state.\n", ea_endpoint->ep_serial);
                        break;
                }
@@ -3340,6 +3464,24 @@ void EndpointAppPBX::ea_message_join(unsigned int join_id, int message_type, uni
                join_disable_dejitter(portlist, message_type, param);
                break;
 
+               /* JOIN sends a Data-Over-Voice message indication */
+               case MESSAGE_DOV_INDICATION:
+               PDEBUG(DEBUG_EPOINT, "EPOINT(%d) epoint with terminal '%s' (caller id '%s') received Data-Over-Voice indication.\n", ea_endpoint->ep_serial, e_ext.number, e_callerinfo.id);
+               join_dov_indication(portlist, message_type, param);
+               break;
+
+               /* JOIN sends a Data-Over-Voice message request */
+               case MESSAGE_DOV_REQUEST:
+               PDEBUG(DEBUG_EPOINT, "EPOINT(%d) epoint with terminal '%s' (caller id '%s') received Data-Over-Voice request.\n", ea_endpoint->ep_serial, e_ext.number, e_callerinfo.id);
+               join_dov_request(portlist, message_type, param);
+               break;
+
+               /* JOIN sends a Data-Over-Voice listen order */
+               case MESSAGE_DOV_LISTEN:
+               PDEBUG(DEBUG_EPOINT, "EPOINT(%d) epoint with terminal '%s' (caller id '%s') received Data-Over-Voice listen order.\n", ea_endpoint->ep_serial, e_ext.number, e_callerinfo.id);
+               join_dov_listen(portlist, message_type, param);
+               break;
+
                default:
                PDEBUG(DEBUG_EPOINT, "EPOINT(%d) epoint with terminal '%s' (caller id '%s') received a wrong message: #%d\n", ea_endpoint->ep_serial, e_ext.number, e_callerinfo.id, message_type);
        }
@@ -3425,7 +3567,7 @@ void EndpointAppPBX::pick_join(char *extensions)
                PDEBUG(DEBUG_EPOINT, "EPOINT(%d) nobody is ringing internally (or we don't have her in the access list), so we disconnect.\n", ea_endpoint->ep_serial);
 reject:
                set_tone(ea_endpoint->ep_portlist, "cause_10");
-               message_disconnect_port(ea_endpoint->ep_portlist, CAUSE_NORMAL, LOCATION_PRIVATE_LOCAL, "");
+               message_disconnect_port(ea_endpoint->ep_portlist, CAUSE_NORMAL, LOCATION_PRIVATE_LOCAL, "", NULL);
                new_state(EPOINT_STATE_OUT_DISCONNECT);
                return;
        }
@@ -3561,19 +3703,23 @@ reject:
 }
 
 
-/* join calls (look for a join that is on hold (same isdn interface/terminal))
+/* join calls (look for a join that is on hold (same isdn/gsm interface/terminal))
  */
-int EndpointAppPBX::join_join_dss1(void)
+int EndpointAppPBX::join_join_dss1(int invoke_id)
 {
-#ifdef WITH_MISDN
        struct lcr_msg *message;
        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 EndpointAppPBX *other_eapp, *remove_eapp_hold, *remove_eapp_active;
        class Port *our_port, *other_port;
-       class Pdss1 *our_pdss1, *other_pdss1;
+#ifdef WITH_MISDN
+       class Pdss1 *our_pdss1 = NULL, *other_pdss1;
+#endif
+#ifdef WITH_GSM_BS
+       class Pgsm_bs *our_gsm_bs = NULL, *other_gsm_bs;
+#endif
        class Endpoint *temp_epoint;
 
        /* are we a candidate to join a join? */
@@ -3591,20 +3737,19 @@ int EndpointAppPBX::join_join_dss1(void)
                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;
+#ifdef WITH_MISDN
+       if ((our_port->p_type & PORT_CLASS_mISDN_MASK) == PORT_CLASS_DSS1)
+               our_pdss1 = (class Pdss1 *)our_port;
+#endif
+#ifdef WITH_GSM_BS
+       if ((our_port->p_type & PORT_CLASS_GSM_MASK) == PORT_CLASS_GSM_BS)
+               our_gsm_bs = (class Pgsm_bs *)our_port;
+#endif
 
        /* find an endpoint that has the same mISDNport/ces that we are on */
        other_eapp = apppbx_first;
@@ -3614,20 +3759,33 @@ int EndpointAppPBX::join_join_dss1(void)
                        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 */
+               if (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 */
+#ifdef WITH_MISDN
+                               if (our_pdss1
+                                && (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 */
+                                       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_hold)?"YES":"NO", other_pdss1->p_m_d_ces);
+                                       if (1 //other_pdss1->p_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 {
+                               } else
+#endif
+#ifdef WITH_GSM_BS
+                               if (our_gsm_bs
+                                && (other_port->p_type==PORT_TYPE_GSM_BS_OUT
+                                 || other_port->p_type==PORT_TYPE_GSM_BS_IN)) { /* port is GSM bs-mode */
+                                       other_gsm_bs = (class Pgsm_bs *)other_port;
+                                       PDEBUG(DEBUG_EPOINT, "EPOINT(%d) comparing other endpoint's port is of type GSM! comparing our imsi with other imsi=%s\n", ea_endpoint->ep_serial, our_gsm_bs->p_g_imsi, other_gsm_bs->p_g_imsi);
+                                       if (!strcmp(other_gsm_bs->p_g_imsi, our_gsm_bs->p_g_imsi)) /* same tei+sapi */
+                                               break;
+                               } else
+#endif
+                               {
                                        PDEBUG(DEBUG_EPOINT, "EPOINT(%d) comparing other endpoint's port is of other type!\n", ea_endpoint->ep_serial);
                                }
                        } else {
@@ -3663,16 +3821,18 @@ int EndpointAppPBX::join_join_dss1(void)
        }
 
        /* now find out which is ACTIVE-IDLE and which is ACTIVE-HELD */
-       if (our_pdss1->p_m_hold && !other_pdss1->p_m_hold) {
+       if (our_port->p_hold && !other_port->p_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_eapp_hold = this;
+               remove_eapp_active = other_eapp;
                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_eapp_hold = other_eapp;
+               remove_eapp_active = this;
                remove_join = other_join;
                remove_joinpbx = other_joinpbx;
                add_join = our_join;
@@ -3683,13 +3843,13 @@ int EndpointAppPBX::join_join_dss1(void)
        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 */
+               if (remove_relation->epoint_id == remove_eapp_hold->ea_endpoint->ep_serial) {
+                       /* detach endpoint on hold */
                        *remove_relation_pointer = remove_relation->next;
                        FREE(remove_relation, sizeof(struct join_relation));
                        cmemuse--;
                        remove_relation = *remove_relation_pointer;
-                       remove_eapp->ea_endpoint->ep_join_id = 0;
+                       remove_eapp_hold->ea_endpoint->ep_join_id = 0;
                        continue;
                }
 
@@ -3703,9 +3863,43 @@ int EndpointAppPBX::join_join_dss1(void)
                remove_relation_pointer = &remove_relation->next;
                remove_relation = remove_relation->next;
        }
-       PDEBUG(DEBUG_EPOINT, "EPOINT(%d) endpoint removed, other enpoints on join relinked.\n", ea_endpoint->ep_serial);
+       PDEBUG(DEBUG_EPOINT, "EPOINT(%d) endpoint (hold) removed, other enpoints on join relinked.\n", ea_endpoint->ep_serial);
+
+       if (invoke_id >= 0) {
+               /* remove relation to endpoint for active join */
+               remove_relation = add_joinpbx->j_relation;
+               remove_relation_pointer = &add_joinpbx->j_relation;
+               while(remove_relation) {
+                       if (remove_relation->epoint_id == remove_eapp_active->ea_endpoint->ep_serial) {
+                               /* detach active endpoint */
+                               *remove_relation_pointer = remove_relation->next;
+                               FREE(remove_relation, sizeof(struct join_relation));
+                               cmemuse--;
+                               remove_relation = *remove_relation_pointer;
+                               remove_eapp_active->ea_endpoint->ep_join_id = 0;
+                               continue;
+                       }
 
-       /* join call relations */
+                       remove_relation_pointer = &remove_relation->next;
+                       remove_relation = remove_relation->next;
+               }
+               PDEBUG(DEBUG_EPOINT, "EPOINT(%d) endpoint (active) removed, other enpoints on join relinked.\n", ea_endpoint->ep_serial);
+
+               /* if active endpoint is in alerting state, send alerting message to join */
+               if (remove_eapp_active->e_state == EPOINT_STATE_IN_ALERTING) {
+                       if (add_joinpbx->j_relation && !add_joinpbx->j_relation->next) {
+                               /* if channel state indicateds "audio" (1), we tell the other endpoint that patterns are available */
+                               if (add_joinpbx->j_relation->channel_state) {
+                                       message = message_create(add_joinpbx->j_relation->epoint_id, add_join->j_serial, EPOINT_TO_JOIN, MESSAGE_PATTERN);
+                                       message_put(message);
+                               }
+                               message = message_create(add_joinpbx->j_relation->epoint_id, add_join->j_serial, EPOINT_TO_JOIN, MESSAGE_ALERTING);
+                               message_put(message);
+                       }
+               }
+       }
+
+       /* join call relations: we add the members of the join on hold to the active join */
        add_relation = add_joinpbx->j_relation;
        add_relation_pointer = &add_joinpbx->j_relation;
        while(add_relation) {
@@ -3716,12 +3910,32 @@ int EndpointAppPBX::join_join_dss1(void)
        remove_joinpbx->j_relation = NULL;
        PDEBUG(DEBUG_EPOINT, "EPOINT(%d) relations joined.\n", ea_endpoint->ep_serial);
 
-       /* release endpoint */
-       message = message_create(remove_joinpbx->j_serial, remove_eapp->ea_endpoint->ep_serial, JOIN_TO_EPOINT, MESSAGE_RELEASE);
+       /* release endpoint on hold */
+       message = message_create(remove_joinpbx->j_serial, remove_eapp_hold->ea_endpoint->ep_serial, JOIN_TO_EPOINT, MESSAGE_RELEASE);
        message->param.disconnectinfo.cause = CAUSE_NORMAL; /* normal */
        message->param.disconnectinfo.location = LOCATION_PRIVATE_LOCAL;
+       if (invoke_id >= 0) {
+               /* send the result with disconnect message to the invoking endpoint */
+               if (remove_eapp_hold->ea_endpoint->ep_serial == ea_endpoint->ep_serial) {
+                       message->param.disconnectinfo.transfer.result = 1;
+                       message->param.disconnectinfo.transfer.invoke_id = invoke_id;
+               }
+       }
        message_put(message);
        
+       if (invoke_id >= 0) {
+               /* release active endpoint */
+               message = message_create(add_joinpbx->j_serial, remove_eapp_active->ea_endpoint->ep_serial, JOIN_TO_EPOINT, MESSAGE_RELEASE);
+               message->param.disconnectinfo.cause = CAUSE_NORMAL; /* normal */
+               message->param.disconnectinfo.location = LOCATION_PRIVATE_LOCAL;
+               /* send the result with disconnect message to the invoking endpoint */
+               if (remove_eapp_active->ea_endpoint->ep_serial == ea_endpoint->ep_serial) {
+                       message->param.disconnectinfo.transfer.result = 1;
+                       message->param.disconnectinfo.transfer.invoke_id = invoke_id;
+               }
+               message_put(message);
+       }
+
        /* if we are not a partyline, we get partyline state from other join */
        add_joinpbx->j_partyline += remove_joinpbx->j_partyline; 
 
@@ -3734,9 +3948,6 @@ int EndpointAppPBX::join_join_dss1(void)
 
        /* 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;
 }
@@ -3802,8 +4013,8 @@ int EndpointAppPBX::join_join_fxs(void)
                                if (other_port->p_type==PORT_TYPE_POTS_FXS_OUT
                                 || other_port->p_type==PORT_TYPE_POTS_FXS_IN) { /* port is FXS */
                                        other_fxs = (class Pfxs *)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 state=%d\n", ea_endpoint->ep_serial, our_fxs->p_m_mISDNport->portnum, other_fxs->p_m_mISDNport->portnum, (other_fxs->p_m_hold)?"YES":"NO", other_fxs->p_state);
-                                       if (1 //other_fxs->p_m_hold /* port is on hold */
+                                       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 state=%d\n", ea_endpoint->ep_serial, our_fxs->p_m_mISDNport->portnum, other_fxs->p_m_mISDNport->portnum, (other_fxs->p_hold)?"YES":"NO", other_fxs->p_state);
+                                       if (1 //other_fxs->p_hold /* port is on hold */
                                         && other_fxs->p_m_mISDNport == our_fxs->p_m_mISDNport) /* same isdn interface */
                                                break;
                                } else {
@@ -3842,7 +4053,7 @@ int EndpointAppPBX::join_join_fxs(void)
        }
 
        /* now find out which is ACTIVE-IDLE and which is ACTIVE-HELD */
-       if (our_fxs->p_m_hold && !other_fxs->p_m_hold) {
+       if (our_port->p_hold && !other_port->p_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;
@@ -3913,21 +4124,28 @@ int EndpointAppPBX::join_join_fxs(void)
 
        /* we send a retrieve to that endpoint */
        // mixer will update the hold-state of the join and send it to the endpoints is changes
+
+       return 0;
 #else
        PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: no mISDN support anyway.\n", ea_endpoint->ep_serial);
-#endif
 
-       return 0;
+       return -1;
+#endif
 }
 
+/* do audio bridge of endpoints on same isdn/gsm terminal */
 int EndpointAppPBX::join_3pty_dss1(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;
+#ifdef WITH_MISDN
+       class Pdss1 *our_pdss1 = NULL, *other_pdss1;
+#endif
+#ifdef WITH_GSM_BS
+       class Pgsm_bs *our_gsm_bs = NULL, *other_gsm_bs;
+#endif
 
        /* are we a candidate to join a join? */
        our_join = find_join_id(ea_endpoint->ep_join_id);
@@ -3944,20 +4162,19 @@ int EndpointAppPBX::join_3pty_dss1(void)
                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;
+#ifdef WITH_MISDN
+       if ((our_port->p_type & PORT_CLASS_mISDN_MASK) == PORT_CLASS_DSS1)
+               our_pdss1 = (class Pdss1 *)our_port;
+#endif
+#ifdef WITH_GSM_BS
+       if ((our_port->p_type & PORT_CLASS_GSM_MASK) == PORT_CLASS_GSM_BS)
+               our_gsm_bs = (class Pgsm_bs *)our_port;
+#endif
 
        /* find an endpoint that has the same mISDNport/ces that we are on */
        other_eapp = apppbx_first;
@@ -3967,20 +4184,33 @@ int EndpointAppPBX::join_3pty_dss1(void)
                        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 */
+               if (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 */
+#ifdef WITH_MISDN
+                               if (our_pdss1
+                                && (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 */
+                                       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_hold)?"YES":"NO", other_pdss1->p_m_d_ces);
+                                       if (1 //other_pdss1->p_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 {
+                               } else
+#endif
+#ifdef WITH_GSM_BS
+                               if (our_gsm_bs
+                                && (other_port->p_type==PORT_TYPE_GSM_BS_OUT
+                                 || other_port->p_type==PORT_TYPE_GSM_BS_IN)) { /* port is GSM bs-mode */
+                                       other_gsm_bs = (class Pgsm_bs *)other_port;
+                                       PDEBUG(DEBUG_EPOINT, "EPOINT(%d) comparing other endpoint's port is of type GSM! comparing our imsi with other imsi=%s\n", ea_endpoint->ep_serial, our_gsm_bs->p_g_imsi, other_gsm_bs->p_g_imsi);
+                                       if (!strcmp(other_gsm_bs->p_g_imsi, our_gsm_bs->p_g_imsi)) /* same tei+sapi */
+                                               break;
+                               } else
+#endif
+                               {
                                        PDEBUG(DEBUG_EPOINT, "EPOINT(%d) comparing other endpoint's port is of other type!\n", ea_endpoint->ep_serial);
                                }
                        } else {
@@ -3990,7 +4220,7 @@ int EndpointAppPBX::join_3pty_dss1(void)
                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);
+               PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: no other endpoint on same terminal.\n", ea_endpoint->ep_serial);
                return -1;
        }
        PDEBUG(DEBUG_EPOINT, "EPOINT(%d) port with same terminal found.\n", ea_endpoint->ep_serial);
@@ -4034,13 +4264,11 @@ int EndpointAppPBX::join_3pty_dss1(void)
 
        /* 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;
 }
 
+/* do audio bridge of endpoints on same fxs terminal */
 int EndpointAppPBX::join_3pty_fxs(void)
 {
 #ifdef WITH_MISDN
@@ -4096,8 +4324,8 @@ int EndpointAppPBX::join_3pty_fxs(void)
                                if (other_port->p_type==PORT_TYPE_POTS_FXS_OUT
                                 || other_port->p_type==PORT_TYPE_POTS_FXS_IN) { /* port is isdn nt-mode */
                                        other_fxs = (class Pfxs *)other_port;
-                                       PDEBUG(DEBUG_EPOINT, "EPOINT(%d) comparing other endpoint's port is of type FXS! comparing our portnum=%d with other's portnum=%d hold=%s state=%d\n", ea_endpoint->ep_serial, our_fxs->p_m_mISDNport->portnum, other_fxs->p_m_mISDNport->portnum, (other_fxs->p_m_hold)?"YES":"NO", other_fxs->p_state);
-                                       if (1 //other_fxs->p_m_hold /* port is on hold */
+                                       PDEBUG(DEBUG_EPOINT, "EPOINT(%d) comparing other endpoint's port is of type FXS! comparing our portnum=%d with other's portnum=%d hold=%s state=%d\n", ea_endpoint->ep_serial, our_fxs->p_m_mISDNport->portnum, other_fxs->p_m_mISDNport->portnum, (other_fxs->p_hold)?"YES":"NO", other_fxs->p_state);
+                                       if (1 //other_fxs->p_hold /* port is on hold */
                                         && other_fxs->p_m_mISDNport == our_fxs->p_m_mISDNport) /* same pots interface */
                                                break;
                                } else {
@@ -4154,16 +4382,18 @@ int EndpointAppPBX::join_3pty_fxs(void)
 
        /* we send a retrieve to that endpoint */
        // mixer will update the hold-state of the join and send it to the endpoints is changes
+
+       return 0;
 #else
        PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: no mISDN support anyway.\n", ea_endpoint->ep_serial);
-#endif
 
-       return 0;
+       return -1;
+#endif
 }
 
+/* split audio bridge */
 int EndpointAppPBX::split_3pty(void)
 {
-#ifdef WITH_MISDN
        class Join *our_join, *other_join;
        class JoinPBX *our_joinpbx, *other_joinpbx;
 
@@ -4204,9 +4434,6 @@ int EndpointAppPBX::split_3pty(void)
 
        /* 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;
 }
@@ -4302,6 +4529,30 @@ int EndpointAppPBX::check_external(const char **errstr, class Port **port)
        return(0);
 }
 
+int EndpointAppPBX::vootp_on(int on)
+{
+#ifndef WITH_VOOTP
+       set_tone(ea_endpoint->ep_portlist, "crypt_off");
+#else
+       if (!e_ext.otp_ident[0]) {
+               set_tone(ea_endpoint->ep_portlist, "crypt_off");
+               return -EINVAL;
+       }
+       if(ea_endpoint->ep_portlist) {
+               struct lcr_msg *message;
+
+               message = message_create(ea_endpoint->ep_serial, ea_endpoint->ep_portlist->port_id, EPOINT_TO_PORT, MESSAGE_VOOTP);
+               message->param.vootp.enable = on;
+               SCPY(message->param.vootp.id, e_ext.otp_ident);
+               message_put(message);
+       }
+       if (!on)
+               set_tone(ea_endpoint->ep_portlist, "crypt_off");
+#endif
+
+       return 0;
+}
+
 void EndpointAppPBX::logmessage(int message_type, union parameter *param, unsigned int port_id, int dir)
 {
        const char *logtext = "unknown";
@@ -4722,6 +4973,13 @@ void EndpointAppPBX::logmessage(int message_type, union parameter *param, unsign
 
                case MESSAGE_TRANSFER:
                trace_header("TRANSFER", dir);
+               if (param->transfer.invoke)
+                       add_trace("action", NULL, "invoke");
+               if (param->transfer.result)
+                       add_trace("action", NULL, "result");
+               if (param->transfer.error)
+                       add_trace("action", NULL, "error");
+               add_trace("invoke-id", NULL, "%d", param->transfer.invoke_id);
                end_trace();
                break;
 
@@ -4732,12 +4990,34 @@ void EndpointAppPBX::logmessage(int message_type, union parameter *param, unsign
                end_trace();
                break;
 
+               case MESSAGE_DOV_INDICATION:
+               case MESSAGE_DOV_REQUEST:
+               trace_header("Data-Over-Voice", dir);
+               if (dir == DIRECTION_OUT)
+                       add_trace("to", NULL, "CH(%lu)", port_id);
+               if (dir == DIRECTION_IN)
+                       add_trace("from", NULL, "CH(%lu)", port_id);
+               {
+                       char dov_str[param->dov.length + 1];
+                       memcpy(dov_str, param->dov.data, param->dov.length);
+                       dov_str[param->dov.length] = '\0';
+                       add_trace("string", NULL, "%s", dov_str);
+               }
+               add_trace("type", NULL, "%d", param->dov.type);
+               end_trace();
+               break;
+
+               case MESSAGE_ENABLEKEYPAD:
+               trace_header("ENABLEKEYPAD", dir);
+               end_trace();
+               break;
+
                default:
                PERROR("EPOINT(%d) message not of correct type (%d)\n", ea_endpoint->ep_serial, message_type);
        }
 }
 
-void EndpointAppPBX::message_disconnect_port(struct port_list *portlist, int cause, int location, const char *display)
+void EndpointAppPBX::message_disconnect_port(struct port_list *portlist, int cause, int location, const char *display, const struct param_transfer *transfer)
 {
        struct lcr_msg *message;
 
@@ -4761,7 +5041,45 @@ void EndpointAppPBX::message_disconnect_port(struct port_list *portlist, int cau
                else
                        SCPY(message->param.notifyinfo.display, get_isdn_cause(cause, location, e_ext.display_cause));
        }
+       if (transfer) {
+               memcpy(&message->param.disconnectinfo.transfer, transfer, sizeof(struct param_transfer));
+       }
        message_put(message);
        logmessage(message->type, &message->param, portlist->port_id, DIRECTION_OUT);
 }
 
+void EndpointAppPBX::dov_msg_write(union parameter *param, int sent)
+{
+       FILE *fp;
+       struct tm *tm;
+       time_t ti;
+       int __attribute__((__unused__)) rc;
+
+       /* no write, if no log file given */
+       if (!e_ext.dov_log[0])
+               return;
+
+       fp = fopen(e_ext.dov_log, "a");
+       if (!fp) {
+               PERROR("EPOINT(%d) failed to open Data-Over-Voice log file '%s'\n", ea_endpoint->ep_serial, e_ext.dov_log);
+               return;
+       }
+
+       ti = time(NULL);
+       tm = localtime(&ti);
+       fprintf(fp, "%02d.%02d.%02d %02d:%02d:%02d ", tm->tm_mday, tm->tm_mon+1, tm->tm_year%100, tm->tm_hour, tm->tm_min, tm->tm_sec);
+
+
+       if (sent) {
+               fprintf(fp, "sent [%s] ", numberrize_callerinfo(e_callerinfo.id, e_callerinfo.ntype, options.national, options.international));
+       } else {
+               fprintf(fp, "received [%s] ", e_dialinginfo.id);
+       }
+
+       rc = fwrite(param->dov.data, param->dov.length, 1, fp);
+
+       fprintf(fp, "\n");
+
+       fclose(fp);
+}
+